天天看點

ART世界探險(12) - OAT檔案分析(2) - ELF檔案頭分析(中)ART世界探險(12) - OAT檔案分析(2) - ELF檔案頭分析(中)

一塊記憶體配置設定給應用程式之後,從代碼的組織上,我們就有将它們分段的需求。

比如,可以分為代碼段,資料段,隻讀資料段,堆棧段,未初始化的資料段等等。

在gas彙編器中,我們通過.section僞指令來指定段名。arm編譯器我買不起,我就忽略它了。

段的描述

預設段名

代碼段

.text

經過初始化的資料段

.data

未經初始化的資料段

.bss

bss是block started by symbol的縮寫,就是為符号預留一些空間。

為什麼要将指令段和資料段分開呢?這要從馮.諾依曼結構和哈佛結構說起。

諾依曼結構是指令和資料都存在同一個存儲器上,比如intel的8086,mips,arm7都是這樣的結構。因為指令和資料都要走同一條總線,是以會産生一些競争。

與此相對,将指令存儲和資料存儲分開,這就是哈佛結構。從arm9開始,arm晶片就是哈佛結構了。它們各走各自的總線,各有各的cache,可以提高命中率。

即使在諾依曼結構下,通常也是将代碼和指令存放在同一存儲器的不同區域。

我們找一個gcc -s生成的彙編來看一下:

源碼就用俗到不能再俗的hello,world的例子:

生成的彙編如下:

像.text,.data和.bss這樣的标準段直接就是僞指令,不用再加.section了。

而像本例這樣,将字元串放到.rodata這樣的區域中,就需要加.section僞指令來聲明一下。

.rodata可以對應c++的const關鍵字,為常量專門區分一個區域。

我們通過用objdump -h指令來檢視我們剛才的helloworld代碼被編譯成多少個段。打出來一看,還真不少,一共有26個。

好吧,不是每種語言都這麼瘋狂的,我們看看go語言生成的段:

除了debug相關的,隻有8個段,比gcc還是簡單一些的。

幸好,oat的section比c++的還要少一些。主要的段有14個。

一個指令段,7個隻讀的資料段,6個調試用的段。

從上面列出的資訊可以看到,段是有自己的類型的,而且還有對應的如可寫,隻讀,可執行等屬性。

常量

含義

sht_null

無效段

sht_progbits

1

程式段

sht_symtab

2

符号表

sht_strtab

3

字元串表

sht_rela

4

重定位表

sht_hash

5

符号表的哈希表

sht_dynamic

6

動态連結資訊

sht_note

7

提示性資訊,注釋

sht_nobits

8

無内容,如未初始化的.bss段

sht_rel

9

重定位資訊

sht_shlib

10

rfu

sht_dynsym

11

動态連結的符号表

段的标志位

shf_write

可寫

shf_alloc

需要配置設定空間,有些段并不需要,而.code,.bss,.data這些都需要配置設定空間

shf_exeinstr

可執行,一般隻有.code段才有這個屬性

下面我們學以緻用,通過readelf -s看看oat檔案的段的類型和屬性:

帶a的是shf_alloc屬性,x是可執行屬性。連同符号表、字元串表,空字段等字段,一共有18個字段。

上面我們了解了section,這是針對elf檔案中的組織單元。下面我們讨論另一個概念叫做segment。

将section映射到記憶體中時,為了節省空間,我們可以将相同類型和屬性的section放在一起。因為section很多,每個都占用整個頁面的話會導緻頁的浪費,而合到一起則隻需要每個segment占滿整頁就可以了。

我們通過readelf -l參數可以檢視segment的配置設定情況,如下例:

描述

pt_null

空值

pt_load

加載到記憶體中

pt_dynamic

動态連結

pt_interp

動态連結的輔助資訊

pt_note

暫時用不到

pt_shlib

pt_phdr

程式表頭的位置和大小

gnu_eh_frame

.eh_frame_hdr專用

總結起來,過程就是,先裝載資料,再裝載代碼,然後是動态連結相關的,最後是系統的一些輔助資訊。

積累了前面這麼多基礎知識之後,我們終于可以向elf檔案頭的後半部分前進了。

做為堅定的64位的愛好者,我以64位為例來講解後面的檔案頭,兼顧32位。

偏移量

字段

長度

0x18

e_entry

程式的入口點,32位下為4位元組

0x20

e_phoff

程式頭表的位址,其實就是segment表,對應segment與section的關系的,32位下為4位元組

0x28

e_shoff

段頭表的位址,指向section的表,32位下為4位元組

0x30

e_flags

根據架構不同的标志位

0x34

e_ehsize

elf頭的長度,在64位下是64位元組,在32位下是52位元組。差的12個位元組就是上面的三個字段e_entry,e_phoff,e_shoff給鬧的

0x36

e_phentsize

程式頭表項的長度

0x38

e_phnum

程式頭表的記錄數

0x3a

e_shentsize

section表記錄的長度

0x3c

e_shnum

section表的記錄數

0x3e

e_shstrndx

section表中的哪一項是名字項

好,我們還把上章的那個例子拿出來:

還記得我們上次分析的頭0x18個位元組嗎?

7f 45 4c 46: magic number,0x7f-e-l-f

02:64位

01:little endian,小端

01:版本号1

03:linux

8個0:沒用到

03 00:動态連結庫

b7 00: aarch64

01 00 00 00: 版本号1

溫故而知新,我們開始這節學到的内容:

00 00 00 00 00 00 00 00:e_entry入口位址,0

40 00 00 00 00 00 00 00:程式表頭位址:本檔案0x40開始

e0 3a aa 00 00 00 00 00:section表的位址:0xaa3ae0

00 00 00 00: flag,全0

40 00:elf頭的長度,0x40,64位元組。

38 00:每個segment表項的長度,0x38,56位元組

07 00:segment一共多少項:7項

40 00:section表記錄的長度,0x40,64位元組

12 00:section共有多少個,18個

11 00:名字的index,17