有過 readelf 的經驗之後,我們已經對 ELF 格式中的檔頭內容瞭若指掌,所以 size 也就不費吹灰之力的完成了。接下來是小型工具程式之二的 strip 工具!
目前為止我們已經使用 readelf 很多次,也透過 as 的實作由上而下地反覆咀嚼過 ELF 格式的奧妙,我們對於區段標頭與區段的概念,應該已經算是很熟悉了。當然,我們也留意到一些很有趣的作法,比方說:
.shstrtab
區段(或是某一個在 ELF 檔頭標明的區段)裡面撈取這個字串,作為該區段的名稱使用。.strtab
裡面的字串。這些標籤,到頭來是為了連結而存在的。當一個程式的各個部份(可重定物件檔)被連結成一個可執行檔之後,再執行時再經由動態連結器載入動態函式庫;也許設計者沒有意識到這些過程的實際運作,但是他們理解自己的程式的方法必然牽涉到其中步驟,具體來說,當他們想著:「我這裡要呼叫另外一個檔案的函式、這裡要用到上一個資料夾的哪一個檔案裡面定義的全域變數......」之類的念頭來進行開發時,他們心中的藍圖仍然是與電腦截然不同的程式,他們是以標籤為導向、不清楚實際位址的一種抽象,與最後編譯出來的成果完全不同。
那麼,如果我們已經成功取得了連結好的執行檔,是不是有些標籤就可以不需要了呢?是否有辦法能夠去除掉這些不必要的標籤呢?
是的,strip 工具程式的主要功能就是這個用途。更廣泛來說,strip 工具還有其他好處(以下是筆者想得到的部份):
舉個例子來看看吧!還記得日前我們用以測試的程式嗎?拿 add.o 和 main.c 連結之後的成果來看:
$ riscv64-unknown-linux-gnu-gcc add.o main.c
$ riscv64-unknown-linux-gnu-strip a.out -o b.out # 將 a.out 程式 strip 掉成為 b.out,然後觀察其中的標籤
$ /tmp/readelf -S ./b.out | awk '{print $1 $2}'
0.shstrtab
1.comment
2.bss
3.sdata
4.got
5.dynamic
6.fini_array
7.init_array
8.preinit_array
9.eh_frame
10.eh_frame_hdr
11.rodata
12.text
13.plt
14.rela.plt
15.gnu.version_r
16.gnu.version
17.dynstr
18.dynsym
19.hash
20.note.ABI-tag
21.interp
22
如果看 a.out
的話,會發現多了九個區段如下:
1.strtab
2.symtab
3.debug_loc
4.debug_str
5.debug_frame
6.debug_line
7.debug_abbrev
8.debug_info
9.debug_aranges
這就完全印證了前一段的說法:strip 工具會移除不需要的標籤和字串(這兩個在 a.out
形成時就已經可以功成身退,相對的,動態連結所需的 .dynsym
、.dynstr
區段之類的部份就不會被 strip 影響到),還有 debug 需要的內容。
筆者這個部份打算實作簡單版本的功能,那就是將 .strtab
和 .symtab
區段移除掉。動筆之前的計畫是這樣的:因為只有打算移除這兩個區段,那麼就拿其它的區段來填補即可。
在這裡,筆者又必須承認自己的錯誤了(當然,這也表示又學到了一課)。之前為了叛逆的原因,沒有思考為什麼主流的實作裡面都會把區段檔頭放在檔案的最後面(當然,也是所有區段的最後面才開始接第一個區段檔頭)。現在要實作 strip 工具程式才恍然大悟:因為區段檔頭在最後面的話,一切就如同上面那段一樣輕描淡寫;然而一旦區段檔頭之後還有內容,對於二進位檔來說,就沒有什麼像是 backspace 那樣編輯文字檔一般的方便機制了。
也就是說,我們之後有機會時必須回去修改 as 的生成 ELF 的部份,改成區段標頭在後的方式。但是實際上,由於物件檔不可以被 strip,所以我們目前做的事情還不會造成 strip 的困難,只是日後若是有機會實作到我們自己的 ld,那麼就也必須回想起這點才行。
日後也應該考慮設計更泛用的介面:都有了讀取 ELF 檔,為什麼沒有輸出 ELF 的 API 呢?as 的時候採取的是非常土法煉鋼的方式逐塊拼湊 ELF 檔的元件,現在 strip 也算是有這樣泛用的需求了,日後的 objcopy 可能有一點,而 ld 則更不用說。
看起來,strip 比起昨日的 size 程式有挑戰得多了!之後再來解析實作的內容。各位讀者,我們明日再會!