1. 程式人生 > >可執行檔案的格式(ELF格式)詳解

可執行檔案的格式(ELF格式)詳解

各種講解elf檔案格式一上來就是各種資料型別,看了半天卻不知道這些資料型別是幹啥的,所以咱就先找個例子直接上手,這樣對elf檔案格式有個具體而生動的瞭解。

然後再去看那些手冊,就完全不懼了~。

我們使用一個彙編程式max.s並對其進行編譯連結產生的兩個elf檔案來對比分析elf檔案。

例子程式max.s來自《Linux C 一站式程式設計》。

ps:這是一本看完可以真正可以深入理解C語言精華的書,涵蓋面極廣,上到資料結構、linux系統、網路通訊,下到編譯連結、組合語言、記憶體定址。真的很好的哦親。

彙編程式max.s用於取一組正整數的最大值,使用的是AT&T語法,程式原始碼如下

  1. .section .data

  2. data_items:

  3. .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

  4. .section .text

  5. .globl _start

  6. _start:

  7. movl $0, %edi

  8. movl data_items(,%edi,4), %eax # data_items+ 4*(edi) --> eax

  9. movl %eax, %ebx # (eax) --> ebx

  10. start_loop: # ebx store the max value

  11. cmpl $0, %eax

  12. je loop_exit

  13. incl %edi

  14. movl data_items(,%edi,4), %eax # data_items+ 4*(edi) --> eax

  15. cmpl %ebx, %eax

  16. jle start_loop # eax <= ebx

  17. movl %eax, %ebx # eax > ebx

  18. jmp start_loop

  19. loop_exit:

  20. movl $1, %eax # exit system call.

  21. int $0x80

程式解釋:

在原始碼中定義了2個section,一個是section名字叫.data,另一個section叫.text, 聲明瞭_start為全域性的符號。

在.data section中定義了一個符號data_items,在.text section中定義了3個符號_start 、 start_loop、loop_exit。其中 _start符號被定義為全域性符號。

程式邏輯也很簡單,依次遍歷陣列並比較就得出了最大值,將最大值儲存在ebx中,最後使用系統呼叫退出。

編譯

$as -o max.o max.s

連結

$ld -o max max.o

執行並測試程式

$./max

$echo $?

222

222就是max.s執行返回的最大值。

下面先來分析編譯出的max.o檔案

  1. $ du -b max.o

  2. 704 max.o #此elf檔案大小為704B

  3. $ readelf -a max.o #讀取elf檔案

  4. ELF Header:

  5. Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

  6. Class: ELF32

  7. Data: 2's complement, little endian

  8. Version: 1 (current)

  9. OS/ABI: UNIX - System V

  10. ABI Version: 0

  11. Type: REL (Relocatable file)

  12. Machine: Intel 80386 #執行機器

  13. Version: 0x1

  14. Entry point address: 0x0

  15. Start of program headers: 0 (bytes into file)

  16. Start of section headers: 200 (bytes into file) #section headers table在檔案中的偏移

  17. Flags: 0x0

  18. Size of this header: 52 (bytes) #elf header在檔案中佔了52個位元組

  19. Size of program headers: 0 (bytes)

  20. Number of program headers: 0 #檔案中無program headers

  21. Size of section headers: 40 (bytes) #section headers table 中的每個section header descriptor有40B

  22. Number of section headers: 8 #檔案中有8個section headers

  23. Section header string table index: 5

  24. Section Headers:

  25. [Nr] Name Type Addr Off Size ES Flg Lk Inf Al

  26. [ 0] NULL 00000000 000000 000000 00 0 0 0

  27. [ 1] .text PROGBITS 00000000 000034 00002a 00 AX 0 0 4 #這是我們在max.s中定義的section, .text section

  28. [ 2] .rel.text REL 00000000 0002b0 000010 08 6 1 4

  29. [ 3] .data PROGBITS 00000000 000060 000038 00 WA 0 0 4 #這是我們在max.s中定義的section, .data section,section size 為 0x38B,即56B(14*4B)

  30. [ 4] .bss NOBITS 00000000 000098 000000 00 WA 0 0 4

  31. [ 5] .shstrtab STRTAB 00000000 000098 000030 00 0 0 1 #.shstrtab 存放各section的名字,比如".text" ".data"

  32. [ 6] .symtab SYMTAB 00000000 000208 000080 10 7 7 4 #.symtab 存放所有section中定義的的符號名字,比如 "data_items","start_loop"

  33. [ 7] .strtab STRTAB 00000000 000288 000028 00 0 0 1

  34. Key to Flags:

  35. W (write), A (alloc), X (execute), M (merge), S (strings)

  36. I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)

  37. O (extra OS processing required) o (OS specific), p (processor specific)

  38. There are no section groups in this file.

  39. There are no program headers in this file.

  40. Relocation section '.rel.text' at offset 0x2b0 contains 2 entries: #.rel.text 告訴連結器指令哪些地方需要定位,這裡表示的是.text section中需要改動的地方,在section中的偏移是8和17

  41. Offset Info Type Sym.Value Sym. Name

  42. 00000008 00000201 R_386_32 00000000 .data

  43. 00000017 00000201 R_386_32 00000000 .data

  44. There are no unwind sections in this file.

  45. Symbol table '.symtab' contains 8 entries: #符號就是為一個記憶體地址起了一個名字。

  46. Num: Value Size Type Bind Vis Ndx Name #Ndx表示 符號所在的的section編號見Section Headers 中的[Nr]列

  47. 0: 00000000 0 NOTYPE LOCAL DEFAULT UND #Value 表示此符號在相應section中的偏移

  48. 1: 00000000 0 SECTION LOCAL DEFAULT 1

  49. 2: 00000000 0 SECTION LOCAL DEFAULT 3

  50. 3: 00000000 0 SECTION LOCAL DEFAULT 4

  51. 4: 00000000 0 NOTYPE LOCAL DEFAULT 3 data_items

  52. 5: 0000000e 0 NOTYPE LOCAL DEFAULT 1 start_loop

  53. 6: 00000023 0 NOTYPE LOCAL DEFAULT 1 loop_exit

  54. 7: 00000000 0 NOTYPE GLOBAL DEFAULT 1 _start #這裡_start 符號是GLOBAL的, 因為原始碼中使用.globl _start 標明此符號為全域性的

  55. No version information found in this file.

這是max.o檔案詳細的區域資訊

結合readelf讀出的資訊,可以看到,在max.o的這個elf檔案中,有3種類型的資料"區域",分別是elf header、section、section headers。

[1] elf header描述了這個elf檔案的一些資訊,如資料格式是big-endian 或者 little-endian、執行平臺、section header 的個數等。

[2] section headers是一個表,表中的每個條目描述了一個section,如section在檔案中的偏移,大小等。

[3] section中就是elf檔案中“真正”的資訊了。

下面來依次解釋max.o中的各個section。

.data 和.text 屬於PROGBITS型別的section,這是將來要正常執行的程式和程式碼。

.shstrtab和.strtab屬於STRTAB型別的section,可以在檔案中看到,它們都存著字串,shstrtab存的是section的名字,而.strtab存的是符號的名字(符號表示一個固定的記憶體地址)。

.symtab是屬於SYMTAB型別的section,它描述了.strtab中的符號在"記憶體"中對應的"記憶體地址",當然這裡的還不是真正的記憶體地址,只是一個偏移量,等到連結之後就是真正的了。

.rel.text是屬於REL型別的section,它為連結器正確連結提供了資訊,在下面會詳細解釋。

$objdump -d max.o

  1. max.o: file format elf32-i386

  2. Disassembly of section .text:

  3. 00000000 <_start>:

  4. 0: bf 00 00 00 00 mov $0x0,%edi

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

  6. c: 89 c3 mov %eax,%ebx

  7. 0000000e <start_loop>:

  8. e: 83 f8 00 cmp $0x0,%eax

  9. 11: 74 10 je 23 <loop_exit>

  10. 13: 47 inc %edi

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

  12. 1b: 39 d8 cmp %ebx,%eax

  13. 1d: 7e ef jle e <start_loop>

  14. 1f: 89 c3 mov %eax,%ebx

  15. 21: eb eb jmp e <start_loop>

  16. 00000023 <loop_exit>:

  17. 23: b8 01 00 00 00 mov $0x1,%eax

  18. 28: cd 80 int $0x80

看一下連結之後的程式碼

$ld  -o max max.o

$objdump -d max

  1. max: file format elf32-i386

  2. Disassembly of section .text:

  3. 08048074 <_start>:

  4. 8048074: bf 00 00 00 00 mov $0x0,%edi

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

  6. 8048080: 89 c3 mov %eax,%ebx

  7. 08048082 <start_loop>:

  8. 8048082: 83 f8 00 cmp $0x0,%eax

  9. 8048085: 74 10 je 8048097 <loop_exit>

  10. 8048087: 47 inc %edi

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

  12. 804808f: 39 d8 cmp %ebx,%eax

  13. 8048091: 7e ef jle 8048082 <start_loop>

  14. 8048093: 89 c3 mov %eax,%ebx

  15. 8048095: eb eb jmp 8048082 <start_loop>

  16. 08048097 <loop_exit>:

  17. 8048097: b8 01 00 00 00 mov $0x1,%eax

  18. 804809c: cd 80 int $0x80

