天天看點

ELF檔案雜記

eu-readelf --help

Usage: eu-readelf [OPTION...] FILE...

Print information from ELF file in human-readable form.

 ELF input selection:

      --elf-section[=SECTION]   Use the named SECTION (default .gnu_debugdata)

                             as (compressed) ELF input data

 ELF output selection:

  -a, --all                  All these plus -p .strtab -p .dynstr -p .comment

  -A, --arch-specific        Display architecture specific information, if any

  -d, --dynamic              Display the dynamic segment

  -e, --exception            Display sections for exception handling

  -h, --file-header          Display the ELF file header

  -I, --histogram            Display histogram of bucket list lengths

  -l, --program-headers      Display the program headers

  -n, --notes                Display the ELF notes

  -r, --relocs               Display relocations

  -s, --symbols              Display the symbol table

  -S, --section-headers      Display the sections' headers

  -V, --version-info         Display versioning information

Additional output selection:

  -c, --archive-index        Display the symbol index of an archive

  -p, --strings[=SECTION]    Print string contents of sections

  -w, --debug-dump[=SECTION] Display DWARF section content.  SECTION can be one

                             of abbrev, aranges, decodedaranges, frame,

                             gdb_index, info, loc, line, decodedline, ranges,

                             pubnames, str, macinfo, macro or exception

  -x, --hex-dump=SECTION     Dump the uninterpreted contents of SECTION, by

                             number or name

 Output control:

  -N, --numeric-addresses    Do not find symbol names for addresses in DWARF

                             data

  -U, --unresolved-address-offsets

                             Display just offsets instead of resolving values

                             to addresses in DWARF data

 ...

1. ELF頭

0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............

0000010: 0300 3e00 0100 0000 f008 0000 0000 0000  ..>.............

0000020: 4000 0000 0000 0000 9021 0000 0000 0000  @........!......

0000030: 0000 0000 4000 3800 0700 4000 1c00 1900  [email protected]...@.....

# eu-readelf -h libattr.so

ELF Header:

  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  Class:                             ELF64

  Data:                              2's complement, little endian

  Ident Version:                     1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              DYN (Shared object file)

  Machine:                           AMD x86-64

  Version:                           1 (current)

  Entry point address:               0x8f0

  Start of program headers:          64 (bytes into file)

  Start of section headers:          8592 (bytes into file)

  Flags:                             

  Size of this header:               64 (bytes)

  Size of program header entries:    56 (bytes)

  Number of program headers entries: 7

  Size of section header entries:    64 (bytes)

  Number of section headers entries: 28

  Section header string table index: 25

#define EI_NIDENT 16

typedef struct {

    unsigned char e_ident[EI_NIDENT];

    Elf64_Half e_type;

    Elf64_Half e_machine;

    Elf64_Word e_version;

    Elf64_Addr e_entry;

    Elf64_Off e_phoff;

    Elf64_Off e_shoff;

    Elf64_Word e_flags;

    Elf64_Half e_ehsize;

    Elf64_Half e_phentsize;

    Elf64_Half e_phnum;

    Elf64_Half e_shentsize;

    Elf64_Half e_shnum;

    Elf64_Half e_shstrndx;

} Elf64_Ehdr;

2. 程式頭(segment)

ELF頭中相關聯的字段:

Start of program headers(e_phoff): 64 (bytes into file)

Size of program header entries(e_phentsize): 56 (bytes)

Number of program headers entries(e_phnum): 7

# eu-readelf -l libattr.so

Program Headers:

  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align

  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000fbc 0x000fbc R E 0x200000

  LOAD           0x001dc0 0x0000000000201dc0 0x0000000000201dc0 0x0002a8 0x0002b0 RW  0x200000

  DYNAMIC        0x001de0 0x0000000000201de0 0x0000000000201de0 0x0001f0 0x0001f0 RW  0x8

  NOTE           0x0001c8 0x00000000000001c8 0x00000000000001c8 0x000024 0x000024 R   0x4

  GNU_EH_FRAME   0x000e4c 0x0000000000000e4c 0x0000000000000e4c 0x00004c 0x00004c R   0x4

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

  GNU_RELRO      0x001dc0 0x0000000000201dc0 0x0000000000201dc0 0x000240 0x000240 R   0x1

 Section to Segment mapping:

  Segment Sections...

   00      [RO: .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame]

   01      [RELRO: .init_array .fini_array .jcr .data.rel.ro .dynamic .got] .got.plt .bss

   02      [RELRO: .dynamic]

   03      [RO: .note.gnu.build-id]

   04      [RO: .eh_frame_hdr]

   05     

   06      [RELRO: .init_array .fini_array .jcr .data.rel.ro .dynamic .got]

