我如何摆脱通话__x86.get_pc_thunk.ax [英] How do i get rid of call __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屋!