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  ....@.8...@.....

# 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 snprintf@GLIBC_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 puts@GLIBC_2.2.5 (2)
    6: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF gettimeofday@GLIBC_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 __cxa_finalize@GLIBC_2.2.5 (2)
   10: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF vsnprintf@GLIBC_2.2.5 (2)
   11: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF localtime_r@GLIBC_2.2.5 (2)
   12: 0000000000000000      0 OBJECT  GLOBAL DEFAULT    UNDEF stderr@GLIBC_2.2.5 (2)
   13: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF fwrite@GLIBC_2.2.5 (2)
   14: 0000000000000000      0 FUNC    GLOBAL DEFAULT    UNDEF fflush@GLIBC_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 *name1@GOTPCREL(%rip) # 16 bytes from .PLT0
    pushq $index1
    jmp .PLT0
.PLT2:
    jmp *name2@GOTPCREL(%rip) # 16 bytes from .PLT1
    pushl $index2
    jmp .PLT0
    
以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。
1. 初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。
2. 进程映像中的每个共享目标文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。
3. 例如,该程序会调用 name1,以将控制权转移给标签 .PLT1。
4. 第一条指令会跳至全局偏移表项中对应于 name1 的地址。最初,全局偏移表保存下一条 pushq 指令的地址,而不是 name1 的实际地址。
即第一次调用name1函数时,jmp *name1@GOTPCREL(%rip)中*name1@GOTPCREL(%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 指令调用
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值