1

GCC 和 Clang 在 32 位 x86 位置无关代码中使用这些辅助函数将当前执行地址放入寄存器,例如:

call    __i686.get_pc_thunk.bx
addl    $_GLOBAL_OFFSET_TABLE_, %ebx
movl    $2, 4(%esp)
leal    .LC0@GOTOFF(%ebx), %eax
movl    %eax, (%esp)
call    dlopen@PLT

似乎实现是等效的:

__x86.get_pc_thunk.bx:
    movl    (%esp), %ebx
    ret

__i686.get_pc_thunk.bx:
    movl (%esp), %ebx
    ret

除了更名之外还有什么区别(似乎i686更旧)?有 i686 前缀而不是 i386 的原因吗?

4

2 回答 2

2

因此,在对提交历史和错误跟踪器进行了一些挖掘之后,我想我基本上已经弄清楚了。

很久以前,glibc 曾经有自己的 PIC 代码处理方式,其中涉及调用/弹出模式来获取 GOT 地址。

大约在 2002 年__i686.get_pc_thunk.*完成类似任务的 , 被添加到 GCC,最初是作为内部符号。

不久之后它也出现在 glibc 中,可能是为了避免在使用 GCC 编译时出现代码重复。

但是,当为 Pentium 2 或更高版本 ( -march=i686) 构建时,GCC 定义了预处理器宏__i686=1,破坏了 glibc 对存根代码的编译。这个问题很早就被发现了,但是几年来 glibc 使用了各种变通方法来处理这个问题。

在 2011 年(GCC 4.7?),名称更改为,__x86.get_pc_thunk.*并且 glibc添加了一些检查以使用匹配的名称。最终对旧 GCC 版本的支持与旧名称一起被删除。GCC 和 glibc__x86.get_pc_thunk.*现在都只能使用(尽管 GCC 也可以生成内联调用/弹出版本)。

所以,总结一下:

两者之间没有实际区别,由于预定义的宏冲突,名称更改只是历史性的。

参考:

https://gcc.gnu.org/git/?p=gcc.git&a=search&h=HEAD&st=commit&s=get_pc_thunk

https://sourceware.org/git/?p=glibc.git&a=search&st=commit&s=get_pc_thunk

https://sourceware.org/bugzilla/show_bug.cgi?id=411

https://sourceware.org/bugzilla/show_bug.cgi?id=4507

于 2020-04-24T15:41:16.340 回答
1

只是一个不同的名称选择,不重要的 AFAIK。

i686 是使用 PPro 新指令(如 CMOV 和 FCOMI,以及 586 CMPGXCHG 和 CPUID)的 32 位代码的标准名称。现代 GNU/Linux 发行版通常将 gcc 配置为将其用作-m3232 位代码的默认目标,而不是真正的基线 i386。例如 gcc -v 将显示i686-linux-gnuGCC 的 32 位版本。

通常 clang 使用call next_insn/pop reg将 EIP 读入寄存器。(有趣的事实:这实际上并没有破坏除原始 Pentium-Pro 或 Via Nano3000 以外的 CPU 的返回地址预测:http: //blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0 - CPU 特殊情况call rel32=0因为不是真正的调用,也不要将返回地址放入预测器堆栈。)


包括要返回的寄存器的get_pc_thunk.bx名称。32 位 PIC 代码过去只使用 EBX 作为 GOT 指针寄存器,但 GCC 现在可以选择任何方便的寄存器并为其发出一个 thunk 函数,就像....get_pc_thunk.ax叶函数一样不必保存/恢复 EBX。

PIE 确实使可执行文件变慢,对于 32 位代码可能会降低 15%,而对于 64 位代码则降低几个百分点。x86-64 具有 RIP 相对寻址,从而避免了对这些 thunk 的需要。IMO 32 位 PIE 不值这个价,除非你真的需要通过主可执行文件的 ASLR 来加强对 ROP 和 Spectre 攻击的攻击。

于 2020-04-22T11:29:20.750 回答