Android,iOS静态库符号隐藏

870 阅读4分钟

背景

对外提供Android和iOS的静态库SDK时,遇到过以下问题

  1. 依赖静态库版本不兼容
  2. 符号冲突导致编译失败

方案

静态库生成流程

首先要了解下静态库是怎么产生的

考虑这样的一个场景,有一个工程源文件a.c和依赖的第三方静态库lib.a,现在需要生成一个out.a的静态库,那整个流程如下

compile: a.c -> a.o    // 编译生成reallocatable文件
package: a.o -> out.a  // 打包生成out.a

上面的流程有以下几点是需要注意的

  1. lib.a没有被使用,这是因为静态库实际上是reallocatable文件打包生成的,并没有经过链接的过程,那只需要编译的时候能够找到符号即可
  2. reallocatable文件包含了该编译单元的符号,包含了导出符号和导入符号,实际上后续对符号的处理就是基于reallocatable文件里的符号表来做的
  3. 每一个源文件通过编译都会生成对应的reallocatable文件,out.a会包含所有的reallocatable文件,所以依赖静态库实际上是依赖的是多个reallocatable文件

方案思路

  1. 合并reallocatable文件,具体就是需要将多个reallocatable合并成一个reallocatable文件,这里的多个reallocatable也包含依赖的第三方库
  2. 修改合并后reallocatable文件的符号表,隐藏不需要暴露的符号

需要注意iOS和Android的二进制文件格式是不一样的,iOS是MACH-O,Android是ELF,但是以上的概念是一致的

iOS隐藏方案

iOS可以通过在XCode上配置选项来实现隐藏

  1. 合并reallocatable,可以分为以下两个步骤
  • 通过GENERATE_MASTER_OBJECT_FILE的选项可以把编译出来的reallocatable合并成一个reallocatable文件,这一步合并的是工程里编译的文件
  • 通过PRELINK_LIBS的选项将需要合并的第三方添加进去,这一步合并的第三方库的reallocatable文件
  1. 删除导出符号,通过设置EXPORTED_SYMBOLS_FILE的文件路径,在文件里配置需要导出的符号即可,文件示例内容如下,每一行为一个导出的函数,因此该文件导出了f1,f2两个函数
f1
f2

需要注意的是

  1. 合并.o并不属于编译流程,而是链接流程,通过编译日志可以看出添加了设置后编译日志会多出一个链接过程
  1. GENERATE_MASTER_OBJECT_FILE会在编译完成后执行链接命令同时添加一个-r的选项(partial linking),而PRELINK_LIBS只是在链接是追加了静态库的路径
  1. 删除符号并不是真正的删除了符号,只是把符号对外的范围从Global变为Local,可以通过nm命令对比符号表的变化
  2. 导出符号的名称由于c++的name mangling的原因,没有很好的方案.可以通过nm来查询后获取对应的符号名再添加到导出表里手动解决

Android隐藏方案

相对于iOS,Android需要通过命令来实现

  1. 合并reallocatable
aarch64-linux-android-ld -o master.o -r a.o -static -llib
  1. 删除符号(修改符号可见性)
aarch64-linux-android-objcopy --keep-global-symbols symbols.txt master.o master.o
  1. 打包静态库
aarch64-linux-android-ar rcs all.a master.o

需要注意的是

  • 步骤1需要区分架构armeabi-v7a需要使用arm-linux-androideabi-ld,步骤2,3不区分架构
  • 步骤1可以添加-s来删除调试信息
  • symbols.txt的格式和iOS相同,没有处理过的话符号名称不一定相同,所以一般是不能通用的
  • 这些命令需要放在编译后执行,CMake可以添加编译后脚本来执行

Android基本上和iOS是一致的,但是需要更多的介入,在实际应用的过程中v8是正常的,但是v7链接处理过的库会报defined in discarded section的错误,不清楚是不是v7用的链接器有问题,将模板元生成的弱符号改成局部符号就会出现该错误,所以Android上更通用的方案并不是删除符号(修改符号可见性),而是修改符号名称

aarch64-linux-android-objcopy --redefine-syms symbols.txt master.o master.o

这里symbols.txt的内容如下,也是一行代表一个替换

oldsym newsym

这里有点类似添加命名空间,不过重命名符号相对于添加命名空间,不需要更改文件,且由于添加后缀一般都会打破name managling的规则,所以很难冲突,并且可以解决c函数无法添加命名空间的问题,也是一个较为通用的方案

总结

以上的方案可以解决符号冲突的问题,但也会有以下的问题

  1. 可能也会导致编译器的后续优化的问题
  2. 对于模板元符号的隐藏可能会导致体积增大

总的来说如果对体积有要求,以上方案需谨慎考虑