程序的鏈接課件資料_第1頁
程序的鏈接課件資料_第2頁
程序的鏈接課件資料_第3頁
程序的鏈接課件資料_第4頁
程序的鏈接課件資料_第5頁
已閱讀5頁,還剩89頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

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

2、接重定位信息、重定位過程可執(zhí)行文件的加載第三講:動態(tài)鏈接動態(tài)鏈接的特性、程序加載時的動態(tài)鏈接、程序運行時的動態(tài)鏈接、動態(tài)鏈接舉例一個典型程序的轉(zhuǎn)換處理過程#include int main()printf(hello, worldn);經(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 r i n t f ( h e l10 32 32 32 32 112 114 105 110 116 102 40 34 10

3、4 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”預處理(cpp)編譯(cc1)匯編(as)鏈接(ld)printf.o計算機不能直接執(zhí)行hello.c!hello.c源程序(文本)hello.i源程序(文本)hello.s匯編語言程序(文本)hello.o可重定位目標程序(二進制)hello可執(zhí)行目標程序(二進制)原始的鏈接概念早在高級編程語言出現(xiàn)之前就已存在最早程序員用機器語言編寫程序,并記錄在

4、紙帶或卡片上鏈接器的由來穿孔表示0,未穿孔為10:0101 01101:0010 01012: 3: 4: 5:0110 01116: 假設:0010-jmp若在第5條指令前加入指令,則程序員需重新計算jmp指令的目標地址(重定位),然后重新打孔。太原始了,無法忍受,咋辦?用符號表示而不用0/1表示!用符號表示跳轉(zhuǎn)位置和變量位置,是否簡化了問題?匯編語言出現(xiàn)用助記符表示操作碼用符號表示位置用助記符表示寄存器.更高級編程語言出現(xiàn)程序越來越復雜,需多人開發(fā)不同的程序模塊子程序(函數(shù))起始地址和變量起始地址是符號定義(definition)調(diào)用子程序(函數(shù)或過程)和使用變量即是符號的引用(refer

5、ence)一個模塊定義的符號可以被另一個模塊引用最終須鏈接(即合并),合并時須在符號引用處填入定義處的地址如上例,先確定L0的地址,再在jmp指令中填入L0的地址鏈接器的由來0:0101 01101:0010 01012: 3: 4: 5:0110 01116: add B jmp L0 L0:sub C 使用鏈接的好處鏈接帶來的好處1:模塊化(1)一個程序可以分成很多源程序文件(2)可構(gòu)建公共函數(shù)庫,如數(shù)學庫,標準C庫等鏈接帶來的好處2:效率高(1)時間上,可分開編譯只需重新編譯被修改的源程序文件,然后重新鏈接(2)空間上,無需包含共享庫所有代碼 源文件中無需包含共享庫函數(shù)的源碼,只要直接調(diào)

6、用即可 可執(zhí)行文件和運行時的內(nèi)存中只需包含所調(diào)用函數(shù)的代碼 而不需要包含整個共享庫一個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() int temp; bufp1 = &buf1; temp = *bufp0; *bufp0 = *bufp1; *bufp1 = temp;你能說出哪些是符號定義?哪些是符號的引用?局部變量temp分配在棧中,不會在過程外被引用

7、,因此不是符號定義可執(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)換(預處理、編譯、匯編)為可重定位目標文件完全可執(zhí)行的目標文件GCC編譯器的靜態(tài)鏈接過程-O2:2級優(yōu)化-g:生成調(diào)試信息-o:目標文件名鏈接過程的本質(zhì)main()main.oint *bufp0=&buf0swap()swap.o系統(tǒng)代碼int buf2=1,2系統(tǒng)數(shù)據(jù)可重定位目標文件可執(zhí)行目標

8、文件.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ì):合并相同的“節(jié)”目標文件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 x

