如何让ARM64平台GCC不为其他目标文件符号生成GOT条目
问题根源
ARM64架构的BL分支指令仅支持±128MB的PC相对寻址范围,远小于x86_64的±2GB。GCC默认会假设跨目标文件的函数可能超出这个范围,因此自动生成通过GOT(全局偏移表)的间接调用;而同一文件内的函数地址可确定,就直接使用PC相对寻址。
解决办法
针对你的环境(Apple M1上的Ubuntu 20.04.5 LTS,GCC 9.4.0),可以用以下几个编译/链接选项消除这类冗余GOT条目:
1. 添加-mno-long-calls编译选项
这个选项会强制GCC为所有函数调用生成PC相对的BL指令,跳过GOT的间接调用。链接时如果函数真的超出128MB范围会报错,但绝大多数程序都不会碰到这个限制。
编译命令示例:
gcc -mno-long-calls 1.c 2.c -o output
2. 使用-fno-plt编译选项
禁用PLT(过程链接表)后,GCC会直接从GOT加载函数地址调用,而链接器处理同一可执行文件内的函数时,会自动优化掉GOT条目,替换为直接的PC相对调用。同时这个选项还能提升程序启动速度,避免PLT的延迟绑定开销。
编译命令示例:
gcc -fno-plt 1.c 2.c -o output
3. 优化符号可见性
如果你的函数仅在当前可执行文件内部使用,可以添加-fvisibility=hidden将所有符号默认设为隐藏,仅给需要对外暴露的函数加上__attribute__((visibility("default")))标记。编译器知道这些隐藏符号不会被外部模块引用,就会直接生成PC相对调用,完全不需要GOT代理。
示例代码(2.c中定义target):
__attribute__((visibility("default"))) void target() { // 函数实现 }
编译命令:
gcc -fvisibility=hidden 1.c 2.c -o output
验证生效方法
用objdump反编译可执行文件,检查调用target的指令是否为直接的bl target,而非通过GOT加载地址的指令:
objdump -d output | grep -A5 -B5 target
如果输出中出现bl target,说明冗余GOT条目已经被成功消除。
内容的提问来源于stack exchange,提问作者user2828102