typedef struct {

    Elf64_Word p_type;        //此數組元素描述的段類型或解釋此數組元素的資訊的方式

    Elf64_Word p_flags;        //與段相關的标志

    Elf64_Off p_offset;        //相對段的第一個位元組所在檔案的起始位置的偏移

    Elf64_Addr p_vaddr;        //段的第一個位元組在記憶體中的虛拟位址,不是必須的,因類DSO可以加載到虛拟位址空間的任意位址

    Elf64_Addr p_paddr;        //段在與實體尋址相關的系統中的實體位址

    Elf64_Xword p_filesz;    //段的檔案映像中的位元組數

    Elf64_Xword p_memsz;    //段的記憶體映像中的位元組數

    Elf64_Xword p_align;

} Elf64_Phdr;

p_type:

    PHDR: 指定程式頭表在檔案及程式記憶體映像中的位置和大小。此段類型不能在一個檔案中多次出現。此外,僅當程式頭表是程式記憶體映像的一部分時,才可以出現此段。此類型(如果存在)必須位于任何可裝入段的各項的前面

    LOAD: 指定由 p_filesz 和 p_memsz 描述的可裝入段。檔案中的位元組會映射到記憶體段的起始位置。如果段的記憶體大小 (p_memsz) 大于檔案大小 (p_filesz),則将多餘位元組的值定義為 0。這些位元組跟在段的已初始化區域後面。檔案大小不能大于記憶體大小。

    DYNAMIC: 如果目标檔案參與動态連結,則其程式頭表将包含一個類型為 DYNAMIC 的元素。此段包含 .dynamic 節

    INTERP:指定要作為解釋程式調用的以空字元結尾的路徑名的位置和大小。對于動态可執行檔案,必須設定此類型。此類型可出現在共享目标檔案中。此類型不能在一個檔案中多次出現。此類型(如果存在)必須位于任何可裝入段的各項的前面

    NOTE: 指定輔助資訊的位置和大小

    GNU_EH_FRAME: 此段包含棧擴充表

    GNU_STACK: 描述程序棧。隻能存在一個 GNU_STACK 元素

    GNU_RELRO: ???

3. 節頭表

# eu-readelf -S libattr.so

Section Headers:

[Nr] Name                 Type         Addr             Off      Size     ES Flags Lk Inf Al

[ 0]                      NULL         0000000000000000 00000000 00000000  0        0   0  0

[ 1] .note.gnu.build-id   NOTE         00000000000001c8 000001c8 00000024  0 A      0   0  4

[ 2] .gnu.hash            GNU_HASH     00000000000001f0 000001f0 0000004c  0 A      3   0  8

[ 3] .dynsym              DYNSYM       0000000000000240 00000240 00000258 24 A      4   2  8

[ 4] .dynstr              STRTAB       0000000000000498 00000498 00000166  0 A      0   0  1

[ 5] .gnu.version         GNU_versym   00000000000005fe 000005fe 00000032  2 A      3   0  2

[ 6] .gnu.version_r       GNU_verneed  0000000000000630 00000630 00000020  0 A      4   1  8

[ 7] .rela.dyn            RELA         0000000000000650 00000650 000000d8 24 A      3   0  8

[ 8] .rela.plt            RELA         0000000000000728 00000728 000000f0 24 A      3  10  8

[ 9] .init                PROGBITS     0000000000000818 00000818 0000001a  0 AX     0   0  4

[10] .plt                 PROGBITS     0000000000000840 00000840 000000b0 16 AX     0   0 16

[11] .text                PROGBITS     00000000000008f0 000008f0 0000042f  0 AX     0   0 16

[12] .fini                PROGBITS     0000000000000d20 00000d20 00000009  0 AX     0   0  4

[13] .rodata              PROGBITS     0000000000000d30 00000d30 0000011b  0 A      0   0  8

[14] .eh_frame_hdr        PROGBITS     0000000000000e4c 00000e4c 0000004c  0 A      0   0  4

[15] .eh_frame            PROGBITS     0000000000000e98 00000e98 00000124  0 A      0   0  8

[16] .init_array          INIT_ARRAY   0000000000201dc0 00001dc0 00000008  0 WA     0   0  8

