第三章:目标文件里有什么
3.1 目标文件的格式
ELF 文件格式
ELF(Executable and Linkable Format)是一种用于可执行文件、目标代码、共享库和核心转储的标准文件格式。
ELF 文件的基本结构:
- ELF 头(ELF Header)
- 程序头表(Program Header Table)
- 节头表(Section Header Table)
- 节(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
readelf -h elf_demo.o
readelf -S elf_demo.o
readelf -s elf_demo.o
|
段的概念
ELF 文件中的主要段:
- .text:代码段,存放可执行指令
- .data:数据段,存放已初始化的全局变量和静态变量
- .bss:未初始化数据段,存放未初始化的全局变量和静态变量
- .rodata:只读数据段,存放常量
- .comment:注释信息
- .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 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 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 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 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
|
实践练习
- 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
readelf -h elf_test.o readelf -S elf_test.o readelf -s elf_test.o
|
- 段分析
1 2 3 4 5
| objdump -h elf_test.o
objdump -s elf_test.o
|
- 符号分析
1 2 3 4 5
| nm elf_test.o
readelf -s elf_test.o
|
思考题
- ELF 文件格式的主要组成部分是什么?
- 代码段和数据段的区别是什么?
- 强符号和弱符号的区别是什么?
- 为什么需要符号修饰?
- 如何解决符号冲突问题?
参考资料
- 《程序员的自我修养:链接、装载与库》
- ELF 文件格式规范
- GNU Binutils 文档
- System V ABI
- C++ 名字修饰