簡體   English   中英

GCC在x86,win32上的空程序的匯編輸出

[英]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

  • 32位:十六進制中的-16等於十六進制表示中的0xfffffff0
  • 64位:十六進制中的-16等於十六進制表示中的0xfffffffffffffff0

因此它將屏蔽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, %eaxleave與執行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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM