2009-08-22 48 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向下增長。