gpt4 book ai didi

c - 我如何摆脱调用 __x86.get_pc_thunk.ax

转载 作者:太空宇宙 更新时间:2023-11-04 05:23:11 24 4
gpt4 key购买 nike

我试图编译一个非常简单的 C 程序并将其转换为汇编语言。

我使用的是 Ubuntu,操作系统类型是 64 位。

这是C程序。

void add();

int main() {
add();
return 0;
}

如果我使用 gcc -S -m32 -fno-asynchronous-unwind-tables -o simple.S simple.c 这就是我的汇编源代码文件的样子:
.file   "main1.c"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call add
movl $0, %eax
movl %ebp, %esp
popl %ebp
ret
.size main, .-main
.ident "GCC: (Debian 4.4.5-8) 4.4.5" // this part should say Ubuntu instead of Debian
.section .note.GNU-stack,"",@progbits

但它看起来像这样:
.file   "main0.c"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl %eax, %ebx
call add@PLT
movl $0, %eax
popl %ecx
popl %ebx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.section

.text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
.globl __x86.get_pc_thunk.ax
.hidden __x86.get_pc_thunk.ax
.type __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
movl (%esp), %eax
ret
.ident "GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
.section .note.GNU-stack,"",@progbits

在我的大学,如果我使用的是 64 位 Linux 版本,他们告诉我使用 Flag -m32。有人可以告诉我我做错了什么吗?
我什至使用了正确的标志吗?

-fno-pie 后编辑
.file   "main0.c"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $4, %esp
call add
movl $0, %eax
addl $4, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
.section .note.GNU-stack,"",@progbits

它看起来更好,但并不完全相同。
例如 leal 是什么意思?

最佳答案

作为一般规则,您不能期望两个不同的编译器为相同的输入生成相同的汇编代码,即使它们具有相同的版本号;他们的代码生成可以有任意数量的额外“补丁”。只要可观察到的行为是相同的,任何事情都会发生。

您还应该知道 GCC,默认情况下是 -O0模式,故意生成不良代码。它针对调试的便利性和编译速度进行了调整,而不是为了生成代码的清晰度或效率。由 gcc -O1 生成的代码通常更容易理解。比gcc -O0生成的代码.

您还应该知道 main函数通常需要进行其他函数不需要的额外设置和拆卸。指令leal 4(%esp),%ecx是额外设置的一部分。如果您只想了解与您编写的代码相对应的机器码,而不是ABI 的具体细节, 将您的测试函数命名为 main 以外的其他名称.

(正如评论中所指出的,设置代码并没有像它可能的那样紧密地调整,但这通常无关紧要,因为它在程序的生命周期中只执行一次。)

现在,回答从字面上问的问题,出现的原因

call __x86.get_pc_thunk.ax

是因为您的编译器默认生成“位置无关”的可执行文件。位置无关意味着操作系统可以在(虚拟)内存中的任何地址加载程序的机器代码,并且它仍然可以工作。这允许诸如 address space layout randomization 之类的东西,但要使其工作,您必须采取特殊步骤在每个访问全局变量或调用另一个函数(有一些异常(exception))的函数的开头设置一个“全局指针”。如果您打开优化,实际上更容易解释生成的代码:
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx

这只是设置 main的堆栈帧和需要保存的保存寄存器。你可以忽略它。
        call    __x86.get_pc_thunk.bx
addl $_GLOBAL_OFFSET_TABLE_, %ebx

特殊功能 __x86.get_pc_thunk.bx加载它的返回地址——这是 addl 的地址紧随其后的指令 -- 进入 EBX 寄存器。然后我们将魔法常数 _GLOBAL_OFFSET_TABLE_ 的值添加到该地址。 ,在与位置无关的代码中,它是使用 _GLOBAL_OFFSET_TABLE_ 的指令的地址之间的差值。和 global offset table的地址.因此,EBX 现在指向全局偏移表。
        call    add@PLT

现在我们打电话 add@PLT , 表示拨打 add ,而是跳过“程序链接表”来做。 PLT 负责处理 add 的可能性。在共享库中定义,而不是在主可执行文件中定义。 PLT 中的代码使用全局偏移表,并假设您在调用 @PLT 符号之前已将 EBX 设置为指向它。这就是为什么 main即使似乎没有人使用它,也必须设置 EBX。如果你改写了类似的东西
 extern int number;
int main(void) { return number; }

然后你会看到直接使用 GOT,比如
    call    __x86.get_pc_thunk.bx
addl $_GLOBAL_OFFSET_TABLE_, %ebx
movl number@GOT(%ebx), %eax
movl (%eax), %eax

我们用GOT的地址加载EBX,然后我们可以加载全局变量 number的地址从 GOT 中,然后我们实际上取消引用地址以获取 number 的值.

如果您编译 64 位代码,您将看到不同且更简单的内容:
    movl    number(%rip), %eax

我们可以直接加载 number,而不是在 GOT 上乱搞。从程序计数器的固定偏移量开始。与 PC 相关的寻址与 x86 架构的 64 位扩展一起添加。同样,您的原始程序,在 64 位位置无关模式下,只会说
    call    add@PLT

无需先设置 EBX。调用仍然必须通过 PLT,但 PLT 本身使用 PC 相对寻址,不需要调用者的任何帮助。
__x86.get_pc_thunk.bx之间的唯一区别和 __x86.get_pc_thunk.ax是他们将返回地址存储在哪个寄存器中:EBX for .bx ,EAX 为 .ax .我也看到 GCC 生成 .cx.dx变种。这只是它想将哪个寄存器用于全局指针的问题——如果要通过 PLT 进行调用,它必须是 EBX,但如果没有,那么它可以使用任何寄存器,因此它会尝试选择一个其他任何东西都不需要的。

为什么要调用一个函数来获取返回地址?较旧的编译器会改为这样做:
    call 1f
1: pop %ebx

但这搞砸了 return-address prediction ,所以现在编译器会遇到一些额外的麻烦来确保每个 callret 配对.

关于c - 我如何摆脱调用 __x86.get_pc_thunk.ax,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50105581/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com