實例分析ELF文件靜態鏈接


參考文獻:

ELF V1.2

《程序員的自我修養---鏈接、裝載與庫》第4章 靜態鏈接

開發平台:

[thm@tanghuimin static_link]$ uname -a Linux tanghuimin 2.6.32-358.el6.x86_64 #1 SMP Fri Feb 22 00:31:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux 

1.ELF文件格式概貌

readelf -h 查看elf文件頭部信息可以看到Type值有三種:RELEXECDYN

REL文件是只被編譯沒有被鏈接過的文件,其格式屬於左邊一種,elf header+section1,2,3...+section header table,每個section對應一個section header table entrysection header table為各個section提供索引。沒有被鏈接過的文件沒有program header,不能被加載到內存中運行,readelf -l會提示”There are no program headers in this file”

EXECDYN文件屬於被鏈接過的文件,其格式屬於右邊一種,elf header+program header table+segment1,2,3...+section header table。每個segment對應一個program header table entryprogram header table為各個segment提供索引。EXECDYN文件有program headers,可以被加載到內存中運行,readelf -l可以看到一個segment是由一個或多個section構成,TypeLOADsegment可以被加載到內存中運行,其他類型的segment提供輔助信息。

 

 

 

2.實例分析

 

 

(1)創建文件

 

創建文件common.c

int val = 1; int func(void) { return (val+10); } 

創建文件test.c

extern int val; extern int func(void); int main() { val = 10; func(); return 0; } 

 

 

(2)編譯

 

編譯兩個.c文件

gcc -c test.c

gcc -c common.c

生成的test.ocommon.o屬於REL類型

來分析一下編譯后生成的REL文件

(2.1)先看看test.o:

readelf -s test.o查看test.o的符號表

 

 

[thm@tanghuimin static_link]$ readelf -s test.o Symbol table '.symtab' contains 11 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000     0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000     0 FILE LOCAL DEFAULT ABS test.c 2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000    26 FUNC    GLOBAL DEFAULT    1 main 9: 0000000000000000     0 NOTYPE GLOBAL DEFAULT UND val 10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND func

 

因為valfunc不是在test.c中定義的,所以這兩個符號的Ndx(符號所在sectionindex)為UND。為了能讓程序順利執行,我們希望在未來鏈接的過程中可以從其他文件中找到valfunc這兩個符號,並確定這兩個符號的地址,確定未定義符號的地址的過程即是“重定位”(relocation)

readelf -S test.o可以看到test.osection header table

[thm@tanghuimin static_link]$ readelf test.o -S There are 12 section headers, starting at offset 0x128: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0]                   NULL             0000000000000000  00000000 
       0000000000000000  0000000000000000           0     0     0 [ 1] .text             PROGBITS         0000000000000000  00000040 000000000000001a 0000000000000000  AX       0     0     4 [ 2] .rela.text        RELA             0000000000000000  00000548 
       0000000000000030  0000000000000018          10     1     8 [ 3] .data             PROGBITS         0000000000000000 0000005c 0000000000000000  0000000000000000  WA       0     0     4 [ 4] .bss              NOBITS           0000000000000000 0000005c 0000000000000000  0000000000000000  WA       0     0     4 [ 5] .comment          PROGBITS         0000000000000000 0000005c 000000000000002d 0000000000000001  MS       0     0     1 [ 6] .note.GNU-stack   PROGBITS         0000000000000000  00000089 
       0000000000000000  0000000000000000           0     0     1 [ 7] .eh_frame         PROGBITS         0000000000000000  00000090 
       0000000000000038  0000000000000000   A       0     0     8 [ 8] .rela.eh_frame    RELA             0000000000000000  00000578 
       0000000000000018  0000000000000018          10     7     8 [ 9] .shstrtab         STRTAB           0000000000000000 000000c8 0000000000000059  0000000000000000           0     0     1 [10] .symtab           SYMTAB           0000000000000000  00000428 
       0000000000000108  0000000000000018          11     8     8 [11] .strtab           STRTAB           0000000000000000  00000530 
       0000000000000016  0000000000000000           0     0     1 Key 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) 

我們重點關注rela section

可以看到rela.text entry的描述中,link=10info=1link表示被重定位的符號所在的符號表的section indexinfo表示需要被重定位的sectionindex,通俗點講就是,將來有朝一日我知道了該符號的地址,我該把這個地址寫到哪個section里面去,這里是.text

readelf -r test.o可以看到rel section里的詳細信息。

 

[thm@tanghuimin static_link]$ readelf test.o -r Relocation section '.rela.text' at offset 0x548 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000006  000900000002 R_X86_64_PC32     0000000000000000 val - 8 00000000000f 000a00000002 R_X86_64_PC32 0000000000000000 func - 4 Relocation section '.rela.eh_frame' at offset 0x578 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0 

 

offset表示該符號在被重定位的section中的偏移,info的高4個字節表示該符號在.symtab中的index,低4字節表示重定位的類型,不同的類型計算目標地址的方法不一樣。

綜上所述,我們可以得出符號valfunc的種種信息:

val的重定位地址是在.text的偏移為6處,將來的鏈接過程中,連接器要將val的地址寫到這個位置上來,val.symtab中的index9

func的重定位地址是在.text的偏移為f處,將來的鏈接過程中,連接器要將func的地址寫到這個位置上來,func.symtab中的為a

關於重定位的類型,《ELF V1.2》的第5793頁有詳細說明。

這里兩個符號的類型 R_X86_64_PC32,重定位地址的計算方法為S+A-P,即符號地址和下條指令間的偏移量。

objdump -S test.o查看匯編文件

 

[thm@tanghuimin static_link]$ objdump -S test.o test.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0:    55                       push   %rbp 1:    48 89 e5                 mov    %rsp,%rbp 4:    c7 05 00 00 00 00 0a     movl   $0xa,0x0(%rip)        # e <main+0xe> b: 00 00 00 e: e8 00 00 00 00           callq  13 <main+0x13> 
  13:    b8 00 00 00 00           mov    $0x0,%eax 18: c9 leaveq 19:    c3                       retq   

 

可以看到.text中偏移6處四個字節(val的地址)為全0,偏移f處四個字節(func的地址)為全0

(2.2)再來看看common.o:

readelf -s查看common.o的符號表

 

...... 8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 val 9: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 func

 

可以看到val定義在index3.data里,func定義在index1.text里,這兩個符號都是在common.c文件內部定義的。

readelf -S查看common.osection header table

...... [ 2] .rela.text        RELA             0000000000000000  00000528 
       0000000000000018  0000000000000018          10     1     8 ......

readelf -r查看common.o的重定位詳細信息

 

Relocation section '.rela.text' at offset 0x528 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000006  000800000002 R_X86_64_PC32     0000000000000000 val – 4 ......

 

以上信息可以得出,需要被重定位的符號是val,它在.symtab中的index8,需要被重定位的地址是在.text中偏移為6處,重定位類型為 R_X86_64_PC32,即.text偏移為6處的地址是val地址和下一條指令間的偏移。

Objdump -S查看common.o的匯編文件:

 

...... 0000000000000000 <func>: 0:    55                       push   %rbp 1:    48 89 e5                 mov    %rsp,%rbp 4:    8b 05 00 00 00 00        mov    0x0(%rip),%eax        # a <func+0xa> a: 83 c0 0a                 add    $0xa,%eax d: c9 leaveq e: c3 retq 

 

可以看到偏移為6處的四個字節(val的地址)全為0,需要在鏈接的時候寫入val的地址。

 

 

(3)鏈接

 

將兩個.o文件鏈接,

 

gcc -o test test.o common.o

 

生成的testEXEC類型

 


 

靜態鏈接的過程引用《程序員的自我修養》第101頁的概述:

 

第一步:空間與地址分配

 

掃描所有的輸入目標文件,獲得它們的各個段的長度、屬性和位置,並且將輸入目標文件中的符號表中所有的符號定義和符號引用收集起來,統一放到一個全局符號表。這一步中,連接器將能獲得所有輸入目標文件的段長度,並且將它們合並,計算出輸出文件中各個段合並后的長度與位置,並建立映射關系。

 

第二步:符號解析與重定位

 

使用上面第一步中收集到的所有信息,讀取輸入文件中段的數據、重定位信息,並且進行符號解析與重定位、調整代碼中的地址等。事實上第二步是鏈接過程的核心,特別是重定位的過程。

 


 

提取關鍵字可以是:合並段,全局符號表,重定位

 

來看看重定位之后的test文件

 

readelf -l查看test進程在內存中的映像分布:

 

...... LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 
                 0x0000000000000664 0x0000000000000664  R E    200000 LOAD 0x0000000000000668 0x0000000000600668 0x0000000000600668 
                 0x00000000000001e8 0x00000000000001f8  RW     200000 ......

 

可以看到text segment被映射到虛擬地址0x400000處,data segment被映射到虛擬地址0x600668處。

readelf test -s查看test的符號表

 

...... 54: 000000000060084c     4 OBJECT  GLOBAL DEFAULT   24 val ...... 57: 0000000000400490    15 FUNC    GLOBAL DEFAULT   13 func ...... 64: 0000000000400474    26 FUNC    GLOBAL DEFAULT   13 main ......

 

反匯編

objdump -S test > test.S

...... 0000000000400474 <main>: 114   400474:       55                      push   %rbp 115   400475:       48 89 e5                mov    %rsp,%rbp 116   400478:       c7 05 ca 03 20 00 0a    movl   $0xa,0x2003ca(%rip)        # 60084c <val> 
117   40047f:       00 00 00 
118   400482:       e8 09 00 00 00          callq  400490 <func> 
119    :       b8 00 00 00 00          mov    $0x0,%eax 120 40048c: c9 leaveq 121 40048d: c3 retq 122   40048e:       90 nop 123   40048f:       90 nop 124 
125 0000000000400490 <func>: 126   400490:       55                      push   %rbp 127   400491:       48 89 e5                mov    %rsp,%rbp 128   400494:       8b 05 b2 03 20 00       mov    0x2003b2(%rip),%eax        # 60084c <val> 
129   40049a:       83 c0 0a                add    $0xa,%eax 130 40049d: c9 leaveq 131 40049e: c3 retq 132   40049f:       90 nop ...... 

main函數中

地址0x400478處:

400478: c7 05 ca 03 20 00 0a movl $0xa,0x2003ca(%rip) # 60084c <val>

%rip+0x2003ca=0x400482+0x2003ca=0x60084c=val的地址

地址0x400482處:

118 400482: e8 09 00 00 00 callq 400490 <func>

該條指令的下一條指令地址為0x4004870x400487+0x09=0x400490=func的地址

func

地址0x 400494處:

128 400494: 8b 05 b2 03 20 00 mov 0x2003b2(%rip),%eax # 60084c <val>

%rip+ 0x2003b2= 0x40049a+0x2003b2=0x60084c=val的地址

由此可見這三處重定位的地址都為符號地址與下條指令間的偏移,符合上面分析的重定位類型。  

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM