第四章程序的鏈接_第1頁
第四章程序的鏈接_第2頁
第四章程序的鏈接_第3頁
第四章程序的鏈接_第4頁
第四章程序的鏈接_第5頁
已閱讀5頁,還剩89頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、第四章第四章 程序的鏈接程序的鏈接目標(biāo)文件格式符號(hào)解析與重定位共享庫與動(dòng)態(tài)鏈接可執(zhí)行文件的鏈接生成可執(zhí)行文件的鏈接生成 主要教學(xué)目標(biāo) 使學(xué)生了解鏈接器是如何工作的,從而能夠養(yǎng)成良好的程序設(shè)計(jì)習(xí)慣,并增加程序調(diào)試能力。 通過了解可執(zhí)行文件的存儲(chǔ)器映像來進(jìn)一步深入理解進(jìn)程的虛擬地址空間的概念。 包括以下內(nèi)容 鏈接和靜態(tài)鏈接概念 三種目標(biāo)文件格式 符號(hào)及符號(hào)表、符號(hào)解析 使用靜態(tài)庫鏈接 重定位信息及重定位過程 可執(zhí)行文件的存儲(chǔ)器映像 可執(zhí)行文件的加載 共享(動(dòng)態(tài))庫鏈接程序的鏈接程序的鏈接 分以下三個(gè)部分介紹 第一講:目標(biāo)文件格式 程序的鏈接概述、鏈接的意義與過程 ELF目標(biāo)文件、重定位目標(biāo)文件格式

2、、可執(zhí)行目標(biāo)文件格式 第二講:符號(hào)解析與重定位 符號(hào)和符號(hào)表、符號(hào)解析 與靜態(tài)庫的鏈接 重定位信息、重定位過程 可執(zhí)行文件的加載 第三講:動(dòng)態(tài)鏈接 動(dòng)態(tài)鏈接的特性、程序加載時(shí)的動(dòng)態(tài)鏈接、程序運(yùn)行時(shí)的動(dòng)態(tài)鏈接、動(dòng)態(tài)鏈接舉例一個(gè)典型程序的轉(zhuǎn)換處理過程一個(gè)典型程序的轉(zhuǎn)換處理過程#include int main()printf(hello, worldn);經(jīng)典的經(jīng)典的“ hello.c ”C-源程序源程序# i n c l u d e n n i n t m a i n ( ) n 104 62 10 10 105 110 116 32 109 97 105 110 40 41 10 123n p

3、 r i n t f ( h e l10 32 32 32 32 112 114 105 110 116 102 40 34 104 101 108l o , w o r l d n ) ; n 108 111 44 32 119 111 114 108 100 92 110 34 41 59 10 125hello.c的的ASCII文本表示文本表示功能:輸出“hello,world”預(yù)處理(cpp)編譯(cc1)匯編(as)鏈接(ld)printf.o計(jì)算機(jī)不能直接執(zhí)行hello.c!hello.c源程序(文本)hello.i源程序(文本)hello.s匯編語言程序(文本)hello.o可重

4、定位目標(biāo)程序(二進(jìn)制)hello可執(zhí)行目標(biāo)程序(二進(jìn)制)原始的鏈接概念早在高級(jí)編程語言出現(xiàn)之前就已存在最早程序員用機(jī)器語言編寫程序,并記錄在紙帶或卡片上鏈接器的由來鏈接器的由來穿孔表示0,未穿孔為10:0101 01101:0010 01012: 3: 4: 5:0110 01116: 假設(shè):0010-jmp若在第5條指令前加入指令,則程序員需重新計(jì)算jmp指令的目標(biāo)地址(重定位),然后重新打孔。太原始了,無法忍受,咋辦?用符號(hào)表示而不用0/1表示! 用符號(hào)表示跳轉(zhuǎn)位置和變量位置,是否簡化了問題? 匯編語言出現(xiàn) 用助記符表示操作碼 用符號(hào)表示位置 用助記符表示寄存器 . 更高級(jí)編程語言出現(xiàn) 程

5、序越來越復(fù)雜,需多人開發(fā)不同的程序模塊 子程序(函數(shù))起始地址和變量起始地址是符號(hào)定義(definition) 調(diào)用子程序(函數(shù)或過程)和使用變量即是符號(hào)的引用(reference) 一個(gè)模塊定義的符號(hào)可以被另一個(gè)模塊引用 最終須鏈接(即合并),合并時(shí)須在符號(hào)引用處填入定義處的地址如上例,先確定L0的地址,再在jmp指令中填入L0的地址鏈接器的由來鏈接器的由來0:0101 01101:0010 01012: 3: 4: 5:0110 01116: add B jmp L0 L0:sub C 使用鏈接的好處使用鏈接的好處鏈接帶來的好處1:模塊化(1)一個(gè)程序可以分成很多源程序文件(2)可構(gòu)建公共

6、函數(shù)庫,如數(shù)學(xué)庫,標(biāo)準(zhǔn)C庫等鏈接帶來的好處2:效率高(1)時(shí)間上,可分開編譯只需重新編譯被修改的源程序文件,然后重新鏈接(2)空間上,無需包含共享庫所有代碼 源文件中無需包含共享庫函數(shù)的源碼,只要直接調(diào)用即可 可執(zhí)行文件和運(yùn)行時(shí)的內(nèi)存中只需包含所調(diào)用函數(shù)的代碼 而不需要包含整個(gè)共享庫一個(gè)一個(gè)C語言程序舉例語言程序舉例int buf2 = 1, 2;void swap(); int main() swap(); return 0; main.cswap.cextern int buf; int *bufp0 = &buf0;static int *bufp1;void swap() in

