gcc生成執行檔案過程為:
原始檔(*.c檔案)編譯成物件檔案(*.o檔案);連結程式ld,把物件檔案(*.o檔案)連結成可執行程式。因此要透徹連結的過程, 需要先了解物件檔案(*.o檔案)是怎樣構成的?
下面用個簡單的例子來說明:
#include<stdio.h>int global_var=5;extern int other_file_var;int main(){ int a=1; int b=a+other_file_var; return 0;}
gcc -c test.c -o test.o 生成test.o;檢視test.o內容(vim test.o):圖1
看到的是一堆亂碼,因為檢視方式不對, 就像mp3格式的檔案需要用音樂播放器才能播放一樣,物件檔案(*.o)是elf格式的, 需要用objdump, readelf 工具來檢視。
從elf格式的官方文件,可以瞭解到elf格式檔案的結構如下圖所示:
圖2
下面將一個個部分來分析。
1. ELF檔案頭(Header)分析readelf -h test.o 檢視elf檔案頭部資訊
主要欄位的含義已在圖中標識;由Size of this headers可知,頭部佔用了64位元組;用 hexdump -n 64 test.o 檢視頭部64位元組資料內容(16進位制格式);圖中紅框裡的資料就是test.o檔案的前64位元組,也就是elf頭部,對比上面兩圖,(圖1)中的魔數7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 剛好與(圖2)中的前16位元組(小段編碼)對應,後面每個欄位的含義也是一一對應的;頭部資訊結構可以參考/usr/include/elf.h 裡ELf64_Ehdr, 32位的可以參考ELf32_Ehdr結構typedef struct{ unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf64_Half e_type; /* Object file type */ Elf64_Half e_machine; /* Architecture */ Elf64_Word e_version; /* Object file version */ Elf64_Addr e_entry; /* Entry point virtual address */ Elf64_Off e_phoff; /* Program header table file offset */ Elf64_Off e_shoff; /* Section header table file offset */ Elf64_Word e_flags; /* Processor-specific flags */ Elf64_Half e_ehsize; /* ELF header size in bytes */ Elf64_Half e_phentsize; /* Program header table entry size */ Elf64_Half e_phnum; /* Program header table entry count */ Elf64_Half e_shentsize; /* Section header table entry size */ Elf64_Half e_shnum; /* Section header table entry count */ Elf64_Half e_shstrndx; /* Section header string table index */} Elf64_Ehdr;
由欄位Start Of Section Header可知,在test.o檔案的656位元組處有一個"段的頭部表"。2. ELF段頭部表(Section Header)分析用readelf -S test.o檢視"段的頭部表",在這個表裡儲存了檔案裡所有段的屬性資訊,如段的名字,段在檔案的開始位置, 段的長度等:
圖4
由圖可知
主要欄位的含義已從圖中標識;段表的資料在偏離檔案開始的0x290處,跟ELF Header Start Of Section Header欄位保持一致;這個段表裡總共有12項,不同的項描述了不同段的屬性;2.1 text段
text段在偏離test.o檔案開頭0x40位元組處,長度為0x20位元組,用hexdump -s 0x40 -n 0x20 test.o,檢視text段的16進位制內容。
圖5
然後objdump -d test.o 打印出程式的反彙編程式碼,
圖6
由上面兩圖可知,text部分的資料恰好是main函式的機器碼,也就是text段裡儲存的是程式碼段。
2.2 data段
data段在偏離test.o檔案開頭0x60位元組處,長度為0x4位元組,用hexdump -s 0x60 -n 0x4 test.o,檢視data段的16進位制內容,
圖7
4個位元組剛好是個int的長度,裡面儲存的數值是5,也就是全域性變數global_var的值,驗證了已初始化的全域性變數儲存在data段。
2.3 bss段
2.4 .rela.text (text的重定位段)
main函數里的global_var定義在別的檔案,後面連結需要根據別的檔案來確定它的虛擬記憶體地址,由於text中有需要重定位的變數,所以就有了.rela.text段。readelf -r test.o
圖8
offset列表示需要重定位的符號在該段中的偏移,這裡表示偏離text段0xd處;info列高4個位元組是該符號在.symtab中的索引,見下圖;圖9
2.5 .symtab 符號表
圖9
ndx列是該符號在段表中的索引:(1)比如global_var的索引為3,圖4中索引3表示data段,也就是說global_var這個符號在data段;(2)如果是“UND“則表示該符號定義在別的檔案,需要重定位,重定位資訊見“2.4 .rela.text”。
圖10