1. 程式人生 > >ELF檔案解析(一):Segment和Section

ELF檔案解析(一):Segment和Section

ELF 是Executable and Linking Format的縮寫,即可執行和可連結的格式,是Unix/Linux系統ABI (Application Binary Interface)規範的一部分。

Unix/Linux下的可執行二進位制檔案、目的碼檔案、共享庫檔案和core dump檔案都屬於ELF檔案。

左邊是ELF的連結檢視,可以理解為是目的碼檔案的內容佈局。右邊是ELF的執行檢視,可以理解為可執行檔案的內容佈局。 注意目的碼檔案的內容是由section組成的,而可執行檔案的內容是由segment組成的。

要注意區分段(segment)和節(section)的概念,這兩個概念在後面會經常提到。 我們寫彙編程式時,用.text,.bss,.data這些指示,都指的是section,比如.text

,告訴彙編器後面的程式碼放入.text section中。 目的碼檔案中的section和section header table中的條目是一一對應的。section的資訊用於連結器對程式碼重定位。

而檔案載入記憶體執行時,是以segment組織的,每個segment對應ELF檔案中program header table中的一個條目,用來建立可執行檔案的程序映像。 比如我們通常說的,程式碼段、資料段是segment,目的碼中的section會被連結器組織到可執行檔案的各個segment中。 .text section的內容會組裝到程式碼段中,.data, .bss等節的內容會包含在資料段中。

在目標檔案中,program header不是必須的,我們用gcc生成的目標檔案也不包含program header。 一個好用的解析ELF檔案的工具是readelf。對我本機上的一個目的碼檔案sleep.o執行readelf -S sleep.o,輸出如下:

There are 12 section headers, starting at offset 0x270:

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
       0000000000000015  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  000001e0
       0000000000000018  0000000000000018   I       9     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000055
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000055
       0000000000000000  0000000000000000  WA       0     0     1
  ... ... ... ...
  [11] .shstrtab         STRTAB           0000000000000000  00000210
       0000000000000059  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

readelf -S是顯示檔案中的Section資訊,sleep.o中共有12個section, 我們省略了其中一些Section的資訊。 可以看到,除了我們熟悉的.text, .data, .bss,還有其它Section,這等我們以後展開講Section的時候還會專門講到。 看每個Section的Flags我們也可以得到一些資訊,比如.text section的Flags是AX,表示要分配記憶體,並且是可執行的,這一節是程式碼無疑了。 .data 和 .bss的Flags的Flags都是WA,表示可寫,需分配記憶體,這都是資料段的特徵。

使用readelf -l可以顯示檔案的program header資訊。我們對sleep.o執行readelf -l sleep.o。會輸出There are no program headers in this file.。 program header和檔案中的segment一一對應,因為目的碼檔案中沒有segment,program header也就沒有必要了。

可執行檔案的內容組織成segment,因此program header table是必須的。 section header不是必須的,但沒有strip過的二進位制檔案中都含有此資訊。 對本地可執行檔案sleep執行readelf -l sleep,輸出如下:

Elf file type is DYN (Shared object file)
Entry point 0x1040
There are 11 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x0000000000000268 0x0000000000000268  R      0x8
  INTERP         0x00000000000002a8 0x00000000000002a8 0x00000000000002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000560 0x0000000000000560  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x00000000000001d5 0x00000000000001d5  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x0000000000000110 0x0000000000000110  R      0x1000
  LOAD           0x0000000000002de8 0x0000000000003de8 0x0000000000003de8
                 0x0000000000000248 0x0000000000000250  RW     0x1000
  DYNAMIC        0x0000000000002df8 0x0000000000003df8 0x0000000000003df8
                 0x00000000000001e0 0x00000000000001e0  RW     0x8
  NOTE           0x00000000000002c4 0x00000000000002c4 0x00000000000002c4
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_EH_FRAME   0x0000000000002004 0x0000000000002004 0x0000000000002004
                 0x0000000000000034 0x0000000000000034  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002de8 0x0000000000003de8 0x0000000000003de8
                 0x0000000000000218 0x0000000000000218  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
   03     .init .plt .text .fini
   04     .rodata .eh_frame_hdr .eh_frame
   05     .init_array .fini_array .dynamic .got .got.plt .data .bss
   06     .dynamic
   07     .note.ABI-tag .note.gnu.build-id
   08     .eh_frame_hdr
   09
   10     .init_array .fini_array .dynamic .got

如輸出所示,檔案中共有11個segment。只有型別為LOAD的段是執行時真正需要的。 除了段資訊,還輸出了每個段包含了哪些section。比如第二個LOAD段標誌為R(只讀)E(可執行)的,它的編號是03,表示它包含哪些section的那一行內容為: 03 .init .plt .text .fini。 可以發現.text包含在其中,這一段就是程式碼段。 再比如第三個LOAD段,索引是04,標誌為R(只讀),但沒有可執行的屬性,它包含的section有.rodata .eh_frame_hdr .eh_frame,其中rodata表示只讀的資料,也就是程式中用到的字串常量等。 最後一個LOAD段,索引05,標誌RW(可讀寫),它包含的節是.init_array .fini_array .dynamic .got .got.plt .data .bss,可以看到.data和.bss都包含其中,這段是資料段無疑。

今天先講到這裡,後面的內容這樣組織:

  • 首先講一下Elf檔案的header,因為檔案一開始幾十個位元組就是Elf header的資料,這個資料結構包含了很多資訊,還能告訴我們program header table, section header table在檔案中什麼位置。
  • 接下來會講一下如何解讀section header table,以及section的資料如何組織的。
  • 然後會講program header table,以及segment的資料組織。section是如何組織成段的,這一點我們也要弄請求。
  • 最後我們會講程式如果被loader載入到記憶體中,生成程序映像的。 歡迎繼續關注。