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, %ebpreplace它。 在这一点上调整ebp的最后4位是GCC特有的,我不知道为什么这个编译器会这样做。 它会工作而不做。 现在最后我们进入业务, call ___main ,谁是__main? 我也不知道…也许更多的GCC特定的程序,最后唯一的事情你的主要()做,设置返回值为0与movl $0, %eaxleave这是一样的做movl %ebp, %esp; popl %ebp movl %ebp, %esp; popl %ebp恢复ebp状态,然后ret完成。 ret eip并继续从那一点开始线程stream(因为它的main(),这个ret可能导致一些处理程序结束的内核过程)。

大部分都是关于pipe理堆栈的。 我写了一个关于如何使用堆栈的详细教程,解释为什么所有这些东西都是有用的。 但它在葡萄牙…