技術(shù)分析:從字節(jié)碼的粒度來探索ELF文件
好了,下面我們繼續(xù)把剩下的 36 個(gè)字節(jié)(52 - 16 = 32),也以這樣的字節(jié)碼含義畫出來:
16 - 31 個(gè)字節(jié):
32 - 47 個(gè)字節(jié):
48 - 51 個(gè)字節(jié):
具體的內(nèi)容就不用再解釋了,一切都在感情深、一口悶,話不多說,都在酒里~~ 哦不對(duì),重點(diǎn)都在圖里!
字符串表表項(xiàng) Entry
在一個(gè) ELF 文件中,存在很多字符串,例如:變量名、Section名稱、鏈接器加入的符號(hào)等等,這些字符串的長度都是不固定的,因此用一個(gè)固定的結(jié)構(gòu)來表示這些字符串,肯定是不現(xiàn)實(shí)的。
于是,聰明的人類就想到:把這些字符串集中起來,統(tǒng)一放在一起,作為一個(gè)獨(dú)立的 Section 來進(jìn)行管理。
在文件中的其他地方呢,如果想表示一個(gè)字符串,就在這個(gè)地方寫一個(gè)數(shù)字索引:表示這個(gè)字符串位于字符串統(tǒng)一存儲(chǔ)地方的某個(gè)偏移位置,經(jīng)過這樣的按圖索驥,就可以找到這個(gè)具體的字符串了。
比如說啊,下面這個(gè)空間中存儲(chǔ)了所有的字符串:
在程序的其他地方,如果想引用字符串 “hello,world!”,那么就只需要在那個(gè)地方標(biāo)明數(shù)字 13 就可以了,表示:這個(gè)字符串從偏移 13 個(gè)字節(jié)處開始。
那么現(xiàn)在,咱們再回到這個(gè) main 文件中的字符串表,
在 ELF header 的最后 2 個(gè)字節(jié)是 0x1C 0x00,它對(duì)應(yīng)結(jié)構(gòu)體中的成員 e_shstrndx,意思是這個(gè) ELF 文件中,字符串表是一個(gè)普通的 Section,在這個(gè) Section 中,存儲(chǔ)了 ELF 文件中使用到的所有的字符串。
既然是一個(gè) Section,那么在 Section header table 中,就一定有一個(gè)表項(xiàng) Entry 來描述它,那么是哪一個(gè)表項(xiàng)呢?
這就是 0x1C 0x00 這個(gè)表項(xiàng),也就是第 28 個(gè)表項(xiàng)。
這里,我們還可以用指令 readelf -S main 來看一下這個(gè) ELF 文件中所有的 Section 信息:
其中的第 28 個(gè) Section,描述的正是字符串表 Section:
可以看出來:這個(gè) Section 在 ELF 文件中的偏移地址是 0x0016ed,長度是 0x00010a 個(gè)字節(jié)。
下面,我們從 ELF header 的二進(jìn)制數(shù)據(jù)中,來推斷這信息。
讀取字符串表 Section 的內(nèi)容
那我就來演示一下:如何通過 ELF header 中提供的信息,把字符串表這個(gè) Section 給找出來,然后把它的字節(jié)碼打印出來給各位看官瞧瞧。
要想打印字符串表 Section 的內(nèi)容,就必須知道這個(gè) Section 在 ELF 文件中的偏移地址。
要想知道偏移地址,只能從 Section head table 中第 28 個(gè)表項(xiàng)描述信息中獲取。
要想知道第 28 個(gè)表項(xiàng)的地址,就必須知道 Section head table 在 ELF 文件中的開始地址,以及每一個(gè)表項(xiàng)的大小。
正好最后這 2 個(gè)需求信息,在 ELF header 中都告訴我們了,因此我們倒著推算,就一定能成功。
ELF header 中的第 32 到 35 字節(jié)內(nèi)容是:F8 17 00 00(注意這里的字節(jié)序,低位在前),表示的就是 Section head table 在 ELF 文件中的開始地址(e_shoff)。
0x000017F8 = 6136,也就是說 Section head table 的開始地址位于 ELF 文件的第 6136 個(gè)字節(jié)處。
知道了開始地址,再來算一下第 28 個(gè)表項(xiàng) Entry 的地址。
ELF header 中的第 46、47 字節(jié)內(nèi)容是:28 00,表示每個(gè)表項(xiàng)的長度是 0x0028 = 40 個(gè)字節(jié)。
注意這里的計(jì)算都是從 0 開始的,因此第 28 個(gè)表項(xiàng)的開始地址就是:6136 + 28 * 40 = 7256,也就是說用來描述字符串表這個(gè) Section 的表項(xiàng),位于 ELF 文件的 7256 字節(jié)的位置。
既然知道了這個(gè)表項(xiàng) Entry 的地址,那么就扒開來看一下其中的二進(jìn)制內(nèi)容:
執(zhí)行指令:od -Ad -t x1 -j 7256 -N 40 main。
其中的 -j 7256 選項(xiàng),表示跳過前面的 7256 個(gè)字節(jié),也就是我們從 main 這個(gè) ELF 文件的 7256 字節(jié)處開始讀取,一共讀 40 個(gè)字節(jié)。
這 40 個(gè)字節(jié)的內(nèi)容,就對(duì)應(yīng)了 Elf32_Shdr 結(jié)構(gòu)體中的每個(gè)成員變量:
這里主要關(guān)注一下上圖中標(biāo)注出來的 4 個(gè)字段:
sh_name: 暫時(shí)不告訴你,馬上就解釋到了;
sh_type:表示這個(gè) Section 的類型,3 表示這是一個(gè) string table;
sh_offset: 表示這個(gè) Section,在 ELF 文件中的偏移量。0x000016ed = 5869,意思是字符串表這個(gè) Section 的內(nèi)容,從 ELF 文件的 5869 個(gè)字節(jié)處開始;
sh_size:表示這個(gè) Section 的長度。0x0000010a = 266 個(gè)字節(jié),意思是字符串表這個(gè) Section 的內(nèi)容,一共有 266 個(gè)字節(jié)。
還記得剛才我們使用 readelf 工具,讀取到字符串表 Section 在 ELF 文件中的偏移地址是 0x0016ed,長度是 0x00010a 個(gè)字節(jié)嗎?
與我們這里的推斷是完全一致的!
既然知道了字符串表這個(gè) Section 在 ELF 文件中的偏移量以及長度,那么就可以把它的字節(jié)碼內(nèi)容讀取出來。
執(zhí)行指令: od -Ad -t c -j 5869 -N 266 main,所有這些參數(shù)應(yīng)該不用再解釋了吧?!
看一看,瞧一瞧,是不是這個(gè) Section 中存儲(chǔ)的全部是字符串?
剛才沒有解釋 sh_name 這個(gè)字段,它表示字符串表這個(gè) Section 本身的名字,既然是名字,那一定是個(gè)字符串。
但是這個(gè)字符串不是直接存儲(chǔ)在這里的,而是存儲(chǔ)了一個(gè)索引,索引值是 0x00000011,也就是十進(jìn)制數(shù)值 17。
現(xiàn)在我們來數(shù)一下字符串表 Section 內(nèi)容中,第 17 個(gè)字節(jié)開始的地方,存儲(chǔ)的是什么?
不要偷懶,數(shù)一下,是不是看到了:“.shstrtab” 這個(gè)字符串(是字符串的分隔符)?!
好了,如果看到這里,你全部都能看懂,那么關(guān)于字符串表這部分的內(nèi)容,說明你已經(jīng)完全理解了,給你一百個(gè)贊。。
讀取代碼段的內(nèi)容
從下面的這張圖(指令:readelf -S main):
可以看到代碼段是位于第 14 個(gè)表項(xiàng)中,加載(虛擬)地址是 0x08048470,它位于 ELF 文件中的偏移量是 0x000470,長度是 0x0001b2 個(gè)字節(jié)。
那我們就來試著讀一下其中的內(nèi)容。
首先計(jì)算這個(gè)表項(xiàng) Entry 的地址:6136 + 14 * 40 = 6696。
然后讀取這個(gè)表項(xiàng) Entry,讀取指令是 od -Ad -t x1 -j 6696 -N 40 main:
同樣的,我們也只關(guān)心下面這 5 個(gè)字段內(nèi)容:
sh_name: 這回應(yīng)該清楚了,表示代碼段的名稱在字符串表 Section 中的偏移位置。0x9B = 155 字節(jié),也就是在字符串表 Section 的第 155 字節(jié)處,存儲(chǔ)的就是代碼段的名字;剡^頭去找一下,看一下是不是字符串 “.text”;
sh_type:表示這個(gè) Section 的類型,1(SHT_PROGBITS) 表示這是代碼;
sh_addr:表示這個(gè) Section 加載的虛擬地址是 0x08048470,這個(gè)值與 ELF header 中的 e_entry 字段的值是相同的;
sh_offset: 表示這個(gè) Section,在 ELF 文件中的偏移量。0x00000470 = 1136,意思是這個(gè) Section 的內(nèi)容,從 ELF 文件的 1136 個(gè)字節(jié)處開始;
sh_size:表示這個(gè) Section 的長度。0x000001b2 = 434 個(gè)字節(jié),意思是代碼段一共有 434 個(gè)字節(jié)。
以上這些分析結(jié)構(gòu),與指令 readelf -S main 讀取出來的完全一樣!
PS: 在查看字符串表 Section 中的字符串時(shí),不要告訴我,你真的是從 0 開始數(shù)到 155 !可以計(jì)算一下:字符串表的開始地址是 5869(十進(jìn)制),加上 155,結(jié)果就是 6024,所以從 6024 開始的地方,就是代碼段的名稱,也就是 “.text”。
知道了以上這些信息,我們就可以讀取代碼段的字節(jié)碼了.使用指令:od -Ad -t x1 -j 1136 -N 434 main 即可。
內(nèi)容全部是黑乎乎的的字節(jié)碼,我就不貼出來了。
Program header
文章的開頭,我就介紹了:我是一個(gè)通用的文件結(jié)構(gòu),鏈接器和加載器在看待我的時(shí)候,眼光是不同的。
為了對(duì) Program header 有更感性的認(rèn)識(shí),我還是先用 readelf 這個(gè)工具來從總體上看一下 main 文件中的所有段信息。
執(zhí)行指令:readelf -l main,得到下面這張圖:
顯示的信息已經(jīng)很明白了:
這是一個(gè)可執(zhí)行程序;
入口地址是 0x8048470;
一共有 9 個(gè) Program header,是從 ELF 文件的 52 個(gè)偏移地址開始的;
布局如下圖所示:
開頭我還告訴過你:Section 與 Segment 本質(zhì)上是一樣的,可以理解為:一個(gè) Secgment 由一個(gè)或多個(gè) Sections 組成。
從上圖中可以看到,第 2 個(gè) program header 這個(gè)段,由那么多的 Section 組成,這下更明白一些了吧?!
從圖中還可以看到,一共有 2 個(gè) LOAD 類型的段:
我們來讀取第一個(gè) LOAD 類型的段,當(dāng)然還是扒開其中的二進(jìn)制字節(jié)碼。
第一步的工作是,計(jì)算這個(gè)段表項(xiàng)的地址信息。
從 ELF header 中得知如下信息:
字段 e_phoff :Program header table 位于 ELF 文件偏移 52 個(gè)字節(jié)的地方。
字段 e_phentsize: 每一個(gè)表項(xiàng)的長度是 32 個(gè)字節(jié);
字段 e_phnum: 一共有 9 個(gè)表項(xiàng) Entry;
通過計(jì)算,得到可讀、可執(zhí)行的 LOAD 段,位于偏移量 116 字節(jié)處。
執(zhí)行讀取指令:od -Ad -t x1 -j 116 -N 32 main:
按照上面的慣例,我還是把其中幾個(gè)需要關(guān)注的字段,與數(shù)據(jù)結(jié)構(gòu)中的成員變量進(jìn)行關(guān)聯(lián)一下:
p_type: 段的類型,1: 表示這個(gè)段需要加載到內(nèi)存中;
p_offset: 段在 ELF 文件中的偏移地址,這里值為 0,表示這個(gè)段從 ELF 文件的頭部開始;
p_vaddr:段加載到內(nèi)存中的虛擬地址 0x08048000;
p_paddr:段加載的物理地址,與虛擬地址相同;
p_filesz: 這個(gè)段在 ELF 文件中,占據(jù)的字節(jié)數(shù),0x0744 = 1860 個(gè)字節(jié);
p_memsz:這個(gè)段加載到內(nèi)存中,需要占據(jù)的字節(jié)數(shù),0x0744= 1860 個(gè)字節(jié)。注意:有些段是不需要加載到內(nèi)存中的;
經(jīng)過上述分析,我們就知道:從 ELF 文件的第 1 到 第 1860 個(gè)字節(jié),都是屬于這個(gè) LOAD 段的內(nèi)容。
在被執(zhí)行時(shí),這個(gè)段需要被加載到內(nèi)存中虛擬地址為 0x08048000 這個(gè)地方,從這里開始,又是一個(gè)全新的故事了。
再回顧一下
到這里,我已經(jīng)像洋蔥一樣,把自己的層層外衣都扒開,讓你看到最細(xì)的顆粒度了,這下子,您是否對(duì)我有足夠的了解了呢?
其實(shí)只要抓住下面 2 個(gè)重點(diǎn)即可:
ELF header 描述了文件的總體信息,以及兩個(gè) table 的相關(guān)信息(偏移地址,表項(xiàng)個(gè)數(shù),表項(xiàng)長度);
每一個(gè) table 中,包括很多個(gè)表項(xiàng) Entry,每一個(gè)表項(xiàng)都描述了一個(gè) Section/Segment 的具體信息。
鏈接器和加載器也都是按照這樣的原理來解析 ELF 文件的,明白了這些道理,后面在學(xué)習(xí)具體的鏈接、加載過程時(shí),就不會(huì)迷路啦!