[17] .fini_array          FINI_ARRAY   0000000000201dc8 00001dc8 00000008  0 WA     0   0  8

[18] .jcr                 PROGBITS     0000000000201dd0 00001dd0 00000008  0 WA     0   0  8

[19] .data.rel.ro         PROGBITS     0000000000201dd8 00001dd8 00000008  0 WA     0   0  8

[20] .dynamic             DYNAMIC      0000000000201de0 00001de0 000001f0 16 WA     4   0  8

[21] .got                 PROGBITS     0000000000201fd0 00001fd0 00000030  8 WA     0   0  8

[22] .got.plt             PROGBITS     0000000000202000 00002000 00000068  8 WA     0   0  8

[23] .bss                 NOBITS       0000000000202068 00002068 00000008  0 WA     0   0  4

[24] .comment             PROGBITS     0000000000000000 00002068 0000002d  1 MS     0   0  1

[25] .shstrtab            STRTAB       0000000000000000 00002095 000000f9  0        0   0  1

[26] .symtab              SYMTAB       0000000000000000 00002890 00000660 24       27  45  8

[27] .strtab              STRTAB       0000000000000000 00002ef0 000002ed  0        0   0  1

typedef struct {

    Elf64_Word sh_name;    //節的名稱。此成員值是節頭字元串表節(.shstrtab)的索引,用于指定以空字元結尾的字元串的位置

    Elf64_Word sh_type;

    Elf64_Xword sh_flags;

    Elf64_Addr sh_addr;    //如果節顯示在程序的記憶體映像中,則此成員會指定節的第一個位元組所在的位址。否則,此成員值為零                        

    Elf64_Off sh_offset; //從檔案的起始位置到節中第一個位元組的位元組偏移, 對于NOBITS節,此成員表示檔案中的概念性偏移,因為該節在檔案中不占用任何空間

    Elf64_Xword sh_size; //節的大小(以位元組為機關)

    Elf64_Word sh_link; //節頭表索引連結

    Elf64_Word sh_info; //額外資訊

    Elf64_Xword sh_addralign; //一些節具有位址對齊限制

    //一些節包含固定大小的項的表,如符号表。對于這樣的節,此成員會指定每一項的大小(位元組)。如果節不包含固定大小的項的表,則此成員值為零。

    Elf64_Xword sh_entsize; 

} Elf64_Shdr;

運作時連結程式(ld.so, ld-linux.so*)執行以下操作:

1. 分析可執行檔案的動态資訊節 (.dynamic) 并确定所需的依賴項。

2. 查找并裝入這些依賴項,分析其動态資訊節以确定是否需要其他依賴項。

3. 執行所有必需的重定位以綁定這些目标檔案,為執行程序做好準備。

4. 調用這些依賴項提供的所有初始化函數。

5. 将控制權移交給應用程式。

6. 可在應用程式執行時調用,以執行延遲函數綁定。

7. 可由應用程式調用以使用 dlopen(3C) 擷取其他目标檔案,并使用 dlsym(3C) 綁定到這些目标檔案中的符号。

4. 動态節

[20] .dynamic             DYNAMIC      0000000000201de0 00001de0 000001f0 16 WA     4   0  8

#eu-readelf -d libattr.so

Dynamic segment contains 31 entries:

 Addr: 0x0000000000201de0  Offset: 0x001de0  Link to section: [ 4] '.dynstr'

  Type              Value

  NEEDED            Shared library: [libstdc++.so.6]

  NEEDED            Shared library: [libm.so.6]

  NEEDED            Shared library: [libgcc_s.so.1]

  NEEDED            Shared library: [libc.so.6]

  INIT              0x0000000000000818

  FINI              0x0000000000000d20

  INIT_ARRAY        0x0000000000201dc0

  INIT_ARRAYSZ      8 (bytes)

  FINI_ARRAY        0x0000000000201dc8

  FINI_ARRAYSZ      8 (bytes)

  GNU_HASH          0x00000000000001f0

  STRTAB            0x0000000000000498

  SYMTAB            0x0000000000000240

  STRSZ             358 (bytes)

  SYMENT            24 (bytes)

  PLTGOT            0x0000000000202000

  PLTRELSZ          240 (bytes)

  PLTREL            RELA

  JMPREL            0x0000000000000728

  RELA              0x0000000000000650

  RELASZ            216 (bytes)

  RELAENT           24 (bytes)

  VERNEED           0x0000000000000630

  VERNEEDNUM        1

  VERSYM            0x00000000000005fe

  RELACOUNT         3            

typedef struct {

    Elf64_Xword d_tag;

    union {

        Elf64_Xword d_val;

        Elf64_Addr d_ptr;

    } d_un;

} Elf64_Dyn;

NEEDED: 以空字元結尾的字元串的.dynstr 字元串表偏移,用于提供所需依賴項的名稱。動态數組可以包含多個此類型的項。盡管這些項與其他類型的項的關系不重要,但其相對順序卻很重要。

5. 重定位

[ 7] .rela.dyn            RELA         0000000000000650 00000650 000000d8 24 A      3   0  8

[ 8] .rela.plt            RELA         0000000000000728 00000728 000000f0 24 A      3  10  8

#readelf -r libattr.so

重定位節 '.rela.dyn' 位于偏移量 0x650 含有 9 個條目:

  Offset          Info           Type           Sym. Value    Sym. Name + Addend

000000201dc0  000000000008 R_X86_64_RELATIVE                    9a0

000000201dc8  000000000008 R_X86_64_RELATIVE                    960

000000201dd8  000000000008 R_X86_64_RELATIVE                    201dd8

000000201fd0  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

000000201fd8  000400000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0

000000201fe0  000700000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0

000000201fe8  000800000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0

000000201ff0  000900000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize + 0

000000201ff8  000c00000006 R_X86_64_GLOB_DAT 0000000000000000 stderr + 0

重定位節 '.rela.plt' 位于偏移量 0x728 含有 10 個條目:

  Offset          Info           Type           Sym. Value    Sym. Name + Addend

000000202018  000200000007 R_X86_64_JUMP_SLO 0000000000000000 snprintf + 0

000000202020  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0

000000202028  000500000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0

000000202030  000600000007 R_X86_64_JUMP_SLO 0000000000000000 gettimeofday + 0

000000202038  001600000007 R_X86_64_JUMP_SLO 0000000000000ba0 _Z3LogP8_IO_FILEPKcz + 0

000000202040  000900000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize + 0

000000202048  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 vsnprintf + 0

000000202050  000b00000007 R_X86_64_JUMP_SLO 0000000000000000 localtime_r + 0

000000202058  000d00000007 R_X86_64_JUMP_SLO 0000000000000000 fwrite + 0

000000202060  000e00000007 R_X86_64_JUMP_SLO 0000000000000000 fflush + 0

typedef struct {

    //對于可重定位檔案,該值表示節偏移。重定位節說明如何修改檔案中的其他節。重定位偏移會在第二節中指定一個存儲單元。

    //對于可執行檔案或共享目标檔案,該值表示受重定位影響的存儲單元的虛拟位址。此資訊使重定位項對于運作時連結程式更為有用。

    Elf64_Addr r_offset;    //指定應用重定位操作的位置

    //例如,調用指令的重定位項包含所調用的函數的符号表索引。如果索引是未定義的符号索引STN_UNDEF,則重定位将使用零作為符号值。

    Elf64_Xword r_info;        //指定必須對其進行重定位的符号表索引以及要應用的重定位類型(Type為類型, Sym.Value為符号表索引)

    Elf64_Sxword r_addend;  //指定常量加數,用于計算将存儲在可重定位字段中的值

} Elf64_Rela;

注:在所有情況下,r_offset 值都會指定受影響存儲單元的第一個位元組的偏移或虛拟位址。重定位類型可指定要更改的位以及計算這些位的值的方法。

X86_64_RELATIVE: 表示内部變量或函數引用;X86_64_GLOB_DAT,X86_64_JUMP_SLOT:表示外部變量或函數引用(位于其它的DSO中)

dynamic linker的三個任務:确認并加載相關依賴, 重定位應用程式和所有的依賴, 正确的初始化應用程式和其依賴。

最繁重的任務就是重定位, 它的時間複雜度是O(R +nr), 其中R是相關重定位的個數, r是命名重定位的個數, n是主程式所用到的DSO的個數。

以下表示法用于說明重定位計算: 

    A 用于計算可重定位字段的值的加數。

    B 執行過程中将共享目标檔案裝入記憶體的基本位址。通常,生成的共享目标檔案的基本虛拟位址為 0。但是,共享目标檔案的執行位址不相同。

    G 執行過程中,重定位項的符号位址所在的全局偏移表中的偏移。

    GOT 全局偏移表的位址。

    L 符号的過程連結表項的節偏移或位址。

    P 使用 r_offset 計算出的重定位的存儲單元的節偏移或位址。

    S 索引位于重定位項中的符号的值。

    Z 索引位于重定位項中的符号的大小。

