######## 该系列博文为书籍《程序员的自我修养》的笔记 ##########
【说明】
我们知道,在windows下和在linux有一点是一样的,那就是我们的源代码都要编译,链接成可执行文件,而在链接之前,我们需要把各个源代码编译成目标文件,在linux下是.o后缀,显然这些目标文件是有格式的,而不是单单包含着指令和数据。这一章就来介绍目标的格式,只有知道了格式,才能理解链接的过程。windows下可执行文件是PE格式的,而linux下是ELF格式的,他们有一定差别,却来至同一个祖先COFF。
【什么是目标文件】
目标文件就是源代码编译后但未进行链接的那些中间文件,linux 下是.o文件 windows下是.obj文件。其实目标文件和最后的可执行文件格式是类似的,所以一般都采用一种格式。
【目标文件里有什么】
先来看一张图,里面标记了一个简单的C语言,各个部分在对应的目标文件中的位置。
1)显然,我们看到目标文件开头有一个文件头,我们猜测里面有整个目标文件的信息。
2)初始化且非0的全局变量放在.data 段(后面section 我们都称为段)
3)未初始化和初始值为0的全局变量放在.bss 段
4)函数中的静态变量处理方法同全局变量
5)指令放.text 段
※:为什么要分段呢:
1)数据和指令有不同访问权限,分开好管理
2)现在CPU指令寄存器和数据寄存器是分开的,两者分段,发挥局部定理的优势
3)便于共享,比如指令,既然是只读的,如果大家都要用,内存里放一份就可以了
【探索.o文件】
将下面的例子编译成.o文件
int printf(const char *format, ... );
int global_init_var = 84;
int global_uninit_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;
}
执行objdump -h simp.o 如下
1)这个命令是读取.o 文件的基本信息(而不是头文件信息)
2)我们看到,段比我们想象的要多,很多段我们都不认识,在后面就知道了。
3)我们先来关注其中的 Size 和 File off 属性,然后将他们整理得到如下的文件分布,其实.note.GNU-stack 先无视
【展开.text】
我们一个一个看看这几个段中就放了什么东西,先来看看.text 里面应该放着所有的指令
利用指令 objdump -s -d simp.o 可以得到目标文件的内容
这个的.text 的内容,这样子看不出来,因为里面放的是机器码,还好这个命令比较聪明,加了-d会把输出反汇编
这个就是我们源代码中的指令部分反汇编后的结果了。。。(一部分)
【展开.data和.rodata】
其中 .rodata 是只读数据段,为什么它大小是4呢,其实是因为我们刚才的%d\n 字符串。。。。3 + 一个结束符
还是刚才的命令中,可以找到这么一段
瞧,,看到了吧 .rodata 中真的有个 %d
其中的54000000 是十六进制的,由于机器是小端机所以真值应该为0x00000054 也就是 十进制84 刚好是gloval_init_var,后面的85是什么猜猜都知道了
【.bss】
关于.bss 段,其实.bss段里并没有真正的给变量分配空间,在文件里.bss段其实只保存了大小,而不像.data段有具体的数值,为什么?因为.bss段里面数据全 是0啊,你既然知道全是0了 ,还放那么多0在那干什么,浪费空间吗,只要装入内存的时候,按大小在内存里清出那么大一片0区域不就行啦。反正运行的时候用 的是内存里的数据。
【其他段】
除了这些常用的段,还有一些
1)用户是可以自己定义段名的,但是一般不要把自己的段用"."开头,不然容易和系统的冲突
2)自定义段名方法: