目标文件里有什么
目标文件的格式
现在PC平台流行的可执行文件格式(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它们都是COFF(Common file format)格式的变种。目标文件就是源代码编译后但未进行链接的那些中间文件(Windows下的.obj和Linux下的.o),它跟可执行文件的内容与格式很相似,所以一般与可执行文件采用一种格式存储。
COFF的主要贡献是在目标文件里面引入了“段”的机制,不同的目标文件可以拥有不同数量及不同类型的“段”。另外,它还定义了调试数据格式。
不光是可执行文件(Windows的.exe和Linux下的ELF可执行文件)按照可执行文件格式存储。动态链接库(DLL,Dynamic Linking Library)(Windows的.dll和Linux的.so)及静态链接库(Static Linking Library)(Windows的.lib和Linux的.a)文件都按照可执行文件格式存储。它们在Windows下都按照PE-COFF格式存储,Linux下按照ELF格式存储。下表为系统中采用的ELF文件格式类型:
ELF文件类型 | 说明 | 实例 |
---|---|---|
可重定位文件(Relocatable File) | 这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类 | Linux的.o Windows的.obj |
可执行文件(Executable File) | 这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件,他们一般都没有扩展名 | 比如/bin/bash文件 Windows的.exe |
共享目标文件 (Shared Object File) | 这种文件包含了代码和数据,可以在以下两种情况使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态链接,产生可以将几个这种共享目标文件与可执行文件结合,作为进程影响的一部分来运行 | Linux的.so,如/lib/glibc-2.5.so Windows的DLL |
核心转储文件(Core Dump File) | 当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件 | Linux下的从core dump |
在Linux下,我们可以使用file命令来查看相应的文件格式:
1 | $ file hello.o |
目标文件是什么样的
程序的源代码编译后的机器指令经常被放倒代码段(Code Section)里,代码段中常见的名字有.code
,.text
;全局变量和局部静态变量数据经常放在数据段(Data Section),数据段的一般名字都叫.data
。一个简单的程序被编译成目标文件后的结构如下图所示:
)
下面让我们来看看目标文件中具体会有什么。
挖掘SimpleSection.o文件
1 | int printf(const char* format, ...); |
我们可以先将上述程序生成.o
文件,然后利用objdump命令查看其ELF文件段的信息。
1 | $ gcc -c SimpleSection.c |
从上面的结果来看,SimpleSection.o
的段的数量比我们想象中的多,除了最基本的代码段、数据段和BSS段以外,还有4个段分别为只读数据段.rodata
、注释信息段.commit
、堆栈提示段.note.GNU-stack
和堆栈回溯段.eh_frame
。每个段的第一行容易理解的是段的长度(Size)和段所在的位置(File Offset),每个段中的第二行中的CONTENTS
,ALLOC
等标识段的各种属性,CONTENTS
表示该段在文件中存在。我们可以看到BSS段没有CONTENTS
,表示实际上在ELF文件中不存在内容。.note.GNU-stack
段虽有CONTENTS
,但长度为0。那么在ELF文件中实际存在的也就是.text
、.data
、.rodata
、.comment
和.eh_frame
这5个段了。只考虑前四个段,那么它们在ELF中的结构如下图所示。
代码段
我们可以利用objdump来查看代码段的内容。Contents of section .text 就是.text
的十六进制内容,总共0x57字节。
1 | // -s可以将所有段的内容以十六进制的方式打印出来 |
数据段和只读数据段
1 | ...... |
.data
段保存的是那些已经初始化了的全局变量和局部静态变量,分别为global_init_varabal
、static_var
,这两个变量每个4字节,一共8个字节,即54000000
对应于十进制84,55000000
对应于十进制85。另外,我们在程序中用到了一个字符串常量“%d\n”,它是一种只读数据,所以放到了.rodata
段。
.rodata段存放的只读数据一般是程序里面的只读变量(如const修饰的变量)和字符串常量。单独设立“.rodata”不光在语意上支持了c++的const关键字,而且操作系统在加载的时候可以将“.rodata”段的属性映射成只读,这样对于这个段的任何修改操作都会作为非法操作处理,保证了程序的安全性。
BSS段
.bss
段保存的是未初始化的全局变量和局部静态变量,如global_uninit_varabal
、static_var2
。可以看到两个变量共占有8个字节,实际上该段的大小为4字节,这是因为不同的语言与不同的编译器实现有关,有的编译器会将全局的未初始化变量存放在.bss
段,有的只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss
段分配空间。(编译单元内部可见的静态变量的确是存放在.bss
段)
1 | Sections: |
其他段
常用的段名 | 说明 |
---|---|
.rodata1 | 跟.rodata一样 |
.comment | 编译器版本信息,如GCC: (Ubuntu 7. 5.0-3ubuntu1~18.04) 7.5.0. |
.debug | 调试信息 |
.dynamic | 动态链接信息 |
.hash | 符号哈希表 |
.line | 调试时的行号表,即源代码行号与编译后指令的对应表 |
.note | 额外的编译器信息。如程序的公司名、发布的版本号等 |
.strtab | String Table字符串表,用于存储ELF文件中用到的各种字符串 |
.symtab | Symbol Table符号表 |
.shstrtab | Section String Table段名表 |
.plt .got | 动态链接的跳转表和全局入口表 |
.init .fini | 程序初始化与终结代码段 |
参考资料
- 程序员的自我修养-链接,装载与库
- 本文标题:目标文件有什么
- 创建时间:2023-09-12 00:25:16
- 本文链接:2023/09/12/目标文件有什么/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!