Linux C目標檔案
LinuxC目標檔案
宗旨:技術的學習是有限的,分享的精神是無限的。
一、目標檔案格式(ELF格式)
編譯器編譯原始碼後生成的檔案叫做目標檔案。目標檔案是已經編譯後的可執行檔案,只是還沒有經過連結的過程。
PC平臺流行的可執行檔案格式:windows下的PE和Linux下的ELF。
動態連結庫和靜態連結庫也是按照可執行檔案儲存的。
1、ELF檔案歸於4類:
ELF文講型別 |
說明 |
舉例 |
可重定位檔案 |
目標檔案.o |
Linux的.o, windows下的.obj |
可執行檔案 |
直接可執行的檔案 |
/bin/bash windows的.exe |
共享目標檔案 |
.so DLL |
|
核心轉儲檔案 |
程序意外終止 |
core dump |
Linux下的file命令檢視相應的檔案格式:
2、目標檔案
編譯後的機器指令程式碼、資料、符號表、除錯資訊、字串等。
一般目標檔案將這些檔案資訊按不同的屬性,以“節”的形式儲存。
機器指令放在程式碼段.text,已初始化全域性變數和靜態變數放在資料段裡.data,未初始化全域性變數和靜態變數放在資料段裡.bss。.bss只是為變數預留的位置而已,並沒有內容,不佔據空間。
二、剖析目標檔案section.o
// section.c: #include<stdio.h> int init_var = 84; int uninit_var; void fun(int i) { printf(" %d \n", i); } int main(void) { static int static_var = 85; static int static_var2; int a = 1; int b; fun(init_var + uninit_var + a + b); return 0; }
gcc -c section.c生成section.o——只編譯不連結
objdump -h section.o// ELF檔案的各個段的基本資訊打印出來。
除了最基本的程式碼段、資料段、BSS段,還有三個段:只讀資料段(.rodata)、註釋資訊段(.comment)和堆疊提示段(.note.GNU-stack)。
段的屬性:最容易理解的就是段的長度Size和段所在的位置File off(偏移量)。每個段第二行的“CONTENTS”表示該段在檔案中存在——BSS段沒有“CONTENTS “,實際上在ELF中不存在;”note.GNU-stack“有“CONTENTS”但大小為0,奇怪。
size命令檢視ELF檔案中的程式碼段、資料段和BSS段長度。
[email protected]:~/mystudy# size section.o
text data bss dec hex filename
88 8 4 100 64 section.o
1、程式碼段
objdump的“-s”十六進位制方式列印,“-d”反彙編。提取出程式碼段的內容:
“Contents of section.text”就是.text的資料以十六進位制方式打印出來的內容,0x58位元組,與size命令的長度符合。對照反彙編結果,.text包含兩個函式,fun()和main()。.text的第一個位元組就是”0x55”就是fun()函式的第一條“push %ebp”指令,而最後一個位元組0xc3正是main()函式的最後一條指令“ret”。
2、資料段和只讀資料段
.data段儲存的是初始化的全域性變數和靜態變數,section.c中有這樣兩個變數init_var和static_var,都是int型,剛好8位元組。所以.data的大小是8位元組。
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 20256420 0a00 %d ..
.data前四個位元組,0x54、0x00、0x00、0x00 —— 0x54 = 84;——大端機
3、BSS段
.bss段儲存的是未化的全域性變數和靜態變數,section.c中有這樣兩個變數uninit_var和static_var2。但是通過size命令看到.bss只有4位元組。通過符號表(後面說)看到,只有static_var2被放入了.bss段,uninit_var沒有。與不同的語言和不同的編譯器有關。
4、其他段
常用段名 |
說明 |
.rodata |
只讀資料,如字串常量,const只讀變數 |
.comment |
編譯器版本資訊, |
.debug |
除錯資訊 |
.dynamic |
動態連結資訊 |
.hash |
符號雜湊表 |
.line |
行號表 |
.note |
額外的編譯器資訊:公司名,釋出版本號等 |
.strtab |
字串表 |
.symtab |
符號表 |
.shstrtab |
段名錶 |
.plt .got |
動態連結的跳轉表和全域性入口表 |
.init .fini |
程式初始化與終結程式碼段 |
三、ELF檔案結構
提取重要的結構:ELFHeader(ELF檔案頭)、.text、.data、.bss、其他段、段表、字串表、符號表等。
ELF檔案頭——描述了整個檔案的檔案屬性:是否可執行、是靜態還是動態連線及入口地址、目標硬體、目標作業系統等資訊。
段表——所有段的資訊:段名、段的長度、在檔案中的偏移、讀寫許可權及段的其他屬性。
1、檔案頭(readelf命令)
ELF檔案頭定義:ELF魔數、檔案機器位元組長度、檔案儲存方式、版本、執行平臺、ABI版本、ELF重定位型別、硬體平臺、硬體平臺版本、入口地址、程式頭入口地址和長度、段表的位置和長度及段的數量等。
ELF檔案頭結構及相關常數被定義在”/usr/include/elf.h”,32位“ELF32_Ehdr”
#define EI_NIDENT(16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Objectfile type */
Elf32_Half e_machine; /*Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entrypoint virtual address */
Elf32_Off e_phoff; /* Programheader table file offset */
Elf32_Off e_shoff; /* Sectionheader table file offset */
Elf32_Word e_flags; /*Processor-specific flags */
Elf32_Half e_ehsize; /* ELFheader size in bytes */
Elf32_Half e_phentsize; /* Programheader table entry size */
Elf32_Half e_phnum; /* Program header table entrycount */
Elf32_Half e_shentsize; /* Sectionheader table entry size */
Elf32_Half e_shnum; /* Sectionheader table entry count */
Elf32_Half e_shstrndx; /* Sectionheader string table index */
} Elf32_Ehdr;
結構與readelf輸出的ELF檔案頭資訊相比:只有e_ident對應了readelf輸出中的“Class Data Version OS/ABI ABI Version”5個引數,剩下的引數一一對應。
ELF魔數:Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
16位元組對應了Elf32_Ehdr的e_ident這個16位元組成員。這16位元組被ELF標準規定來標識ELF檔案的平臺屬性:如字長、位元組序、版本等。16位元組含義:
前4位元組是所有ELF檔案都必須相同的標識碼:0x7F、0X45、0X4C、0X46(這四個位元組就是ELF檔案的魔數)。幾乎所有的可執行檔案的開始的幾個位元組都是魔數,如a.out最開始兩個位元組是0x01、0x07;PE最開始兩個位元組是0x4d、0x5a。這個魔數用來確認檔案型別。第5個位元組用來標識ELF檔案類的,0x01表示是32位的,0x02表示64位的。第6位元組是位元組序,規定ELF檔案是大端的還是小端的。第7位元組規定ELF檔案的主版本號,一般是1.後面的九個位元組ELF標準沒有定義,一般寫0。
e_type檔案型別:ET_REL—— 1 ——可重定位檔案,一般是.o檔案;ET_EXEC—— 2 —— 可執行檔案; ET_DYN —— 2 ——共享目標檔案,一般是.so檔案。
e_machine機器型別:ELF檔案的平臺屬性,EM_386 —— 3 —— x86
2、段表
ELF的段結構就是由段表決定的,編譯器、聯結器和裝載器都是靠段表來定位和訪問各個段的屬性的。段表在ELF檔案中的位置由ELF檔案頭Elf32_Ehdr結構中的” e_shoff” 成員決定。section.o中,段表位於偏移0x104(260位元組)處。
前面用”objdump -h”檢視ELF檔案中的段,此命令只是把ELF檔案中的關鍵段顯示出來了,省略了其他輔助性的段:符號表、字串表、重定位表等。
readelf -S命令
段表是以“Elf32_Shdr”結構體為元素的陣列,陣列元素的個數等於段的個數,每個“Elf32_Shdr”結構體對應一個段。section.o:11個元素的陣列。/usr/include/elf.h:
typedef struct
{
Elf32_Word sh_name; /* Section name (string tblindex) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr atexecution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional sectioninformation */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holdstable */
} Elf32_Shdr;
總結section.o段表的位置
起始地址 |
大小 |
|
ELF Header e_shoff = 0x104 |
0 |
0x34 |
.text |
0x34 |
0x52 |
.data |
0x88 |
0x08 |
.rodata |
0x90 |
0x06 |
.comment |
0x96 |
0x1d |
.shstrtab |
0xB3 |
0x51 |
Section Table |
0x104 |
0x1b8 |
.symtab |
0x2bc |
0xf0 |
.rel.text |
0x3fc |
0x28 |
長度為0x424 = 1060,這個長度正好是section.o檔案的大小。
段的型別(sh_type):段的名字只有在編譯和連結的過程中有意義。SHT_NULL – 0 – 無效段, SHT_PROGBITS– 1 – 程式段, SHT_SYMTAB – 2 – 表示該段的內容為符號表, SHT_STRTAB – 3 – 字串表, SHT_RELA –4 – 重定位表, SHT_HASH – 5 – 符號表的雜湊表,SHT_DYNAMIC – 6 – 動態連結資訊, SHT_NOTE – 7 – 提示性資訊, SHT_NOBITS– 8 –該段在檔案中沒內容, SHT_REL – 9 –該段包含了重定位資訊,SHT_SHLIB – 10 – 保留,SHT_DNYSYM – 11 – 動態連結的符號表。
段的標誌位(sh_flag):表示該段在程序虛擬地址空間中的屬性,可寫可執行等。SHF_WRITE – 1 – 該段在程序空間中可寫; SHF_ALLOC– 2 – 在程序空間中要分配空間; SHF_EXECINSTR– 4 –該段在程序空間中可以被執行,一般指程式碼段。
段的連結資訊(sh_link、sh_info):
sh_type |
sh_link |
sh_info |
SHT_DYNAMIC |
字串表在段表的下標 |
0 |
SHT_HASH |
符號表在段表中的下標 |
0 |
SHT_REL |
相應符號表在段表中的下標 |
該重定位表所作用的段在段表中的下標 |
SHT_RELA |
||
SHT_SYMTAB |
作業系統相關 |
作業系統相關 |
SHTDYNSYM |
||
other |
SHN_UNDEF |
0 |
3、重定位表
section.o中有一個“rel.text”的段,型別是“SHT_REL”——重定位表。
程式碼段和資料段中那些對絕對地址的引用的位置——相應的重定位表。section.o中的“rel.text”就是對“.text”段的重定位表——printf函式的呼叫;而“.data”段沒有對絕對地址的引用,只包含了幾個常量,故沒有“.rel.data”。
4、字串表——段名,變數名等
四、連結的介面——符號
可以使用很多工具檢視ELF檔案的符號表,readelf,objdump,nm等;
1、ELF符號表結構
ELF符號表是檔案中的一個段“.symtab”
/* Symbol table entry. */
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tblindex) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;