一塊記憶體配置設定給應用程式之後,從代碼的組織上,我們就有将它們分段的需求。
比如,可以分為代碼段,資料段,隻讀資料段,堆棧段,未初始化的資料段等等。
在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