You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 15:32:35