趁着五一假期,又重读了《高级C++编译技术》这本书,着重看了里面查看库文件和可执行文件详细信息的工具集,比如
库文件有哪些符号
、依赖哪些动态库
、如何修改静态库等
。说实话,已经不是第一次看这部分内容了,但每次看完总是似懂非懂,内容太多也总是记不住。这次看的过程中,也自己写些demo实操了一遍,同时把整个过程记录下来方便以后回顾。
一、快速查看
1、file
(1) 功能
1、 查看文件类型
2、 查看可执行文件位数。通过objdump -f /path/to/program
看ELF文件头也可以。
(2) 示例
2、size
(1) 功能
1、 查看静态库包含哪些目标文件
2、 查看ELF节的字节长度信息
(2) 示例
二、详细信息分析
1、ldd
(1) 功能
1、 显示出二进制文件启动时需要静态加载的动态库的完整列表(包含链接器命令行指定的链接库
和间接依赖的动态库
)
链接器会将直接依赖项的列表(在构建过程中,链接器命令行指定的那些动态库)写进二进制文件的ELF格式字段中。在分析加载依赖项时,ldd首先会扫描二进制文件并找到这些链接库信息,并递归查找间接依赖项、去除重复项。
(2) 示例
(3) 优缺点
1、 无法识别运行时通过调用dlopen()函数动态加载的动态库;
2、 一些环境下某些版本的ldd可能会尝试执行程序来获取依赖信息,可能会导致安全问题。对于不受信任的可执行文件,可以采取下面的安全的方法(下面的方法只会查找直接依赖项,不会递归查找间接依赖项):
1. `objdump -p /path/to/program | grep NEEDED`
2. `readelf -d /path/to/grogram | grep NEEDED`
2、nm
(1) 功能
1、 列出二进制文件的所有符号列表 nm <二进制文件路径>
。如果二进制包含C++代码,默认打印经过名称修饰之后的符号名(通过-C
选项打印没有经过修饰的符号名);
2、 只列出动态节中的符号(只针对共享库使用,共享库的导出符号、对外可见的符号) nm -D <二进制文件路径>
;
3、 列出没有经过名称修饰的符号名 nm -C <二进制文件路径>
;
4、 列出二进制文件中未定义符号(这个二进制本身不包含的、但期望在运行从其他动态库中加载的) nm -u <二进制文件路径>
;
(2) 示例
nm输出的常见符号类型:
符号类型 | 含义 |
---|---|
B | 该符号的值出现在非初始化数据段(bss)中,全局未初始化变量 |
C | 该符号为common。common symbol是未初始话数据段。该符号没有包含于一个普通section中。只有在链接过程中才进行分配。符号的值表示该符号需要的字节数。例如在一个c文件中,定义int test,并且该符号在别的地方会被引用,则该符号类型即为C。否则其类型为B |
D | 该符号的值位于初始化数据段(data)中,全局初始化变量 |
R | 该符号位于只读数据段(rodata)中,const变量 |
T | 该符号位于代码段(text)中,通常是函数 |
U | 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U |
V | 该符号是一个weak object |
以下是两个小代码:
- add.cpp
#include "add.h"
int g_var_in_add = 10; // 全局初始化,位于data节
int add(int left, int right)
{
return left + right;
}
- sub.cpp
#include "sub.h"
int g_var_in_sub = 20; // 全局初始化,位于data节
int g_var2_in_sub; // 全局未初始化,位于.bss节
const int g_var3_in_sub = 30; // 只读,位于rodata节
int sub(int left, int right)
{
return left - right;
}
- 列出二进制文件的所有符号列表
- 只列出动态节中的符号(只针对共享库使用。
共享库的导出符号
、对外可见的符号
)
- 列出没有经过名称修饰的符号名
3、objdump
(1) 功能
1、 解析ELF头部 objdump -f <二进制文件路径>
;
2、 列出所有符号 objdump -t <二进制文件路径>
,与nm <二进制文件路径>
输出完全相同;
3、 只列出动态节符号 objdump -T <二进制文件路径>
,与nm -D <二进制文件路径>
输出完全相同;
4、 查看动态节(DT_RPATH和DT_RUNPATH的设置) objdump -p <二进制文件路径>
;
5、 查看重定位节 objdump -R <二进制文件路径>
;
6、 打印没有经过名称修饰的符号名 objdump -C <二进制文件路径>
,与nm -C <二进制文件路径>
输出完全相同
7、 查看节中的数据 objdump -x -j <.节名> <二进制文件路径>
;
8、 反汇编二进制文件,并于源代码对照 objdump -d -M <intel|att> -S <二进制文件路径>
。(源码对照选项-S
,只有在构建时加了-g
选项时才能使用,包括从源代码->.so->binary都需要加-g);
(2) 示例
- 解析ELF头部
objdump -f <二进制文件路径>
- 查看节中的数据
objdump -x -j <.节名> <二进制文件路径>
- 反汇编二进制文件,并与源代码对照
objdump -d -M <intel|att> -S <二进制文件路径>
。(源码对照选项-S
,只有在构建时加了-g
选项时才能使用,包括从源代码->.so->binary都需要加-g)
#include <iostream>
#include "add.h"
#include "sub.h"
int g_data_in_test = 1111;
int g_data_1_in_test;
int main()
{
int add_val = add(1, 2);
int sub_val = sub(10, 3);
std::cout << "add_val:" << add_val << std::endl;
std::cout << "sub_val:" << sub_val << std::endl;
return 0;
}
4、readelf
(1) 功能
与objdump几乎相同。readelf提供的功能,objdump几乎都有。不同之处在于:
1、 readelf只支持ELF二进制格式(.o .a .so binary),objdump支持大约50中不同二进制格式;
2、 readelf不依赖二进制文件描述库,GNU的所有目标文件解析工具都依赖这个库,因此readelf可以独立提供ELF格式信息。
1、 解析ELF头 readelf -h <二进制文件路径>
;
2、 列出所有节 readelf -S <二进制文件路径>
;
3、 列出所有符号 readelf --symbols <二进制文件路径>
,与nm <二进制文件路径>
、objdump -t <二进制文件路径>
输出完全相同;
4、 只列出动态符号 readelf --dyn-syms <二进制文件路径>
,与nm -D <二进制文件路径>
、objdump -T <二进制文件路径>
输出完全相同;
5、 查看动态节(DT_RPATH和DT_RUNPATH的设置) readelf -d <二进制文件路径>
,与objdump -p <二进制文件路径>
功能相同;
6、 查看重定位节 readelf -r <二进制文件路径>
,与objdump -R <二进制文件路径>
功能相同;
7、 查看节中的数据 readelf -x <节名> <二进制文件路径>
,与objdump -x -j <节名> <二进制文件路径>
功能相同;
8、 确定二进制文件是否包含调试信息(构建时是否开启了-g选项) readelf --debug-dump<选项> <二进制文件路径>
。–debug-dump支持的选项有:
--debug-dump[=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]
(2) 示例
- 解析ELF头
readelf -h <二进制文件路径>
- 确定二进制文件是否包含调试信息(构建时是否开启了-g选项)
readelf --debug-dump<选项> <二进制文件路径>
三、部署阶段
1、chrpath
(1) 功能
1、 列出ELF二进制文件的rpath(DT_RPATH字段) chrpath -l <二进制文件位置>
;
2、 修改ELF二进制文件的rpath chrpath -r <new_rpath> <二进制文件路径>
;
3、 直接删除ELF二进制文件的rpath chrpath -d <二进制文件路径>
;
4、 可以将DT_RPATH转换成DT_RUNPATH chrpath -c <二进制文件路径>
;
注意:
1、 只能在原本存在的DT_RPATH字符串长度内修改rpath,新值超过原本字符串长度之后会报错;
(2) 示例
四、静态库工具
1、ar
(1) 功能
1、 可以将目标文件打包成静态库 ar crv 静态库名 <目标文件列表>
;
2、 可以列出静态库包含的目标文件 ar -t 静态库名
;
3、 可以移除静态库中某些目标文件 ar -d 静态库名 <待删除的目标文件名>
;
4、 可以使用较新的版本替代这些目标文件 ar -r 静态库名 <待添加的目标文件名>
;
5、 重拍静态库中目标文件顺序 ar -m -b <基准目标文件名 A> 静态库名 <要重排的目标文件名 B>
将会把B放到A的前面;
(2) 示例
- 将目标文件打包成静态库
- 列出静态库包含的目标文件
- 先移除静态库中某些目标文件,再添加某些目标文件,然后重排目标文件顺序