9、8(%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 : 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 mo

10、v 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 leave 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

11、 j) int x = i + j; return x;可執(zhí)行文件的存儲器映像0%esp (棧頂)brk0 xC000000000 x08048000內(nèi)核虛存區(qū)共享庫區(qū)域堆(heap)(由malloc動態(tài)生成)用戶棧(User stack)動態(tài)生成未使用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é)1GB鏈接操作的步驟1)確定標號引用關(guān)系(符號解

12、析)2)合并相關(guān).o文件3)確定每個標號的地址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: add B jmp L0 call P1 L0: sub C B: 10C: 20P0.o重定位鏈接操作的步驟Step 1. 符號解析(Symbol resolution)程序中有定義和引用的符號 (包括變量和函數(shù)等)void swap() /* 定義符號swap */swap(); /* 引用符號swap */int *xp =

13、&x; /* 定義符號 xp, 引用符號 x */編譯器將定義的符號存放在一個符號表( symbol table)中.符號表是一個結(jié)構(gòu)數(shù)組每個表項包含符號名、長度和位置等信息鏈接器將每個符號的引用都與一個確定的符號定義建立關(guān)聯(lián)Step 2. 重定位將多個代碼段與數(shù)據(jù)段分別合并為一個單獨的代碼段和數(shù)據(jù)段計算每個定義的符號在虛擬地址空間中的絕對地址將可執(zhí)行文件中符號引用處的地址修改為重定位后的地址信息 add B jmp L0 L0:sub C 三類目標文件 可重定位目標文件 (.o)其代碼和數(shù)據(jù)可和其他可重定位文件合并為可執(zhí)行文件每個.o 文件由對應的.c文件生成每個.o文件代碼和數(shù)據(jù)地址都從0

14、開始可執(zhí)行目標文件 (默認為a.out)包含的代碼和數(shù)據(jù)可以被直接復制到內(nèi)存并被執(zhí)行代碼和數(shù)據(jù)地址為虛擬地址空間中的地址共享的目標文件 (.so)特殊的可重定位目標文件,能在裝入或運行時被裝入到內(nèi)存并自動被鏈接,稱為共享庫文件Windows 中稱其為 Dynamic Link Libraries (DLLs) 目標文件的格式目標代碼(Object Code)指編譯器和匯編器處理源代碼后所生成的機器語言目標代碼目標文件(Object File)指包含目標代碼的文件最早的目標文件格式是自有格式,非標準的標準的幾種目標文件格式DOS操作系統(tǒng)(最簡單) :COM格式,文件中僅包含代碼和數(shù)據(jù),且被加載到

15、固定位置System V UNIX早期版本:COFF格式,文件中不僅包含代碼和數(shù)據(jù),還包含重定位信息、調(diào)試信息、符號表等其他信息,由一組嚴格定義的數(shù)據(jù)結(jié)構(gòu)序列組成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í)行):Exec

16、utable object files 節(jié)(section)是 ELF 文件中具有相同特征的最小可處理單位 .text節(jié): 代碼.data節(jié): 數(shù)據(jù).rodata: 只讀數(shù)據(jù).bss: 未初始化數(shù)據(jù)由不同的段(segment)組成,描述節(jié)如何映射到存儲段中,可多個節(jié)映射到同一段,如:可合并.data節(jié)和.bss節(jié),并映射到一個可讀可寫數(shù)據(jù)段中 鏈接視圖執(zhí)行視圖鏈接視圖可重定位目標文件可被鏈接(合并)生成可執(zhí)行文件或共享目標文件靜態(tài)鏈接庫文件由若干個可重定位目標文件組成包含代碼、數(shù)據(jù)(已初始化.data和未初始化.bss)包含重定位信息(指出哪些符號引用處需要重定位)文件擴展名為.o(相當于Wi

17、ndows中的 .obj文件)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的鏈接視圖.text節(jié).data節(jié).bss節(jié)為了進行鏈接,還需要其他許多信息,如符號表、重定位信息等許多其他的節(jié)(Section)可重定位目標文件格式ELF 頭占16字節(jié),包括字長、字節(jié)序(大端/小端)、文件類型 (.o, exec, .so)、機器類型(如 IA-32)、節(jié)頭表的偏移、節(jié)頭表的表項大小及表項個數(shù).text 節(jié)編