名稱                 值 字段 計算     名稱原來定義為(*_AMD64_*)

R_X86_64_            NONE 0 無 無

R_X86_64_64         1 word64 S + A

R_X86_64_PC32         2 word32 S + A - P

R_X86_64_GOT32         3 word32 G + A

R_X86_64_PLT32         4 word32 L + A - P

R_X86_64_COPY         5 無 

R_X86_64_GLOB_DAT     6 word64 S

R_X86_64_JUMP_SLOT     7 word64 S

R_X86_64_RELATIVE     8 word64 B + A

R_X86_64_GOTPCREL     9 word32 G + GOT + A - P

R_X86_64_32         10 word32 S + A

R_X86_64_32S         11 word32 S + A

R_X86_64_16         12 word16 S + A

R_X86_64_PC16         13 word16 S + A - P

R_X86_64_8             14 word8 S + A

R_X86_64_PC8         15 word8 S + A - P

R_X86_64_PC64         24 word64 S + A - P

R_X86_64_GOTOFF64     25 word64 S + A - GOT

R_X86_64_GOTPC32     26 word32 GOT + A + P

R_X86_64_SIZE32     32 word32 Z + A

R_X86_64_SIZE64     33 word64 Z + A

6. 符号表

兩種符号表.dynsym和.symtab

# eu-readelf -s libattr.so 

Symbol table [ 3] '.dynsym' contains 25 entries:

 2 local symbols  String table: [ 4] '.dynstr'

  Num:            Value   Size Type    Bind   Vis          Ndx Name

    0: 0000000000000000      0 NOTYPE  LOCAL  DEFAULT    UNDEF 

    1: 0000000000000818      0 SECTION LOCAL  DEFAULT        9 

    2: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

    3: 0000000000000000      0 NOTYPE  WEAK   DEFAULT    UNDEF __gmon_start__

    4: 0000000000000000      0 NOTYPE  WEAK   DEFAULT    UNDEF _Jv_RegisterClasses

    5: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

    6: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

    7: 0000000000000000      0 NOTYPE  WEAK   DEFAULT    UNDEF _ITM_deregisterTMCloneTable

    8: 0000000000000000      0 NOTYPE  WEAK   DEFAULT    UNDEF _ITM_registerTMCloneTable

    9: 0000000000000000      0 FUNC    WEAK   DEFAULT    UNDEF [email protected]_2.2.5 (2)

   10: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

   11: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

   12: 0000000000000000      0 OBJECT  GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

   13: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

   14: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF [email protected]_2.2.5 (2)

   15: 0000000000000cd3     76 FUNC    GLOBAL DEFAULT       11 _Z12StructPackedv

   16: 0000000000202070      0 NOTYPE  GLOBAL DEFAULT       23 _end

   17: 0000000000202068      0 NOTYPE  GLOBAL DEFAULT       22 _edata

   18: 0000000000000c64    111 FUNC    GLOBAL DEFAULT       11 _Z13StructAlignedv

   19: 0000000000202068      0 NOTYPE  GLOBAL DEFAULT       23 __bss_start

   20: 0000000000000818      0 FUNC    GLOBAL DEFAULT        9 _init

   21: 0000000000000d20      0 FUNC    GLOBAL DEFAULT       12 _fini

   22: 0000000000000ba0    178 FUNC    GLOBAL DEFAULT       11 _Z3LogP8_IO_FILEPKcz

   23: 00000000000009d8     18 FUNC    GLOBAL DEFAULT       11 _Z10not_hiddenv

   24: 0000000000000c52     18 FUNC    GLOBAL DEFAULT       11 _Z15UnavailableFuncv

typedef struct {

    //目标檔案的符号字元串表的索引,其中包含符号名稱的字元表示形式。如果該值為非零,則表示指定符号名稱的字元串表索引。否則,符号表項沒有名稱

    Elf64_Word st_name;        

    unsigned char st_info;        //符号的類型和綁定屬性

    unsigned char st_other;        //符号的可見性

    Elf64_Half st_shndx;        //所定義的每一個符号表項都與某節有關。此成員包含相關節頭表索引

    Elf64_Addr st_value;        //關聯符号的值

    Elf64_Xword st_size;        //關聯符号的大小

} Elf64_Sym;