7、t temp; bufp1 = &buf1; temp = *bufp0; *bufp0 = *bufp1; *bufp1 = temp;你能說出哪些是符號(hào)定義?哪些是符號(hào)的引用?局部變量temp分配在棧中,不會(huì)在過程外被引用,因此不是符號(hào)定義可執(zhí)行文件的生成可執(zhí)行文件的生成使用GCC編譯器編譯并鏈接生成可執(zhí)行程序P: $ gcc -O2 -g -o p main.c swap.c $ ./p鏈接 (ld)程序轉(zhuǎn)換(cpp, cc1, as)main.cmain.o程序轉(zhuǎn)換(cpp, cc1, as)swap.cswap.op源程序文件分別轉(zhuǎn)換(預(yù)處理、編譯、匯編)為可重定位目標(biāo)文件完

8、全可執(zhí)行的目標(biāo)文件GCC編譯器的靜態(tài)鏈接過程-O2:2級(jí)優(yōu)化-g:生成調(diào)試信息-o:目標(biāo)文件名鏈接過程的本質(zhì)鏈接過程的本質(zhì)main()main.oint *bufp0=&buf0swap()swap.o系統(tǒng)代碼int buf2=1,2系統(tǒng)數(shù)據(jù)可重定位目標(biāo)文件可執(zhí)行目標(biāo)文件.text.data.text.data.text.dataint buf2=1,2Headersmain()swap()0int *bufp0=&buf0更多系統(tǒng)代碼系統(tǒng)數(shù)據(jù).text.symtab.debug.dataint *bufp1.bss系統(tǒng)代碼static int *bufp1.bss鏈接本質(zhì):合

9、并相同的“節(jié)”目標(biāo)文件目標(biāo)文件00000000 : 0: 55 push %ebp 1: 89 e5 mov %esp, %ebp 3: 83 ec 10 sub $0 x10, %esp6: 8b 45 0c mov 0 xc(%ebp), %eax 9: 8b 55 08 mov 0 x8(%ebp), %edx c: 8d 04 02 lea (%edx,%eax,1), %eax f: 89 45 fc mov %eax, -0 x4(%ebp) 12: 8b 45 fc mov -0 x4(%ebp), %eax 15: c9 leave 16: c3 ret 080483d4 :

10、80483d4: 55 push %ebp 80483d5: 89 e5 mov %esp, %ebp 80483d7: 83 ec 10 sub $0 x10, %esp 80483da: 8b 45 0c mov 0 xc(%ebp), %eax 80483dd: 8b 55 08 mov 0 x8(%ebp), %edx 80483e0: 8d 04 02 lea (%edx,%eax,1), %eax 80483e3: 89 45 fc mov %eax, -0 x4(%ebp) 80483e6: 8b 45 fc mov -0 x4(%ebp), %eax 80483e9: c9 l

11、eave 80483ea: c3 ret objdump -d test.o objdump -d test /* main.c */int add(int, int);int main( ) return add(20, 13);/* test.c */int add(int i, int j) int x = i + j; return x;可執(zhí)行文件的存儲(chǔ)器映像可執(zhí)行文件的存儲(chǔ)器映像0%esp (棧頂)brk0 xC000000000 x08048000內(nèi)核虛存區(qū)共享庫區(qū)域堆(heap)(由malloc動(dòng)態(tài)生成)用戶棧(User stack)動(dòng)態(tài)生成未使用0 0讀寫數(shù)據(jù)段(.data,

12、.bss)只讀代碼段(.init, .text, .rodata)從可執(zhí)行文件裝入程序(段)頭表描述如何映射ELF 頭程序(段)頭表.text 節(jié).data 節(jié).bss 節(jié).symtab 節(jié).debug 節(jié).rodata 節(jié).line 節(jié).init 節(jié).strtab 節(jié)1GB鏈接操作的步驟鏈接操作的步驟1)確定標(biāo)號(hào)引用關(guān)系(符號(hào)解析)2)合并相關(guān).o文件3)確定每個(gè)標(biāo)號(hào)的地址4)在指令中填入新地址代碼數(shù)據(jù)P0: add B jmp L0 call P1 L0: sub C P1: add A sub B B: 10C: 20A: 30P1: add A sub B A: 30P1.oP0: a

13、dd B jmp L0 call P1 L0: sub C B: 10C: 20P0.o重定位鏈接操作的步驟鏈接操作的步驟 Step 1. 符號(hào)解析(Symbol resolution) 程序中有定義和引用的符號(hào) (包括變量和函數(shù)等) void swap() /* 定義符號(hào)swap */ swap(); /* 引用符號(hào)swap */ int *xp = &x; /* 定義符號(hào) xp, 引用符號(hào) x */ 編譯器將定義的符號(hào)存放在一個(gè)符號(hào)表( symbol table)中. 符號(hào)表是一個(gè)結(jié)構(gòu)數(shù)組 每個(gè)表項(xiàng)包含符號(hào)名、長度和位置等信息 鏈接器將每個(gè)符號(hào)的引用都與一個(gè)確定的符號(hào)定義建立關(guān)聯(lián)

