1. 程式人生 > 其它 >從ELF檔案談起

從ELF檔案談起

本文資訊來源:

又是一期硬核內容:ELF檔案格式

What's the difference of section and segment in ELF file format

ELF Sections & Segments and Linux VMA Mappings

ELF簡介

ELF全稱 executable and linkable format 精靈

是一種linux下常用的可執行檔案 物件 共享庫的標準檔案格式

還有許多其他可執行檔案格式 PE Mach-O COFF COM

核心中處理elf相關程式碼參考: binfmt_elf.c

elf中的資料按照Segment(段)和Section(節)兩個概念進行劃分

ELF檔案格式

ELF Header

  • 架構 ABI版本等基礎資訊
  • program header table的位置和數量
  • section header table的位置和數量

Program header table

  • 每個表項定義了一個segment
  • 每個segment可包含多個section

Section header table

  • 每個表項定義了一個section

readelf命令

可用readelf命令來展示elf檔案的相關資訊

用法如下:

用法:readelf <選項> elf-檔案
 顯示關於 ELF 格式檔案內容的資訊
 Options are:
  -a --all               Equivalent to: -h -l -S -s -r -d -V -A -I
  -h --file-header       Display the ELF file header
  -l --program-headers   Display the program headers
     --segments          An alias for --program-headers
  -S --section-headers   Display the sections' header
     --sections          An alias for --section-headers
  -g --section-groups    Display the section groups
  -t --section-details   Display the section details
  -e --headers           Equivalent to: -h -l -S

比如,使用readelf來檢視date的資訊

readelf -l /bin/date

輸出

Elf 檔案型別為 DYN (Position-Independent Executable file)
Entry point 0x38c0
There are 13 program headers, starting at offset 64

程式頭:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000028a8 0x00000000000028a8  R      0x1000
  LOAD           0x0000000000003000 0x0000000000003000 0x0000000000003000
                 0x0000000000010001 0x0000000000010001  R E    0x1000
  LOAD           0x0000000000014000 0x0000000000014000 0x0000000000014000
                 0x0000000000005cf0 0x0000000000005cf0  R      0x1000
  LOAD           0x0000000000019ff0 0x000000000001aff0 0x000000000001aff0
                 0x00000000000010b0 0x0000000000001268  RW     0x1000
  DYNAMIC        0x000000000001ab98 0x000000000001bb98 0x000000000001bb98
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000040 0x0000000000000040  R      0x8
  NOTE           0x0000000000000378 0x0000000000000378 0x0000000000000378
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000040 0x0000000000000040  R      0x8
  GNU_EH_FRAME   0x0000000000018000 0x0000000000018000 0x0000000000018000
                 0x0000000000000454 0x0000000000000454  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000019ff0 0x000000000001aff0 0x000000000001aff0
                 0x0000000000001010 0x0000000000001010  R      0x1

 Section to Segment mapping:
  段節...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .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 .data.rel.ro .dynamic .got .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .data.rel.ro .dynamic .got 

可知:

  • 在載入到記憶體中時,程式被分成了13個Segment(從PHDR到GNU_RELRO)
  • 每個Segment都包含了1個或者更多的Section

Segment vs Section

Segment

  • 包含著執行時需要的資訊

  • 用於告訴作業系統,段應該被載入到虛擬記憶體中的什麼位置?每個段都有那些許可權?(read, write, execute)

  • 每個Segment主要包含載入地址 檔案中的範圍 記憶體許可權 對齊方式等資訊

Section

  • 包含著連結時需要的資訊

  • 用於告訴連結器,elf中每個部分是什麼,哪裡是程式碼,哪裡是隻讀資料,哪裡是重定位資訊

  • 每個Section主要包含Section型別 檔案中的位置 大小等資訊

  • 連結器會把Section放入Segment中

Segment和Section的關係

  • 相同許可權的Section會放入同一個Segment,例如.text和.rodata section
  • 一個Segment包含許多Section,一個Section可以屬於多個Segment

連結指令碼

執行

ld --verbose

可以看到本系統中所用的指令碼

我的Archlinux 5.16.13-arch1-1的連結指令碼一部分是這樣:

  .gnu.version_r  : { *(.gnu.version_r) }
  .rela.dyn       :
    {
      *(.rela.init)
      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
      *(.rela.fini)
      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
      *(.rela.ctors)
      *(.rela.dtors)
      *(.rela.got)
      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
      *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
      *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
      *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
      *(.rela.ifunc)
    }

表示:

.gnu.version_r Section 會被放入 .gnu.version_r Segment

.rela.init等一大堆的Section,會被放入 .rela.dyn Segment

彙編中的偽指令全部都是Section,要等連結之後才會有Segment

NASM中 .section.segment 這兩個是等效的,都表示 Section

ELF檔案分類

可執行檔案(ET_EXEC)

可直接執行的程式,必須包含segment

物件檔案(ET_REL,*.o)

需要與其他物件檔案連結,必須包含section

動態庫(ET_DYN,*.so)

與其他物件檔案/可執行檔案連結

必須同時包含segment和section

ELF的記憶體對映

檢視記憶體對映情況

cat /proc/[pid]/maps

比如執行

cat /proc/self/maps

檢視cat本身的記憶體對映

563b04d75000-563b04d77000 r--p 00000000 fe:00 6294104                    /usr/bin/cat
563b04d77000-563b04d7c000 r-xp 00002000 fe:00 6294104                    /usr/bin/cat
563b04d7c000-563b04d7f000 r--p 00007000 fe:00 6294104                    /usr/bin/cat
563b04d7f000-563b04d80000 r--p 00009000 fe:00 6294104                    /usr/bin/cat
563b04d80000-563b04d81000 rw-p 0000a000 fe:00 6294104                    /usr/bin/cat
563b058b6000-563b058d7000 rw-p 00000000 00:00 0                          [heap]
7f5f7324b000-7f5f73837000 r--p 00000000 fe:00 6364075                    /usr/lib/locale/locale-archive
7f5f73837000-7f5f7383a000 rw-p 00000000 00:00 0 
7f5f7383a000-7f5f73866000 r--p 00000000 fe:00 6294921                    /usr/lib/libc.so.6
7f5f73866000-7f5f739dc000 r-xp 0002c000 fe:00 6294921                    /usr/lib/libc.so.6
7f5f739dc000-7f5f73a30000 r--p 001a2000 fe:00 6294921                    /usr/lib/libc.so.6
7f5f73a30000-7f5f73a31000 ---p 001f6000 fe:00 6294921                    /usr/lib/libc.so.6
7f5f73a31000-7f5f73a34000 r--p 001f6000 fe:00 6294921                    /usr/lib/libc.so.6
7f5f73a34000-7f5f73a37000 rw-p 001f9000 fe:00 6294921                    /usr/lib/libc.so.6
7f5f73a37000-7f5f73a46000 rw-p 00000000 00:00 0 
7f5f73a70000-7f5f73a92000 rw-p 00000000 00:00 0 
7f5f73a92000-7f5f73a94000 r--p 00000000 fe:00 6294911                    /usr/lib/ld-linux-x86-64.so.2
7f5f73a94000-7f5f73abb000 r-xp 00002000 fe:00 6294911                    /usr/lib/ld-linux-x86-64.so.2
7f5f73abb000-7f5f73ac6000 r--p 00029000 fe:00 6294911                    /usr/lib/ld-linux-x86-64.so.2
7f5f73ac7000-7f5f73ac9000 r--p 00034000 fe:00 6294911                    /usr/lib/ld-linux-x86-64.so.2
7f5f73ac9000-7f5f73acb000 rw-p 00036000 fe:00 6294911                    /usr/lib/ld-linux-x86-64.so.2
7ffec90a6000-7ffec90c8000 rw-p 00000000 00:00 0                          [stack]
7ffec918d000-7ffec9191000 r--p 00000000 00:00 0                          [vvar]
7ffec9191000-7ffec9193000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

從左到右為:

  • 虛擬地址的起始和結束
  • 該記憶體對映的型別flag r(read) w(write) x(execute) p(private) s(shared)
  • 實際物件在該記憶體對映上相對於起始的偏移量
  • major:minor: the major and minor number pairs of the device holding the file that has been mapped.
  • 對映檔案的索引節點號碼
  • 該記憶體對映檔案的名稱