不同目标檔案類型的符号表的各項對于 st_value 成員的解釋稍有不同。

1. 在可重定位檔案中,st_value 包含節索引為 SHN_COMMON 的符号的對齊限制。

2. 在可重定位檔案中,st_value 包含所定義符号的節偏移。st_value 表示從 st_shndx所辨別的節的起始位置的偏移。

3. 在可執行檔案和共享目标檔案中,st_value 包含虛拟位址。為使這些檔案的符号更适用于運作時連結程式,節偏移(檔案解釋)會替換為與節編号無關的虛拟位址(記憶體解釋)。

7. Hash表

[ 2] .gnu.hash            GNU_HASH     00000000000001f0 000001f0 0000004c  0 A      3   0  8

=========================

========nbucket ===========

=========================

======== nchain  ===========

=========================

======== bucket[0] =========

=========......==============

===== bucket[nbucket-1] ======

=========================

========= chain[0] ==========

==========...... =============

===== chain[nchain-1] ========

==========================

bucket 數組包含 nbucket 項,chain 數組包含 nchain 項。索引從 0 開始。bucket 和 chain 都包含符号表索引。連結清單的各項與符号表對應。符号表的項數應等于 nchain,是以符号表索引也可選擇連結清單的各項。

接受符号名稱的散列函數會傳回一個值,用于計算 bucket 索引。是以,如果散列函數為某個名稱傳回值 x,則 bucket [x% nbucket] 将會計算出索引 y。此索引為符号表和連結清單的索引。如果符号表項不是需要的名稱,則 chain[y] 将使用相同的散列值計算出符号表的下一項。

# eu-readelf -I libattr.so

Histogram for bucket list length in section [ 2] '.gnu.hash' (total of 3 buckets):

 Addr: 0x00000000000001f0  Offset: 0x0001f0  Link to section: [ 3] '.dynsym'

 Symbol Bias: 15

 Bitmask Size: 8 bytes  30% bits set  2nd hash shift: 6

 Length  Number  % of total  Coverage

      0       0        0.0%

      1       0        0.0%      0.0%

      2       0        0.0%      0.0%

      3       2       66.7%     60.0%

      4       1       33.3%    100.0%

 Average number of tests:   successful lookup: 2.200000

                          unsuccessful lookup: 3.333333

Weak類型的symbol隻在靜态連接配接時起作用。

變量引用和函數調用相關:

    執行call指令函數調用時,下一指令位址會被壓到棧頂,而esp寄存器始終指向棧頂,(%esp)即擷取棧頂值

    由于動态連結時,隻有在子產品裝載後才能知道其他子產品中資料的位址,是以子產品間資料通路就用到大名鼎鼎的全局偏移表GOT(Global Offset Table)了。

    簡單來說,GOT位于資料段(.data段),儲存本子產品用到的外部符号(變量或函數)的實際位址;因為裝載前不知道外部符号的實際位址,是以在裝載時由動态連結器對每個子產品的GOT進行更新設定真實位址。GOT在資料段中的位置是不變的。

    在編譯時,本子產品的所有對外部變量的通路(即需要知道外部變量位址的地方),都間接引用到GOT中的對應項;因為GOT的相對位置不變,是以可以像“子產品内部資料通路”那樣,編譯時确定GOT的位置。由于GOT在資料段中,是以每個程序都可以自定義GOT内部的值,這樣實作PIC。

x86_64: 

RIP可以了解成Relative Instruction-Pointer。Intel對RIP有個簡單的解釋:

The 64-bit instruction pointer RIP points to the next instruction to be executed, and supports a 64-bit flat memory model.

即RIP指針指向下一條指令,是不是就是PC?

Intel還談到了RIP相對尋址的用處:

RIP-relative addressing: this is new for x64 and allows accessing data tables and such in the code relative to the current instruction pointer, making position independent code easier to implement.

8. GOT

通常,與位置無關的代碼不能包含絕對虛拟位址。全局偏移表在專用資料中包含絕對位址。是以這些位址可用,并且不會破壞程式文本的位置獨立性和共享性。

程式使用與位置無關的位址來引用其 GOT 并提取絕對值。此方法可将與位置無關的引用重定向到絕對位置。

最初,GOT 包含其重定位項所需的資訊。系統為可裝入目标檔案建立記憶體段後,運作時連結程式将會處理這些重定位項。某些重定位項的類型可以為 R_xxxx_GLOB_DAT,用于引用 GOT。

運作時連結程式可确定關聯符号值,計算其絕對位址以及将相應的記憶體表各項設定為正确的值。盡管連結編輯器建立目标檔案時絕對位址未知,但運作時連結程式知道所有記憶體段的位址,是以可以計算其中包含的符号的絕對位址。

如果程式要求直接通路某符号的絕對位址,則該符号将具有一個 GOT 項。由于可執行檔案和共享目标檔案具有不同的 GOT,是以一個符号的位址可以出現在多個表中。

運作時連結程式在向程序映像中的任何代碼授予控制權之前,将首先處理所有的 GOT 重定位。此處理操作可確定絕對位址在執行過程中可用。

表項零保留用于存儲動态結構(使用符号 _DYNAMIC 引用)的位址。使用此符号,運作時連結程式等程式可在尚未處理其重定位項的情況下查找各自的動态結構。

此方法對于運作時連結程式尤其重要,因為它必須對自身進行初始化,而不依賴于其他程式來重定位其記憶體映像。

系統可為不同程式中的同一共享目标檔案選擇不同的記憶體段位址。系統甚至可以為同一程式的不同執行方式選擇不同的庫位址。

但是,一旦建立程序映像,記憶體段即不會更改各位址。隻要存在程序,其記憶體段就會位于固定的虛拟位址

# eu-readelf -S /lib64/ld-linux-x86-64.so.2 

There are 28 section headers, starting at offset 0x27748:

Section Headers:

[Nr] Name                 Type         Addr             Off      Size     ES Flags Lk Inf Al

...

[18] .dynamic             DYNAMIC      0000000000221e00 00021e00 00000190 16 WA     5   0  8

[19] .got                 PROGBITS     0000000000221f90 00021f90 00000058  8 WA     0   0  8

...

# eu-readelf -x .got /lib64/ld-linux-x86-64.so.2 

Hex dump of section [19] '.got', 88 bytes at offset 0x21f90:

  0x00000000 001e2200 00000000 00000000 00000000 ..".............

  0x00000010 00000000 00000000 760a0000 00000000 ........v.......

  0x00000020 860a0000 00000000 960a0000 00000000 ................

  0x00000030 a60a0000 00000000 b60a0000 00000000 ................

  0x00000040 00000000 00000000 00000000 00000000 ................

  0x00000050 00000000 00000000                   ........

從上面可以看到,ld-linux-x86-64.so.2中的GOT的偏移為00021f90, 長度為0x58,每個表項的長度為8。

根據上面的描述,表項零用于存儲動态結構的位址,其表項零的内容為001e2200 00000000(0x221e00), 與.dynamic節的偏移位址一緻。

初始建立程式的記憶體映像時,運作時連結程式會将全局偏移表中的第二項和第三項設定為特殊值,參考PLT。

9. PLT

全局偏移表可将與位置無關的位址計算結果轉換為絕對位置。同樣,過程連結表也可将與位置無關的函數調用轉換為絕對位置。

連結編輯器無法解析不同動态目标檔案之間的執行傳輸(如函數調用)。是以,連結編輯器會安排程式将控制權轉移給過程連結表中的各項。

這樣,運作時連結程式就會重定向各項,而不會破壞程式文本的位置獨立性和共享性。可執行檔案和共享目标檔案包含不同的過程連結表。

對于 x64 動态目标檔案,過程連結表位于共享文本中,但使用專用全局偏移表中的位址。

運作時連結程式可确定目标的絕對位址,并相應地修改全局偏移表的記憶體映像。這樣,運作時連結程式就會重定向各項,而不會破壞程式文本的位置獨立性和共享性。

可執行檔案和共享目标檔案包含不同的過程連結表。

# eu-readelf -S /lib64/ld-linux-x86-64.so.2 

There are 28 section headers, starting at offset 0x27748:

Section Headers:

[Nr] Name                 Type         Addr             Off      Size     ES Flags Lk Inf Al

...

[ 8] .rela.dyn            RELA         00000000000008b0 000008b0 00000138 24 A      4   0  8

[ 9] .rela.plt            RELA         00000000000009e8 000009e8 00000078 24 AI     4  19  8

[10] .plt                 PROGBITS     0000000000000a60 00000a60 00000060 16 AX     0   0 16

...

#eu-readelf -x .plt.got /lib64/ld-linux-x86-64.so.2 

Hex dump of section [11] '.plt.got', 8 bytes at offset 0xac0:

  0x00000000 ff251a15 22006690                   .%..".f.

# eu-readelf -x .plt /lib64/ld-linux-x86-64.so.2 

Hex dump of section [10] '.plt', 96 bytes at offset 0xa60:

  0x00000000 ff353215 2200ff25 34152200 0f1f4000 .52."..%4."...@.

  0x00000010 ff253215 22006800 000000e9 e0ffffff .%2.".h.........

  0x00000020 ff252a15 22006801 000000e9 d0ffffff .%*.".h.........

  0x00000030 ff252215 22006802 000000e9 c0ffffff .%".".h.........

  0x00000040 ff251a15 22006803 000000e9 b0ffffff .%..".h.........

  0x00000050 ff251215 22006804 000000e9 a0ffffff .%..".h.........

# eu-readelf -d /lib64/ld-linux-x86-64.so.2 

Dynamic segment contains 25 entries:

 Addr: 0x0000000000221e00  Offset: 0x021e00  Link to section: [ 5] '.dynstr'

  Type              Value

  SONAME            Library soname: [ld-linux-x86-64.so.2]

  HASH              0x00000000000001f0

  GNU_HASH          0x00000000000002b0

  STRTAB            0x0000000000000630

  SYMTAB            0x0000000000000390

  STRSZ             415 (bytes)

  SYMENT            24 (bytes)

  PLTGOT            0x0000000000221f90    

  PLTRELSZ          120 (bytes)

  PLTREL            RELA                //section type

  JMPREL            0x00000000000009e8    //.rela.plt節偏移

  RELA              0x00000000000008b0    //.rela.dyn節偏移

  RELASZ            312 (bytes)

  RELAENT           24 (bytes)

  VERDEF            0x0000000000000808

  VERDEFNUM         5

  BIND_NOW          

  FLAGS_1           NOW

  VERSYM            0x00000000000007d0

  RELACOUNT         10

x64: 過程連結表示例

.PLT0:

    pushq GOT+8(%rip) # GOT[1]

    jmp *GOT+16(%rip) # GOT[2]

    nop; nop

    nop; nop

.PLT1:

    jmp *[email protected](%rip) # 16 bytes from .PLT0

    pushq $index1

    jmp .PLT0

.PLT2:

    jmp *[email protected](%rip) # 16 bytes from .PLT1

    pushl $index2

    jmp .PLT0

以下步驟介紹了運作時連結程式和程式如何通過過程連結表和全局偏移表來協作解析符号引用。

1. 初始建立程式的記憶體映像時,運作時連結程式會将全局偏移表中的第二項和第三項設定為特殊值。以下步驟說明了這些值。

2. 程序映像中的每個共享目标檔案都有各自的過程連結表,并且控制權僅轉移給位于同一目标檔案内的過程連結表項。

3. 例如,該程式會調用 name1,以将控制權轉移給标簽 .PLT1。

4. 第一條指令會跳至全局偏移表項中對應于 name1 的位址。最初,全局偏移表儲存下一條 pushq 指令的位址,而不是 name1 的實際位址。

即第一次調用name1函數時,jmp *[email protected](%rip)中*n[email protected](%rip)的計算的值為下一條 pushq 指令的位址,實際執行了跳轉到下一條pushq 指令的操作。

5. 該程式将在棧中推送一個重定位索引 (index1)。該重定位索引是重定位表中一個32位的非負索引。重定位表由動态節(.dynamic)中的 JMPREL 項辨別。

指定的重定位項的類型為R_X86_64_JMP_SLOT,其偏移指定了前面的jmp指令中使用的全局偏移表項。該重定位項還包含符号表索引,以供運作時連結程式用于擷取引用的符号 name1。

6. 推送該重定位索引後,程式将跳至過程連結表中的第一項 .PLT0。

pushq 指令會在棧中推送全局偏移表的第二項 (GOT+8) 的值,進而為運作時連結程式提供一個字的辨別資訊。

然後,程式将跳至第三個全局偏移表項 (GOT+16) 中的位址,以繼續跳至運作時連結程式。

7. 運作時連結程式将展開棧、檢查指定的重定位項、擷取符号的值、在全局偏移項表中存儲 name1 的實際位址并跳至目标。

8. 過程連結表項的後續執行結果會直接傳輸給 name1,而不會再次調用運作時連結程式。位于 .PLT1 的 jmp 指令将跳至 name1,而不是對 pushq 指令調用

繼續閱讀