18、譯后的代碼部分.rodata 節(jié)只讀數(shù)據(jù),如 printf 格式串、switch 跳轉(zhuǎn)表等.data 節(jié)已初始化的全局變量.bss 節(jié)未初始化全局變量,僅是占位符,不占據(jù)任何實際磁盤空間。區(qū)分初始化和非初始化是為了空間效率ELF 頭.text 節(jié).rodata 節(jié).bss 節(jié).symtab 節(jié).rel.txt 節(jié).rel.data 節(jié).debug 節(jié)Section header table(節(jié)頭表)0.data 節(jié).strtab 節(jié).line 節(jié) switch-case語句舉例int sw_test(int a, int b, int c) int result; switch(a) case

19、 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; 跳轉(zhuǎn)表在目標文件的只讀節(jié)中,按4字節(jié)邊界對齊。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)對應結(jié)構(gòu)和64位系統(tǒng)對應結(jié)構(gòu)(32位版本、64位版本)以下是3

20、2位系統(tǒ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_version; 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_she

21、ntsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; Elf32_Ehdr;定義了ELF魔數(shù)、版本、小端/大端、操作系統(tǒng)平臺、目標文件的類型、機器結(jié)構(gòu)類型、程序執(zhí)行的入口地址、程序頭表(段頭表)的起始位置和長度、節(jié)頭表的起始位置和長度等魔數(shù):文件開頭幾個字節(jié)通常用來確定文件的類型或格式a.out的魔數(shù):01H 07HPE格式魔數(shù):4DH 5AH加載或讀取文件時,可用魔數(shù)確認文件類型是否正確ELF頭信息舉例$ readelf -h main.o ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00

22、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 (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

23、 file) Flags: 0 x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 15 Section header string table index: 12 ELF 頭.text 節(jié).rodata 節(jié).bss 節(jié).symtab 節(jié).rel.txt 節(jié).rel.data 節(jié).debug 節(jié)Section header(節(jié)

24、頭表)0.data 節(jié).strtab 節(jié).line 節(jié)可重定位目標文件的ELF頭沒有程序頭表15x40B.strtab在節(jié)頭表中的索引ELF文件的魔數(shù)節(jié)頭表(Section Header Table)除ELF頭之外,節(jié)頭表是ELF可重定位目標文件中最重要的部分內(nèi)容描述每個節(jié)的節(jié)名、在文件中的偏移、大小、訪問屬性、對齊方式等以下是32位系統(tǒng)對應的數(shù)據(jù)結(jié)構(gòu)(每個表項占40B)typedef struct Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offs

25、et; 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ù)/符號/字符串/節(jié)標志:該節(jié)在虛擬空間中的訪問屬性虛擬地址:若可被加載,則對應虛擬地址在文件中的偏移地址,對.bss節(jié)而言則無意義節(jié)在文件中所占的長度sh_link和sh_info用于與鏈接相關(guān)的節(jié)(如.rel.text節(jié)、.rel.data節(jié)、.symtab節(jié)等)節(jié)的對齊要求節(jié)中每個表項的長度

26、,0表示無固定長度表項節(jié)頭表信息舉例$ readelf -S test.oThere are 11 section headers, starting at offset 0 x120:Section Headers: Nr Name Type Addr Off Size ES Flg Lk Inf Al 0 NULL00000000 000000 000000 00 0 0 0 1 .text PROGBITS00000000 000034 00005b 00 AX 0 0 4 2 .rel.text REL 00000000 000498 000028 08 9 1 4 3 .data P

27、ROGBITS00000000 000090 00000c 00 WA 0 0 4 4 .bss NOBITS00000000 00009c 00000c 00 WA 0 0 4 5 .rodata PROGBITS00000000 00009c 000004 00 A 0 0 1 6 .comment PROGBITS00000000 0000a0 00002e 00 0 0 1 7 .note.GNU-stackPROGBITS00000000 0000ce 000000 00 0 0 1 8 .shstrtab STRTAB 00000000 0000ce 000051 00 0 0 1

28、 9 .symtab SYMTAB00000000 0002d8 000120 10 10 134 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 (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)ELF 頭.te

