1.目标文件
- 可重定位目标文件(main.o例如):包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并,创建一个可执行目标文件
- 可执行目标文件(.exe/a.out):包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行
- 共享目标文件(.dll/.so):一种特殊类型的重定位目标文件,可以在加载或运行时被动态的加载到存储器并连接。
编译器和汇编器生成可重定位目标文件(包括共享目标文件)。链接器生成可执行目标文件。
2.可重定位目标文件和可执行目标文件的格式
可重定位目标文件格式:
可执行目标文件格式:
3.下面我们开始分析上面图片
- ELF header: 用来描述整个目标文件的组织结构,包括节的数目、节的大小、程序的入口地址。使用linux的readelf工具就可查看目标文件的ELF文件结构,命令如下:gcc -c xxx.c;readelf -a xxx.o
- 段头部表:描述分节和地址空间的映射关系,格式如下图,和上面可执行目标文件对照起来看。
- 节头部表:描述目标文件节的信息。
- text:代码段
- rodata:只读数据
- data:已经初始化的全局c变量(包括static变量),注:局部变量只会出现在调用堆栈当中。
- bss:未初始化的全局c变量。在目标文件当中这个节实际不占用空间,仅仅是占位符。未初始化的变量不需要占据任何的实际磁盘空间。
而对于未被初始化的全局变量和静态局部变量,编译的时候并未被分配空间,而是仅仅在.bss段中标记它们,当程序运行的时候才为它们在内存中分配空间,并把它们初始化为零。
4.未初始化的全局变量和static局部变量
gcc -c main.c
readelf -a main.o
上图是目标文件main.o中的符号表。i的Ndx值是COM:代表未被分配空间的未初始化的数据目标;j的Ndx是3,我们查看到这是.bss段的索引,所以j在.bss段有占位符(并不是空间)。
像i这种未初始化内置类型的变量,他并没有分配空间,是没法设置断点的。
下面我们分析i、j最终分配空间时,属于线性地址空间的哪部分?
这里我们需要使用cat /proc/processid/maps命令这里是要查看进程的ID,可以使用ps -aux |grep a.out获取进程的ID。
根据我的测试,像目标文件的.data,.bss段的内容都会在地址空间的数据段分配空间(bss段内容在第一次使用才才在数据段分配内存),像const int k=0和字符串常量是在代码代码段中分配内存的。这点也可以从深入理解操作系统的P466页得到验证。
参考文章: