Posts 聊聊elf
Post
Cancel

聊聊elf

1. ELF 文件结构概览

ELF(Executable and Linkable Format)是一种常见的文件格式,用于可执行文件、目标文件、共享库和核心转储文件。其整体结构如下:

ELF 文件的主要组成部分

  1. ELF Header
    • 描述文件的全局信息。
    • 提供 Program Header Table 和 Section Header Table 的位置。
    • 文件结构的入口,位于文件的起始部分。
  2. Program Header Table
    • 描述运行时段(Segment)的信息。
    • 仅对可执行文件共享库有意义,目标文件通常不包含。
    • 负责运行时内存布局的描述。
  3. Sections
    • 包含代码、数据、符号表等逻辑存储单元。
    • 对链接和调试非常重要。
    • 常见节包括 .text(代码段)、.data(数据段)、.bss(未初始化数据段)等。
  4. Section Header Table
    • 描述文件中各节(Section)的元信息。
    • 包含每个节的名称、类型、大小、偏移等。
    • 用于链接和调试。

ELF 文件的具体结构布局

文件从头到尾的逻辑顺序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+---------------------+
| ELF Header          | <-- 文件头,描述文件全局信息
+---------------------+
| Program Header Table| <-- 可选,描述段的运行时布局
+---------------------+
| .text               | <-- 代码段,存放可执行指令
+---------------------+
| .rodata             | <-- 只读数据段
+---------------------+
| .data               | <-- 已初始化数据段
+---------------------+
| .bss (逻辑存在)     | <-- 未初始化数据段,文件中不占空间
+---------------------+
| .symtab             | <-- 符号表,用于调试和链接
+---------------------+
| .strtab             | <-- 字符串表
+---------------------+
| .rel.text           | <-- 重定位表
+---------------------+
| Section Header Table| <-- 描述所有节的信息
+---------------------+

2. ELF Header 详解

命令详解

  • 查询 elf header
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    xm@xm:~/xx/complier$ readelf -h SimpleSection.o
    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
    Version:                           1 (current)
    OS/ABI:                            UNIX - System V
    ABI Version:                       0
    Type:                              REL (Relocatable file)
    Machine:                           Advanced Micro Devices X86-64
    Version:                           0x1
    Entry point address:               0x0
    Start of program headers:          0 (bytes into file)
    Start of section headers:          1184 (bytes into file)
    Flags:                             0x0
    Size of this header:               64 (bytes)
    Size of program headers:           0 (bytes)
    Number of program headers:         0
    Size of section headers:           64 (bytes)
    Number of section headers:         14
    Section header string table index: 13
    
  • elf.h 详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// vim /usr/include/elf.h 
typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf64_Half    e_type;                 /* Object file type */
  Elf64_Half    e_machine;              /* Architecture */
  Elf64_Word    e_version;              /* Object file version */
  Elf64_Addr    e_entry;                /* Entry point virtual address */
  Elf64_Off     e_phoff;                /* Program header table file offset */
  Elf64_Off     e_shoff;                /* Section header table file offset */
  Elf64_Word    e_flags;                /* Processor-specific flags */
  Elf64_Half    e_ehsize;               /* ELF header size in bytes */
  Elf64_Half    e_phentsize;            /* Program header table entry size */
  Elf64_Half    e_phnum;                /* Program header table entry count */
  Elf64_Half    e_shentsize;            /* Section header table entry size */
  Elf64_Half    e_shnum;                /* Section header table entry count */
  Elf64_Half    e_shstrndx;             /* Section header string table index */
} Elf64_Ehdr;

字段对应表

