目标文件内容
1. “文件头”:描述整个文件的文件的文件属性,包括文件是否可执 行、是静态链接还是动态链接及入口地址(可执行文件)、目标硬件、目标操作系统等信息,文件头还包括一个段表。段表其实就是一个描述文件中各个段的数组,描述段在文件中的偏移位置及段的属性等。
2. .text段:编译后执行语句都编译成机器代码,保存在该段。
3. .data段:已经初始化的全局变量和局部静态变量都保存在该段。
4. .bass段:未初始化的全局变量和局部静态变量一般放在该段。
总体来说,程序源代码被编译之后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。
.rodata段:存放的是只读数据,一般是程序里面的只读变量和字符串变量。
以“.” 前缀的段是系统保留的,程序可以室友一些非系统保留的段名创建新段。
__attribute__((section("name"))) int val = 21;
//GCC的扩展机制,指定变量所处的段
初始化为0或未初始化的变量会被编译器优化放在.bss段(不占磁盘空间),这样可节省磁盘空间。
数据和指令分开好处主要有如下几个方面:
- 权限:数据区域对进程来说是可读写的,指令区域对于进程来说是只读的。程序被装载后,数据和指令分别被映射到两个虚存区域,权限可以分别被设置为可读写和只读。防止程序的指令被有意或无意修改。
- 指令区和数据区分离有利于提高程序的局部性。对CPU的缓存命中率提高有好处。
- 最重要的原因。系统中运行多个该程序的副本时,他们的指令都是一样的,所以内存中只要保存一份改程序的指令部分(共享指令)。对于只读指令来说是这样,对于其他只读数据也是一样。共享指令在现代操作系统中占有极为重要的地位,特别是在有动态链接库的系统中,可以节省大量内存。
用binutils的工具objdump查看目标文件
-h:把elf 文件的各个段的基本信息打印出来。
-x:打印更多信息。
size命令:可以查看elf文件的代码段、数据段和BSS 段的长度。
-s:所有段的内容以十六进制打印出来。
-d:反汇编所有包含指令的段。
链接的接口——符号
每个定义的符号有一个定义的值(符号值),就是变量和函数的地址。
符号分类:
1. 定义子在本目标文件的全局符号,可被其他目标文件引用。
2. 在本目标文件引用的全局符号,却没有定义在本目标文件。(外部符号)
3. 段名:一般由编译器产生,值为该段起始地址。
4. 局部符合,只在编译单元内部可见。
5. 行号信息,目标文件指令与源代码中代码行的对应关系,可选。
6. 特殊符号:没有在程序中定义,但可以直接声明并且引用它。
1). __executable_start,程序最开始的地址(不是入口地址)。
2). __etext 或 _etext 或 etext ,代码最末尾地址。
3). _edata 或 edata ,数据段最末尾地址。
4). _end 或 end ,程序结束地址。
c++ 编译器会将在 extern “C” 的大括号内的代码当作C语言代码处理,C++的名称修饰机制将不会起作用。
C++的宏 “__cplusplus” :C++编译器在编译C++程序时默认定义该宏,可以使用该条件宏判断当前编译单元是不是C++代码。
弱符号和强符号(针对定义)
C/C++语言,编译器默认将函数和初始化了的全局变量作为强符号,未初始化的全局变量作为弱符号。(可用 GCC 的 “__attribute__((weak))
“定义任何一个强符号为弱符号)
链接器对于全局符号处理规则:
1. 不允许强符号多次定义,否则会报错。
2. 如果一个符号在某个目标文件中是强符号,其他文件都是弱符号,选择强符号。
3. 所有目标文件中都是弱符号,选择占用空间最大一个。
正是由于第三条,所以编译时弱符号所需空间大小是未知的,编译器无法为某一弱符号分配空间。但链接器在链接过程中可以确定弱符号大小。
强引用和弱引用
在链接成最终可执行文件时,需正确决议,若没能找到该符号定义,链接器报符号未定义错误,称为强引用。
弱引用,如果符号未定义,链接器不抱错。未定义的弱引用,链接器一般默认为0。(__attribute__((weakref))
声明外部函数引用为弱引用)
弱符号和弱引用主要用于库的链接过程。
- 库中定义的弱符号可以被用户定义的强符号覆盖,从而使得程序可以使用自定义版本的库函数;
- 程序可以对某些扩展功能模块的引用定义为弱引用,当我们将扩展模块与程序链接在一起时,功能模块就可以正常使用;若去掉也可以正常链接,只是少了某些功能,是程序功能更加容易裁剪和组合。