我如何摆脱通话__x86.get_pc_thunk.ax [英] How do i get rid of call __x86.get_pc_thunk.ax

查看:114
本文介绍了我如何摆脱通话__x86.get_pc_thunk.ax的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I tried to compile and convert a very simple C program to assembly language.

我正在使用Ubuntu,操作系统类型为64位.

I am using Ubuntu and the OS type is 64 bit.

这是C程序.

void add();

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

如果我使用gcc -S -m32 -fno-asynchronous-unwind-tables -o simple.S simple.c,这就是我的汇编源代码文件的外观:

if i use gcc -S -m32 -fno-asynchronous-unwind-tables -o simple.S simple.c this is how my assembly source code File should look like:

.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.有人可以告诉我我做错了吗? 我甚至使用了正确的标志吗?

At my University they told me to use the Flag -m32 if I am using a 64 bit Linux version. Can somebody tell me what I am doing wrong? Am I even using the correct Flag?

在-fno-pie之后编辑

edit after -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是什么意思?

it looks better but it's not exactly the same. for example what does leal mean?

推荐答案

通常,您不能期望两个不同的编译器为相同的输入生成相同的汇编代码,即使它们具有相同的版本号也是如此.他们可以在代码生成中添加任意数量的额外补丁".只要可观察到的行为相同,一切都会发生.

As a general rule, you cannot expect two different compilers to generate the same assembly code for the same input, even if they have the same version number; they could have any number of extra "patches" to their code generation. As long as the observable behavior is the same, anything goes.

您还应该知道,GCC在其默认的-O0模式下会生成故意损坏的代码.对其进行了调整,以使其易于调试和加快编译速度,而不是出于所生成代码的清晰度或效率的考虑.与gcc -O0生成的代码相比,通常更容易理解gcc -O1生成的代码.

You should also know that GCC, in its default -O0 mode, generates intentionally bad code. It's tuned for ease of debugging and speed of compilation, not for either clarity or efficiency of the generated code. It is often easier to understand the code generated by gcc -O1 than the code generated by gcc -O0.

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

You should also know that the main function often needs to do extra setup and teardown that other functions do not need to do. The instruction leal 4(%esp),%ecx is part of that extra setup. If you only want to understand the machine code corresponding to the code you wrote, and not the nitty details of the ABI, name your test function something other than main.

(正如评论中指出的那样,设置代码并未像可能的那样进行严格的调整,但是通常并不重要,因为它在程序的生命周期中仅执行一次.)

(As pointed out in the comments, that setup code is not as tightly tuned as it could be, but it doesn't normally matter, because it's only executed once in the lifetime of the program.)

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

Now, to answer the question that was literally asked, the reason for the appearance of

call __x86.get_pc_thunk.ax

是因为编译器默认会生成与位置无关"的可执行文件.位置无关意味着操作系统可以将程序的机器代码加载到(虚拟)内存中的任何地址,并且仍然可以运行.这样可以实现地址空间布局随机化,但是要使其正常工作,您必须采取特殊步骤在访问全局变量或调用另一个函数的每个函数的开始处设置全局指针"(有一些例外).如果打开优化,实际上更容易解释生成的代码:

is because your compiler defaults to generating "position-independent" executables. Position-independent means the operating system can load the program's machine code at any address in (virtual) memory and it'll still work. This allows things like address space layout randomization, but to make it work, you have to take special steps to set up a "global pointer" at the beginning of every function that accesses global variables or calls another function (with some exceptions). It's actually easier to explain the code that's generated if you turn optimization on:

main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ebx
        pushl   %ecx

这仅仅是设置main的堆栈框架并保存需要保存的寄存器.您可以忽略它.

This is all just setting up main's stack frame and saving registers that need to be saved. You can ignore it.

        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx

特殊功能__x86.get_pc_thunk.bx将其返回地址(即紧随其后的addl指令的地址)加载到EBX寄存器中.然后,我们向该地址添加魔术常数_GLOBAL_OFFSET_TABLE_的值,在与位置无关的代码中,该常数是使用_GLOBAL_OFFSET_TABLE_的指令的地址与该地址之间的差异. 全局偏移表.因此,EBX现在指向全局偏移表.

The special function __x86.get_pc_thunk.bx loads its return address -- which is the address of the addl instruction that immediately follows -- into the EBX register. Then we add to that address the value of the magic constant _GLOBAL_OFFSET_TABLE_, which, in position-independent code, is the difference between the address of the instruction that uses _GLOBAL_OFFSET_TABLE_ and the address of the global offset table. Thus, EBX now points to the global offset table.

        call    add@PLT

现在,我们调用add@PLT,这意味着调用add,但是跳过过程链接表"以执行此操作. PLT负责在共享库而不是主可执行文件中定义add的可能性. PLT中的代码使用全局偏移表,并假定您已经在调用@PLT符号之前将EBX设置为指向它.这就是为什么main必须设置EBX的原因,尽管似乎没有使用它.如果您写的是类似的

Now we call add@PLT, which means call add, but jump through the "procedure linkage table" to do it. The PLT takes care of the possibility that add is defined in a shared library rather than the main executable. The code in the PLT uses the global offset table and assumes that you have already set EBX to point to it, before calling an @PLT symbol.  That's why main has to set up EBX even though nothing appears to use it. If you had instead written something like

 extern int number;
 int main(void) { return number; }

然后您会看到直接使用GOT,类似

then you would see a direct use of the GOT, something like

    call    __x86.get_pc_thunk.bx
    addl    $_GLOBAL_OFFSET_TABLE_, %ebx
    movl    number@GOT(%ebx), %eax
    movl    (%eax), %eax

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

We load up EBX with the address of the GOT, then we can load the address of the global variable number from the GOT, and then we actually dereference the address to get the value of number.

如果您改为编译64位代码,则会看到一些不同且更简单的内容:

If you compile 64-bit code instead, you'll see something different and much simpler:

    movl    number(%rip), %eax

我们可以从程序计数器的固定偏移中加载number,而不是通过GOT进行所有这些处理.与PC相关的寻址以及对x86体系结构的64位扩展被添加.同样,您的原始程序在64位与位置无关的模式下只会说

Instead of all this mucking around with the GOT, we can just load number from a fixed offset from the program counter. PC-relative addressing was added along with the 64-bit extensions to the x86 architecture. Similarly, your original program, in 64-bit position-independent mode, will just say

    call    add@PLT

无需先设置EBX.呼叫仍然必须通过PLT,但是PLT使用PC相对寻址本身,并且不需要呼叫者的任何帮助.

without setting up EBX first. The call still has to go through the PLT, but the PLT uses PC-relative addressing itself and doesn't need any help from its caller.

__x86.get_pc_thunk.bx__x86.get_pc_thunk.ax之间的唯一区别是它们将返回地址存储在哪个寄存器中:EBX表示.bx,EAX表示.ax.我还看到了GCC生成.cx.dx变体.只是要使用哪个寄存器作为全局指针的问题-如果要通过PLT进行调用,则必须为EBX,但是如果没有,则可以使用任何寄存器,因此它尝试使用选择其他不需要的东西.

The only difference between __x86.get_pc_thunk.bx and __x86.get_pc_thunk.ax is which register they store their return address in: EBX for .bx, EAX for .ax. I have also seen GCC generate .cx and .dx variants. It's just a matter of which register it wants to use for the global pointer -- it must be EBX if there are going to be calls through the PLT, but if there aren't any then it can use any register, so it tries to pick one that isn't needed for anything else.

为什么调用一个函数来获取返回地址?较早的编译器会改为执行此操作:

Why does it call a function to get the return address? Older compilers would do this instead:

    call 1f
1:  pop  %ebx

但是这搞砸了返回地址预测,所以如今编译器开始使用麻烦一点,以确保每个call与一个ret配对.

but that screws up return-address prediction, so nowadays the compiler goes to a little extra trouble to make sure every call is paired with a ret.

这篇关于我如何摆脱通话__x86.get_pc_thunk.ax的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