ELF Header 字段Elf64_Ehdr 字段SimpleSection.o 输出值解释
Magice_ident7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00ELF 文件的魔数(7f 45 4c 46 为 “ELF”),标识 ELF 格式。
Classe_identELF64文件的架构,表示 64 位 ELF 文件。
Datae_ident2's complement, little endian字节序,小端字节序。
Versione_version1 (current)ELF 文件的版本号。
OS/ABIe_identUNIX - System V操作系统 ABI,表示 UNIX System V。
ABI Versione_ident0ABI 的版本号。
Typee_typeREL (Relocatable file)文件类型,REL 表示可重定位文件。
Machinee_machineAdvanced Micro Devices X86-64目标架构,表示 x86-64 架构。
Versione_version0x1文件的版本号。
Entry point addresse_entry0x0程序入口地址。对于目标文件,入口地址为 0。
Start of program headerse_phoff0 (bytes into file)程序头表的偏移量。目标文件不包含程序头表,因此为 0。
Start of section headerse_shoff1184 (bytes into file)节头表的偏移量,从文件的 1184 字节处开始。
Flagse_flags0x0处理器特定的标志,通常为 0。
Size of this headere_ehsize64 (bytes)ELF 文件头的大小。对于 64 位 ELF 文件,大小为 64 字节。
Size of program headerse_phentsize0 (bytes)每个程序头表项的大小。目标文件不包含程序头表,因此为 0。
Number of program headerse_phnum0程序头表项的数量。目标文件不包含程序头表项,因此为 0。
Size of section headerse_shentsize64 (bytes)每个节头表项的大小。通常为 64 字节。
Number of section headerse_shnum14节头表项的数量。该文件包含 14 个节。
Section header string table indexe_shstrndx13节头表字符串表的索引,指向节名称的字符串表。

解释

  1. MagicMagic 字段是用于识别 ELF 文件的标识符。在文件的开头有一个固定的字节序列 7f 45 4c 46,表示文件为 ELF 格式,后续的字段提供更多的文件信息。

  2. Class:表示 ELF 文件的架构,ELF64 表示这是一个 64 位的 ELF 文件。

  3. Data:指示文件中的数据使用的字节序,2's complement, little endian 表示采用小端字节序。

  4. Type:表示文件的类型,REL 表示这是一个可重定位文件(通常是目标文件 .o)。

  5. Machine:表示目标机器架构,Advanced Micro Devices X86-64 表示该 ELF 文件是为 x86-64 架构编译的。

  6. Version:ELF 文件的版本号,通常为 0x1

  7. Entry point address:表示程序的入口地址,目标文件通常没有入口点,所以此处为 0x0

  8. Program Header Table 和 Section Header Table 的偏移Start of program headers 为 0,表示没有程序头表(因为这是一个目标文件);Start of section headers 为 1184 字节,表示节头表从文件的 1184 字节开始。

  9. Size of headersSize of this header 为 64 字节,表示 ELF 文件头的大小;Size of section headers 为 64 字节,表示每个节头表项的大小。

  10. Number of section headersNumber of section headers 为 14,表示文件中包含 14 个节头表项。

  11. Section header string table indexSection header string table index 为 13,表示节头表的字符串表在第 13 个节头表项中。

段(Segment)与节(Section)

1
elf 中, 段与节是两个完全截然不同的概念。 后续有遇到再描述。

节(Section)是文件中的概念, 段(Segment)是运行时的概念。

翻译也有问题, 先留个坑吧

3. Section Header Table (中文翻译为段表)

命令查看

1
2
    objdump -h SimpleSection.o   // 只能查看主要段
    readelf -S SimpleSection.o   // 查看所有段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
xm@xm:~/xx/complier$ readelf -S SimpleSection.o
There are 14 section headers, starting at offset 0x4a0:

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
       000000000000005f  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000380
       0000000000000078  0000000000000018   I      11     1     8
  [ 3] .data             PROGBITS         0000000000000000  000000a0
       0000000000000008  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  000000a8
       0000000000000004  0000000000000000  WA       0     0     4
  [ 5] .rodata           PROGBITS         0000000000000000  000000a8
       0000000000000004  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  000000ac
       000000000000002c  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000d8
       0000000000000000  0000000000000000           0     0     1
  [ 8] .note.gnu.propert NOTE             0000000000000000  000000d8
       0000000000000020  0000000000000000   A       0     0     8
  [ 9] .eh_frame         PROGBITS         0000000000000000  000000f8
       0000000000000058  0000000000000000   A       0     0     8
  [10] .rela.eh_frame    RELA             0000000000000000  000003f8
       0000000000000030  0000000000000018   I      11     9     8
  [11] .symtab           SYMTAB           0000000000000000  00000150
       00000000000001b0  0000000000000018          12    12     8
  [12] .strtab           STRTAB           0000000000000000  00000300
       000000000000007a  0000000000000000           0     0     1
  [13] .shstrtab         STRTAB           0000000000000000  00000428
       0000000000000074  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)

