编译过程<2>(目标文件)
Last updated on 7 months ago
前言
从源码到可执行文件,中间的过程中会生成 .o 文件,也就是目标文件,目标文件放的是什么呢?前面经常用objdump 反汇编过,似乎是以莫种格式存放的,什么格式呢? 可执行文件又是什么格式呢?
本文只讨论Linux下的目标文件
目标文件的格式
我们先用 file 命令查看
1 |
|
- 可执行文件 main 是ELF 64-bit LSB shared object ,表示它已经经过链接,可以直接在系统上执行
- 目标文件 hello.o 是 ELF 64-bit LSB relocatable,可重定位的目标文件,包含了编译后的程序的机器码和未解析的重定位信息
两者都有 ELF ,那ELF是什么?
ELF(Executable and Linkable Format)是一种常见的可执行文件和目标文件格式,用于在类Unix操作系统上存储程序的二进制代码以及与之相关的元数据。对于windows ,称之为 PE(Portable Executable)
从file 命令看目标文件和可执行文件 都是elf 格式,所以再Linux 下,统称为 ELF文件; 想动态库,静态库也是 elf 格式的文件
目标文件是怎样的?
源码编译成目标文件后,可以分为两个部分:
- 代码段(.text): 存放汇编指令
- 数据段(.bss / .data ):数据又分为两部分
- 初始化的全局变量和静态变量存放再 data
- 未初始化的存放再 .bss(Block Started by Symbol)
用size 命令可观测到
1 |
|
**为什么需要bss 区? **
本质上都可以存放,但 未初始化的全局变量都为0,如果放date区扩展空间,并存放数据为0,反而会占用空间,为了节省空间,未初始化的全局变量和声明为static的局部变量不占有任何空间,只是保存了在运行时它们要占的空间的大小,所以bss
也被戏称为“Better Save Space”
为什么要区分代码段和数据段?
区分读写权限: 数据是可读写的,而代码段是只读的,防止代码段被恶意修改
代码段的复用: 内存中加载的同一程序的多个实例,或者不同程序中引用相同库的情况下,它们可以共享相同的代码段。这种共享可以节省系统内存,并提高程序的执行效率
对于第二点,我们可以举个例子看看, 编译并生成两个相同的可执行文件, 然后分别gdb 进去,看两者内存的映射
1
2
3
4
5
6
7
8
9
10
11
12hrp@ubuntu:~$ cat test.c
// test.c
#include <stdio.h>
void foo() {
printf("Hello from foo()\n");
}
int main() {
foo();
return 0;
}
hrp@ubuntu:~$ gcc -g test.c -o test2
hrp@ubuntu:~$ gcc -g test.c -o test1可以发现两个运行者的程序共用一段内存地址
但这理由证据还不够充分,怎么知道 这就是代码段的映射的内存呢?
于是我去 /proc/pid/maps 看这个程序的内存映射; 地址和gdb 看的时一样的, 但多了个信息 r-xp ,其中 x 标志可执行,也就说明这个内存区间是存放代码段,且运行两个程序,都是相同的内存地址,所以是 同一程序的多个实例共享 同一个代码段的!
1
2
3
4
5hrp@ubuntu:~$ cat /proc/3915/maps
555555554000-555555555000 r-xp 00000000 08:01 1102662 /home/hrp/test1
555555754000-555555755000 r--p 00000000 08:01 1102662 /home/hrp/test1
555555755000-555555756000 rw-p 00001000 08:01 1102662 /home/hrp/test1
...
深入 .o 文件组成
都有那些组成
从objdump 反汇编看( -h 选项是 打印出这个目标文件的section 内容)
1 |
|
除了刚才说的 三个 section,还有多了好几个section 名字
.rodata
:包含只读数据的段.comment
:包含注释的节.note.GNU-stack
:用于控制栈的属性的节- .eh_frame`:包含异常处理信息的节
这里偏移量是从 0x40开始的,那0~0x40是什么呢?
对于elf 格式的文件,都有个elf 头,用 描述这个elf 的基本信息,elf 头大小是固定的, 也可以通过 readelf -h 查看(后面再解释elf )
1 |
|
所以现在的elf 结构是
接下来以 “看得见” 的方式介绍 各个 段:
.text
//待更新