第三章:目标文件里有什么

第三章:目标文件里有什么

3.1 目标文件的格式

ELF 文件格式

ELF(Executable and Linkable Format)是一种用于可执行文件、目标代码、共享库和核心转储的标准文件格式。

ELF 文件的基本结构:

  1. ELF 头(ELF Header)
  2. 程序头表(Program Header Table)
  3. 节头表(Section Header Table)
  4. 节(Sections)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int global_var = 10;
static int static_var = 20;

void func() {
static int local_static = 30;
int local_var = 40;
printf("Values: %d, %d, %d, %d\n",
global_var, static_var, local_static, local_var);
}

int main() {
func();
return 0;
}

使用以下命令查看 ELF 文件信息:

1
2
3
4
5
6
7
8
9
10
11
# 编译
gcc -c elf_demo.c -o elf_demo.o

# 查看 ELF 头
readelf -h elf_demo.o

# 查看节头表
readelf -S elf_demo.o

# 查看符号表
readelf -s elf_demo.o

段的概念

ELF 文件中的主要段:

  1. .text:代码段,存放可执行指令
  2. .data:数据段,存放已初始化的全局变量和静态变量
  3. .bss:未初始化数据段,存放未初始化的全局变量和静态变量
  4. .rodata:只读数据段,存放常量
  5. .comment:注释信息
  6. .debug:调试信息

示例代码:

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
#include <stdio.h>

// .text 段
void func() {
printf("Hello\n");
}

// .data 段
int global_init = 10;

// .bss 段
int global_uninit;

// .rodata 段
const char *str = "Hello, World!";

int main() {
// .text 段
func();

// .data 段
static int static_init = 20;

// .bss 段
static int static_uninit;

return 0;
}

使用以下命令查看段信息:

1
2
3
4
5
# 编译
gcc -c sections.c -o sections.o

# 查看段信息
objdump -h sections.o

符号表

符号表记录了目标文件中定义和引用的符号信息。主要包括:

  1. 全局符号
  2. 局部符号
  3. 调试符号

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

// 全局符号
int global_var = 10;

// 静态全局符号
static int static_global = 20;

// 函数符号
void func() {
// 局部符号
int local_var = 30;
printf("%d\n", local_var);
}

int main() {
func();
return 0;
}

使用以下命令查看符号表:

1
2
3
4
5
# 编译
gcc -c symbols.c -o symbols.o

# 查看符号表
nm symbols.o

3.2 链接的接口——符号

符号的定义与引用

符号分为:

  1. 强符号:函数和已初始化的全局变量
  2. 弱符号:未初始化的全局变量

示例代码:

1
2
3
4
5
6
7
// 强符号
int strong_var = 10;
void strong_func() {}

// 弱符号
int weak_var;
void weak_func() {}
1
2
3
4
5
6
7
8
9
10
11
// 引用符号
extern int strong_var;
extern void strong_func();
extern int weak_var;
extern void weak_func();

int main() {
strong_func();
weak_func();
return 0;
}

符号表

符号表的结构:

  1. 符号名
  2. 符号值
  3. 符号大小
  4. 符号类型
  5. 符号绑定信息
  6. 符号可见性

使用以下命令查看详细的符号信息:

1
2
3
4
5
6
7
# 编译
gcc -c symbol_def.c -o symbol_def.o
gcc -c symbol_ref.c -o symbol_ref.o

# 查看符号表
readelf -s symbol_def.o
readelf -s symbol_ref.o

符号修饰与函数签名

C++ 中的符号修饰:

  1. 函数名修饰
  2. 类名修饰
  3. 命名空间修饰

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

namespace MySpace {
class MyClass {
public:
void myFunc(int x) {
std::cout << x << std::endl;
}
};
}

int main() {
MySpace::MyClass obj;
obj.myFunc(10);
return 0;
}

使用以下命令查看修饰后的符号:

1
2
3
4
5
# 编译
g++ -c name_mangling.cpp -o name_mangling.o

# 查看符号表
nm name_mangling.o

实践练习

  1. ELF 文件分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建示例文件
cat > elf_test.c << EOL
#include <stdio.h>

int global_var = 10;
static int static_var = 20;

void func() {
printf("Hello\n");
}

int main() {
func();
return 0;
}
EOL

# 编译
gcc -c elf_test.c -o elf_test.o

# 分析 ELF 文件
readelf -h elf_test.o
readelf -S elf_test.o
readelf -s elf_test.o
  1. 段分析
1
2
3
4
5
# 查看段信息
objdump -h elf_test.o

# 查看段内容
objdump -s elf_test.o
  1. 符号分析
1
2
3
4
5
# 查看符号表
nm elf_test.o

# 查看详细的符号信息
readelf -s elf_test.o

思考题

  1. ELF 文件格式的主要组成部分是什么?
  2. 代码段和数据段的区别是什么?
  3. 强符号和弱符号的区别是什么?
  4. 为什么需要符号修饰?
  5. 如何解决符号冲突问题?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. ELF 文件格式规范
  3. GNU Binutils 文档
  4. System V ABI
  5. C++ 名字修饰