为何PLT表中无push指令?是理解有误还是编译方式不对?
关于PLT指令结构的疑问解答
问题场景
示例C代码
#include<stdio.h> int foo(void); int main() { puts("Hello World"); int x = 10; fprintf(stdout,"Hello World 22"); return foo(); }
编译命令
gcc -shared -fpic hello_world.c -o hello_world.so
生成的PLT汇编
puts@plt
0000000000001070 <puts@plt>: 1070: f3 0f 1e fa endbr64 1074: f2 ff 25 9d 2f 00 00 bnd jmpq *0x2f9d(%rip) # 4018 <puts@GLIBC_2.2.5> 107b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
foo@plt
0000000000001080 <foo@plt>: 1080: f3 0f 1e fa endbr64 1084: f2 ff 25 95 2f 00 00 bnd jmpq *0x2f95(%rip) # 4020 <foo> 108b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
疑问:原以为PLT指令通常是1.跳转至GOT表;2.压入重定位标识符;3.跳转至动态链接器,但此处似乎直接跳转至GLIBC。是理解有误还是编译方式不正确?
解答
你的理解是基于传统PLT结构,但当前x86_64架构下GCC默认使用新式PLT,二者结构不同,你的编译方式完全正确。
1. 新式PLT与传统PLT的差异
传统PLT(常见于i386或早期x86_64)确实是三步结构:跳转GOT表、压入重定位标识符、跳转至动态链接器解析函数。而新式PLT做了简化:
- 每个PLT条目核心仅一条
bnd jmpq指令,直接跳转至对应GOT表项存储的地址。 endbr64是控制流完整性(CFI)相关指令,用于安全防护,不属于PLT核心逻辑。- 重定位标识符的传递不再由单个PLT条目完成,而是通过全局的PLT0条目统一处理。
2. 延迟绑定的实际流程
你看到的汇编注释# 4018 <puts@GLIBC_2.2.5>是objdump对GOT表项地址的符号标注,并非指跳转目标直接是GLIBC地址,实际流程分两步:
- 首次调用:GOT表项初始指向PLT0的入口,程序跳转到PLT0后,PLT0会将重定位所需的符号索引等信息传递给动态链接器的
_dl_runtime_resolve函数,完成符号解析后,动态链接器会将puts的真实地址写入GOT表项。 - 后续调用:GOT表项已存储GLIBC中
puts的真实地址,此时PLT的jmp指令就会直接跳转到该地址,这也是注释显示puts@GLIBC_2.2.5的原因。
3. 关于foo()的PLT
foo是仅声明未实现的外部函数,其PLT结构与puts一致。程序运行时,动态链接器会尝试从已加载的其他共享库中解析foo的地址,流程和puts完全相同。
内容的提问来源于stack exchange,提问作者daniel
相关产品推荐
相关产品推荐