对应头文件 vim /usr/include/elf.h

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tbl index) */
  Elf32_Word    sh_type;                /* Section type */
  Elf32_Word    sh_flags;               /* Section flags */
  Elf32_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf32_Off     sh_offset;              /* Section file offset */
  Elf32_Word    sh_size;                /* Section size in bytes */
  Elf32_Word    sh_link;                /* Link to another section */
  Elf32_Word    sh_info;                /* Additional section information */
  Elf32_Word    sh_addralign;           /* Section alignment */
  Elf32_Word    sh_entsize;             /* Entry size if section holds table */
} Elf32_Shdr;

表格整理

readelf -S

NrNameTypeAddressOffsetSizeEntSizeFlagsLinkInfoAlign描述
0 NULL0x000000000x0000000x0000000x000000 000空节,占位用,表示索引 0 的节无实际内容。
1.textPROGBITS0x000000000x0000400x00005F0x000000AX001包含可执行代码的段,存储程序的机器指令。
2.rela.textRELA0x000000000x0003800x0000780x000018I1118.text 段的重定位表,存储需要修正的符号地址及相关信息。
3.dataPROGBITS0x000000000x0000A00x0000080x000000WA004存储已初始化的全局变量和静态变量值,加载后可修改。
4.bssNOBITS0x000000000x0000A80x0000040x000000WA004未初始化数据段,仅分配内存但不占用文件空间。
5.rodataPROGBITS0x000000000x0000A80x0000040x000000A001只读数据段,存储不可修改的数据,如字符串字面量和常量。
6.commentPROGBITS0x000000000x0000AC0x00002C0x000001MS001编译器注释信息段,通常包含编译器版本号等信息。
7.note.GNU-stackPROGBITS0x000000000x0000D80x0000000x000000 001标识栈的权限属性的段,通常不含内容。
8.note.gnu.propertNOTE0x000000000x0000D80x0000200x000000A008包含与二进制文件相关的属性信息,例如安全性标志或平台特定的元数据。
9.eh_framePROGBITS0x000000000x0000F80x0000580x000000A008异常处理框架的段,用于支持堆栈展开和 C++ 异常处理。
10.rela.eh_frameRELA0x000000000x0003F80x0000300x000018I1198.eh_frame 的重定位表,用于修正异常处理相关的符号引用。
11.symtabSYMTAB0x000000000x0001500x0001B00x000018 12128符号表,列出了 ELF 文件中的所有符号及其属性。
12.strtabSTRTAB0x000000000x0003000x00007A0x000000 001字符串表,存储符号表中的符号名及其他字符串。
13.shstrtabSTRTAB0x000000000x0004280x0000740x000000 001节名称字符串表,存储 ELF 文件中所有节名称。

elf.h 简述

Elf32_Shdr 字段readelf -S 表头描述
sh_nameName节名称的索引,指向 .shstrtab 字符串表中对应节名的位置。
sh_typeType节类型,描述节的内容和用途,如 PROGBITSSYMTABRELA 等。
sh_flagsFlags节标志,描述节的属性,如是否可写 (W)、是否可执行 (X)、是否需要分配内存 (A) 等。
sh_addrAddress节在内存中的加载地址,如果未分配(如 NOBITS 段),则为 0x0
sh_offsetOffset节在文件中的偏移量,表示从文件开头到节内容的起始位置的字节数。
sh_sizeSize节的大小(以字节为单位),如果节为空则为 0
sh_entsizeEntSize如果节包含固定大小的条目(如符号表),表示每个条目的大小;否则为 0
sh_linkLink节的关联节索引,具体用途取决于节的类型(如符号表关联到字符串表)。
sh_infoInfo额外信息字段,含义取决于节的类型(如 .symtab 的此字段表示局部符号的数目)。
sh_addralignAlign节的对齐要求,表示节在内存或文件中起始地址必须满足的对齐字节数(如 48 等)。
字段名经典值说明
sh_name索引值表示节名称在 .shstrtab 中的偏移值,例如:
 0x00无名称的节(如第 0 节)。
 0x01对应节名为 .text
 0x0A对应节名为 .data
