当库和可执行程序在IPC端所占的空间有限制时,就需要考虑对库和可执行程序进行裁剪,以达到功能需求。现采用将所有的库编译成静态库,最后集成为一个可执行程序的方法,整理裁剪过程如下:
一、编译阶段
1、编译静态库为release版本,并添加以下编译选项:
编译参数 | 参数详解 |
---|---|
-fvisibility=hidden | 可以将所有导出的符号设置为隐藏,然后在库的代码中通过设置 __ attribute __(visibility(“default”)) 导出想要的类和函数 |
-ffunction-sections | 将每个function创建为一个sections,其中每个sections名与function保持一致 |
-fdata-sections | 将每个data创建为一个sections,其中每个sections名与data名保持一致 |
-fno-exceptions | 禁用异常机制 |
-fno-unwind-tables | 正在运行的程序从给定执行点返回函数调用堆栈的数据放置在.eh_frame部分,此选项会禁这些数据的生成,无法返回函数调用堆栈或使用C ++异常功能。 |
-fno-asynchronous-unwind-tables | 用来不生成CFI指令 |
-fomit-frame-pointer | 开启该选项,主要是用于去掉所有函数SFP(Stack Frame Pointer)的,即在函数调用时不保存栈帧指针SFP,代价是不能通过backtrace进行调试根据堆栈信息了。 |
-s | 把符号表从最终的可执行文件中删除 |
2、在编译静态库时去掉-fPIC选项;
3、修改代码中的日志等级为warn级别;
二、链接阶段
1、编译可执行程序时,除了添加编译静态库的几个选项外,还需添加以下编译选项:
编译参数 | 参数详解 |
---|---|
-Wl,–gc-sections | 指示链接器去掉不用的section |
-Wl,–no-export-dynamic | 不导出所有的全局符号到动态符号表中 |
2、链接静态库带到可执行程序时,需要将静态库依赖的静态库全部包含进来,并且使用–start-group和–end-group反复在.a中进行搜索直到所有的未定义字符都被找到为止,不用再担心链接时静态库顺序问题,cmake编译时链接如下:
TARGET_LINK_LIBRARIES(${PROJECT_NAME} -Wl,--start-group ${THIRD_LIB} -Wl,--end-group)
3、编译完可执行程序时,使用strip命令使可执行文件中剥掉一些符号信息和调试信息,使文件变小。
三、段信息
1、使用readelf -S
命令查看一个可执行的elf文件的节信息,常见段如下:
段名 | 说明 |
---|---|
.text | 存放程序运行代码(机器码) |
.data | 存放了经过初始化的全局变量和静态变量 |
.bss | 保存了那些用到但未被初始化的数据 |
.rodata | 只读数据(常量、字符串常量) |
.shstrtab | 段名字符串表 |
.symtab | 保存了连接时所需的符号信息 |
.strtab | 保存了.symtab所需的符号信息。 |
.init | C++编译器生成的用来实现全局构造;该段自动产生名为init的函数,该函数早于main执行 |
.fini | 同.init都为实现全局构造;该段自动产生名为fini的函数,该函数在main函数结束之后执行 |
.comment | 包含编译器版本信息,不重要 |
.debug | 保存调试相关信息,如.debug_info .debug_line等 |
.dynstr | 保存动态链接符号字符串名 |
.dynsym | 保存动态链接符号 |
.fini_array | 保存程序或共享对象退出时的退出函数地址 |
.hash | 符号表的hash表,用于加快符号查找 |
.init_array | 保存程序或共享对象加载时的初始化函数指针 |
.interp | 动态链接库路径 |
.line | 调试时行号信息 |
.note | 编译器、链接器、操作系统加入的平台相关的额外信息 |
.preinit_array | 同init_array 但早于init_array执行 |
.tbss | 线程的未初始化数据 |
.tdata | 线程的初始化数据 |
.ctors | 保存全局构造函数指针 |
.data.rel.ro | 类似.rodata |
.dtors | 保存了全局析构函数指针 |
eh_frame | C++异常处理内容 |
.eh_frame_hdr | 同eh_frame |
.got.plt | 保存动态链接的延迟绑定相关信息 |
.jcr | Java语言相关信息 |
.note.ABI-tag | 保存程序ABI信息 |
.stab | 调试信息 |
.dynamic | 动态链接信息,存储了动态链接的符号表地址、字符串表地址及大小、哈希表地址,共享对象的SO-NAME、搜索路径,初始化代码地址,结束代码地址,依赖的共享对象文件名,动态链接重定位表地址、重定位入口数量等。 |
.gnu.version | 动态链接符号版本,.dynsym中的每个符号对应一项(该符号所需版本在.gnu.versiond中的序号) |
.gnu.versiond | 动态链接符号版本的定义(definitions),每个版本的标志位、序号、共享库名称、主次版本号 |
.gnu.versionr | 动态链接符号版本的需求(requirements),依赖的共享库名称和版本序号 |
.preinitarray | 早于初始化阶段前执行的函数指针,在.initarray之前执行 |
.rel.data | 静态链接文件中,数据段的重定位表 |
.rel.dyn | 动态链接文件中,对数据引用(.got、.data)的重定位表 |
.rel.plt | 动态链接文件中,对函数引用(.got.plt)的重定位表 |
.rel.text | 静态链接文件中,代码段的重定位表 |
2、可以通过objdump -s 可执行程序
命令查看对应段中的具体内容;
3、使用readelf -l 可执行程序 | grep interpreter
查看可执行程序所需要的动态链接器的路径;
4、使用readelf --dyn-syms 可执行程序
命令查看 .dynsym 表;
5、使用readelf -d 可执行程序
命令查看“.dynamic”段的内容;
6、使用objdump -s -j .dynstr 可执行程序
命令查看.dynstr的基地址;
7、使用readelf -V 可执行程序
命令输出.gnu.version和.gnu.version_r内容;
8、使用objcopy -R 段名字 可执行程序
命令删除可执行程序中对应的段;
参考文章:
https://lug.ustc.edu.cn/wiki/user/boj/linkers-and-loaders
https://blog.csdn.net/zvvzxzko2006/article/details/48519845