發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長度6~500個(gè)字
最新活動(dòng)更多
-
3月27日立即報(bào)名>> 【工程師系列】汽車電子技術(shù)在線大會(huì)
-
4月30日立即下載>> 【村田汽車】汽車E/E架構(gòu)革新中,新智能座艙挑戰(zhàn)的解決方案
-
5月15-17日立即預(yù)約>> 【線下巡回】2025年STM32峰會(huì)
-
即日-5.15立即報(bào)名>>> 【在線會(huì)議】安森美Hyperlux™ ID系列引領(lǐng)iToF技術(shù)革新
-
5月15日立即下載>> 【白皮書】精確和高效地表征3000V/20A功率器件應(yīng)用指南
-
5月16日立即參評(píng) >> 【評(píng)選啟動(dòng)】維科杯·OFweek 2025(第十屆)人工智能行業(yè)年度評(píng)選
推薦專題
-
10 月之暗面,絕地反擊
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺(tái)
- 4 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 5 國產(chǎn)智駕迎戰(zhàn)特斯拉FSD,AI含量差幾何?
- 6 光計(jì)算迎來商業(yè)化突破,但落地仍需時(shí)間
- 7 東陽光:2024年扭虧、一季度凈利大增,液冷疊加具身智能打開成長空間
- 8 地平線自動(dòng)駕駛方案解讀
- 9 封殺AI“照騙”,“淘寶們”終于不忍了?
- 10 優(yōu)必選:營收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?