文章目录
1. 目标文件
ELF是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储的标准文件格式;使用ELF标准的目的是为软件开发人员提供一组二进制接口定义,这些接口可以延伸到多种操作环境中,从而减少重新编码、编译程序的需要
目标文件有三种类型,都是ELF文件类型
- 1.
可重定位的目标文件
由汇编器汇编生成的.o文件,链接器拿一个或一些可重定位的目标文件作为输入,经链接处理后,生成一个可执行的目标文件或者一个可被共享的对象文件
例:g++ -c test.cpp得到test.o文件
- 2.
可执行的目标文件
文本编辑器vi、调试用的工具gdb、播放mp3歌曲的软件mplayer等
例:g++ test.o -o main得到的main文件
- 3.
可被共享的目标文件
即所谓的动态库文件,也即.so文件
例:g++ -fPIC -shared -o libmymath.so add.cpp sub.cpp得到的libmymath.so文件
动态库在发挥作用的过程中,必须经过两个步骤:
- 1.链接器拿它和其他可重定位的文件(.o文件)以及其他.so文件作为输入,经链接处理后,生成另外的可共享的目标文件或者可执行的目标文件
- 2.在运行时,动态链接器拿它和一个可执行的目标文件以及一些可共享的目标文件来一起处理,在Linux系统里面创建一个进程映像
以下例子为演示静态链接与动态链接时的例子
2. ELF的组成格式
有两种视图可以说明ELF的组成格式,分别是链接视图与执行视图
链接视图 | 执行视图 |
---|---|
ELF头部 | ELF头部 |
程序头部表(可选) | 程序头部表 |
节区1 | 节区1 |
… | … |
节区n | 节区n |
… | … |
节区头部表 | 节区头部表(可选) |
- 左边的视图表示的是可重定位文件的格式:组成不同的可重定位文件,以参与可执行文件或者可被共享的对象文件的链接构建
- 右边视图表示可执行文件以及可被共享的对象文件,以在运行时内存中进程映像的构建
利用file
命令获取文件属性:
file add.o sub.o libmymath.so main
3. 阅读ELF文件的工具–readelf
3.1 链接视图下的ELF内容
链接视图下是可重定向的文件,.o文件
- ELF头部
ELF里面的内容,除了file命令所显示出来的那些之外,更重要的是包含另外一些数据,用于描述ELF文件头之外的内容
使用命令readelf -h add.o
查看add,o的ELF Header的文件头信息:
我们可以看到倒数第二行中显示包含有11个section节区,那么什么是所谓的section呢?可以说,section是在ELF文件里头,用以装载内容数据的最小容器,以下是有关的一些section简介:
section | 内容 |
---|---|
text | 装载了可执行代码 |
data | 装载了被初始化的数据 |
bss | 装载了未被初始化的数据 |
.rec | 装载了重定位条目 |
symtab/dynsym | 装载了符号信息 |
strtab/dynstr | 装载了字符串信息 |
以及一些其他的section,比如满足调试的目的、满足动态链接与加载的目的 |
- ELF section表
使用命令readelf -S add.o
查看所有section的内容:
3. ELF的.text section
可以使用readelf -x SecNum + 文件名(SecNum为节区的索引)来查看节区的内容,但是这样得到的都是机器码:
所以采用另外一个工具objdump查看section中的内容,例:objdump -d -j .text add.o
,-d表示对由-j选定的section进行反汇编,即由机器码出发,得到其相应的汇编代码:
- ELF的.data section
最初的add.cpp:
#include "add.h"
int add(int a, int b)
{
return a+b;
}
得到这样的结果,并没有任何的变量显示:
于是我们添加一个变量在其中:
#include "add.h"
int result = 12;
int add(int a, int b)
{
return a+b;
}
得到如下结果,可以发现其中显示了变量值,其值被初始化为0x0000000c,即十进制12,之前在测试大端还是小端的时候测试出来结果为小端 判断系统是大端还是小端,所以低位存储在低地址,即为0c000000:
- ELF的.strtab section
使用命令 readelf -x num file
以16进制方式显示指定段内容,num为指定段表中段的索引,使用readelf -x 10 add.o
查看.strtab section的内容:
也可以使用hexdump直接dumping出.strtab section开头的32Byte数据,输入以下命令hexdump -s 0x1b0 -n 32 -c add.o
(0x1b0为其偏移)字符信息:
- ELF的.symtab section
字符串表在真正链接和生成进程映像过程中是不需要使用的,但是其对我们调试程序来说有大的帮助
使用命令readelf -s file
显示符号表段中的项:
3.2 执行视图下的ELF内容
执行视图即表示可执行文件或共享文件
使用readelf -l main
查看可执行文件main程序头的信息,其中Flags为R或E表示可读或可执行的,W表示是可写的:
也可用hexdump查看,执行命令hexdump -s 0x238 -n 32 -C main
:
0x238为INTERP的offset值
4. 获得二进制文件里符号的工具–nm
nm是用来查看指定程序中的符号表相关内容的工具
举个例子吧:
//test.cpp
#include <iostream>
using namespace std;
class Test {
public:
int hello() {
cout << "Hello World" << endl;
return 0;
}
};
int main()
{
Test test;
int iRet = test.hello();
cout << "iRet=" << iRet << endl;
return 0;
}
编译得到可执行文件test,然后nm test
:
上述得到的结果,第一列表示当前符号的地址;第二列表示当前符号的类型;第三列表示当前符号的名称
可以在nm命令后加上-C选项,转换成便于阅读的符号:
nm命令对程序的帮助:
- 判断指定程序中有没有定义指定的符号(比较常用的方式:nm -C proc | grep symbol)
- 解决程序编译时undefined reference的错误,以及mutiple definition的错误
- 查看某个符号的地址,以及在进程空间的大概位置(bss、data、text区,具体可以通过第二列的类型来判断)
nm指令
5. 减少目标文件大小的工具:
unix下文件压缩有compress和tar,但compress后,其解压缩必须使用命令uncompress来解压
使用strip命令就没有这个问题,它能清除执行文件中不必要的标示符及调试信息,可减少文件大小而不影响正常使用;但与compress不同的是,文件一旦进行strip操作后就不能恢复原样了,所以strip可以认为是一个“减肥”工具而非压缩工具,而且,被strip后的文件不含调试信息。现在让我们来测试一下:
可以看到test文件缩减了三分之一
strip命令能从ELF文件中有选择地除去行号信息、重定位信息、调试段、typchk段、注释段、文件头及所有或部分符号表,但一旦使用该命令,则很难调试文件的符号;因此,通常只在已经调试和测试过的生成模块上使用strip命令,来缩减对象文件所需的存储量开销
strip命令