iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 19
0

前情提要


昨日介紹 nm 的用法,今日則讓我們來實作看看吧!

實作


從 size 與 readelf 工具程式的經驗裡,我們對於 ELF 的檔頭操作已經很熟悉;但是說到標籤的操作,我們就會稍微遲疑一下了:這個東西是在區段裡面的資訊,該怎麼處理才好?筆者這裡先回到 API 中翻找,看看有沒有可用的介面。

從線上手冊看來,elf.File 型別有 SymbolsDynamicSymbols 方法,會回傳 []Symbol 的動態長度陣列給呼叫端,而這個 Symbol 型別是

type Symbol struct {
        Name        string
        Info, Other byte
        Section     SectionIndex
        Value, Size uint64
}

相較於我們之前在 as 篇之中纏鬥過的標準規格型別:

ym64 struct {
        Name  uint32 
        Info  uint8  
        Other uint8  
        Shndx uint16 
        Value uint64 
        Size  uint64 
}

最大的差異就是 Name 代表名稱的成員了吧。這個變數原本代表索引,必須搭配 Shndx 成員所指引的區段存取對應的字串作為真正的名稱。簡化過的 Symbol 型別則可以直接將之存為字串。我們要實作 nm 工具程式的話,每個標籤的三個資訊都可以從這裡取得,分別是

  • 名稱,Name
  • 型態,Info 變數中蘊含的標籤型態資訊
  • 位址,Value

所以結構體的宣告與前日的 size 完全相同即可,但是因為要取得的部份從所有區段檔頭轉變而為只有標籤區段,所以需要作相應的修改。先看 Run 函式的部份:

 func (nmu *nmUtil) Run(args map[string]interface{}) error {

+        symtab, _ := nmu.file.Symbols()
+        dynsym, _ := nmu.file.DynamicSymbols()
+        syms := append(symtab, dynsym...)

         str := "]"
         for _, d := range syms {
                 raw, err := json.Marshal(d)
                 if err != nil {
                         return err
                 }

                 str = "," + string(raw) + str
         }
         re, _ := regexp.Compile("^,")
         str = re.ReplaceAllString(str, "[")

         nmu.raw = []byte(str)
         return nil
 }

為了組成所有的標籤,加號後面的三行就是相對應的功能實作。其中第三行特別有趣,因為 golang 對於動態陣列的加入函數 append 同樣可以用於連接的用途,但是語法上其實是先透過 ... 運算子展開動態陣列(正確的 go 語言術語是 slice),然後再使用 append 的多重參數特性去存取。

至於輸出顯示的部份則稍微調整了輸出的欄位:

 func (nmu *nmUtil) Output(args map[string]interface{}) error {

         w := new(tabwriter.Writer)
         w.Init(os.Stdout, 0, 8, 1, ' ', 0)

+        var output []elf.Symbol
         json.Unmarshal(nmu.raw, &output)

+        fmt.Fprintln(w, "Offset/Address\tType\tName")
+        for _, s := range output {
+                fmt.Fprintf(w, "%016x\t%s\t%s\n", s.Value,
+                        elf.SymType(elf.ST_TYPE(s.Info)).GoString(), s.Name)
+        }
         fmt.Fprintln(w)
         w.Flush()

         return nil

可以看到 output 變數在這裡必須改變型別(readelf 和 size 時這裡都是 elf.Section64 的陣列型別)。特別值得一提的是,第二欄的標籤型別內容的存取十分迂迴,那是因為 Info 本身是複合成員變數,我們必須透過 ST_TYPE 函數來取得它型別部份的資訊。

最後的結果類似這樣子:

Offset/Address   Type            Name
0000000000010360 elf.STT_FUNC    __libc_start_main
0000000000010350 elf.STT_FUNC    printf
0000000000012000 elf.STT_OBJECT  __TMC_END__
0000000000010360 elf.STT_FUNC    __libc_start_main@@GLIBC_2.26
0000000000010428 elf.STT_FUNC    main
0000000000012038 elf.STT_NOTYPE  __bss_start
0000000000012828 elf.STT_NOTYPE  __global_pointer$
0000000000010370 elf.STT_FUNC    _start
0000000000012040 elf.STT_NOTYPE  _end
0000000000010452 elf.STT_FUNC    __libc_csu_init
0000000000012028 elf.STT_OBJECT  _IO_stdin_used
0000000000012030 elf.STT_OBJECT  __dso_handle
0000000000012000 elf.STT_NOTYPE  __data_start
0000000000010350 elf.STT_FUNC    printf@@GLIBC_2.26
0000000000012038 elf.STT_NOTYPE  _edata
000000000001041c elf.STT_FUNC    add
0000000000012000 elf.STT_NOTYPE  data_start
00000000000104aa elf.STT_FUNC    __libc_csu_fini
0000000000012020 elf.STT_OBJECT  _GLOBAL_OFFSET_TABLE_
00000000000104b4 elf.STT_NOTYPE  __GNU_EH_FRAME_HDR
0000000000011e20 elf.STT_NOTYPE  __init_array_start
0000000000011e30 elf.STT_OBJECT  _DYNAMIC
0000000000011e28 elf.STT_NOTYPE  __init_array_end
0000000000010330 elf.STT_OBJECT  _PROCEDURE_LINKAGE_TABLE_
0000000000000000 elf.STT_FILE
00000000000104f0 elf.STT_OBJECT  __FRAME_END__
0000000000000000 elf.STT_FILE    crtstuff.c
0000000000000000 elf.STT_FILE    elf-init.c
0000000000000000 elf.STT_FILE    main.c
0000000000011e20 elf.STT_OBJECT  __frame_dummy_init_array_entry
0000000000010418 elf.STT_FUNC    frame_dummy
0000000000011e28 elf.STT_OBJECT  __do_global_dtors_aux_fini_array_entry
0000000000012038 elf.STT_OBJECT  completed.3093
00000000000103f8 elf.STT_FUNC    __do_global_dtors_aux
00000000000103ca elf.STT_FUNC    register_tm_clones
00000000000103a6 elf.STT_FUNC    deregister_tm_clones
0000000000000000 elf.STT_FILE    crtstuff.c
0000000000000000 elf.STT_FILE    init.c

後面其實還有一堆 Name 無內容的標籤是 Section 型態的標籤。

小結


今天實作了昨天介紹的 nm 工具程式。strip 比之前想像的還要複雜許多,筆者打算接下來也是先介紹數組工具程式之後,有餘力再跟著實作。各位讀者,我們明日再會!


上一篇
第十八日:nm 工具程式介紹
下一篇
第二十日:objdump 工具介紹
系列文
與妖精共舞:在 RISC-V 架構上使用 GO 語言實作 binutils 工具包30

尚未有邦友留言

立即登入留言