14、Step 2. 重定位 將多個(gè)代碼段與數(shù)據(jù)段分別合并為一個(gè)單獨(dú)的代碼段和數(shù)據(jù)段 計(jì)算每個(gè)定義的符號(hào)在虛擬地址空間中的絕對(duì)地址 將可執(zhí)行文件中符號(hào)引用處的地址修改為重定位后的地址信息 add B jmp L0 L0:sub C 三類目標(biāo)文件三類目標(biāo)文件 可重定位目標(biāo)文件 (.o) 其代碼和數(shù)據(jù)可和其他可重定位文件合并為可執(zhí)行文件 每個(gè).o 文件由對(duì)應(yīng)的.c文件生成 每個(gè).o文件代碼和數(shù)據(jù)地址都從0開始 可執(zhí)行目標(biāo)文件 (默認(rèn)為a.out) 包含的代碼和數(shù)據(jù)可以被直接復(fù)制到內(nèi)存并被執(zhí)行 代碼和數(shù)據(jù)地址為虛擬地址空間中的地址 共享的目標(biāo)文件 (.so) 特殊的可重定位目標(biāo)文件,能在裝入或運(yùn)行時(shí)被裝入

15、到內(nèi)存并自動(dòng)被鏈接,稱為共享庫文件 Windows 中稱其為 Dynamic Link Libraries (DLLs) 目標(biāo)文件的格式目標(biāo)文件的格式 目標(biāo)代碼(Object Code)指編譯器和匯編器處理源代碼后所生成的機(jī)器語言目標(biāo)代碼 目標(biāo)文件(Object File)指包含目標(biāo)代碼的文件 最早的目標(biāo)文件格式是自有格式,非標(biāo)準(zhǔn)的 標(biāo)準(zhǔn)的幾種目標(biāo)文件格式 DOS操作系統(tǒng)(最簡單) :COM格式,文件中僅包含代碼和數(shù)據(jù),且被加載到固定位置 System V UNIX早期版本:COFF格式,文件中不僅包含代碼和數(shù)據(jù),還包含重定位信息、調(diào)試信息、符號(hào)表等其他信息,由一組嚴(yán)格定義的數(shù)據(jù)結(jié)構(gòu)序列組成

16、Windows: PE格式(COFF的變種),稱為可移植可執(zhí)行(Portable Executable,簡稱PE) Linux等類UNIX:ELF格式(COFF的變種),稱為可執(zhí)行可鏈接(Executable and Linkable Format,簡稱ELF)Executable and Linkable Format (ELF) 兩種視圖 鏈接視圖(被鏈接):Relocatable object files 執(zhí)行視圖(被執(zhí)行):Executable object files 節(jié)(section)是 ELF 文件中具有相同特征的最小可處理單位 .text節(jié): 代碼.data節(jié): 數(shù)據(jù).rod

17、ata: 只讀數(shù)據(jù).bss: 未初始化數(shù)據(jù)由不同的段(segment)組成,描述節(jié)如何映射到存儲(chǔ)段中,可多個(gè)節(jié)映射到同一段,如:可合并.data節(jié)和.bss節(jié),并映射到一個(gè)可讀可寫數(shù)據(jù)段中 鏈接視圖執(zhí)行視圖鏈接視圖鏈接視圖可重定位目標(biāo)文件可重定位目標(biāo)文件可被鏈接(合并)生成可執(zhí)行文件或共享目標(biāo)文件靜態(tài)鏈接庫文件由若干個(gè)可重定位目標(biāo)文件組成包含代碼、數(shù)據(jù)(已初始化.data和未初始化.bss)包含重定位信息(指出哪些符號(hào)引用處需要重定位)文件擴(kuò)展名為.o(相當(dāng)于Windows中的 .obj文件)int x=100; int y;void prn(int n) printf(“%dn”,n);vo

18、id main( ) static int a=1; static int b; int i=200,j; prn(x+a+i); ELF的鏈接視圖.text節(jié).data節(jié).bss節(jié)為了進(jìn)行鏈接,還需要其他許多信息,如符號(hào)表、重定位信息等許多其他的節(jié)(Section)可重定位目標(biāo)文件格式可重定位目標(biāo)文件格式ELF 頭 占16字節(jié),包括字長、字節(jié)序(大端/小端)、文件類型 (.o, exec, .so)、機(jī)器類型(如 IA-32)、節(jié)頭表的偏移、節(jié)頭表的表項(xiàng)大小及表項(xiàng)個(gè)數(shù).text 節(jié) 編譯后的代碼部分.rodata 節(jié) 只讀數(shù)據(jù),如 printf 格式串、switch 跳轉(zhuǎn)表等.data 節(jié)

19、已初始化的全局變量.bss 節(jié) 未初始化全局變量,僅是占位符,不占據(jù)任何實(shí)際磁盤空間。區(qū)分初始化和非初始化是為了空間效率0.strtab 節(jié).line 節(jié) switch-case語句舉例語句舉例int sw_test(int a, int b, int c) int result; switch(a) case 15: c=b&0 x0f; case 10: result=c+50; break; case 12: case 17: result=b+50; break; case 14: result=b break; default: result=a; return result

20、; 跳轉(zhuǎn)表在目標(biāo)文件的只讀節(jié)中,按4字節(jié)邊界對(duì)齊。Reax=a-10=iif (a-10)7 轉(zhuǎn) L5轉(zhuǎn).L8+4*i 處的地址1011121314151617a=ELF頭(頭(ELF Header)ELF頭位于ELF文件開始,包含文件結(jié)構(gòu)說明信息。分32位系統(tǒng)對(duì)應(yīng)結(jié)構(gòu)和64位系統(tǒng)對(duì)應(yīng)結(jié)構(gòu)(32位版本、64位版本)以下是32位系統(tǒng)對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)#define EI_NIDENT 16typedef struct unsigned char e_identEI_NIDENT; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_versio

