天天看點

目标檔案ELF

程式源檔案經過編譯後将會生産目标檔案,對于linux系統,生成的目标檔案是以.o結尾的ELF檔案。

假設有源檔案test.c,如圖1所示,代碼中有函數,有全局變量,有局部靜态變量,有初始化變量,有未初始化變量。

目标檔案ELF

圖1 test.c源檔案

經過gcc -c test.c 編譯後将生成目标檔案test.o,test.o的二進制内容如圖2所示

目标檔案ELF

圖2 目标檔案test.o 的二進制内容

對于上述二進制内容,能難知其所雲,可以用readelf或者objdump檢視目标檔案的格式化内容。目标檔案是以段(section)存放的,而段表是各個段資訊的描述。

使用指令:

readelf -h -S test.o

讀取目标檔案中的檔案頭和段表資訊。輸出的内容如圖3所示:

目标檔案ELF

圖3 檔案頭和段表内容

可以看到檔案頭(ELF Header),包含着該目标檔案的描述,其對應資料結構為圖4所示,其中包含了魔法數,檔案類型,資料存放格式,版本号,ABI類型,ABI版本,CPU類型,程式資訊和段表資訊等。其中,魔法數為7f, 45, 4c, 46, 02 ,01, 01資料開頭的16byte的資料,辨別着ELF檔案,也由圖2可以看到檔案開始就是7f, 45, 4c, 46, 01, 01的資料。

目标檔案ELF

圖4 檔案頭資料結構

由圖3中列印了段表的資訊,包括段名,段類型,段大小,段所在檔案的偏移等。而段表所在檔案的偏移由檔案頭e_shoff表示。

使用指令

objdump -s -d test.o

檢視各段的二進制和彙編代碼,如圖5所示

目标檔案ELF

圖5 段二進制和彙編代碼

結合圖3的段表資訊和圖5輸出的段的内容來分析各個段:

.text:代碼段,有圖3可以看到,它在檔案中偏移為0x40,而檔案頭的大小為64bytes,是以在檔案頭之後緊接着是代碼段。代碼段的大小為0x4e, 加上偏移之後是0x9e,再算上位址對齊,應該為0xa0,可以知道接在代碼段之後為.data(資料段);

.rela.text:代碼重定位段,存放着代碼段中在連結階段需要重定位的符号;

.data:資料段,其大小為0x08,存放着已經初始化的全局變量和靜态變量,圖1知ELF存儲為小端模式,而圖5顯示資料為54000000和55000000,即global_init_var 0x54和static_init_var 0x55,而未初始化的内容存放在.bss段中;

.rodata:隻讀資料段,存放的為%d\n的字元常量

.bss:存放未初始化的全局變量和靜态變量,圖3中其大小0x04,而源代碼中未初始化的變量有兩個,實際上.bss中,暫時隻存放了靜态變量static_uint_var, 因為未初始化的全局變量global_uint_var設計的弱符号的問題,暫時還不能确定該變量的大小,隻是在符号表中辨別為common類型,待連結階段确定了符号的大小,才為其在.bss配置設定大小;

.shstrtab:段名字串符号表,存放段名,實際上段表中存放的隻是段名在段名字元串标的下标;

.symtab:符号表;

使用指令:

readelf -s test.o

得到目标檔案的符号表如圖6所示,符号表有notype, section, object, func等類型,可以看出未初始化global_uint_var全局變量辨別為Common類型。Value的值對于變量和函數對應着符号的位址。

目标檔案ELF

圖6 符号表

.strtab:字元串表,實際上符号表也隻存放符号名的下标

由上分析可知:在目标檔案中存放檔案頭,代碼段,資料段,隻讀資料段,段名表, 段表,符号表,字元串标,重定位段等。

目标檔案ELF

圖7 目标檔案結構

參考文獻:

《程式員的自我修養》

繼續閱讀