sh_type0x0 (NULL)无意义的节,例如第 0 节。
 0x1 (PROGBITS)包含程序代码或数据的节。
 0x2 (SYMTAB)符号表,包含 ELF 文件的符号信息。
 0x3 (STRTAB)字符串表,存储符号名称等字符串。
 0x8 (NOBITS)未初始化数据段(如 .bss)。
sh_flags0x1 (SHF_WRITE)节内容可写。
 0x2 (SHF_ALLOC)节内容需要加载到内存中。
 0x4 (SHF_EXECINSTR)节内容包含可执行指令。
 0x30 (SHF_MERGE + SHF_STRINGS)节内容可以合并且包含字符串(如 .comment)。
sh_addr地址值节加载到内存的地址,例如:
 0x00000000非加载段,通常用于调试信息或注释。
 0x08048000可执行文件的起始代码段地址。
sh_offset偏移值节在文件中的偏移量,例如:
 0x40表示从文件开始处偏移 0x40 字节是此节的起始位置。
sh_size字节数节的大小,例如:
 0x100节的大小为 256 字节。
 0x0节无内容,例如 .note.GNU-stack
sh_entsize0x0节中条目大小未知或无固定大小。
 0x10节中每个条目大小为 16 字节,例如符号表中的条目。
sh_link节索引值依赖节的索引,例如:
 0x0B.symtab 的字符串表在 .strtab(索引 0x0B)。
 0x00无关联的节。
sh_info数值节的额外信息,例如:
 0x0A.symtab 中局部符号的数量。
 0x00不需要额外信息。
sh_addralign对齐值节在内存中对齐的字节数,例如:
 0x1字节对齐,无特别对齐要求。
 0x88 字节对齐,常用于 .data.bss
 0x1016 字节对齐,常用于 SIMD 指令相关的数据段。

4. 聊聊 Section Header Table 中的各种段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SimpleSection.c 文件示例

int printf(const char* format, ...);

int global_init_var = 84;
int global_uint_var;

void func1(int i) {
    printf("%d\n", i);
}

int main(void) {
    static int static_var = 85;
    static int static_var2;
    int a = 1;
    int b;

    func1(static_var + static_var2 + a + b);

    return a;
}
1
2
3
objdump -s -d SimpleSection.o  
 // -s  显示目标文件中所有段的内容(以十六进制和 ASCII 格式显示)。 
 //  -d 反汇编文件中的可执行代码部分,显示对应的汇编指令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
xm@xm:~/xx/complier$ objdump -s -d SimpleSection.o 

SimpleSection.o:     file format elf64-x86-64

Contents of section .text:
 0000 f30f1efa 554889e5 4883ec10 897dfc8b  ....UH..H....}..
 0010 45fc89c6 488d3d00 000000b8 00000000  E...H.=.........
 0020 e8000000 0090c9c3 f30f1efa 554889e5  ............UH..
 0030 4883ec10 c745f801 0000008b 15000000  H....E..........
 0040 008b0500 00000001 c28b45f8 01c28b45  ..........E....E
 0050 fc01d089 c7e80000 00008b45 f8c9c3    ...........E... 
Contents of section .data:
 0000 54000000 55000000                    T...U...        
Contents of section .rodata:
 0000 25640a00                             %d..            
Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520392e  .GCC: (Ubuntu 9.
 0010 342e302d 31756275 6e747531 7e32302e  4.0-1ubuntu1~20.
 0020 30342e32 2920392e 342e3000           04.2) 9.4.0.    
Contents of section .note.gnu.property:
 0000 04000000 10000000 05000000 474e5500  ............GNU.
 0010 020000c0 04000000 03000000 00000000  ................
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 28000000 00450e10 8602430d  ....(....E....C.
 0030 065f0c07 08000000 1c000000 3c000000  ._..........<...
 0040 00000000 37000000 00450e10 8602430d  ....7....E....C.
 0050 066e0c07 08000000                    .n......        

Disassembly of section .text:

0000000000000000 <func1>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   48 83 ec 10             sub    $0x10,%rsp
   c:   89 7d fc                mov    %edi,-0x4(%rbp)
   f:   8b 45 fc                mov    -0x4(%rbp),%eax
  12:   89 c6                   mov    %eax,%esi
  14:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 1b <func1+0x1b>
  1b:   b8 00 00 00 00          mov    $0x0,%eax
  20:   e8 00 00 00 00          callq  25 <func1+0x25>
  25:   90                      nop
  26:   c9                      leaveq 
  27:   c3                      retq   

0000000000000028 <main>:
  28:   f3 0f 1e fa             endbr64 
  2c:   55                      push   %rbp
  2d:   48 89 e5                mov    %rsp,%rbp
  30:   48 83 ec 10             sub    $0x10,%rsp
  34:   c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  3b:   8b 15 00 00 00 00       mov    0x0(%rip),%edx        # 41 <main+0x19>
  41:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 47 <main+0x1f>
  47:   01 c2                   add    %eax,%edx
  49:   8b 45 f8                mov    -0x8(%rbp),%eax
  4c:   01 c2                   add    %eax,%edx
  4e:   8b 45 fc                mov    -0x4(%rbp),%eax
  51:   01 d0                   add    %edx,%eax
  53:   89 c7                   mov    %eax,%edi
  55:   e8 00 00 00 00          callq  5a <main+0x32>
  5a:   8b 45 f8                mov    -0x8(%rbp),%eax
  5d:   c9                      leaveq 
  5e:   c3                      retq   

.text 代码段

1
2
3
[ 1] .text             PROGBITS         0000000000000000  00000040
000000000000005f  0000000000000000  AX       0     0     1

字段说明
段索引号[ 1 ]ELF 文件段表的第一个条目
段名.text存储程序的可执行指令
类型PROGBITS段包含程序的实际代码或数据
虚拟地址0000000000000000段在内存中的起始虚拟地址(目标文件为 0)
文件偏移00000040段在 ELF 文件中的起始位置,偏移 64 字节
段大小000000000000005f段大小为 95 字节
对齐要求1起始地址可以是任意字节边界
标志AXA: 可加载到内存;X: 可执行

代码段没有什么好细聊的, 可以看看上面针对 .txt 的反汇编

.data 数据段

1
2
[ 3] .data             PROGBITS         0000000000000000  000000a0
     0000000000000008  0000000000000000  WA       0     0     4
  1. 数据段段存储已初始化的全局变量和静态变量
  2. 如何查看 .data 数据段的值?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      xm@xm:~/xx/complier$ objdump -s -j .data SimpleSection.o 
    
      SimpleSection.o:     file format elf64-x86-64
    
      Contents of section .data:
      0000 54000000 55000000                    T...U...      
    
      ------
      54000000 转换为十进制是 84,可能对应变量 global_init_var 的初始化值。
      55000000 转换为十进制是 85,可能对应 static_var 或其他静态变量的初始化值。
         
      --- 
      int global_init_var = 84;  // 这个变量初始化为 84
      static int static_var = 85; // 这个变量初始化为 85
    
  3. 它的内容会在程序运行时加载到内存,并在程序结束时释放。
  4. 修改后的值存储在进程的 内存空间 中,具体是在程序的 数据段。
  5. 代码段(.text)和只读数据段(.rodata):多个进程共享一份内存,节省内存资源。
  6. 数据段(.data)和 BSS 段:每个进程有独立的一份数据,它们存储各自的全局变量和静态变量。
  7. 堆和栈:每个进程都有独立的堆和栈,用于存储局部变量、动态分配内存等。
  8. COW(copy on write 机制):当进程需要修改共享部分内存时,操作系统才会为该进程分配新的内存空间。
  9. 每个进程都有自己独立的 .data 段,在程序执行期间,静态变量的值存储在该进程的 .data 区域中。如果静态变量在运行时被修改,新的值就会写入到该进程的数据段中。

rodata 只读数据段

1
2
[ 5] .rodata           PROGBITS         0000000000000000  000000a8
     0000000000000004  0000000000000000   A       0     0     1
1
2
3
4
5
6
xm@xm:~/xx/complier$ objdump -s -j .rodata SimpleSection.o 

SimpleSection.o:     file format elf64-x86-64

Contents of section .rodata:
 0000 25640a00                             %d..    

代码中没有显式定义 const 数据,但编译器可能将常量字符串(如格式化字符串 %d\n)自动放置在 .rodata 段中,因为它是只读的。编译器会将所有不需要修改的字符串或数据(如常量字符串)放入只读数据段 .rodata。

.bss 段

用于存储 未初始化的全局变量 和 静态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bss 段只有4字节, 只有一个变量
[ 4] .bss              NOBITS           0000000000000000  000000a8
       0000000000000004  0000000000000000  WA       0     0     4



xm@xm:~/xx/complier$ nm -S SimpleSection.o 
0000000000000000 0000000000000028 T func1
0000000000000000 0000000000000004 D global_init_var
                 U _GLOBAL_OFFSET_TABLE_
0000000000000004 0000000000000004 C global_uint_var
0000000000000028 0000000000000037 T main
                 U printf
0000000000000004 0000000000000004 d static_var.1920
0000000000000000 0000000000000004 b static_var2.1921  // static_var2 被放入bss 段

问题说明:

  1. 按道理来说, static_var2 和 global_uninit_var 都应该放在 .bss 段, 现在却是有static_var2 被放入。
  2. 与编译器实现有关:有些会将全局的未初始化变量 存放到 .bss 段, 有些则不会, 仅仅只预留一个 未定义的全局变量符号, 后续 common 块章节展开。 global_uint_var 被置为 common, 因为强弱符号的关系, 现在不能确定该弱符号的大小(4 or 8 字节)
  3. 若有一个变量,static int static_global_var; // 未初始化,放入.bss
  4. static int a = 1; static int b = 0; 像 static int a = 1; 这样的变量被显式初始化为非零值,因此会被放入 .data 段。像 static int b = 0; 或 static int c;(未初始化,默认值为零)的变量,会被放入 .bss 段。

重定位表

1
2
3
4
5
6
7
readelf -S SimpleSection.o

  [ 2] .rela.text        RELA             0000000000000000  00000380
       0000000000000078  0000000000000018   I      11     1     8

  [10] .rela.eh_frame    RELA             0000000000000000  000003f8
       0000000000000030  0000000000000018   I      11     9     8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
xm@xm:~/xx/complier$ readelf -r SimpleSection.o

Relocation section '.rela.text' at offset 0x380 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000017  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4
000000000021  001000000004 R_X86_64_PLT32    0000000000000000 printf - 4
00000000003d  000300000002 R_X86_64_PC32     0000000000000000 .data + 0
000000000043  000400000002 R_X86_64_PC32     0000000000000000 .bss - 4
000000000056  000e00000004 R_X86_64_PLT32    0000000000000000 func1 - 4

Relocation section '.rela.eh_frame' at offset 0x3f8 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
000000000040  000200000002 R_X86_64_PC32     0000000000000000 .text + 28
  1. rela.text 中对 printf & func1 的调用重定位。
  2. .data 段中暂时没有绝对地址引用, 所以没有重定位表。 若后续有, 则也会存在。

字符串表 strtab & shstrtab

1
2
3
4
5
  [12] .strtab           STRTAB           0000000000000000  00000300
       000000000000007a  0000000000000000           0     0     1

  [13] .shstrtab         STRTAB           0000000000000000  00000428
       0000000000000074  0000000000000000           0     0     1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xm@xm:~/xx/complier$ readelf -x .strtab SimpleSection.o  // 字符串表

Hex dump of section '.strtab':  
  0x00000000 0053696d 706c6553 65637469 6f6e2e63 .SimpleSection.c
  0x00000010 00737461 7469635f 7661722e 31393230 .static_var.1920
  0x00000020 00737461 7469635f 76617232 2e313932 .static_var2.192
  0x00000030 3100676c 6f62616c 5f696e69 745f7661 1.global_init_va
  0x00000040 7200676c 6f62616c 5f75696e 745f7661 r.global_uint_va
  0x00000050 72006675 6e633100 5f474c4f 42414c5f r.func1._GLOBAL_
  0x00000060 4f464653 45545f54 41424c45 5f007072 OFFSET_TABLE_.pr
  0x00000070 696e7466 006d6169 6e00              intf.main.


xm@xm:~/xx/complier$ readelf -x .shstrtab SimpleSection.o  // 段表字符串表

Hex dump of section '.shstrtab':
  0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
  0x00000010 002e7368 73747274 6162002e 72656c61 ..shstrtab..rela
  0x00000020 2e746578 74002e64 61746100 2e627373 .text..data..bss
  0x00000030 002e726f 64617461 002e636f 6d6d656e ..rodata..commen
  0x00000040 74002e6e 6f74652e 474e552d 73746163 t..note.GNU-stac
  0x00000050 6b002e6e 6f74652e 676e752e 70726f70 k..note.gnu.prop
  0x00000060 65727479 002e7265 6c612e65 685f6672 erty..rela.eh_fr
  0x00000070 616d6500  
表名strtabshstrtab
全名字符串表(String Table)节头字符串表(Section Header String Table)
作用存储符号名、字符串等(如函数名、变量名)。用于符号表(.symtab)等节的符号名称。存储节(section)的名称,供节头表(.shstrtab)使用。
内容包含 ELF 文件中的符号名(如函数名、全局变量名)。包含 ELF 文件中所有节的名称(如 .text.data.bss 等)。
引用被符号表(如 .symtab)中的条目引用,指定符号的名称。被节头表中的每个节引用,指定每个节的名称。
存储格式每个符号名称后跟一个 null 字符(\0)。每个节名称后跟一个 null 字符(\0)。
位置位于 .strtab 节中。位于 .shstrtab 节中。
使用场景用于符号表中符号的名字引用,链接器、调试器等工具通过它查找符号名称。用于识别 ELF 文件中的不同节,链接器和调试器通过它识别和处理不同的节。
示例.textmainprintf 等符号名。.text.data.bss 等节名。

.symtab 符号表

具体见下一大章节。

4. 聊聊符号表

基础结构

1
 符号表就是Elf64_Sym的数组
1
2
3
4
5
6
7
8
9
10
11
// vim /usr/include/elf.h

typedef struct
{
  Elf64_Word    st_name;                /* Symbol name (string tbl index) */
  unsigned char st_info;                /* Symbol type and binding */
  unsigned char st_other;               /* Symbol visibility */
  Elf64_Section st_shndx;               /* Section index */
  Elf64_Addr    st_value;               /* Symbol value */
  Elf64_Xword   st_size;                /* Symbol size */
} Elf64_Sym;

符号表 和 字符串表

特性symtab 符号表strtab 字符串表
内容存储符号的描述信息(类型、地址、大小、绑定等)存储符号的名称(字符串)
目的管理符号的信息,供链接器、调试器等使用存储符号名称的实际字符串,符号表通过索引引用它们
格式每个条目包含符号的元数据(结构体形式)字符串以 null 结尾的字符序列,连续存储
是否包含符号名不直接包含符号名称,而是通过索引引用 strtab 中的字符串直接包含符号名称作为字符串
索引关系符号表中的每个条目都包含一个指向 strtab 中字符串的索引无索引,它只是存储字符串的地方

查看字符表内容

1
2
3
4
5
6
7
8
9
xm@xm:~/xx/complier$ nm SimpleSection.o 
0000000000000000 T func1
0000000000000000 D global_init_var
                 U _GLOBAL_OFFSET_TABLE_
0000000000000004 C global_uint_var
0000000000000028 T main
                 U printf
0000000000000004 d static_var.1920
0000000000000000 b static_var2.1921
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
xm@xm:~/xx/complier$ readelf -s SimpleSection.o 

Symbol table '.symtab' contains 18 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS SimpleSection.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 static_var.1920
     7: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1921
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    9 
    11: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    12: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    13: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM global_uint_var
    14: 0000000000000000    40 FUNC    GLOBAL DEFAULT    1 func1
    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    17: 0000000000000028    55 FUNC    GLOBAL DEFAULT    1 main

字段说明:

  • Num:符号在符号表中的编号。
  • Value:符号的值,通常是地址或其他数据。例如,变量的地址或函数的起始地址。
  • Size:符号的大小,以字节为单位。函数的大小表示其代码的长度,变量的大小表示其内存占用。
  • Type:符号的类型:
    • NOTYPE:没有类型(通常为文件符号或无类型符号)。
    • FILE:表示源文件符号。
    • SECTION:表示一个节(节号)。
    • OBJECT:表示符号是一个对象,通常是变量。
    • FUNC:表示符号是一个函数。
  • Bind:符号的绑定类型:
    • LOCAL:符号是本地的,仅在当前文件中可见。
    • GLOBAL:符号是全局的,可以被其他文件访问。
    • COM:符号是公共的(通常用于未初始化的全局变量)。
    • UND:符号未定义,通常是外部符号。
  • Vis:符号的可见性,通常是 DEFAULT,表示符号在程序中的默认可见性。
  • Ndx:符号所属的节(段)。ABS 表示符号是一个绝对值,UND 表示符号未定义。 数字表示段号, 具体可在readelf -S SimpleSection.o 中找到对应段。 3 -> .data 段。
  • Name:符号的名称,如函数名、变量名等。

readelf -s SimpleSection.o 符号表解析

NumValueSizeTypeBindVisNdxName说明
000000000000000000NOTYPELOCALDEFAULTUND(none)无类型符号,通常是文件头或不定义的符号。
100000000000000000FILELOCALDEFAULTABSSimpleSection.c文件符号,表示源文件 SimpleSection.c
200000000000000000SECTIONLOCALDEFAULT1(none)ELF 文件的第 1 节。
300000000000000000SECTIONLOCALDEFAULT3(none)ELF 文件的第 3 节。
400000000000000000SECTIONLOCALDEFAULT4(none)ELF 文件的第 4 节。
500000000000000000SECTIONLOCALDEFAULT5(none)ELF 文件的第 5 节。
600000000000000044OBJECTLOCALDEFAULT3static_var.1920局部符号 static_var.1920,位于第 3 节,大小 4 字节。
700000000000000004OBJECTLOCALDEFAULT4static_var2.1921局部符号 static_var2.1921,位于第 4 节,大小 4 字节。
800000000000000000SECTIONLOCALDEFAULT7(none)ELF 文件的第 7 节。
900000000000000000SECTIONLOCALDEFAULT8(none)ELF 文件的第 8 节。
1000000000000000000SECTIONLOCALDEFAULT9(none)ELF 文件的第 9 节。
1100000000000000000SECTIONLOCALDEFAULT6(none)ELF 文件的第 6 节。
1200000000000000004OBJECTGLOBALDEFAULT3global_init_var全局变量 global_init_var,位于第 3 节,大小 4 字节。
1300000000000000044OBJECTGLOBALCOM3global_uint_var公共符号(未初始化)global_uint_var,大小 4 字节。
14000000000000000040FUNCGLOBALDEFAULT1func1全局函数 func1,位于第 1 节,大小 40 字节。
1500000000000000000NOTYPEGLOBALDEFAULTUND_GLOBAL_OFFSET_TABLE_未定义符号 _GLOBAL_OFFSET_TABLE_,通常是由链接器生成的。
1600000000000000000NOTYPEGLOBALDEFAULTUNDprintf外部符号 printf,来自外部库(如 libc)。
17000000000000002855FUNCGLOBALDEFAULT1main全局函数 main,位于第 1 节,大小 55 字节。

总结:

从符号表中,我们可以看到目标文件 SimpleSection.o 包含了多个符号,包括:

  • 局部符号:如 static_var.1920static_var2.1921,这些是局部静态变量。
  • 全局符号:如 global_init_varglobal_uint_varfunc1main,它们在其他文件中可能被引用。
  • 未定义符号:如 _GLOBAL_OFFSET_TABLE_printf,这些符号会在链接阶段解析。

对应关系说明

Elf64_Sym 字段readelf -s 输出字段说明
st_nameName符号的名称(字符串表中的索引)
st_valueValue符号的值或地址
st_sizeSize符号的大小
st_infoType, Bind符号的类型和绑定信息(TypeBind
st_other符号的其他信息,通常用于符号的可见性(Vis
st_shndxNdx符号所在的节的索引

具体例子

以符号 main 为例:

Elf64_Sym 字段readelf -s 输出字段示例值
st_nameNamemain
st_valueValue0x28
st_sizeSize55
st_infoType, BindFUNC / GLOBAL
st_other
st_shndxNdx1
This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags