1. 程式人生 > >linux核心之elf格式

linux核心之elf格式

圖 18.1. ELF檔案


2.2.1目標檔案

下面用readelf工具讀出目標檔案max.o的ELF Header和Section Header Table,然後我們逐段分析。

$ readelf -a max.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          200 (bytes into file) //從檔案地址200(0xc8)開始,
 // 每個Section Header佔40位元組,共40*8=320位元組,到檔案地址0x207結束
。 Flags: 0x0 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: 8 Section header string table index: 5 2.2.2 section頭 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     00000000 000034 00002a 00  AX  0   0  4   [ 2] .rel.text         REL             00000000 0002b0 000010 08      6   1  4   [ 3] .data             PROGBITS    00000000 000060 000038 00  WA  0   0  4   [ 4] .bss              NOBITS         00000000 000098 000000 00  WA  0   0  4   [ 5] .shstrtab         STRTAB       00000000 000098 000030 00      0   0  1   [ 6] .symtab           SYMTAB      00000000 000208 000080 10      7   7  4   [ 7] .strtab           STRTAB          00000000 000288 000028 00      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) There are no program headers in this file.

注:從Section Header中讀出各Section的描述資訊,

其中.text.data是我們在彙編程式中宣告的Section,而其它Section是彙編器自動新增的。

Addr是這些段載入到記憶體中的地址(我們講過程式中的地址都是虛擬地址),載入地址要在連結時填寫,現在空缺,所以是全0。

OffSize列指出了各Section的起始檔案地址和長度。比如.data段從檔案地址0x60開始,一共0x38個位元組,回去翻一下程式,.data段定義了14個4位元組的整數,一共是56個位元組,也就是0x38。根據以上資訊可以描繪出整個目標檔案的佈局。

表 18.1. 目標檔案的佈局

