1 ELF文件結構

圖中顯示了ELF可重定位文件的構成,ELF文件頭的開始16個字節描述了文件中字的大小和字節序(大端模式還是小端模式)。文件頭還包含了ELF頭的大小,文件類型(可重定位,可執行和共享),機器類型,節頭表的位置和大小。節頭表中的每項對應於文件中的一個節,用於描述節的位置和大小。
ELF文件頭:

ELF頭對應的代碼定義為:
#define EI_NIDENT 16 typedef struct elf32_hdr{ unsigned char e_ident[EI_NIDENT]; //開始的16個字節 Elf32_Half e_type; //文件類型 Elf32_Half e_machine; //運行的機器類型 Elf32_Word e_version; //版本 Elf32_Addr e_entry; //程序入口地址 Elf32_Off e_phoff; //程序頭表在文件中的偏移 Elf32_Off e_shoff; //節頭表在文件中的偏移 Elf32_Word e_flags; //標記 Elf32_Half e_ehsize; //elf文件頭大小 Elf32_Half e_phentsize; //程序頭表項的大小 Elf32_Half e_phnum; //程序頭表中表項項的個數 Elf32_Half e_shentsize; //節頭表項大小 Elf32_Half e_shnum; //節頭表中表項的個數 Elf32_Half e_shstrndx; //節頭表的字符串節所在節頭表中下標 } Elf32_Ehdr;
節頭表項對應的代碼定義為:
typedef struct elf32_shdr { Elf32_Word sh_name; //節的名字,在符號表中的下標 Elf32_Word sh_type; //節的類型,描述符號,代碼,數據,重定位等 Elf32_Word sh_flags; //讀寫執行標記 Elf32_Addr sh_addr; //節在執行時的虛擬地址 Elf32_Off sh_offset; //節在文件中的偏移量 Elf32_Word sh_size; //節的大小 Elf32_Word sh_link; //其它節的索引 Elf32_Word sh_info; //節的其它信息 Elf32_Word sh_addralign; //節對齊 Elf32_Word sh_entsize; //節擁有固定大小項的大小 } Elf32_Shdr;
在ELF文件的ELF頭和節頭表直接是節頭表描述的節本身。一個典型的重定位目標文件包含了下面的節:
.text:機器代碼
.rodata:只讀數據,例如printf的字符串常量參數,jump跳轉表。
.data:已初始化的全局C變量。本地C變量在運行程序的棧中。
.bss:未初始化的全局變量。這個節在文件中並不占用實際的空間。
.symtab:符號表,包含了本模塊定義和引用的全局符號信息,不包含局部變量的信息。
.rel.text:.text節中需要重定位的信息。一般,任何調用外部函數或者引用全局變量的指令都需要被修改,但是,如果指令調用本地函數,則不需要被修改。重定位信息在可執行文件中可以不需要。
.rel.data:重定位任何被這個模塊定義和引用的全局變量信息。通常在一個全局變量的值是另一個全局變量地址或外部函數地址的情況下需要重新修改這個值。
.debug:包含調試信息
.line:在.text節中的機器指令與原始C代碼所在的行之間的映射。
.strtab:在.symtab和.debug節中所用的字符串表。

程序頭表項對應定義:
typedef struct elf32_phdr{ Elf32_Word p_type; //段的類型,LOAD,DYNAMIC等 Elf32_Off p_offset; //段在文件中的偏移量 Elf32_Addr p_vaddr; //段的物理地址 Elf32_Addr p_paddr; //段的虛擬地址 Elf32_Word p_filesz; //段在文件中的大小 Elf32_Word p_memsz; //段在內存中的大小 Elf32_Word p_flags; //讀寫執行標記 Elf32_Word p_align; //段的對齊 } Elf32_Phdr;
只有可執行文件和共享對象文件有程序頭表,程序頭表描述了程序中的段,以及程序文件中的節是映射到哪個段中的信息。當程序被裝載進內存中時,是以段為單位進行虛擬內存地址映射的。

2 符號和符號表
每個重定位目標模塊m都包含一個符號表來描述被m定義和引用的符號。在鏈接器上下文中,有以下三種符號:
- 全局鏈接符號。模塊m定義的,可以被其它模塊引用。全局鏈接符號包括非靜態的函數和非靜態的全局變量。
- 外部鏈接符號。被模塊m引用但定義在其它模塊的符號,可以是其它函數定義的全局鏈接符號。
- 本地鏈接符號。只能被模塊m內部使用,本地鏈接符號包括靜態函數和靜態變量(包括靜態全局變量和靜態局部變量)。
本地鏈接符號和本地程序符號是不同的,本地鏈接符號在.symtab表中出現,當本地程序符號不會出現在.symtab表中,而是出現在程序運行時棧中。
需要注意的是靜態成員變量和全局變量並不會出現在程序運行時棧中,編譯器會在.data或則.bss段分配空間給它們。
符號表是匯編器根據編譯器編譯到匯編語言中的符號建立的。符號表包含在節.symtab中,它是一個數組,數組的元素定義如下:
typedef struct elf32_sym{ Elf32_Word st_name; //符號名字,在字符串表中的下標 Elf32_Addr st_value; //符號在節的偏移量,或者虛擬地址 Elf32_Word st_size; //對象的字節大小 unsigned char st_info; //類型和綁定屬性,變量,函數,節,源文件名等類型,全局或則局部屬性,低四位表示類型,高四位表示屬性 unsigned char st_other; //沒有使用 Elf32_Half st_shndx; //符號所在的節在節頭表中的下標 } Elf32_Sym;
st_shndx表示符號所在節在節頭表中的下標,但存在三個特殊的虛擬節,這些節在節頭表中並沒有表項,如COMMON是表示未初始化的數據對象,UNDEF表示符號是未定義的,ABS表示符號不應該被重定位,就是用絕對地址表示。

3 解決全局符號多重定義
在編譯的時候,編譯器會告訴匯編器每個全局符號是強符號還是弱符號,匯編器則將這些信息放入重定位文件對象的符號表中。函數和初始化的全局變量是強符號,未初始化的全局變量是弱符號。
在給出了符號的強弱表示后,鏈接器就根據下面的規則來處理符號的多重定義:
- 多個強符號是不允許的
- 有一個強符號和多個弱符號,選擇強符號
- 有多個弱符號,選擇其中的任意一個
