[英]GCC's assembly output of an empty program on 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
你能解釋一下這里發生的事嗎? 這是我努力理解它。 我使用了as
manual和我的最小x86 ASM知識:
.file "test.c"
是邏輯文件名的指令。 .def
:根據文檔“開始定義符號名稱的調試信息” 。 什么是符號(函數名稱/變量?)以及什么樣的調試信息? .scl
:docs說“存儲類可以標記符號是靜態的還是外部的” 。 這是我從C中知道的靜態和外部嗎? 什么是'2'? .type
:存儲參數“作為符號表條目的類型屬性” ,我不知道。 .endef
:沒問題。 .text
:現在這是有問題的,它似乎是一個叫做section的東西,我已經讀過它代碼的地方,但是文檔並沒有告訴我太多。 .globl
“使符號對ld可見。” ,手冊很清楚。 _main:
這可能是我的main函數的起始地址(?) pushl_
:一個長(32位)推送,將EBP放在堆棧上 movl
:32位移動。 偽C: EBP = ESP;
andl
:邏輯AND。 Pseudo-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
開始一個文本部分 - 代碼就在這里。
.globl _main
將_main符號定義為global,這將使鏈接器和鏈接的其他模塊可見。
.def _main; .scl 2; .type 32; .endef
與_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調用約定的函數返回值。
離開
離開指令非常簡短
movl ebp,esp popl ebp
即它“取消”在函數開始時完成的操作 - 將幀指針和堆棧恢復到其以前的狀態。
RET
返回調用此函數的人。 它將從堆棧中彈出指令指針(相應的調用指令將放置在那里)並跳轉到那里。
這里概述了一個非常類似的練習: 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
s。
此時,您通常希望esp
在內存中向下移動以為局部變量分配堆棧空間。 你的main
沒有,所以gcc不打擾。
對__main
的調用對主入口點是特殊的,通常不會出現在子例程中。
其余的就像你猜測的那樣。 寄存器eax
是在二進制規范中放置整數返回碼的地方。 leave
撤消堆棧幀和ret
返回給調用者。 在這種情況下,調用者是低級C運行時,它將執行額外的魔術(如調用atexit()
函數,設置進程的退出代碼並要求操作系統終止進程。
關於那個andl $ -16,%esp
因此它將屏蔽ESP的最后4位(btw:2 ** 4等於16)並保留所有其他位(無論目標系統是32位還是64位)。
繼andl $-16,%esp
這工作,因為低位設置為零將隨時調節%esp
價值下降 ,並在棧上的x86向下增長。
我沒有所有的答案,但我可以解釋我所知道的。
函數使用ebp
在其流中存儲esp
的初始狀態,引用傳遞給函數的參數在哪里以及它自己的局部變量在哪里。 函數做的第一件事就是保存給定ebp
執行pushl %ebp
,它對於調用函數至關重要,而不是用它自己當前的堆棧位置esp
替換它做movl %esp, %ebp
。 此時將ebp
的最后4位ebp
是GCC特定的,我不知道為什么這個編譯器會這樣做。 如果不這樣做就可以。 現在終於我們開始做生意, call ___main
,誰是__main? 我不知道......也許更多GCC特定的程序,最后你的main()做的唯一的事情,用movl $0, %eax
設置返回值為movl $0, %eax
並leave
與執行movl %ebp, %esp; popl %ebp
相同的值movl %ebp, %esp; popl %ebp
movl %ebp, %esp; popl %ebp
恢復ebp
狀態,然后ret
完成。 ret
彈出eip
並從該點繼續線程流,無論它在哪里(作為main(),這個ret可能會導致一些處理程序結束的內核程序)。
其中大部分都是關於管理堆棧。 我寫了一篇關於如何使用堆棧的詳細教程,解釋為什么所有這些都是有用的。 但它的葡萄牙語......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.