21、n; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; Elf32_Ehdr;定義了ELF魔數(shù)、版本、小端/大端、操作系統(tǒng)平臺(tái)、目標(biāo)文件的類型、機(jī)器結(jié)構(gòu)類型、程序執(zhí)行的入口地址、程序頭表(段頭表)的起始位置和長度、節(jié)頭表的起始位置

22、和長度等魔數(shù):文件開頭幾個(gè)字節(jié)通常用來確定文件的類型或格式a.out的魔數(shù):01H 07HPE格式魔數(shù):4DH 5AH加載或讀取文件時(shí),可用魔數(shù)確認(rèn)文件類型是否正確ELF頭信息舉例頭信息舉例$ readelf -h main.o ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL

23、(Relocatable file) Machine: Intel 80386 Version: 0 x1 Entry point address: 0 x0 Start of program headers: 0 (bytes into file) Start of section headers: 516 (bytes into file) Flags: 0 x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section he

24、aders: 40 (bytes) Number of section headers: 15 Section header string table index: 12 0.strtab 節(jié).line 節(jié)可重定位目標(biāo)文件的ELF頭沒有程序頭表15x40B.strtab在節(jié)頭表中的索引ELF文件的魔數(shù)節(jié)頭表(節(jié)頭表(Section Header Table)除ELF頭之外,節(jié)頭表是ELF可重定位目標(biāo)文件中最重要的部分內(nèi)容描述每個(gè)節(jié)的節(jié)名、在文件中的偏移、大小、訪問屬性、對(duì)齊方式等以下是32位系統(tǒng)對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)(每個(gè)表項(xiàng)占40B)typedef struct Elf32_Word sh_name

25、; 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;節(jié)名字符串在.strtab中的偏移節(jié)類型:無效/代碼或數(shù)據(jù)/符號(hào)/字符串/節(jié)標(biāo)志:該節(jié)在虛擬空間中的訪問屬性虛擬地址:若可被加載,則對(duì)應(yīng)虛擬地址在文件中的偏移地址,對(duì).bss節(jié)而言則無意義節(jié)在文

26、件中所占的長度sh_link和sh_info用于與鏈接相關(guān)的節(jié)(如.rel.text節(jié)、.rel.data節(jié)、.symtab節(jié)等)節(jié)的對(duì)齊要求節(jié)中每個(gè)表項(xiàng)的長度,0表示無固定長度表項(xiàng)節(jié)頭表信息舉例節(jié)頭表信息舉例$ readelf -S test.o There are 11 section headers, starting at offset 0 x120:Section Headers: Nr Name Type Addr Off Size ES Flg Lk Inf Al 0 NULL 00000000 000000 000000 00 0 0 0 1 .text PROGBITS 00

27、000000 000034 00005b 00 AX 0 0 4 2 .rel.text REL 00000000 000498 000028 08 9 1 4 3 .data PROGBITS 00000000 000090 00000c 00 WA 0 0 4 4 .bss NOBITS 00000000 00009c 00000c 00 WA 0 0 4 5 .rodata PROGBITS 00000000 00009c 000004 00 A 0 0 1 6 .comment PROGBITS 00000000 0000a0 00002e 00 0 0 1 7 .note.GNU-s

28、tack PROGBITS 00000000 0000ce 000000 00 0 0 1 8 .shstrtab STRTAB 00000000 0000ce 000051 00 0 0 1 9 .symtab SYMTAB 00000000 0002d8 000120 10 10 13 4 10 .strtab STRTAB 00000000 0003f8 00009e 00 0 0 1Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (gr

29、oup), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)0.strtab 節(jié).line 節(jié)可重定位目標(biāo)文件中,每個(gè)可裝入節(jié)的起始地址總是0節(jié)頭表信息舉例節(jié)頭表信息舉例$ readelf -S test.o There are 11 section headers, starting at offset 0 x120:Section Headers: Nr Name Off Size ES Flg Lk Inf Al 0 000000 000000 00 0 0 0 1 .

30、text 000034 00005b 00 AX 0 0 4 2 .rel.text 000498 000028 08 9 1 4 3 .data 000090 00000c 00 WA 0 0 4 4 .bss 00009c 00000c 00 WA 0 0 4 5 .rodata 00009c 000004 00 A 0 0 1 6 .comment 0000a0 00002e 00 0 0 1 7 .note.GNU-stack 0000ce 000000 00 0 0 1 8 .shstrtab 0000ce 000051 00 0 0 1 9 .symtab 0002d8 00012

31、0 10 10 13 4 10 .strtab 0003f8 00009e 00 0 0 1Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) .有4個(gè)節(jié)將會(huì)分配(A)存儲(chǔ)空間.text:可執(zhí)行.data和.bss:可讀可寫.rodata:可讀ELF頭e_shoff=ment.shstrtab節(jié)頭表.symtab.strtab.rel.text00000000003400009000009c0000a000

32、00ce.bss0001200002d80003f800049800011f5b0c040c2e511b81209e2800008f000496可重定位目標(biāo)文件test.o的結(jié)構(gòu)執(zhí)行視圖執(zhí)行視圖可執(zhí)行目標(biāo)文件可執(zhí)行目標(biāo)文件包含代碼、數(shù)據(jù)(已初始化.data和未初始化.bss)定義的所有變量和函數(shù)已有確定地址(虛擬地址空間中的地址)符號(hào)引用處已被重定位,以指向所引用的定義符號(hào)沒有文件擴(kuò)展名或默認(rèn)為a.out(相當(dāng)于Windows中的 .exe文件)可被CPU直接執(zhí)行,指令地址和指令給出的操作數(shù)地址都是虛擬地址為了能執(zhí)行,還需將具相同訪問屬性的節(jié)合并成段(Segment),并說明每個(gè)段的屬性,如:

