有人可以解释一下GCC正在为这段代码做些什么吗?什么是初始化?原始代码是:
#include <stdio.h>
int main()
{
}
它被翻译成:
.file "test1.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
call __alloca
call ___main
leave
ret
如果编译器/汇编大师通过解释堆栈,寄存器和段初始化来启动我,我将不胜感激。我无法从代码中删除头部或尾部。
编辑: 我正在使用gcc 3.4.5。命令行参数是gcc -S test1.c
谢谢你, kunjaan。
答案 0 :(得分:14)
我应该在我的所有评论前面说,我仍然在学习。
我会忽略部分初始化。部分初始化的解释基本上我覆盖的其他所有内容都可以在这里找到: http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax
ebp register是stack frame基指针,因此是BP。它存储指向当前堆栈开头的指针。
esp寄存器是堆栈指针。它保存堆栈顶部的内存位置。每次我们在堆栈上推送esp时都会更新esp,以便它始终指向堆栈顶部的地址。
所以ebp指向基础,esp指向顶部。所以堆栈看起来像:
esp -----> 000a3 fa
000a4 21
000a5 66
000a6 23
esb -----> 000a7 54
如果您在堆栈上按e4,则会发生以下情况:
esp -----> 000a2 e4
000a3 fa
000a4 21
000a5 66
000a6 23
esb -----> 000a7 54
请注意,堆栈会向较低的地址增长,这一点在下面非常重要。
前两个步骤称为过程序言,或者更常见的是function prolog,它们准备堆栈以供局部变量使用。请参阅底部的程序prolog引用。
在步骤1中,我们通过调用将指针保存到堆栈上的旧堆栈帧, pushl%ebp。由于main是第一个被调用的函数,我不知道%ebp的前一个值是什么。
步骤2,我们正在输入一个新的堆栈帧,因为我们正在输入一个新的功能(main)。因此,我们必须设置一个新的堆栈帧基指针。我们使用esp中的值作为堆栈帧的开头。
步骤3.在堆栈上分配8个字节的空间。正如我们上面提到的,堆栈向低地址增长,因此减去8,将堆栈顶部移动8个字节。
第4步;分配堆栈,我发现了不同的意见。我真的不确定这是做什么的。我怀疑这样做是为了允许在堆栈上分配大指令(SIMD),
http://gcc.gnu.org/ml/gcc/2008-01/msg00282.html
此代码“和”ESP与0xFFFF0000, 将堆栈与下一个堆栈对齐 最低的16字节边界。一个 检查Mingw的源代码 揭示这可能是针对SIMD的 出现在“_main”中的说明 例程,仅在对齐的情况下运行 地址。因为我们的例程没有 包含SIMD指令,此行 是不必要的。
http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax
步骤5到11似乎对我没有任何意义。我在谷歌上找不到任何解释。真正了解这些东西的人能否提供更深入的了解。我听说过这些东西用于C的异常处理。
步骤5,将主0的返回值存储在eax中。
步骤6和7我们在未知原因的情况下将十六进制加到eax中。 eax = 01111 + 01111 = 11110
步骤8我们将eax 4位向右移位。 eax = 00001因为最后一位是00001 |的结束111.
步骤9我们将eax 4位向左移位,eax = 10000。
步骤10和11将堆栈中前4个分配字节中的值移动到eax中,然后将其从eax中移回。
步骤12和13设置c库。
我们已达到function epilogue。也就是说,函数的一部分将堆栈指针,esp和ebp返回到调用此函数之前的状态。
步骤14,将esp设置为ebp的值,将堆栈顶部移动到main调用之前的地址。然后它将ebp设置为指向我们在步骤1中保存在堆栈顶部的地址。
可以用以下说明替换:
mov %ebp, %esp
pop %ebp
步骤15,返回并退出该功能。
1. pushl %ebp
2. movl %esp, %ebp
3. subl $8, %esp
4. andl $-16, %esp
5. movl $0, %eax
6. addl $15, %eax
7. addl $15, %eax
8. shrl $4, %eax
9. sall $4, %eax
10. movl %eax, -4(%ebp)
11. movl -4(%ebp), %eax
12. call __alloca
13. call ___main
14. leave
15. ret
程序Prolog:
功能必须做的第一件事 被称为程序序言。它 首先保存当前的基指针 (ebp)指令pushl%ebp (记住ebp是用于的寄存器 访问函数参数和 局部变量)。现在它复制了 堆栈指针(esp)到基地 指针(ebp)与指令 movl%esp,%ebp。这允许你 访问函数参数为 来自基指针的索引。本地 变量总是减法 来自ebp,例如-4(%ebp)或 (%ebp)-4为第一个局部变量, 返回值始终为4(%ebp) 或(%ebp)+4,每个参数或 参数是在N * 4 + 4(%ebp)之类的 8(%ebp)为第一个参数 旧的ebp是(%ebp)。
http://www.milw0rm.com/papers/52
存在一个非常好的堆栈溢出线程,它回答了大部分问题。 Why are there extra instructions in my gcc output?
可以在此处找到有关x86机器代码指令的良好参考: http://programminggroundup.blogspot.com/2007/01/appendix-b-common-x86-instructions.html
这个讲座包含以下使用的一些想法: http://csc.colstate.edu/bosworth/cpsc5155/Y2006_TheFall/MySlides/CPSC5155_L23.htm
以下是回答您问题的另一种看法: http://www.phiral.net/linuxasmone.htm
这些资料都没有解释一切。
答案 1 :(得分:9)
以下是GCC编译的简单main()
函数的逐步细分,包含大量详细信息:GAS Syntax (Wikipedia)
对于您粘贴的代码,说明细分如下:
leave
指令:拆掉堆栈框架ret
指令:返回调用者(外部运行时函数,或者调用程序的内核函数)答案 2 :(得分:4)
好吧,不太了解GAS,我在英特尔组装上有点生疏,但它看起来像初始化主要的堆栈框架。
如果你看一下,__ main是某种宏,必须执行初始化。 然后,当main的主体为空时,它调用leave指令,返回到调用main的函数。
来自http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax#.22hello.s.22_line-by-line:
此行声明“_main”标签,标记从启动代码调用的位置。
pushl %ebp
movl %esp, %ebp
subl $8, %esp
这些行保存EBP在堆栈上的值,然后将ESP的值移到EBP中,然后从ESP中减去8。每个操作码末尾的“l”表示我们想要使用与“长”(32位)操作数一起使用的操作码版本;
andl $-16, %esp
此代码“和”ESP与0xFFFF0000,使堆栈与下一个最低16字节边界对齐。 (使用simd指令时必要,在这里没用)
movl $0, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
此代码将零移动到EAX中,然后将EAX移动到内存位置EBP-4,该位置位于我们在过程开始时在堆栈上保留的临时空间中。然后它将内存位置EBP-4移回EAX;显然,这不是优化代码。
call __alloca
call ___main
这些功能是C库设置的一部分。由于我们在C库中调用函数,我们可能需要这些函数。它们执行的确切操作取决于平台和安装的GNU工具的版本。
这是一个有用的链接。
答案 3 :(得分:4)
知道你正在使用的gcc版本和libc是什么真的很有帮助。看起来你有一个非常古老的gcc版本或一个奇怪的平台或两者兼而有之。发生的事情是调用约定的一些奇怪之处。我可以告诉你一些事情:
按照惯例将帧指针保存在堆栈中:
pushl %ebp
movl %esp, %ebp
在框架的旧端为空间腾出空间,并将堆栈指针向下舍入到4的倍数(为什么需要我不知道):
subl $8, %esp
andl $-16, %esp
通过疯狂的歌舞,准备从main
返回1:
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
恢复使用alloca
(GNU-ism)分配的所有内存:
call __alloca
向libc宣布main
正在退出(更多GNU-ism):
call ___main
恢复帧和堆栈指针:
leave
返回:
ret
当我在Debian Linux上用gcc 4.3编译完全相同的源代码时会发生什么:
.file "main.c"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Debian 4.3.2-1.1) 4.3.2"
.section .note.GNU-stack,"",@progbits
我以这种方式分解:
告诉调试器和其他工具源文件:
.file "main.c"
代码出现在文本部分:
.text
打败我:
.p2align 4,,15
main
是导出的函数:
.globl main
.type main, @function
main
的切入点:
main:
抓住返回地址,将堆栈对齐到4字节地址,然后再次保存返回地址(为什么我不能说):
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
使用标准约定保存帧指针:
pushl %ebp
movl %esp, %ebp
难以置信的疯狂:
pushl %ecx
popl %ecx
恢复帧指针和堆栈指针:
popl %ebp
leal -4(%ecx), %esp
返回:
ret
调试器的更多信息?:
.size main, .-main
.ident "GCC: (Debian 4.3.2-1.1) 4.3.2"
.section .note.GNU-stack,"",@progbits
顺便说一下,main
是特殊而神奇的;当我编译
int f(void) {
return 17;
}
我得到了一点点理智:
.file "f.c"
.text
.p2align 4,,15
.globl f
.type f, @function
f:
pushl %ebp
movl $17, %eax
movl %esp, %ebp
popl %ebp
ret
.size f, .-f
.ident "GCC: (Debian 4.3.2-1.1) 4.3.2"
.section .note.GNU-stack,"",@progbits
仍然有大量的装饰,我们仍然保存帧指针,移动它,并恢复它,这是完全没有意义的,但其余的代码是有道理的。
答案 4 :(得分:1)
看起来GCC似乎可以编辑main()
以包含CRT初始化代码。我刚刚确认我从MinGW GCC 3.4.5获得与源文本完全相同的汇编列表。
我使用的命令行是:
gcc -S emptymain.c
有趣的是,如果我将函数的名称更改为qqq()
而不是main()
,我会得到以下程序集:
.file "emptymain.c" .text .globl _qqq .def _qqq; .scl 2; .type 32; .endef _qqq: pushl %ebp movl %esp, %ebp popl %ebp ret
对于未启用优化的空函数更有意义。