起始檔案地址 Section或Header
0 ELF Header
0x34 .text
0x60 .data
0x98 .bss(此段為空)
0x98 .shstrtab
0xc8 Section Header Table
0x208 .symtab
0x288 .strtab
0x2b0 .rel.text
這個檔案不大,我們直接用hexdump工具把目標檔案的位元組全部打印出來看。
》hexdump -C max.o
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 03 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  c8 00 00 00 00 00 00 00  34 00 00 00 00 00 28 00  |........4.....(.|
00000030  08 00 05 00 bf 00 00 00  00 8b 04 bd 00 00 00 00  |................|
00000040  89 c3 83 f8 00 74 10 47  8b 04 bd 00 00 00 00 39  |.....t.G.......9|
00000050  d8 7e ef 89 c3 eb eb b8  01 00 00 00 cd 80 00 00  |.~..............|
00000060  03 00 00 00 43 00 00 00  22 00 00 00 de 00 00 00  |....C...".......|
00000070  2d 00 00 00 4b 00 00 00  36 00 00 00 22 00 00 00  |-...K...6..."...|
00000080  2c 00 00 00 21 00 00 00  16 00 00 00 0b 00 00 00  |,...!...........|
00000090  42 00 00 00 00 00 00 00  00 2e 73 79 6d 74 61 62  |B.........symtab|
000000a0  00 2e 73 74 72 74 61 62  00 2e 73 68 73 74 72 74  |..strtab..shstrt|
000000b0  61 62 00 2e 72 65 6c 2e  74 65 78 74 00 2e 64 61  |ab..rel.text..da|
000000c0  74 61 00 2e 62 73 73 00  00 00 00 00 00 00 00 00  |ta..bss.........|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

我們繼續分析readelf輸出的最後一部分,是從.rel.text.symtab這兩個Section中讀出的資訊。

...
Relocation section '.rel.text' at offset 0x2b0 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000008  00000201 R_386_32          00000000   .data
00000017  00000201 R_386_32          00000000   .data

There are no unwind sections in this file.

Symbol table '.symtab' contains 8 entries:
   Num:              Value      Size     Type    Bind            Vis      Ndx Name
     0:             00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1:             00000000     0 SECTION LOCAL  DEFAULT    1
     2:             00000000     0 SECTION LOCAL  DEFAULT    3
     3:            00000000     0 SECTION LOCAL  DEFAULT    4
     4:            00000000     0 NOTYPE  LOCAL  DEFAULT    3     data_items
     5:            0000000e     0 NOTYPE  LOCAL  DEFAULT    1     start_loop
     6:          00000023      0 NOTYPE  LOCAL  DEFAULT    1     loop_exit
     7:         00000000       0 NOTYPE  GLOBAL DEFAULT  1   _start

No version information found in this file.

注: .rel.text告訴連結器指令中的哪些地方需要做重定位,在下一小節詳細討論。 .symtab是符號表。Ndx列是每個符號所在的Section編號,
例如符號data_items在第3個Section裡(也就是.data段),
各Section的編號見Section Header Table。
Value列是每個符號所代表的地址,在目標檔案中,符號地址都是相對於該符號所在Section的相對地址,
比如data_items位於.data段的開頭,所以地址是0,_start位於.text段的開頭,所以地址也是0,
但是start_looploop_exit相對於.text段的地址就不是0了。
從Bind這一列可以看出_start這個符號是GLOBAL的,而其它符號是LOCAL的,
GLOBAL符號是在彙編程式中用.globl指示宣告過的符號。

2.2.3

現在剩下.text段沒有分析,objdump工具可以把程式中的機器指令反彙編(Disassemble),那麼反彙編的結果是否跟原來寫的彙編程式碼一模一樣呢?我們對比分析一下。

$ objdump -d max.o

max.o:     file format elf32-i386

Disassembly of section .text:

00000000 <_start>:
   0:	bf 00 00 00 00       	mov    $0x0,%edi
   5:	8b 04 bd 00 00 00 00 	mov    0x0(,%edi,4),%eax
   c:	89 c3                	mov    %eax,%ebx

0000000e <start_loop>:
   e:	83 f8 00             	cmp    $0x0,%eax
  11:	74 10                	je     23 <loop_exit>
  13:	47                   	inc    %edi
  14:	8b 04 bd 00 00 00 00 	mov    0x0(,%edi,4),%eax
  1b:	39 d8                	cmp    %ebx,%eax
  1d:	7e ef                	jle    e <start_loop>
  1f:	89 c3                	mov    %eax,%ebx
  21:	eb eb                	jmp    e <start_loop>

00000023 <loop_exit>:
  23:	b8 01 00 00 00       	mov    $0x1,%eax
  28:	cd 80                	int    $0x80

2.3. 可執行檔案

現在我們按上一節的步驟分析可執行檔案max,看看連結器都做了什麼改動。

$ readelf -a max
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048074
  Start of program headers:          52 (bytes into file)
  Start of section headers:          256 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         2
  Size of section headers:           40 (bytes)
  Number of section headers:         6
  Section header string table index: 3

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        08048074 000074 00002a 00  AX  0   0  4
  [ 2] .data             PROGBITS        080490a0 0000a0 000038 00  WA  0   0  4
  [ 3] .shstrtab         STRTAB          00000000 0000d8 000027 00      0   0  1
  [ 4] .symtab           SYMTAB          00000000 0001f0 0000a0 10      5   6  4
  [ 5] .strtab           STRTAB          00000000 000290 000040 00      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)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x0009e 0x0009e R E 0x1000
  LOAD           0x0000a0 0x080490a0 0x080490a0 0x00038 0x00038 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text
   01     .data 

There is no dynamic section in this file.

There are no relocations in this file.

There are no unwind sections in this file.

Symbol table '.symtab' contains 10 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 08048074     0 SECTION LOCAL  DEFAULT    1
     2: 080490a0     0 SECTION LOCAL  DEFAULT    2
     3: 080490a0     0 NOTYPE  LOCAL  DEFAULT    2 data_items
     4: 08048082     0 NOTYPE  LOCAL  DEFAULT    1 start_loop
     5: 08048097     0 NOTYPE  LOCAL  DEFAULT    1 loop_exit
     6: 08048074     0 NOTYPE  GLOBAL DEFAULT    1 _start
     7: 080490d8     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
     8: 080490d8     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     9: 080490d8     0 NOTYPE  GLOBAL DEFAULT  ABS _end

No version information found in this file.

在ELF Header中,Type改成了EXEC,由目標檔案變成可執行檔案了,Entry point address改成了0x8048074(這是_start符號的地址),還可以看出,多了兩個Program Header,少了兩個Section Header。

在Section Header Table中,.text.data段的載入地址分別改成了0x08048074和0x080490a0。.bss段沒有用到,所以被刪掉了。.rel.text段就是用於連結過程的,做完連結就沒用了,所以也刪掉了。

多出來的Program Header Table描述了兩個Segment的資訊。.text段和前面的ELF Header、Program Header Table一起組成一個Segment(FileSiz指出總長度是0x9e),.data段組成另一個Segment(總長度是0x38)。VirtAddr列指出第一個Segment載入到虛擬地址0x08048000(注意在x86平臺上後面的PhysAddr列是沒有意義的,並不代表實際的實體地址),第二個Segment載入到地址0x080490a0。Flg列指出第一個Segment的訪問許可權是可讀可執行,第二個Segment的訪問許可權是可讀可寫。最後一列Align的值0x1000(4K)是x86平臺的記憶體頁面大小。在載入時檔案也要按記憶體頁面大小分成若干頁,檔案中的一頁對應記憶體中的一頁,對應關係如下圖所示。


這個可執行檔案很小,總共也不超過一頁大小,但是兩個Segment必須載入到記憶體中兩個不同的頁面,因為MMU的許可權保護機制是以頁為單位的,一個頁面只能設定一種許可權。此外還規定每個Segment在檔案頁面內偏移多少載入到記憶體頁面仍然要偏移多少,比如第二個Segment在檔案中的偏移是0xa0,在記憶體頁面0x08049000中的偏移仍然是0xa0,所以從0x080490a0開始,這樣規定是為了簡化連結器和載入器的實現。從上圖也可以看出.text段的載入地址應該是0x08048074_start符號位於.text段的開頭,所以_start符號的地址也是0x08048074,從符號表中可以驗證這一點。

原來目標檔案符號表中的Value都是相對地址,現在都改成絕對地址了。此外還多了三個符號__bss_start_edata_end,這些符號在連結指令碼中定義,被連結器新增到可執行檔案中。

再看一下反彙編的結果:

$ objdump -d max

max:     file format elf32-i386

Disassembly of section .text:

08048074 <_start>:
 8048074:	bf 00 00 00 00       	mov    $0x0,%edi
 8048079:	8b 04 bd a0 90 04 08 	mov    0x80490a0(,%edi,4),%eax
 8048080:	89 c3                	mov    %eax,%ebx

08048082 <start_loop>:
 8048082:	83 f8 00             	cmp    $0x0,%eax
 8048085:	74 10                	je     8048097 <loop_exit>
 8048087:	47                   	inc    %edi
 8048088:	8b 04 bd a0 90 04 08 	mov    0x80490a0(,%edi,4),%eax
 804808f:	39 d8                	cmp    %ebx,%eax
 8048091:	7e ef                	jle    8048082 <start_loop>
 8048093:	89 c3                	mov    %eax,%ebx
 8048095:	eb eb                	jmp    8048082 <start_loop>

08048097 <loop_exit>:
 8048097:	b8 01 00 00 00       	mov    $0x1,%eax
 804809c:	cd 80                	int    $0x80

指令中的相對地址都改成絕對地址了。我們仔細檢查一下改了哪些地方。首先看跳轉指令,原來目標檔案的指令是這樣:

...
  11:	74 10                	je     23 <loop_exit>
...
  1d:	7e ef                	jle    e <start_loop>
...
  21:	eb eb                	jmp    e <start_loop>
...

現在改成了這樣:

...
 8048085:	74 10                	je     8048097 <loop_exit>
...
 8048091:	7e ef                	jle    8048082 <start_loop>
...
 8048095:	eb eb                	jmp    8048082 <start_loop>
...

改了嗎?其實只是反彙編的結果不同了,指令的機器碼根本沒變。為什麼不用改指令就能跳轉到新的地址呢?因為跳轉指令中指定的是相對於當前指令向前或向後跳多少位元組,而不是指定一個完整的記憶體地址,記憶體地址有32位,這些跳轉指令只有16位,顯然也不可能指定一個完整的記憶體地址,這稱為相對跳轉。這種相對跳轉指令只有16位,只能在當前指令前後的一個小範圍內跳轉,不可能跳得太遠,也有的跳轉指令指定一個完整的記憶體地址,可以跳到任何地方,這稱絕對跳轉.

再看記憶體訪問指令,原來目標檔案的指令是這樣:

...
   5:	8b 04 bd 00 00 00 00 	mov    0x0(,%edi,4),%eax
...
  14:	8b 04 bd 00 00 00 00 	mov    0x0(,%edi,4),%eax
...

現在改成了這樣:

...
 8048079:	8b 04 bd a0 90 04 08 	mov    0x80490a0(,%edi,4),%eax
...
 8048088:	8b 04 bd a0 90 04 08 	mov    0x80490a0(,%edi,4),%eax
...

指令中的地址原本是0x00000000,現在改成了0x080490a0(注意是小端位元組序)。那麼連結器怎麼知道要改這兩處呢?是根據目標檔案中的.rel.text段提供的重定位資訊來改的:

...
Relocation section '.rel.text' at offset 0x2b0 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000008  00000201 R_386_32          00000000   .data
00000017  00000201 R_386_32          00000000   .data
...

第一列Offset的值就是.text段需要改的地方,在.text段中的相對地址是8和0x17,正是這兩條指令中00 00 00 00的位置。