编译中的目标文件介绍
编译中的目标文件介绍
ELF是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储的标准文件格式。
1.ELF的文件类型
a.可重定位的目标文件
这是由汇编器汇编生成的.o文件,链接器拿一个或一些可重定位的目标文件作为输入,经链接处理后,生成一个可执行的目标文件或者一个可被共享的对象文件(.so文件)。
b.可执行的目标文件
在Linux系统里面,存在两种可执行的文件:
1.可执行的目标文件。
2.可执行的脚本(如shell 脚本)文件。(这些脚本不是可执行的目标文件,它们只是文本文件,执行这些脚本所用的解释器才是可执行的)
c.可被共享的目标文件
即动态库文件,也即.so文件。
动态库在发挥作用的过程中,必须经过两个步骤:
1.链接器拿它和其他可重定位的文件(.o文件)以及其他“.so” 文件作为输入,经链接处理后,生成另外的可共享的目标文件( .so文件)或者可执行的目标文件;
2.在运行时,动态链接器拿它和一个可执行的目标文件以及另外一些可共享的目标文件(.so)来一起处理,在Linux 系统里面创建一个进程映像。
2.链接视图下的ELF内容
ELF格式需要使用在两种场合:
1.组成不同的可重定位文件,以参与可执行文件或者可被共享的对象文件的链接构建。
2.组成可执行文件或者可被共享的对象文件,以在运行时内存中进程映像的构建。
ELF对象文件组成:
左边的部分表示的是可重定位文件的格式,而右边部分表示的则是可执行文件以及可被共享的对象文件的格式。ELF文件头被固定地放在不同类对象文件的最前面。
可以用file命令来看文件是属于哪种ELF文件。
以add.h,add.cpp,sub.h,sub.cpp,main.cpp编译得到的add.o、sub.o、libmymath.so和main4个文件为例。
执行file命令获取文件属性:
a.ELF头部
用于描述ELF文件中ELF文件头之外的内容。可以使用readelf工具来读出整个ELF文件头的内容,比如执行readelf -h add.o命令。
执行readelf命令获取文件ELF头部。
1.readelf -h add.o命令是显示add.o 的ELF Header 的文件头信息(就是ELF文件开始的前52Byte)。
1)这个add.o的进入点是0x0(e_entry)。
Entry point address : 0x0
表明可重定位文件( .o文件)不会有程序进入点。所谓程序进入点是指当程序真正执行起来的时候,其第一条要运行的指令的地址。 因为可重定位文件只是供再链接而已,所以它不存在进入点;而可执行文件test和动态库.so都存在所谓的进入点。
2)这个add.o文件包含有11个section(节区),但program headers的数量为0。而可执行文件main和可被共享文件libmymath.so里program headers则不为0。
2.section 是在ELF文件里头,用以装载内容数据的最小容器。
- text section 里装载了可执行代码。
- data section 里面装载了被初始化的数据。
- bss section 里面装载了未被初始化的数据。
- 以.rec打头的sections 里面装载了重定位条目。
- symtab或者dynsym section里面装载了符号信息。
- strtab 或者dynstr section里面装载了字符串信息。
- 其他还有为满足不同目的所设置的section,比如满足调试的目的、满足动态链接与加载的目的等。
b.ELF section表的总体预览
使用readelf工具来查看可重定位对象文件add.o的section表内容:
Offset 表示了该section 离开文件头部位置的距离;
Size 表示section 的字节大小;
EntSize 只对某些形式的sections 有意义;
Align是地址对齐要求;
Link和Info,它们中记录的是section head table中的条目索引,这就意味着,从这两个字段出发可以找到对应的另外两个section;
Flags表示的是对应section的相关标志。用X表示可执行的,标志A说明对应的section是Allocable(可分配的)的。
c.ELF的.text section
使用readelf -x SecNum 来打印出不同section 中的内容。但是,其输出结果都是机器码,对人来说不具备可读性。
可以换用另外一个工具objdump 来看看这些sections中到底具有哪些内容。执行命令:objdump -d -j .text add.o:
objdump的选项-d 表示要对由-j选择项指定的section内容进行反汇编,也就是由机器码出发,推导出相应的汇编指令。
d.ELF的.data section
执行objdump -d -j .data add.o 命令:
存放的是初始化过的数据,如程序中定义的赋过初值的全局变量等。
e.ELF的.strtab section
readelf -x num file 以16进制方式显示指定段内内容。num 指定段表中段的索引,或字符串指定文件中的段名。
执行readelf -x 10 add.o:
字符串表.strtab section中存储着的都是以字符为分隔符的字符串,这些字符串所表示的内容,通常是程序中定义的函数名称、所定义过的变量名称等。
当对象文件中其他地方需要和一个这样的字符串相关联的时候,往往会在对应的地方先存储.strtab section中的索引值。
.shstrtab也是字符串表,只不过其中存储的是section的名字,而非函数或者变量的名称。
f.ELF 的.symtab section
字符串表在真正链接和生成进程映像过程中是不需要使用的,但是其对我们调试程序来说特别有帮助。
前面使用objdump来反汇编.text section的时候,之所以能看到定义了函数add,那也是因为存在字符串表的原因。当然起关键作用的,还是符号表.symtab section在其中作为中介。
readelf -s file,显示符号表段中的项,执行readelf -s add.o 命令得到结果:
在符号表内针对每一个符号,都会相应的设置一个条目。
3.执行视图下的ELF内容
readelf -I main,显示main程序头的信息。执行readelf -I main命令:
如上图,在可执行文件main中,总共有8个segments (program headers)。
该结果也很明白显示出了哪些section映射到哪一个segment当中去。
segment R标志:表示该segment是可读的
segment E标志:表示该segment是可执行的。
segment W标志,表示该segment 是可写的。
类型为INTERP 的segment只包含一个section,那就是.interp。在这个section中,包含了动态链接过程中所使用的解释器路径和名称。
4.阅读ELF文件的工具-readelf
readelf命令用来显示一个或者多个elf格式的目标文件的信息,可以通过它的选项来控制显示哪些信息。
readelf -v //显示版本
readelf -h //显示帮助
readelf -a test //显示test的全部信息
readelf -h test //显示test的ELF Header的文件头信息(就是ELF文件开始的前52Byte)
readelf -l test //显示test的Program Header Table中的每个Prgram Header Entry的信息(如果有)
readelf -s test //显示test的Section Header Table 中的每个Section Header Entry 的信息(如果有)
readelf -g test //显示test的Section Group 的信息(如果有)
readelf -s test //显示test的Symbol Table 中的每个Symbol Table Entry 的信息(如果有)
readelf -e test //显示test的全部头信息(包括ELF Header, Section Header 和ProgramHeader, 等同与readelf -h -l -S test)
readelf -n test //显示test的note段的信息(如果有)
readelf -r test //显示test中的可重定位段的信息(如果有)
readelf -d test //显示test中的Dynamic Section的信息(如果有)
readelf -V test //显示test中的GNU Version 段信息(如果有)
readelf 和obj dump 提供的功能类似,但是它显示的信息更为具体,并且它不依赖BFD库。
运行readelf 的时候,除了-v 和-H 之外,其他的选项必须至少有一个被指定。
5.获得二进制文件里符号的工具-nm
nm是用来查看指定程序中的符号表相关内容的工具。
编译test程序,然后看nm的结果,执行命令nm test:
执行nm命令的效果图:
如上图:
1.第一列是当前符号的地址;
2.第二列是当前符号的类型;
3.第三列是当前符号的名称。
在执行nm命令的时候,加上-C 选项,就可以把这些难以识别的符号,转换成便于阅读的符号。
nm命令对程序的帮助,主要有以下几个方面:
1.判断指定程序中有没有定义指定的符号(比较常用的方式:nm -C proc I grep symbol)。
2.解决程序编译时undefined reference的错误,以及mutiple definition的错误。
3.查看某个符号的地址,以及在进程空间的大概位置(bss、data、text区,具体可以通过第二列的类型来判断)。
6.减少目标文件大小的工具-strip
用strip命令能清除执行文件中不必要的标示符及调试信息,可减小文件大小而不影响正常使用。
文件一旦进行strip操作后就不能恢复原样了,所以strip 可以认为是一个“减肥”工具而不是压缩工具。
被strip后的文件不包含调试信息。
**执行strip 命令后可执行文件变小了,**执行strip test。
strip命令能从ELF文件中有选择地除去行号信息、重定位信息、调试段、typchk段、注释段、文件头以及所有或部分符号表。
一旦使用该命令,则很难调试文件的符号。
通常只在已经调试和测试过的生成模块上使用strip命令,来减少对象文件所需的存储量开销。
其他常用选项:
1.-l(小写L):从对象文件中除去行号信息。
2.-r:除了外部符号和静态符号条目,将全部符号表信息除去。不除去重定位信息。同时除去调试段和typchk 段。这个选项产生一个对象文件,该对象文件仍可以用作输入到链接编辑器中。
3.-t:除去大多数符号表信息,但并不除去函数符号或行号信息。
4.-V:打印strip命令的版本号。
5.-x:除去符号表信息,但并不除去静态或外部符号信息。-x标志同时除去重定位信息,因此将不可能链接到该文件。