29、xt 節(jié).rodata 節(jié).bss 節(jié).symtab 節(jié).rel.txt 節(jié).rel.data 節(jié).debug 節(jié)Section header(節(jié)頭表)0.data 節(jié).strtab 節(jié).line 節(jié)可重定位目標文件中,每個可裝入節(jié)的起始地址總是0節(jié)頭表信息舉例$ readelf -S test.oThere 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 .text 000034 000

30、05b 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 000120 10 10 134 10

31、.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個節(jié)將會分配存儲空間.text:可執(zhí)行.data和.bss:可讀可寫.rodata:可讀ELF頭e_shoff=0 ment.shstrtab節(jié)頭表.symtab.strtab.rel.text00000000003400009000009c0000a00000ce.bss0001200002

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

33、訪問屬性等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í)行目標文件格式ELF 頭.text 節(jié).rodata 節(jié).bss 節(jié).symtab 節(jié)程序頭表.init 節(jié).debug 節(jié)Section header table(節(jié)頭表).data 節(jié).strtab 節(jié).line 節(jié)只讀(代碼)段讀寫(數(shù)據(jù))段無需裝入到存儲空間的

34、信息與可重定位文件稍有不同:ELF頭中字段e_entry給出執(zhí)行程序時第一條指令的地址,而在可重定位文件中,此字段為0多一個程序頭表,也稱段頭表(segment header table),是一個結(jié)構(gòu)數(shù)組多一個.init節(jié),用于定義_init函數(shù),該函數(shù)用來進行可執(zhí)行目標文件開始執(zhí)行時的初始化工作少兩個.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 en

35、dian Version: 1 (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) S

36、ize of program 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í)行目標文件的ELF頭ELF 頭.text 節(jié).rodata 節(jié).bss 節(jié).symtab 節(jié)程序頭表.init 節(jié).debug 節(jié)Section header table(節(jié)頭表).data 節(jié).strtab 節(jié).line 節(jié)29x40B8x32B可執(zhí)行文件的

37、存儲器映像0%esp (棧頂)brk0 xC000000000 x08048000內(nèi)核虛存區(qū)共享庫區(qū)域堆(heap)(由malloc動態(tài)生成)用戶棧(User stack)動態(tài)生成未使用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í)行文件中的程序頭表typedef struct Elf32_Word p_type; Elf32_Off p_o

38、ffset; 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;程序頭表描述可執(zhí)行文件中的節(jié)與虛擬空間中的存儲段之間的映射關(guān)系一個表項(32B)說明虛擬地址空間中一個連續(xù)的段或一個特殊的節(jié) 以下是某可執(zhí)行目標文件程序頭表信息有8個表項,其中兩個為可裝入段(即Type=LOAD)$ readelf l main可執(zhí)行文件中的程序頭表第一可裝入段:第0 x000000 x004d3字節(jié)(

39、包括ELF頭、程序頭表、.init、.text和.rodata節(jié)),映射到虛擬地址0 x8048000開始長度為0 x4d4字節(jié)的區(qū)域,按0 x1000=212=4KB對齊,具有只讀/執(zhí)行權(quán)限(Flg=RE),是只讀代碼段。第二可裝入段:第0 x000f0c開始長度為0 x108字節(jié)的.data節(jié),映射到虛擬地址0 x8049f0c開始長度為0 x110字節(jié)的存儲區(qū)域,在0 x110=272B存儲區(qū)中,前0 x108=264B用.data節(jié)內(nèi)容初始化,后面272-264=8B對應.bss節(jié),初始化為0,按0 x1000=4KB對齊,具有可讀可寫權(quán)限(Flg=RW),是可讀寫數(shù)據(jù)段。SKIP可執(zhí)

40、行文件的存儲器映像00000%esp (棧頂)brk0 xC000000000 x08048000內(nèi)核虛存區(qū)共享庫區(qū)域堆(heap)(由malloc動態(tài)生成)用戶棧(User stack)動態(tài)生成未使用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程序的鏈接分以下三個部分

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

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

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

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

