GCC在x86上的一个空程序的输出输出,win32
我写空的程序来惹恼从stackoverflow编码器,地狱。 我只是在探索GNU工具链。
现在下面可能对我来说太深了,但为了继续空的程序传奇,我已经开始研究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
你能解释一下这里发生了什么? 这是我的努力来理解它。 我已经使用了手册和我最小的x86 ASM知识:
-
.file "test.c"
是逻辑文件名的指令。 -
.def
:根据文档“开始定义符号名称的debugging信息” 。 什么是符号(函数名称/variables?)以及哪种debugging信息? -
.scl
:docs说“存储类可能会标志一个符号是静态的还是外部的” 。 这是从C我知道的静态和外部相同吗? 那'2'是什么? -
.type
:存储参数“作为符号表项的types属性” ,我不知道。 -
.endef
:没问题。 -
.text
:现在这是有问题的,它似乎是所谓的部分,我已经读了它的代码的地方,但文档没有告诉我太多。 -
.globl
“使符号对ld可见”。 ,这个手册很清楚。 -
_main:
这可能是我的主要function的起始地址(?) -
pushl_
:一个长的(32位)推送,将EBP放在堆栈上 -
movl
:32位移动。 假-C:EBP = ESP;
-
andl
:逻辑AND。 假-C:ESP = -16 & ESP
,我真的不知道这个是什么意思。 -
call
:将IP推入堆栈(所以被调用的过程可以返回)并继续__main
所在的位置。 (什么是__main?) -
movl
:这个零必须是我在代码结束时返回的常量。 MOV将这个零置入EAX中。 -
leave
:在ENTER指令(?)后恢复堆栈。 为什么? -
ret
:返回到保存在堆栈上的指令地址
感谢您的帮助!
.file“test.c”
以开始的命令。 是汇编程序的指令。 这只是说这是“file.c”,该信息可以导出到EXE的debugging信息。
.def ___main; .scl 2; .type 32; .endef伪
.def指令定义了一个debugging符号。 scl 2表示存储类2(外部存储类)。type32表示这个sumbol是一个函数。 这些数字将由pe-coff exe格式定义
___main是一个叫做的函数,负责处理gcc需要的引导(它会执行诸如运行c ++静态初始化器和其他需要的内务处理)。
.text
开始一个文本部分 – 代码居住在这里。
.globl _main
将_main符号定义为全局符号,这会使链接器和链接的其他模块可见。
.def _main; .scl 2; .type 32; .endef
和_main一样,创builddebugging符号,指出_main是一个函数。 这可以被debugging器使用。
_主要:
开始一个新的标签(它将结束一个地址)。 上面的.globl指令使得这个地址对其他实体可见。
pushl %ebp
将旧的帧指针(ebp寄存器)保存在堆栈中(这样可以在该函数结束时将其放回原位)
movl %esp, %ebp
将堆栈指针移动到ebp寄存器。 ebp经常被称为框架指针,它指向当前“框架”(函数通常)内的栈顶值,(通过ebp指堆栈上的variables可以帮助debugging器)
andl $ -16,%esp
用fffffff0来将堆栈与16字节的边界进行有效的alignment。 访问堆栈上的alignment值要比不alignment的速度快得多。 所有这些前面的指令都是一个标准的函数序言。
call ___main
调用___main函数来初始化gcc需要的东西。 调用会将当前的指令指针压入堆栈并跳转到___main的地址
movl $0, %eax
将0移动到eax寄存器(返回0; 0),eax寄存器用于保存stdcall调用约定的函数返回值。
离开
休假指令非常简短
movl ebp,esp popl ebp
即它在开始的时候“撤销”了一些东西 – 将帧指针和堆栈恢复到原来的状态。
RET
返回给调用这个函数的人。 它会从堆栈中popup指令指针(相应的调用指令将放置在那里)并跳转到那里。
这里有一个非常类似的练习: http : //en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax
你已经想出了大部分 – 我只是作出强调和补充额外的笔记。
__main
是GNU标准库中的一个子例程,负责各种启动初始化。 这对于C程序来说不是必须的,但是在C代码与C ++连接的情况下是必须的。
_main
是你的_main
程序。 由于_main
和__main
都是代码位置,它们具有相同的存储类和types。 我还没有挖掘.scl
和.type
的定义。 您可以通过定义一些全局variables来获得一些照明。
前三条指令是设置一个堆栈框架,这是一个子程序的工作存储的技术术语 – 大部分是局部variables和临时variables。 推ebp
保存调用者的堆栈框架的基础。 将esp
放入ebp
将设置我们的堆栈框架的基础。 为了防止堆栈中的任何局部variables需要16字节alignment(和x86 SIMD指令要求alignment,但alignment会加快普通types(如int
s和float
s)), andl
堆栈帧alignment到16字节的边界。
在这一点上,你通常会希望esp
在内存中被移走,为本地variables分配堆栈空间。 你的main
没有这样的gcc不打扰。
对__main
的调用对于主入口点是特殊的,并且通常不会出现在子例程中。
其余的就像你猜测一样。 寄存器eax
是在二进制规范中放置整数返回码的地方。 leave
堆栈帧, ret
返回给调用者。 在这种情况下,调用者是低级的C运行时,它将执行额外的魔术(如调用atexit()
函数,为进程设置退出代码并要求操作系统终止进程。
关于那个$ -16,%esp
- 32位:hex中的-16等于hex表示中的0xfffffff0
- 64位:hex中的-16等于hex表示中的0xfffffffffffffff0
所以它将屏蔽掉ESP的最后4位(btw:2 ** 4等于16),并保留所有其他位(不pipe目标系统是32还是64位)。
除了andl $-16,%esp
,这是有效的,因为将低位设置为零总是将值调整为%esp
,堆栈在x86上向下增长。
我没有全部答案,但我可以解释我所知道的。
函数使用ebp
存储esp
在其stream程中的初始状态,引用传递给函数的参数以及其自身的局部variables。 函数做的第一件事就是保存给定的ebp
的状态,并执行pushl %ebp
,这对调用函数是非常重要的,而不是由它自己当前的堆栈位置( esp
movl %esp, %ebp
replace它。 在这一点上调整ebp
的最后4位是GCC特有的,我不知道为什么这个编译器会这样做。 它会工作而不做。 现在最后我们进入业务, call ___main
,谁是__main? 我也不知道…也许更多的GCC特定的程序,最后唯一的事情你的主要()做,设置返回值为0与movl $0, %eax
和leave
这是一样的做movl %ebp, %esp; popl %ebp
movl %ebp, %esp; popl %ebp
恢复ebp
状态,然后ret
完成。 ret
eip
并继续从那一点开始线程stream(因为它的main(),这个ret可能导致一些处理程序结束的内核过程)。
大部分都是关于pipe理堆栈的。 我写了一个关于如何使用堆栈的详细教程,解释为什么所有这些东西都是有用的。 但它在葡萄牙…