2009-08-22 88 views
54

我写空的程序惹恼了地狱的计算器编码器,而不是。我只是在探索gnu工具链。GCC的一个空的程序的汇编输出在x86,Win32的

现在下面可能对我来说太深了,但为了继续空的程序传奇我已经开始检查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:文档说“存储类可能会标记符号是静态的还是外部的”。这是一样的静态外部我知道从C?那'2'是什么?
  • .type:将参数“作为符号表条目的类型属性”,我没有线索。
  • .endef:没问题。
  • .text:现在这是有问题的,它似乎是所谓的部分,我已经阅读它的代码的地方,但文档没有告诉我太多。
  • .globl“使符号LD可见。”,这个手册很清楚。
  • _main:这可能是起始地址为我的主要功能
  • pushl_(?):长(32位)推,这使堆
  • movl上EBP:32位移动。假C:EBP = ESP;
  • andl:逻辑AND。假-C:ESP = -16 & ESP,我真的不明白这是什么意思。
  • call:将IP推送到堆栈(所以被调用的过程可以找回它的路),并继续__main。 (什么是__main?)
  • movl:这个零必须是我在代码结束时返回的常量。 MOV将这个零置入EAX中。
  • leave:在ENTER指令(?)后恢复堆栈。为什么?
  • ret:返回到被保存在堆栈

谢谢您的帮助,在指令地址!

+8

好问题。 :) – 2009-08-22 21:39:03

+4

听起来像是一个真正的极客的优秀练习。 – JesperE 2009-08-22 21:41:12

+3

我找到了COFF规范。这应该提供一些对“.type”中的“32”意味着什么的引用:http://www.microsoft.com/whdc/system/platform/firmware/PECOFFdwn.mspx – 2009-08-22 21:51:07

回答

54

.file“ test.c的”

命令开始,被指令的汇编。这只是说,这是‘file.c’,这些信息可以导出exe文件的调试信息。

.def ___main; .scl 2; .ty pe 32; .endef

.def指令定义了一个调试符号。 scl 2意味着存储类2(外部存储类)。类型32表示这个sumbol是一个函数。这些数字将被PE-COFF EXE格式

___main被定义为一个叫做函数,它引导的是gcc的需求(它会做的事情一样运行C++静态初始化,需要其他管家)的照顾。

.text 

开始文本部分 - 代码住在这里。

.globl _main

定义_main符号作为全球性的,这将使它在连接器和该公司在连接其它模块可见。

.def  _main; .scl 2;  .type 32;  .endef 

同作为_main的东西,创建调试符号,说明_main是一个函数。这可以被调试器使用。

_main:

开始一个新的标签(这将最终的地址)。上面的.globl指令使得该地址对其他实体可见。

pushl  %ebp 

保存旧的帧指针(EBP寄存器)在栈上(这样它可以在适当位置时,这个函数结束被放回)

movl  %esp, %ebp 

移动的堆栈指针指向ebp寄存器。 ebp通常称为帧指针,它指向当前“帧”(通常是函数)内堆栈值的顶部,(通过ebp指堆栈上的变量可以帮助调试器)

andl $ - 16,%esp

用fffffff0结束堆栈,有效地将它与16字节的边界对齐。访问堆栈中的对齐值要比未对齐时快得多。所有这些前面的说明几乎都是一个标准的函数序言。

call  ___main 

调用___main功能,会做初始化的东西,GCC的需求。呼叫将推动当前指令指针栈上,并跳转到___main的地址

movl  $0, %eax 

移动0〜eax寄存器,(在返回0 0;)EAX寄存器用于保存函数stdcall调用约定的返回值。

离开

leave指令是相当多的简写

movl  ebp,esp 
popl  ebp 

即它 “索马里发展事务处” 的功能开始做的东西 - 恢复框架指针和堆栈到它以前的状态。

RET

返回到调用此功能。它会弹出堆栈中的指令指针(相应的调用指令将放置在那里)并跳到那里。

2

我不知道所有的答案,但我可以解释我所知道的。

ebp被函数用来在其流程中存储初始状态esp,引用传递给函数的参数以及其自身的局部变量。功能做的第一件事是保存在给定ebppushl %ebp的地位,它是使呼叫的功能是至关重要的,不是通过做movl %esp, %ebp自己的当前栈位置esp替换它。在这一点上将ebp的最后4位清零是GCC特有的,我不知道为什么这个编译器会这样做。它会在没有做到的情况下工作。现在最后我们开始做生意了,call ___main,谁是__main?我也不知道...也许更多的GCC特定的程序,最后你的主要()做的唯一的事情,设置返回值为0与movl $0, %eaxleave这是相同的做movl %ebp, %esp; popl %ebp恢复ebp状态,然后ret完。 ret弹出eip,并继续从该点线流动,无论它是(作为其主要的(),这可能沤导致其处理程序结束一些内核程序)。

大部分是所有关于管理堆栈。我写了一篇关于前一段时间如何使用堆栈的详细教程,解释为什么所有这些东西都是有用的。但其在葡萄牙...

5

关于该和L $ -16,ESP%

  • 32位:-16十进制等于十六进制表示
  • 64位0xfffffff0:-16十进制等于十六进制表示

到0xfffffffffffffff0因此,将屏蔽掉ESP的最后4位(顺便说一句:2 ** 4等于16),并且如果目标系统是32将保留所有其它位(不管或64位)。

12

这里有列出一个非常类似的工作:http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax

你想通了大部分 - 我就做了强调和补充附加注释。

__main是在GNU标准库中的子程序,它采取各种启动初始化工作。对于C程序来说这不是必须的,但是为了防止C代码与C++链接,这是必需的。

_main是你的主要子程序。由于_main__main是代码位置,它们具有相同的存储类和类型。我还没有挖掘出.scl.type的定义。您可以通过定义一些全局变量来获得一些照明。

前三条指令设置了一个堆栈帧,它是子程序工作存储的技术术语 - 大部分是局部变量和临时变量。推送ebp保存调用者的堆栈帧的基础。将esp置入ebp设置我们堆栈帧的基础。如果堆栈中的任何局部变量需要16字节对齐(对于x86 SIMD指令需要对齐,但对齐会加快普通类型,如int s和float s。

),则堆栈帧与16字节边界对齐

此时你通常会期望esp在内存中得到下移分配堆栈空间的局部变量。你main现在没有这样的gcc不打扰。

__main的调用是特殊的主并且通常不会出现在子程序中。

其余的就像你猜测一样。寄存器eax是在二进制规范中放置整数返回码的地方。 leave撤消堆栈帧,并且ret返回给调用者。在这种情况下,主叫方是低级别的C运行时将做额外的魔法(比如调用atexit()功能,设置进程的退出代码,并要求操作系统终止进程。

4

andl $-16,%esp,这个工程因为低位设置为零将随时调整%esp价值,并堆栈在x86向下增长。