45、eSizeTypeBindOtNdxName8:08DataGlobal 03buf9:033FuncGlobal01main10:00NotypeGlobal0UNDswapswap.o中的符號表中最后4個條目(共11個)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é)(.data)偏移為0的符號,是全局變量,占8B; main是第1節(jié)(.text

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

47、 “符號的定義”其實質(zhì)是什么?指被分配了存儲空間。為函數(shù)名即指其代碼所在區(qū);為變量名即指其所占的靜態(tài)數(shù)據(jù)區(qū)。 add B jmp L0 L0:sub 23 B: 確定L0的地址,再在jmp指令中填入L0的地址所有定義符號的值就是其目標所在的首地址符號解析也稱符號綁定全局符號的符號解析全局符號的強/弱特性函數(shù)名和已初始化的全局變量名是強符號未初始化的全局變量名是弱符號 int var=5;p1() int var;p2() p1.cp2.c以下符號哪些是強符號?哪些是弱符號?全局符號的符號解析int buf2 = 1, 2;void swap();int main() swap(); retur

48、n 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此處為引用本地局部符號局部變量以下符號哪些是強符號?哪些是弱符號?鏈接器對符號的解析規(guī)則多重定義符號的處理規(guī)則 Rule 1: 強符號不能多次定義強符號只能被定義一次,否則鏈接錯誤 Rule 2: 若一個符號被定義為一次強符號和多次弱符號,則按強定義為準對弱符號的引用被解析為其強定義符號 Ru

49、le 3: 若有多個弱符號定義,則任選其中一個使用命令 gcc fno-common鏈接時,會告訴鏈接器在遇到多個弱定義的全局符號時輸出一條警告信息。符號解析時只能有一個確定的定義(即每個符號僅占一處存儲空間)多重定義符號的解析舉例int x=10;int p1(void);int main() x=p1(); return x;main.cint x=20; int p1() return x;p1.cmain只有一次強定義p1有一次強定義,一次弱定義x有兩次強定義,所以,鏈接器將輸出一條出錯信息 以下程序會發(fā)生鏈接出錯嗎?多重定義符號的解析舉例p1.cy一次強定義,一次弱定義z兩次弱定義p

50、1一次強定義,一次弱定義main一次強定義# 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以下程序會發(fā)生鏈接出錯嗎?該例說明:在兩個不同模塊定義相同變量名,很可能發(fā)生意想不到的結(jié)果 !多重定義符號的解析舉例p1.c該例說明:兩個重復定義的變量具有不同類型時,更容易出現(xiàn)難以理解的結(jié)果 ! main.c問題:打

51、印結(jié)果是什么?d=0,x=1 072 693 248 以下程序會發(fā)生鏈接出錯嗎?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=1.0;6 p1執(zhí)行后d和x處內(nèi)容是什么?FLD1FSTPl &d1.0:0 01111111111 00B =3FF0 0000 0000 0000H多重定義符號的解析舉例打印結(jié)果:d=0,x=1 072 693 248Why?

52、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對應的機器數(shù)3FF0 0000 0000 0000H 低高IA-32是小端方式230-1-(220-1)=230-220=1024*1024*1023=1 072 693 248多重定義全局符號的問題盡量避免使用全局變量一定需要用的話,就按以下規(guī)則使用盡量使用本地變量(static)全局變量要

53、賦初值外部全局變量要使用extern多重定義全局變量會造成一些意想不到的錯誤,而且是默默發(fā)生的,編譯系統(tǒng)不會警告,并會在程序執(zhí)行很久后才能表現(xiàn)出來,且遠離錯誤引發(fā)處。特別是在一個具有幾百個模塊的大型軟件中,這類錯誤很難修正。大部分程序員并不了解鏈接器如何工作,因而養(yǎng)成良好的編程習慣是非常重要的。如何劃分模塊?許多函數(shù)無需自己寫,可使用共享庫函數(shù)如數(shù)學庫, 輸入/輸出庫, 存儲管理庫,字符串處理等避免以下兩種極端做法將所有函數(shù)都放在一個源文件中修改一個函數(shù)需要對所有函數(shù)重新編譯時間和空間兩方面的效率都不高一個源文件中僅包含一個函數(shù)需要程序員顯式地進行鏈接效率高,但模塊太多,故太繁瑣靜態(tài)共享庫靜態(tài)

54、庫 (.a archive files)將所有相關(guān)的目標模塊(.o)打包為一個單獨的庫文件(.a),稱為靜態(tài)庫文件 ,也稱存檔文件(archive)增強了鏈接器功能,使其能通過查找一個或多個庫文件中的符號來解析符號在構(gòu)建可執(zhí)行文件時只需指定庫文件名,鏈接器會自動到庫中尋找那些應用程序用到的目標模塊,并且只把用到的模塊從庫中拷貝出來在gcc命令行中無需明顯指定C標準庫libc.a(默認庫)靜態(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 rc

55、s libc.a atoi.o printf.o random.oC標準靜態(tài)庫Archiver(歸檔器)允許增量更新,只要重新編譯需修改的源碼并將其.o文件替換到靜態(tài)庫中。轉(zhuǎn)換(cpp,cc1,as)常用靜態(tài)庫libc.a ( C標準庫 )1392個目標文件(大約8 MB)包含I/O、存儲分配、信號處理、字符串處理、時間和日期、隨機數(shù)生成、定點整數(shù)算術(shù)運算libm.a (the C math library)401 個目標文件(大約 1 MB)浮點數(shù)算術(shù)運算(如sin, cos, tan, log, exp, sqrt, ) % ar -t /usr/lib/libc.a | sort for

56、k.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_acosf.o e_acosh.o e_acoshf.o e_acoshl.o e_acosl.o e_asin.o e_asinf.o e_asinl.o 自定義一個靜態(tài)庫文件# include void myfunc1() printf(This is myfunc1!n); # include void myfunc2() printf(This is myfunc

57、2n); $ gcc c myproc1.c myproc2.c$ ar rcs mylib.a myproc1.o myproc2.omyproc1.cmyproc2.c舉例:將myproc1.o和myproc2.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無需明顯指出!問題:如何進行符號解析?鏈接器中符號解析的全過程 void myfu

58、nc1(viod); int main() myfunc1(); return 0; main.c調(diào)用關(guān)系:mainmyfunc1printf$ gcc c main.c $ gcc static o myproc main.o ./mylib.a開始E、U、D為空,首先掃描main.o,把它加入E,同時把myfun1加入U,main加入D。接著掃描到mylib.a,將U中所有符號(本例中為myfunc1)與mylib.a中所有目標模塊(myproc1.o和myproc2.o)依次匹配,發(fā)現(xiàn)在myproc1.o中定義了myfunc1,故myproc1.o加入E,myfunc1從U轉(zhuǎn)移到D。在m

59、yproc1.o中發(fā)現(xiàn)還有未解析符號printf,將其加到U。不斷在mylib.a的各模塊上進行迭代以匹配U中的符號,直到U、D都不再變化。此時U中只有一個未解析符號printf,而D中有main和myfunc1。因為模塊myproc2.o沒有被加入E中,因而它被丟棄。E 將被合并以組成可執(zhí)行文件的所有目標文件集合U 當前所有未解析的引用符號的集合D 當前所有定義符號的集合 接著,掃描默認的庫文件libc.a,發(fā)現(xiàn)其目標模塊printf.o定義了printf,于是printf也從U移到D,并將printf.o加入E,同時把它定義的所有符號加入D,而所有未解析符號加入U。處理完libc.a時,U

60、一定是空的。 libc.a無需明顯指出!鏈接器中符號解析的全過程 main.cvoid myfunc1(viod); int main() myfunc1(); return 0; $ gcc static o myproc main.o ./mylib.a解析結(jié)果:E中有main.o、myproc1.o、printf.o及其調(diào)用的模塊D中有main、myproc1、printf及其引用的符號mainmyfunc1printf轉(zhuǎn)換(cpp,cc1,as)main.cmain.omylib.aprintf.o及其調(diào)用模塊myproc靜態(tài)鏈接器(ld)Libc.amyproc1.o完全鏈接的可執(zhí)行

溫馨提示

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

評論

0/150

提交評論