我写空的程序惹恼了地狱的计算器编码器,而不是。 我只是在探索的GNU工具链。
现在以下可能是太深的我,但到continuie我已经开始研究C编译器的输出,东西GNU作为消耗空程序传奇。
gcc version 4.4.0 (TDM-1 mingw32)
test.c的:
int main()
{
return 0;
}
GCC -S test.c的
.file "test.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call ___main
movl $0, %eax
leave
ret
你能解释这里发生了什么? 这是我努力去理解它。 我已经使用as
手册和我最小的x86 ASM知识:
-
.file "test.c"
是逻辑文件名的指令。 -
.def
:根据文档“开始定义一个符号名调试信息”。 什么是调试信息的符号(函数名/变量?)什么样的? -
.scl
:文件说“存储类型可以表明一个符号是静态的还是外部”。 这是相同的静态和外部 I由C知道吗? 什么是“2”? -
.type
:存储参数“作为一个符号表项的类型属性”,我不知道。 -
.endef
:没问题。 -
.text
:现在,这是有问题的,这似乎是一种叫节,我已阅读,它的代码的地方,但该文档没有告诉我太多。 -
.globl
“使符号LD可见。” ,该手册是对这个很清楚。 -
_main:
这可能会为我的主要功能是起始地址(?) -
pushl_
:一个长(32位)推,这使在堆栈上EBP -
movl
:32位举动。 伪C: EBP = ESP;
-
andl
:逻辑和。 伪C: ESP = -16 & ESP
,我实在不明白这什么地步。 -
call
:推动IP堆栈(所以被调用过程可以找到它的归途),并继续在那里__main
是。 (什么是__main?) -
movl
:这个零点必须是恒定的我在我的代码的最后返回。 该MOV地方这个零到EAX。 -
leave
:还原一个ENTER指令后堆栈(?)。 为什么? -
ret
:返回到保存在栈上的指令地址
谢谢您的帮助!
.file “test.c的”
命令开始。 是指令来汇编。 这只是说,这是“file.c”,这些信息可以导出exe文件的调试信息。
.DEF ___main; .scl伪2; .TYPE 32; .endef伪
.DEF指令限定了调试符号。 SCL 2表示存储类2(外部存储类).TYPE 32表示,这是sumbol的函数。 这些数字将被PE-COFF EXE格式的定义
___main是一个叫做函数,它引导的是gcc的需求(它会做的事情一样运行C ++静态初始化,需要其他管家)的照顾。
.text
开头的文字部分 - 代码住在这里。
Kglobl _man
定义_main符号作为全球性的,这将使它在连接器和该公司在连接其它模块可见。
.def _main; .scl 2; .type 32; .endef
同样的事情_main,创建调试符号,说明_main是一个函数。 这可以被调试器使用。
_主要:
启动一个新的标签(这将最终的地址)。 上述.globl指令,使这个地址设置为向其他实体。
pushl %ebp
保存在堆栈上旧的帧指针(EBP寄存器)(这样它可以在适当位置时,这个函数结束被放回)
movl %esp, %ebp
移动堆栈指针到EBP寄存器。 EBP通常被称为帧指针,它指向在当前“帧”内的所述堆栈的所述最高(函数通常),(经由EBP指栈中的变量可以帮助调试器)
和L $ -16,ESP%
ANDS与FFFFFFF0堆栈其用途不同对准它一个16字节边界上。 访问对齐值在栈上比,如果他们不对齐快得多。 所有这些先行指令都已相当多的标准功能序幕。
call ___main
调用___main功能,会做初始化的东西,GCC的需求。 通话将推动在堆栈上当前指令指针,并跳转到___main的地址
movl $0, %eax
(在返回0 0)移动0〜eax寄存器,EAX寄存器被用来保存函数返回值的stdcall调用约定。
离开
leave指令是非常速记
movl ebp,esp popl ebp
即它“索马里发展事务处”的功能开始做的东西 - 恢复帧指针和堆栈到以前的状态。
权
返回到谁调用该函数。 它会从弹出堆栈指令指针(其对应的调用指令将已经摆在那里),并跳转到那里。
这里有一个概述非常类似的工作: http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax
你已经想通了大部分 - 我就做了强调和补充附加注释。
__main
是在GNU标准库中的子程序,它采取各种启动初始化工作。 这不是绝对必要的C程序,但需要公正的情况下C代码使用C ++链接。
_main
是你的主要子程序。 由于两个_main
和__main
是代码位置,他们具有相同的存储类和类型。 我还没挖出的定义.scl
和.type
呢。 您可以通过定义几个全局变量得到一些照明。
前三个指令设置堆栈帧这是一个子程序的工作存储一个技术术语 - 大部分地方和临时变量。 推ebp
保存调用者的堆栈帧的基础。 把esp
到ebp
将我们的堆栈帧的基础。 的andl
对准堆栈帧为16字节的边界,以防万一在堆栈上的任何局部变量需要16字节对齐(用于x86 SIMD指令要求对准,但对准并加快普通类型,如int
S和float
秒。
此时,您通常会想到esp
在内存中得到下移分配堆栈空间的局部变量。 你的main
现在没有这样的gcc不打扰。
要调用__main
是特殊的主入口点,并且通常不会出现在子程序。
其余部分则作为你推测。 注册eax
是把整数返回代码的二进制规范的地方。 leave
撤消堆栈帧和ret
返回给调用者。 在这种情况下,主叫方是低级别的C运行时将做额外的魔法(比如调用atexit()
函数,设置进程的退出代码,并要求操作系统终止进程。
至于说和L $ -16,ESP%
- 32位:-16十进制等于十六进制表示,以0xfffffff0
- 64位:-16十进制等于十六进制表示,以0xfffffffffffffff0
因此,将屏蔽掉ESP的最后4位(顺便说一句:2 ** 4等于16)和(如果目标系统是32或64位无论)将保留所有其他位。
继andl $-16,%esp
这工作,因为低位设置为零将随时调节%esp
价值下降 ,并在栈上的x86向下增长。
我不知道所有的答案,但我可以解释我所知道的。
ebp
使用由函数来存储的初始状态esp
其流动过程中,到了那里被传递给功能的参数,并且其中一个参考是它自己的局部变量。 功能做的第一件事是保存在给定的状态ebp
做pushl %ebp
,它是使呼叫的功能是至关重要的,而不是其自己的当前堆栈中的位置将其替换esp
做movl %esp, %ebp
。 归零的最后4位ebp
在这一点上是GCC的特殊,我不知道为什么这个编译器做到这一点。 它的工作没有做。 现在,我们终于进入业务, call ___main
,谁是__main? 我也不知道......也许多个GCC具体的程序,最后的唯一的事情你的main()那样,设置返回值为0与movl $0, %eax
和leave
是一样的做movl %ebp, %esp; popl %ebp
movl %ebp, %esp; popl %ebp
恢复ebp
状态,然后ret
完成。 ret
弹出eip
,并继续从该点线流动,无论它是(作为其主要的(),这可能沤导致其处理程序结束一些内核程序)。
大部分是所有关于管理堆栈。 我写了一篇关于如何叠前一段时间使用,这将是有益的解释为什么所有这些事情都做了详细的教程。 但其在葡萄牙...