33、在可執(zhí)行文件中的位移、大小、在虛擬空間中的位置、對(duì)齊方式、訪問屬性等int x=100; int y;void prn(int n) printf(“%dn”,n);void main( ) static int a=1; static int b; int i=200,j; prn(x+a+i); ELF的執(zhí)行視圖.text節(jié).data節(jié).bss節(jié)程序頭表用來說明段信息,也稱段頭表可執(zhí)行目標(biāo)文件格式可執(zhí)行目標(biāo)文件格式.bss 節(jié)程序頭表.init 節(jié).data 節(jié).strtab 節(jié).line 節(jié)只讀(代碼)段讀寫(數(shù)據(jù))段無需裝入到存儲(chǔ)空間的信息 與可重定位文件稍有不同: ELF頭中字段e_

34、entry給出執(zhí)行程序時(shí)第一條指令的地址,而在可重定位文件中,此字段為0 多一個(gè)程序頭表,也稱段頭表(segment header table),是一個(gè)結(jié)構(gòu)數(shù)組 多一個(gè).init節(jié),用于定義_init函數(shù),該函數(shù)用來進(jìn)行可執(zhí)行目標(biāo)文件開始執(zhí)行時(shí)的初始化工作 少兩個(gè).rel節(jié)(無需重定位)ELF頭信息舉例頭信息舉例$ readelf -h main ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2s complement, little endian Version: 1

35、 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0 x1 Entry point address: x8048580 Start of program headers: 52 (bytes into file) Start of section headers: 3232 (bytes into file) Flags: 0 x0 Size of this header: 52 (bytes) Size of program

36、headers: 32 (bytes) Number of program headers: 8 Size of section headers: 40 (bytes) Number of section headers: 29 Section header string table index: 26 可執(zhí)行目標(biāo)文件的ELF頭.bss 節(jié)程序頭表.init 節(jié).data 節(jié).strtab 節(jié).line 節(jié)29x40B8x32B可執(zhí)行文件的存儲(chǔ)器映像可執(zhí)行文件的存儲(chǔ)器映像0%esp (棧頂)brk0 xC000000000 x08048000內(nèi)核虛存區(qū)共享庫區(qū)域堆(heap)(由malloc動(dòng)

37、態(tài)生成)用戶棧(User stack)動(dòng)態(tài)生成未使用0 0讀寫數(shù)據(jù)段(.data, .bss)只讀代碼段(.init, .text, .rodata)從可執(zhí)行文件裝入程序(段)頭表描述如何映射ELF 頭程序(段)頭表.text 節(jié).data 節(jié).bss 節(jié).symtab 節(jié).debug 節(jié).rodata 節(jié).line 節(jié).init 節(jié).strtab 節(jié)可執(zhí)行文件中的程序頭表可執(zhí)行文件中的程序頭表typedef struct Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_W

38、ord p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; Elf32_Phdr;程序頭表描述可執(zhí)行文件中的節(jié)與虛擬空間中的存儲(chǔ)段之間的映射關(guān)系一個(gè)表項(xiàng)(32B)說明虛擬地址空間中一個(gè)連續(xù)的段或一個(gè)特殊的節(jié) 以下是某可執(zhí)行目標(biāo)文件程序頭表信息有8個(gè)表項(xiàng),其中兩個(gè)為可裝入段(即Type=LOAD)$ readelf l main可執(zhí)行文件中的程序頭表可執(zhí)行文件中的程序頭表第一可裝入段:第0 x000000 x004d3字節(jié)(包括ELF頭、程序頭表、.init、.text和.rodata節(jié)),映射到虛擬地址0

39、x8048000開始長度為0 x4d4字節(jié)的區(qū)域,按0 x1000=212=4KB對(duì)齊,具有只讀/執(zhí)行權(quán)限(Flg=RE),是只讀代碼段。第二可裝入段:第0 x000f0c開始長度為0 x108字節(jié)的.data節(jié),映射到虛擬地址0 x8049f0c開始長度為0 x110字節(jié)的存儲(chǔ)區(qū)域,在0 x110=272B存儲(chǔ)區(qū)中,前0 x108=264B用.data節(jié)內(nèi)容初始化,后面272-264=8B對(duì)應(yīng).bss節(jié),初始化為0,按0 x1000=4KB對(duì)齊,具有可讀可寫權(quán)限(Flg=RW),是可讀寫數(shù)據(jù)段。SKIP可執(zhí)行文件的存儲(chǔ)器映像可執(zhí)行文件的存儲(chǔ)器映像00000%esp (棧頂)brk0 xC00

40、0000000 x08048000內(nèi)核虛存區(qū)共享庫區(qū)域堆(heap)(由malloc動(dòng)態(tài)生成)用戶棧(User stack)動(dòng)態(tài)生成未使用0 0讀寫數(shù)據(jù)段(.data, .bss)只讀代碼段(.init, .text, .rodata)從可執(zhí)行文件裝入程序(段)頭表描述如何映射ELF 頭程序(段)頭表.text 節(jié).data 節(jié).bss 節(jié).symtab 節(jié).debug 節(jié).rodata 節(jié).line 節(jié).init 節(jié).strtab 節(jié)BACK1GB004d300f0c010140101c0 x08049000程序的鏈接程序的鏈接 分以下三個(gè)部分介紹 第一講:目標(biāo)文件格式 程序的鏈接概述、鏈接

