针对这些问题,这次做一个补充:
一,可重定位文件的格式是什么,以main.o为例,
格式为ELF ,包括:{1,ELF Header
它描述了整个文件的文件属性,包括文件是否可以执行,是静态链接还是动态链接及入口地址(若是可执行 文件),目标硬件,目标操作系统等等。
2, .text(代码段) 按四字节对齐
一般C语言编译后执行语句都编译成机器代码,保存在这个段中
3, .data(数据段)
已初始化的全局变量和局部变量都保存在这个段中
4, .bss(占用虚拟内存空间的数据)
未初始化的全局变量和局部静态变量都保存在这个段中
注:.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,故在文件中不占据空间 。
5, .rodata(只读数据段)
6, .comment(注释信息段)
7, .note.GNU-stack(堆栈提示段)
8, symtab(符号表段)
9, 其他段。
}
光说不练假把式,编写一个程序main.c,在Linux底下编译得到main.obj文件
用objdump -h main.o这个命令得到它的格式
其中,size表示该段的大小,File off为段所在的位置。CONTENTS、ALLOC等表示段的属性。有CONTENTS表示该段在文件中存在,但是.bss段没有,表示,.bss段在文件中不存在内容。而.note.GNU-stack的size为0,所以它在ELF文件中也不存在。
再说明一下,关于.bss段,我们可以看到它的size为4,但是它的file off(地址)却跟.rodata的file off相同,故.bss段并不占磁盘空间,它只是预留了未定义的全局变量符号(?底下会有详细说明)和未定义的局部静态变量符号,等到链接时再给它们分配内存。
详细打印一下各个段的内容:
1,text段
我们可以看到,从.text段从0x00到0x50,正好是0x51个字节。与,中的一样。其中,最左边一行是偏移量,中间四列是十六进制内容,最右边是.text段的ASCII码形式。
反汇编结果如下。
2,.data段和.rodata段
.data段保存的是已经初始化了的全局静态变量和局部静态变量。.
.rodata段中存放的是只读数据,上面的程序中在调用printf时有个参数 '%d ',这就是一种只读数据。还有const修饰的变量等等。
.data段中,前四个字节从低到高为(小端存储):0x63,0x00,0x00,0x00。转化为10进制正好为99(全局初始化global_init)。同理,后四位为1(局部静态初始化static_init)。
OK,现在已经把主要的几个段了解了,现在来学习一下关于ELF的文件结构
来张图说明
其中,关于ELF header前面已经介绍了,包含着整个文件的基本属性;紧接着是ELF各个段,其中ELF文件中与段相关的重要结构就是section header table(段表),什么是段表呢,它描述了ELF文件包含的所有段的信息!比如每个段的段名,长度,在文件中的偏移读写权限等等其他属性。再接着就是字符串表,符号表等等。接下来详细的介绍一下段表。
用readelf -S 来查看详细的段表结构
我们可以看到,段表其实就是一个一维数组,数组元素的个数等于段的个数,但是第一个是NULL的,所以main.o总共有10个有效的段。
关于段表,最重要是两个,一个是段的类型(Type),一个是段的标志位(Flags),他们决定了段的属性。
段的类型分为无效段,程序段(代码段,数据段都是这种类型)或者符号表等等。
段的标志位表示该段在进程虚拟地址空间中的属性,比如是否可写,是否可执行等等。
在.text段下有一个.rel.text段,这个.rel.text段即就是重定位表。此时先提出了,当谈到链接的时候再解答。
-------------------------------------------------------------------------------------------------------------------------------------
OK,此时已经把第一个问题算是解答完毕了,接下来继续解答剩下的两个问题。
-------------------------------------------------------------------------------------------------------------------------------------
二,可执行文件的格式
可执行文件是什么格式呢?上图
这是可执行文件main中的段,
可见,可执行文件也是由很多段组成的,在mian.o的基础上多了更多的段。因此,可执行文件之所以可以运行,肯定与链接之后多出来的这些段有关系。这些都是些什么东东呢,在深入链接之前,我们先解决第三个问题。
程序要运行,需要三步。
第一,需创建虚拟地址空间到物理内存的映射(创建内核地址映射结构体),创建页目录和页表。
第二,加载代码段和数据段。
第三,把可执行文件的入口地址写到cpu的pc寄存器中。
所以为什么可执行文件可以运行而可重定位文件不可运行的问题可以得到解决,
1,.obj中无符号地址,.exe中有符号地址。因为编译环节并不为符号分配地址,故没有内存,数据的地址都统一的0地址。而链接之后为符号分配了内存地址。
2,.obj中无函数入口地址,.exe中有函数入口地址。我们知道程序要运行就必须知道函数入口地址。
3,.exe中有链接之后一个”特殊段“,跟页表有关。
然而这些答案依然让人难以理解透彻,比如说,链接之后究竟产生了什么新东西,函数入口地址又是如何产生的,.exe中的跟页表有关的段到底是什么。。。ok,这些东西都会得到解答。
接下来,我们更加深入到静态链接中去。