往期地址:
本期主题:
目标文件详解
目标文件定义:
编译器编译源代码后生成的文件称为目标文件(object file)
目标文件从结构上来讲,还没有经过链接,所以可能有些符号还没有被调整
现在PC端的可执行文件格式主要包括了windows下的PE(Portable Execute)以及linux下的ELF(Execute Linkable Format),目标文件就是源码经过编译但是还没链接的那些中间文件,例如windows下的.obj文件和linux下.o文件,该类型文件内容与结构和可执行文件几乎一样,所以从广义上来说,可以将目标文件和可执行文件看成一种类型的文件,linux下统称为ELF文件格式。
因此ELF文件格式又可以分为以下几类:
ELF文件类型 | 说明 | 实例 |
---|---|---|
可重定位文件(relocate file) | 编译生成的文件,可被链接生成可执行文件 | linux下的.o以及windows下的.obj文件 |
可执行文件(executable file) | 可以直接执行的程序,典型的ELF文件格式 | linux下的/bin/bash以及windows下的exe文件 |
共享目标文件(shared object file) | 动态链接文件,可以和其他的可重定位文件和共享目标文件一起链接,生成新的目标文件 | linux下的.so和windows下的DLL文件 |
核心转存文件(core dump file) | 进程信息存储文件,当进程意外终止时,系统将该进程的地址空间以及终止时的一些信息存到该文件 | linux下的core dump文件 |
写一个简单的测试程序:main.c
#include
int global_init_var = 100; //data段
int global_uninit_var; //bss段
int main(void)
{
static int static_var = 200; //data段
static int static_var2; //bss段
int a = 1; //text
int b; //text
return 0;
}
jason@ubuntu:~/WorkSpace/3.OS_study/1.object_file$ gcc -c main.c
jason@ubuntu:~/WorkSpace/3.OS_study/1.object_file$ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
可以看到main.o就是一个relocate file,目标文件会按照 段 section 的方式来存储信息。可以简单理解为下图这种情况
其中,
ELF文件的开头是一个文件头,文件头描述了整个文件的文件属性,其中还有段表 section table,描述各个段的情况
.text段,编译后的执行语句都翻译成机器代码放在.text段
.data段,已经初始化的全局变量和局部静态变量
.bss段,未初始化的全局变量和局部静态变量
常用的有objdump、readelf工具
objdump:
用法:objdump <选项> <文件>
显示来自目标 <文件> 的信息。
至少必须给出以下选项之一:
-a, --archive-headers Display archive header information
-f, --file-headers Display the contents of the overall file header
-p, --private-headers Display object format specific file header contents
-P, --private=OPT,OPT… Display object format specific contents
-h, --[section-]headers Display the contents of the section headers
-x, --all-headers Display the contents of all headers
-d, --disassemble Display assembler contents of executable sections,反汇编可执行段, .text段
-D, --disassemble-all Display assembler contents of all sections
–disassemble= Display assembler contents from
-S, --source Intermix source code with disassembly
–source-comment[=] Prefix lines of source code with
-s, --full-contents Display the full contents of all sections requested
-g, --debugging Display debug information in object file
-e, --debugging-tags Display debug information using ctags style
-G, --stabs Display (in raw form) any STABS info in the file
-W[lLiaprmfFsoRtUuTgAckK] or
–dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
=frames-interp,=str,=loc,=Ranges,=pubtypes,
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
=addr,=cu_index,=links,=follow-links]
Display DWARF info in the file
–ctf=SECTION Display CTF info from SECTION
-t, --syms Display the contents of the symbol table(s)
-T, --dynamic-syms Display the contents of the dynamic symbol table
-r, --reloc Display the relocation entries in the file
-R, --dynamic-reloc Display the dynamic relocation entries in the file
@ Read options from
-v, --version Display this program’s version number
-i, --info List object formats and architectures supported
-H, --help Display this information
-j, 可以执行反汇编的段
objdump -x 显示elf文件的所有header信息,包括elf header信息、section header信息还有符号表
objdump -h 显示elf文件的section header信息
objdump -s 将反汇编的信息用十六进制显示
通过上图dump出来的信息,主要看 “size” 和 "file off"段,可以大致将 main.o这个ELF文件结构理解为如下图所示:
看前面的main.o 实际使用 objdump -s -d dump出来的信息
1.text代码段
对代码指令的反汇编,内容里的十六进制与反汇编的指令能够对应上。
2.data数据段
.data段保存已经初始化了的全局变量和局部静态变量。
例如 global_init_var = 100对应的十六进制是0x64能够在data段的16进制内容中找到
3.bss段
.bss段保存 未初始化 的全局变量和局部静态变量
疑问:
前面dump的信息看到,.bss段只有4个byte,但实际上代码中有 global_unint_var和static_var2两个未初始化的变量才对,应该是占8个byte?
于是我们通过符号表来分析这个问题:
objdump -x得到下图结果:
我们发现static_var2在.bss段,但是global_uninit_var却在COM段,这个涉及到链接的相关知识,不在这里细讲,可以初步给一个结论:
前面描述的elf文件结构比较粗,现在我们使用 readelf 工具,来详细了解elf文件结果
readelf 的用法:
用法:readelf <选项> elf-文件
显示关于 ELF 格式文件内容的信息
Options are:
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header Display the ELF file header
-S --section-headers Display the sections’ header
–sections An alias for --section-headers
-s --syms Display the symbol table
–symbols An alias for --syms
使用readelf -h 来查看elf的文件头
从上面的输出结果可以看出:
elf文件的大小端、版本、运行平台、以及程序入口地址,段的数量以及size
前面我们使用了 "objudmp -h"来查看elf文件的段,但是这个不全的,objdump只是把关键的段给显示出来了,我们可使用readelf工具来查
readelf -S 查询段表信息
讲到符号表,就需要讲到链接。
链接的本质就是把多个不同的目标文件相互粘在一起,形成一个整体。例如有这样一个场景,目标文件B需要使用到目标文件A中的某个函数,对于目标文件A而言,这是定义这个函数,而对于目标文件B而言,这是引用。在连接过程中,函数和变量称为符号(symbol)。
因此,在每个目标文件中都有符号表,这是为了链接而准备的。