iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 20
0

前情提要


到昨日為止,大致依照第 15 日的規劃行進,但是目前還欠著 strip 實作的債;主要原因是 strip 當初不小心被歸類在小型程式裡面,但是它會涉及到 ELF 檔本身的改動,因此實作幅度實際上頗大。另外兩個小型程式的 size 和 nm 都已經直接實作完成。接下來我們先來介紹 objdump 工具程式吧!

初衷


既然今日主題是 objdump 介紹,就不得不提筆者以 binutils 工具包為目標的初衷了。筆者認為,binutils 工具包背負著許多過去的包袱,因此想要設計一個只針對 ELF 格式的系統二進位檔案工具。GNU binutils 工具包的部份程式各自提供一些實質重複的功能,筆者認為極不必要,而應該統合起來重新規劃。比方說我們目前為止已經完成的 readelf、size、以及 nm 三個工具,它們都是在閱讀 ELF 檔案,那麼為什麼不能整合成類似這樣的用法:

$ go-binutils show header               # 類似 readelf -h
$ go-binutils show section-header       # 類似 readelf -S
$ go-binutils show program-header       # 類似 readelf -l
$ go-binutils show section-size         # 類似 size
$ go-binutils show symbol               # 類似 nm

這樣豈不是一目了然嗎?常常有許多 stackoverflow 網站上面的問題讓回答者很無奈,他們總會心想,你的這些問題,分明只要用 man 指令調查一下該程式的參數用法,不就找得到了嗎?這個想法,有它的正當性,但卻沒有理解到根本原因出在,POSIX 的命令列參數慣例(也就是我們其實也都已經很習以為常的 -s-a 之類單橫線內容)是有極限的,它們通常很難幫助人們記憶他們的功能:為什麼展示程式標頭的功能要用 -l 參數呢?GNU 應該有發現這個問題,因此推崇雙橫線參數增加可讀性的慣例,於是我們有了 --header--start-address 之類的參數,的確就比較方便在手冊中查找、也比較方便閱讀了。但是既然都已經決定要展示使用者所欲執行的真正動作,為什麼還需要那些礙事的橫線呢?各位喜歡看見 git --addgit --commit 或是 docker --run 嗎?

UNIX 哲學裡面偏好工具應該體積小而數量多,如此便足以產生一些使用組合來圓滿使用者的真正需求。但如果當初真的可以透過 cat、diff、sed、awk 就兜得出版本控制的功能,我們就不需要版本控制工具了。所以筆者並不認為整合一個專為 ELF 格式服務的 binutils 程式會有什麼原則上的問題,因為事實證明這些工具分散又語意重複,筆者撰寫系列文期間也看了一些 stackoverflow 的問答,多得是發問者以為該用 objdump 看,實際上卻應該使用 readelf 取得資訊的例子。整合起來如上述展示內容的 sub-command 型操作方式,豈不是清爽得多?

那麼筆者為何還是要以 binutils 的順序與命名方式來實作功能?因為筆者認為數典忘祖也是不好的,應該要能夠提供類似

$ go-binutils legacy readelf
$ go-binutils legacy size

之類的操作方式。

使用方法介紹


objdump 工具根據輸入參數的不同,能夠展示物件檔的不同部份內容,也有一些附屬參數能夠調整展示的範圍與特性等等。這裡介紹最常用的功能,也是筆者預計要實作的功能,就是 -d 參數的反組譯程式區段功能。

$ riscv64-unknown-linux-gnu-objdump -d a.out
...
0000000000010428 <main>:
   10428:       1141                    addi    sp,sp,-16
   1042a:       e406                    sd      ra,8(sp)
   1042c:       e022                    sd      s0,0(sp)
   1042e:       0800                    addi    s0,sp,16
   10430:       4589                    li      a1,2
   10432:       4505                    li      a0,1
   10434:       fe9ff0ef                jal     ra,1041c <add>
   10438:       87aa                    mv      a5,a0
   1043a:       85be                    mv      a1,a5
   1043c:       67c1                    lui     a5,0x10
   1043e:       4b078513                addi    a0,a5,1200 # 104b0 <__libc_csu_fini+0x6>
   10442:       f0fff0ef                jal     ra,10350 <printf@plt>
   10446:       4781                    li      a5,0
   10448:       853e                    mv      a0,a5
   1044a:       60a2                    ld      ra,8(sp)
   1044c:       6402                    ld      s0,0(sp)
   1044e:       0141                    addi    sp,sp,16
   10450:       8082                    ret
...

以之前連結 add.smain.c 的產物為例,這個指令會反組譯程式內容。這個 a.out 檔即會列出 .text 區段與 .plt 區段的內容(關於 PLT,我們在之後有機會會介紹)。之所以選上這兩個區段,是因為這兩個區段都有可執行內容的旗標被設置。使用之前的 readelf 工具觀察可以看到:

Number          Name           Type                  Flags                          
0               .shstrtab      elf.SHT_STRTAB        0x0                            
1               .strtab        elf.SHT_STRTAB        0x0                            
2               .symtab        elf.SHT_SYMTAB        0x0                            
3               .debug_loc     elf.SHT_PROGBITS      0x0                            
4               .debug_str     elf.SHT_PROGBITS      elf.SHF_MERGE+elf.SHF_STRINGS  
5               .debug_frame   elf.SHT_PROGBITS      0x0                            
6               .debug_line    elf.SHT_PROGBITS      0x0                            
7               .debug_abbrev  elf.SHT_PROGBITS      0x0                            
8               .debug_info    elf.SHT_PROGBITS      0x0                            
9               .debug_aranges elf.SHT_PROGBITS      0x0                            
10              .comment       elf.SHT_PROGBITS      elf.SHF_MERGE+elf.SHF_STRINGS  
11              .bss           elf.SHT_NOBITS        elf.SHF_WRITE+elf.SHF_ALLOC    
12              .sdata         elf.SHT_PROGBITS      elf.SHF_WRITE+elf.SHF_ALLOC    
13              .got           elf.SHT_PROGBITS      elf.SHF_WRITE+elf.SHF_ALLOC    
14              .dynamic       elf.SHT_DYNAMIC       elf.SHF_WRITE+elf.SHF_ALLOC    
15              .fini_array    elf.SHT_FINI_ARRAY    elf.SHF_WRITE+elf.SHF_ALLOC    
16              .init_array    elf.SHT_INIT_ARRAY    elf.SHF_WRITE+elf.SHF_ALLOC    
17              .preinit_array elf.SHT_PREINIT_ARRAY elf.SHF_WRITE+elf.SHF_ALLOC    
18              .eh_frame      elf.SHT_PROGBITS      elf.SHF_ALLOC                  
19              .eh_frame_hdr  elf.SHT_PROGBITS      elf.SHF_ALLOC                  
20              .rodata        elf.SHT_PROGBITS      elf.SHF_ALLOC                  
21              .text          elf.SHT_PROGBITS      elf.SHF_ALLOC+elf.SHF_EXECINSTR
22              .plt           elf.SHT_PROGBITS      elf.SHF_ALLOC+elf.SHF_EXECINSTR
23              .rela.plt      elf.SHT_RELA          elf.SHF_ALLOC+elf.SHF_INFO_LINK
24              .gnu.version_r elf.SHT_GNU_VERNEED   elf.SHF_ALLOC                  
25              .gnu.version   elf.SHT_GNU_VERSYM    elf.SHF_ALLOC                  
26              .dynstr        elf.SHT_STRTAB        elf.SHF_ALLOC                  
27              .dynsym        elf.SHT_DYNSYM        elf.SHF_ALLOC                  
28              .hash          elf.SHT_HASH          elf.SHF_ALLOC                  
29              .note.ABI-tag  elf.SHT_NOTE          elf.SHF_ALLOC                  
30              .interp        elf.SHT_PROGBITS      elf.SHF_ALLOC                  
31                             elf.SHT_NULL          0x0                            

小結


今天介紹 objdump 最常用的方法。明日我們會開始探討相關的實作如何進行。讀者諸君,明日再會!


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

尚未有邦友留言

立即登入留言