Android x86 GAS汇编中调用C函数如何避免文本重分配?
我之前也踩过这个坑,其实这个警告的核心原因是:直接用call foo这种方式调用动态链接的C函数时,编译器需要修改.text段里的指令来完成地址重定位,导致.text段没法被多个进程共享(每个进程加载时都要改写指令)。Android从API23开始对共享库的内存共享效率要求更严格,所以会抛出这个警告,甚至可能编译失败。
下面给你几个可行的解决方案,按推荐程度排序:
1. 使用PLT(过程链接表)调用C函数(首推)
这是动态链接场景下调用外部函数的标准做法,完全避免修改.text段。在GAS x86语法里,只需要把原来的call foo改成:
call foo@PLT
PLT会通过GOT(全局偏移表)间接跳转到C函数的实际地址,所有重定位操作都发生在GOT(属于可写的动态节),.text段全程保持只读状态,完美符合Android对共享库内存共享的要求。
NDK的GAS和链接器会自动处理PLT与GOT的生成,你不需要手动配置额外的节或参数,改完指令重新编译就能消除警告。
2. 寄存器间接调用
如果不想用PLT,也可以先把C函数的地址加载到寄存器,再通过寄存器间接调用:
# 32位x86写法 movl $foo, %eax call *%eax # 64位x86写法 movq $foo, %rax call *%rax
这种方式的重定位条目只会指向GOT,不会修改.text段里的指令,同样能避免文本重分配问题。不过这种写法不如PLT简洁,而且PLT是专门为动态链接优化过的,还是更推荐第一种方案。
3. 关于“移动函数到其他节”的误区
你提到能不能把汇编函数移到其他节,其实这个思路没必要——问题根本不在汇编函数所在的.text节,而是调用C函数时的重定位方式。就算把汇编函数移到其他节,只要还是直接call foo,依然会产生文本重定位的问题。所以优先调整调用方式,而非移动节。
最后验证一下:用call foo@PLT替换原有调用后,编译时的警告应该会消失,API23及以上版本也能正常编译通过,同时共享库的.text段依然保持可共享性,不会浪费内存。
内容的提问来源于stack exchange,提问作者Van der Deken