經過連結,.text程式碼可以真正的正確運行了,可以看到:

1.跳轉指令中的跳轉地址由檔案偏移改成了實際的記憶體地址。

2.注意從.data section中取數的這句,max.o中是mov    0x0(,%edi,4),%eax ,連結後被換成了正確的mov 0x80490a0(,%edi,4),%eax。

連結後的檔案max區域結構如圖所示

可以看到,max檔案中多了一個program headers區域,以及2個segment section。

program headers 是一張表,用於描述segment section。

segment section就是真正拷貝到記憶體並執行的程式碼。

對映圖如下

再使用readelf檢視經過連結後的elf檔案

  1. $ readelf -a max

  2. ELF Header:

  3. Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

  4. Class: ELF32

  5. Data: 2's complement, little endian

  6. Version: 1 (current)

  7. OS/ABI: UNIX - System V

  8. ABI Version: 0

  9. Type: EXEC (Executable file) #型別變為可執行檔案

  10. Machine: Intel 80386

  11. Version: 0x1

  12. Entry point address: 0x8048074 #elf檔案的記憶體入口地址由0變為0x8048074了

  13. Start of program headers: 52 (bytes into file) #program headers table 在檔案中的偏移

  14. Start of section headers: 256 (bytes into file) #section headers table 在檔案中的偏移

  15. Flags: 0x0

  16. Size of this header: 52 (bytes)

  17. Size of program headers: 32 (bytes) #program headers

  18. Number of program headers: 2 #多了2個program headers

  19. Size of section headers: 40 (bytes)

  20. Number of section headers: 6 #少了2個section headers

  21. Section header string table index: 3

  22. Section Headers: #與max.o檔案對比可以發現少了.bss 和 .rel.text兩個section headers

  23. [Nr] Name Type Addr Off Size ES Flg Lk Inf Al

  24. [ 0] NULL 00000000 000000 000000 00 0 0 0

  25. [ 1] .text PROGBITS 08048074 000074 00002a 00 AX 0 0 4

  26. [ 2] .data PROGBITS 080490a0 0000a0 000038 00 WA 0 0 4

  27. [ 3] .shstrtab STRTAB 00000000 0000d8 000027 00 0 0 1

  28. [ 4] .symtab SYMTAB 00000000 0001f0 0000a0 10 5 6 4

  29. [ 5] .strtab STRTAB 00000000 000290 000040 00 0 0 1

  30. Key to Flags:

  31. W (write), A (alloc), X (execute), M (merge), S (strings)

  32. I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)

  33. O (extra OS processing required) o (OS specific), p (processor specific)

  34. There are no section groups in this file.

  35. Program Headers: #此2個program headers 將被裝入至記憶體中分別的2個物理頁中

  36. Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align

  37. LOAD 0x000000 0x08048000 0x08048000 0x0009e 0x0009e R E 0x1000 #裝入至物理頁0x8048000~0x8049000

  38. LOAD 0x0000a0 0x080490a0 0x080490a0 0x00038 0x00038 RW 0x1000 #裝入至物理頁0x8049000~0x804a000

  39. Section to Segment mapping:

  40. Segment Sections...

  41. 00 .text

  42. 01 .data

  43. There is no dynamic section in this file.

  44. There are no relocations in this file.

  45. There are no unwind sections in this file.

  46. Symbol table '.symtab' contains 10 entries:

  47. Num: Value Size Type Bind Vis Ndx Name

  48. 0: 00000000 0 NOTYPE LOCAL DEFAULT UND

  49. 1: 08048074 0 SECTION LOCAL DEFAULT 1

  50. 2: 080490a0 0 SECTION LOCAL DEFAULT 2

  51. 3: 080490a0 0 NOTYPE LOCAL DEFAULT 2 data_items

  52. 4: 08048082 0 NOTYPE LOCAL DEFAULT 1 start_loop

  53. 5: 08048097 0 NOTYPE LOCAL DEFAULT 1 loop_exit

  54. 6: 08048074 0 NOTYPE GLOBAL DEFAULT 1 _start

  55. 7: 080490d8 0 NOTYPE GLOBAL DEFAULT ABS __bss_start

  56. 8: 080490d8 0 NOTYPE GLOBAL DEFAULT ABS _edata

  57. 9: 080490d8 0 NOTYPE GLOBAL DEFAULT ABS _end

  58. No version information found in this file.