为何C++ STL函数调用通过PLT而非直接调用?
观测到的异常现象
编译测试时发现以下不符合预期的行为:
- 所有STL符号均被标记为default可见性,最终被导出为弱符号
- 编译时添加
-no-flt参数的场景下,对STL函数的调用会通过PLT或GOT完成间接跳转
初始预期
STL函数的调用表现应当和同编译单元内未导出的int t(int)函数一致:对后者的调用为直接相对地址调用,不需要经过PLT/GOT。
测试用例
#include <vector> __attribute__((noinline)) int t(int i) { return i + 1; } __attribute__((visibility("default"))) std::size_t f(std::vector<int>& v, int i) { v.push_back(t(i)); return v.size(); }
复现步骤与观测结果
使用-Os -fpic -shared -fvisibility=hidden参数将上述代码编译为动态库:
- 查看反汇编,可见对STL函数的调用为PLT间接调用:
1206: call 10e0 <void std::vector<...>::_M_realloc_insert<int>(...)@plt>
- 通过
nm -C查看符号表,对应STL符号被标记为弱符号:
0000000000001232 W void std::vector<...>::_M_realloc_insert<int>(...)
原因解释
出现这个差异的核心原因有两点:
- STL模板实例化的可见性不受全局
-fvisibility=hidden参数影响
编译参数中添加的-fvisibility=hidden,只会给编译单元内没有显式指定可见性的符号默认设置为hidden可见性。但C标准库实现(如libstdc)在头文件中,已经将所有STL模板的实例化符号显式标记为default可见性,这类显式标注的符号不会被全局可见性参数修改,最终都会以default可见性导出为弱符号。弱符号是COMDAT节的标准要求,用来保证多个目标文件中重复生成的同一模板实例化在链接时不会触发重定义错误,链接器会自动选择其中一个实例保留。 - 动态链接的符号拦截规则要求default可见的弱符号必须走PLT调用
按照ELF动态链接的规则,default可见的弱符号允许被主程序、后续加载的其他动态库中的同名强符号覆盖。编译动态库时,编译器无法预知最终链接运行时是否会出现覆盖该STL符号的其他定义,因此不能把调用直接绑定到本编译单元内生成的STL实例上,必须通过PLT跳转,保证运行时能调用到被全局解析后的正确符号地址。
而自行实现的t()函数没有显式标注可见性,被全局-fvisibility=hidden参数设置为本地隐藏符号,这类符号不可能被外部库覆盖,编译器可以直接生成相对偏移的直接调用指令,不需要经过PLT。
内容的提问来源于stack exchange,提问作者redbaron
相关产品推荐
相关产品推荐