41、的意義與過程 ELF目標(biāo)文件、重定位目標(biāo)文件格式、可執(zhí)行目標(biāo)文件格式 第二講:符號(hào)解析與重定位 符號(hào)和符號(hào)表、符號(hào)解析 與靜態(tài)庫的鏈接 重定位信息、重定位過程 可執(zhí)行文件的加載 第三講:動(dòng)態(tài)鏈接 動(dòng)態(tài)鏈接的特性、程序加載時(shí)的動(dòng)態(tài)鏈接、程序運(yùn)行時(shí)的動(dòng)態(tài)鏈接、動(dòng)態(tài)鏈接舉例符號(hào)和符號(hào)解析符號(hào)和符號(hào)解析 每個(gè)可重定位目標(biāo)模塊m都有一個(gè)符號(hào)表,它包含了在m中定義的符號(hào)。有三種鏈接器符號(hào):Global symbols(模塊內(nèi)部定義的全局符號(hào)) 由模塊m定義并能被其他模塊引用的符號(hào)。例如,非static 函數(shù)和非static的全局變量(指不帶static的全局變量) 如,main.c 中的全局變量名bufE

42、xternal symbols(外部定義的全局符號(hào)) 由其他模塊定義并被模塊m引用的全局符號(hào) 如,main.c 中的函數(shù)名swapLocal symbols(本模塊的局部符號(hào)) 僅由模塊m定義和引用的本地符號(hào)。例如,在模塊m中定義的帶static的函數(shù)和全局變量如,swap.c 中的static變量名bufp1 鏈接器局部符號(hào)不是指程序中的局部變量(分配在棧中的臨時(shí)性變量),鏈接器不關(guān)心這種局部變量符號(hào)和符號(hào)解析符號(hào)和符號(hào)解析int buf2 = 1, 2;extern void swap();int main() swap(); return 0; main.cextern int buf;

43、 int *bufp0 = &buf0;static int *bufp1;void swap() int temp; bufp1 = &buf1; temp = *bufp0; *bufp0 = *bufp1; *bufp1 = temp;swap.c你能說出哪些是全局符號(hào)?哪些是外部符號(hào)?哪些是局部符號(hào)?目標(biāo)文件中的符號(hào)表目標(biāo)文件中的符號(hào)表 符號(hào)表(symtab)中每個(gè)條目的結(jié)構(gòu)如下:typedef struct int name; /*符號(hào)對(duì)應(yīng)字符串在strtab節(jié)中的偏移量*/ int value; /*在對(duì)應(yīng)節(jié)中的偏移量,可執(zhí)行文件中是虛擬地址*/ int size;

44、 /*符號(hào)對(duì)應(yīng)目標(biāo)所占字節(jié)數(shù)*/ char type: 4, /*符號(hào)對(duì)應(yīng)目標(biāo)的類型:數(shù)據(jù)、函數(shù)、源文件、節(jié)*/ binding: 4; /*符號(hào)類別:全局符號(hào)、局部符號(hào)、弱符號(hào)*/ char reserved; char section; /*符號(hào)對(duì)應(yīng)目標(biāo)所在的節(jié),或其他情況*/ Elf_Symbol;其他情況:ABS表示不該被重定位;UND表示未定義;COM表示未初始化數(shù)據(jù)(.bss),此時(shí),value表示對(duì)齊要求,size給出最小大小.symtab 節(jié)記錄符號(hào)表信息,是一個(gè)結(jié)構(gòu)數(shù)組函數(shù)名在text節(jié)中變量名在data節(jié)或bss節(jié)中函數(shù)大小或變量長度目標(biāo)文件中的符號(hào)表目標(biāo)文件中的符號(hào)表 m

45、ain.o中的符號(hào)表中最后三個(gè)條目(共10個(gè))Num:valueSizeTypeBindOtNdxName8:08DataGlobal 03buf9:033FuncGlobal 01main10:00Notype Global 0UNDswap swap.o中的符號(hào)表中最后4個(gè)條目(共11個(gè))Num:valueSizeType Bind OtNdxName8:04 Data Global 03bufp09:00 Notype Global 0UND buf10:036 Func Global 01swap11:44 Data Local 0COMbufp1buf是main.o中第3節(jié)(.dat

46、a)偏移為0的符號(hào),是全局變量,占8B; main是第1節(jié)(.text)偏移為0的符號(hào),是全局函數(shù),占33B; swap是main.o中未定義全局(在其他模塊定義)符號(hào),類型和大小未知bufp1是未分配地址且未初始化的本地變量(ndx=COM), 按4B對(duì)齊且占4B符號(hào)解析(符號(hào)解析(Symbol Resolution)目的:將每個(gè)模塊中引用的符號(hào)與某個(gè)目標(biāo)模塊中的定義符號(hào)建立關(guān)聯(lián)。每個(gè)定義符號(hào)在代碼段或數(shù)據(jù)段中都被分配了存儲(chǔ)空間,將引用符號(hào)與定義符號(hào)建立關(guān)聯(lián)后,就可在重定位時(shí)將引用符號(hào)的地址重定位為相關(guān)聯(lián)的定義符號(hào)的地址。本地符號(hào)在本模塊內(nèi)定義并引用,因此,其解析較簡單,只要與本模塊內(nèi)唯一的

47、定義符號(hào)關(guān)聯(lián)即可。全局符號(hào)(外部定義的、內(nèi)部定義的)的解析涉及多個(gè)模塊,故較復(fù)雜 “符號(hào)的定義”其實(shí)質(zhì)是什么?指被分配了存儲(chǔ)空間。為函數(shù)名即指其代碼所在區(qū);為變量名即指其所占的靜態(tài)數(shù)據(jù)區(qū)。 add B jmp L0 L0:sub 23 B: 確定L0的地址,再在jmp指令中填入L0的地址所有定義符號(hào)的值就是其目標(biāo)所在的首地址符號(hào)解析也稱符號(hào)綁定全局符號(hào)的符號(hào)解析全局符號(hào)的符號(hào)解析全局符號(hào)的強(qiáng)/弱特性 函數(shù)名和已初始化的全局變量名是強(qiáng)符號(hào) 未初始化的全局變量名是弱符號(hào) int var=5;p1() int var;p2() p1.cp2.c以下符號(hào)哪些是強(qiáng)符號(hào)?哪些是弱符號(hào)?全局符號(hào)的符號(hào)解析全

48、局符號(hào)的符號(hào)解析int buf2 = 1, 2;void swap();int main() swap(); return 0; main.cextern int buf; int *bufp0 = &buf0;static int *bufp1;void swap() int temp; bufp1 = &buf1; temp = *bufp0; *bufp0 = *bufp1; *bufp1 = temp;swap.c此處為引用本地局部符號(hào)局部變量以下符號(hào)哪些是強(qiáng)符號(hào)?哪些是弱符號(hào)?鏈接器對(duì)符號(hào)的解析規(guī)則鏈接器對(duì)符號(hào)的解析規(guī)則 多重定義符號(hào)的處理規(guī)則 Rule 1: 強(qiáng)符號(hào)

49、不能多次定義 強(qiáng)符號(hào)只能被定義一次,否則鏈接錯(cuò)誤 Rule 2: 若一個(gè)符號(hào)被定義為一次強(qiáng)符號(hào)和多次弱符號(hào),則按強(qiáng)定義為準(zhǔn) 對(duì)弱符號(hào)的引用被解析為其強(qiáng)定義符號(hào) Rule 3: 若有多個(gè)弱符號(hào)定義,則任選其中一個(gè) 使用命令 gcc fno-common鏈接時(shí),會(huì)告訴鏈接器在遇到多個(gè)弱定義的全局符號(hào)時(shí)輸出一條警告信息。符號(hào)解析時(shí)只能有一個(gè)確定的定義(即每個(gè)符號(hào)僅占一處存儲(chǔ)空間)多重定義符號(hào)的解析舉例多重定義符號(hào)的解析舉例int x=10;int p1(void);int main() x=p1(); return x;main.cint x=20; int p1() return x;p1.cma

50、in只有一次強(qiáng)定義p1有一次強(qiáng)定義,一次弱定義x有兩次強(qiáng)定義,所以,鏈接器將輸出一條出錯(cuò)信息 以下程序會(huì)發(fā)生鏈接出錯(cuò)嗎?多重定義符號(hào)的解析舉例多重定義符號(hào)的解析舉例p1.cy一次強(qiáng)定義,一次弱定義z兩次弱定義p1一次強(qiáng)定義,一次弱定義main一次強(qiáng)定義# include int y=100;int z;void p1(void);int main() z=1000; p1( ); printf(“y=%d, z=%dn”, y, z); return 0;main.cint y;int z;void p1( ) y=200; z=2000;問題:打印結(jié)果是什么?y=200,z=2000以下程序

51、會(huì)發(fā)生鏈接出錯(cuò)嗎?該例說明:在兩個(gè)不同模塊定義相同變量名,很可能發(fā)生意想不到的結(jié)果 !多重定義符號(hào)的解析舉例多重定義符號(hào)的解析舉例p1.c該例說明:兩個(gè)重復(fù)定義的變量具有不同類型時(shí),更容易出現(xiàn)難以理解的結(jié)果 ! main.c問題:打印結(jié)果是什么?d=0,x=1 072 693 248 以下程序會(huì)發(fā)生鏈接出錯(cuò)嗎?1 #include 2 int d=100;3 int x=200;4 void p1(void);5 int main() 6 7 p1();8 printf(“d=%d,x=%dn”,d,x);9 return 0;10 1 double d;23 void p1() 4 5 d=

52、1.0;6 p1執(zhí)行后d和x處內(nèi)容是什么?FLD1FSTPl &d1.0:0 01111111111 00B =3FF0 0000 0000 0000H多重定義符號(hào)的解析舉例多重定義符號(hào)的解析舉例打印結(jié)果:d=0,x=1 072 693 248Why? 1 double d;2 3 void p1( ) 4 5 d=1.0;6 .1 int d=100;2 int x=200;3 int main() 4 5 p1( );6 printf (“d=%d, x=%dn”, d, x );7 return 0;8 main.c p1.cdouble型數(shù)1.0對(duì)應(yīng)的機(jī)器數(shù)3FF0 0000

53、0000 0000H 低高IA-32是小端方式230-1-(220-1)=230-220=1024*1024*1023=1 072 693 248多重定義全局符號(hào)的問題多重定義全局符號(hào)的問題 盡量避免使用全局變量 一定需要用的話,就按以下規(guī)則使用 盡量使用本地變量(static) 全局變量要賦初值 外部全局變量要使用extern多重定義全局變量會(huì)造成一些意想不到的錯(cuò)誤,而且是默默發(fā)生的,編譯系統(tǒng)不會(huì)警告,并會(huì)在程序執(zhí)行很久后才能表現(xiàn)出來,且遠(yuǎn)離錯(cuò)誤引發(fā)處。特別是在一個(gè)具有幾百個(gè)模塊的大型軟件中,這類錯(cuò)誤很難修正。大部分程序員并不了解鏈接器如何工作,因而養(yǎng)成良好的編程習(xí)慣是非常重要的。如何劃分模

54、塊?如何劃分模塊? 許多函數(shù)無需自己寫,可使用共享庫函數(shù) 如數(shù)學(xué)庫, 輸入/輸出庫, 存儲(chǔ)管理庫,字符串處理等 避免以下兩種極端做法 將所有函數(shù)都放在一個(gè)源文件中 修改一個(gè)函數(shù)需要對(duì)所有函數(shù)重新編譯 時(shí)間和空間兩方面的效率都不高 一個(gè)源文件中僅包含一個(gè)函數(shù) 需要程序員顯式地進(jìn)行鏈接 效率高,但模塊太多,故太繁瑣靜態(tài)共享庫靜態(tài)共享庫 靜態(tài)庫 (.a archive files) 將所有相關(guān)的目標(biāo)模塊(.o)打包為一個(gè)單獨(dú)的庫文件(.a),稱為靜態(tài)庫文件 ,也稱存檔文件(archive) 增強(qiáng)了鏈接器功能,使其能通過查找一個(gè)或多個(gè)庫文件中的符號(hào)來解析符號(hào) 在構(gòu)建可執(zhí)行文件時(shí)只需指定庫文件名,鏈接器

55、會(huì)自動(dòng)到庫中尋找那些應(yīng)用程序用到的目標(biāo)模塊,并且只把用到的模塊從庫中拷貝出來 在gcc命令行中無需明顯指定C標(biāo)準(zhǔn)庫libc.a(默認(rèn)庫)靜態(tài)庫的創(chuàng)建靜態(tài)庫的創(chuàng)建轉(zhuǎn)換(cpp,cc1,as)atoi.catoi.o轉(zhuǎn)換(cpp,cc1,as)printf.cprintf.olibc.aArchiver (ar).random.crandom.o$ ar rcs libc.a atoi.o printf.o random.oC標(biāo)準(zhǔn)靜態(tài)庫Archiver(歸檔器)允許增量更新,只要重新編譯需修改的源碼并將其.o文件替換到靜態(tài)庫中。轉(zhuǎn)換(cpp,cc1,as)常用靜態(tài)庫常用靜態(tài)庫libc.a ( C標(biāo)

56、準(zhǔn)庫 ) 1392個(gè)目標(biāo)文件(大約8 MB) 包含I/O、存儲(chǔ)分配、信號(hào)處理、字符串處理、時(shí)間和日期、隨機(jī)數(shù)生成、定點(diǎn)整數(shù)算術(shù)運(yùn)算libm.a (the C math library) 401 個(gè)目標(biāo)文件(大約 1 MB) 浮點(diǎn)數(shù)算術(shù)運(yùn)算(如sin, cos, tan, log, exp, sqrt, ) % ar -t /usr/lib/libc.a | sort fork.o fprintf.o fpu_control.o fputc.o freopen.o fscanf.o fseek.o fstab.o % ar -t /usr/lib/libm.a | sort e_acos.o e

57、_acosf.o e_acosh.o e_acoshf.o e_acoshl.o e_acosl.o e_asin.o e_asinf.o e_asinl.o 自定義一個(gè)靜態(tài)庫文件自定義一個(gè)靜態(tài)庫文件# include void myfunc1() printf(This is myfunc1!n); # include void myfunc2() printf(This is myfunc2n); $ gcc c myproc1.c myproc2.c$ ar rcs mylib.a myproc1.o myproc2.omyproc1.cmyproc2.c舉例:將myproc1.o和my

58、proc2.o打包生成mylib.avoid myfunc1(viod); int main() myfunc1(); return 0; main.c調(diào)用關(guān)系:mainmyfunc1printf$ gcc c main.c $ gcc static o myproc main.o ./mylib.alibc.a無需明顯指出!問題:如何進(jìn)行符號(hào)解析?鏈接器中符號(hào)解析的全過程鏈接器中符號(hào)解析的全過程 void myfunc1(viod); int main() myfunc1(); return 0; main.c調(diào)用關(guān)系:mainmyfunc1printf$ gcc c main.c $ gc

59、c static o myproc main.o ./mylib.a開始E、U、D為空,首先掃描main.o,把它加入E,同時(shí)把myfun1加入U(xiǎn),main加入D。接著掃描到mylib.a,將U中所有符號(hào)(本例中為myfunc1)與mylib.a中所有目標(biāo)模塊(myproc1.o和myproc2.o)依次匹配,發(fā)現(xiàn)在myproc1.o中定義了myfunc1,故myproc1.o加入E,myfunc1從U轉(zhuǎn)移到D。在myproc1.o中發(fā)現(xiàn)還有未解析符號(hào)printf,將其加到U。不斷在mylib.a的各模塊上進(jìn)行迭代以匹配U中的符號(hào),直到U、D都不再變化。此時(shí)U中只有一個(gè)未解析符號(hào)printf,

60、而D中有main和myfunc1。因?yàn)槟Kmyproc2.o沒有被加入E中,因而它被丟棄。E 將被合并以組成可執(zhí)行文件的所有目標(biāo)文件集合U 當(dāng)前所有未解析的引用符號(hào)的集合D 當(dāng)前所有定義符號(hào)的集合 接著,掃描默認(rèn)的庫文件libc.a,發(fā)現(xiàn)其目標(biāo)模塊printf.o定義了printf,于是printf也從U移到D,并將printf.o加入E,同時(shí)把它定義的所有符號(hào)加入D,而所有未解析符號(hào)加入U(xiǎn)。處理完libc.a時(shí),U一定是空的。 libc.a無需明顯指出!鏈接器中符號(hào)解析的全過程鏈接器中符號(hào)解析的全過程 main.cvoid myfunc1(viod); int main() myfunc1(); return 0; $ gcc static o myproc main.

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論