從彙編角度分析C語言的過程呼叫
原文出自【聽雲技術部落格】:http://blog.tingyun.com/web/article/detail/1132
基本術語定義
1.系統棧(system stack)是一個記憶體區,位於程式地址空間的末端。
2.在將資料壓棧時,棧是自頂向下增長的,該記憶體區用於函式的區域性變數提供記憶體。它也支援在呼叫函式時傳遞引數。
3.如果呼叫了巢狀的過程,棧會自上而下增長,並接受新的活動記錄(activation record)來儲存一個過程所需的所有資料。
4.當前執行過程的活動記錄,由標記頂部位置的幀指標(frame point)和標記底部位置的棧指標(stack point)定義。
5.在過程執行時,雖然其頂部的限制是固定的,但底部的限制是可以擴充套件的(在需要更多記憶體空間時)。
分析棧幀(分析如下)
上圖第2個棧幀的分析如下:
1、在棧幀頂部是返回地址,以及儲存的舊的幀指標。返回地址指定了當前過程結束時程式碼的控制流轉向的記憶體地址,而儲存的舊的幀指標則是前一個活動記錄的幀指標。在當前過程結束後,該幀指標的值可用於重建呼叫過程的棧幀,在試圖除錯呼叫棧回溯時,這一點很重要。
2、活動記錄的主要部分是為過程呼叫區域性變數分配的記憶體空間。在C中,這種變數也稱為自動變數(automatic variable)。
3、在函式呼叫時,以引數形式傳遞到函式的值,儲存在棧的底部。
4、所有常見的計算機體系結構都提供了以下兩個棧操作指令:
push指令將一個值放置在棧上,並將棧指標esp減去該值所佔用的記憶體位元組數。棧的末端下移到更低的地址;
pop指令從棧中彈出一個值,並相應增加棧指標esp的值,也就是說,棧的末端上移了。
5、一般體系結構另外提供兩個指令,用於呼叫和退出函式(自動返回到呼叫過程),它們也會自動操作棧:
call指令將指令指標的當前值壓棧,跳轉到被呼叫函式的起始地址。 call 指令 :在AT&T彙編中,call foo(foo是一個標號)等效於以下彙編指令: pushl %eip ,movl f, %eip ;
return指令從棧上彈出返回地址,並跳轉到該地址。過程的實現必須將rerurn作為最後一條指令,由call放置在棧上的返回地址位於棧的底部(實際上是上一個活動記錄的底部,當前活動記錄的頂部)。 ret指令: 在AT&T彙編中,ret等效於以下彙編指令: popl %eip
過程呼叫兩個組成步驟
1、在棧中建立引數列表。傳遞到被呼叫函式的第一個引數最後入棧(從右到左)。這使得C中可以傳遞可變數目的引數,然後將其從棧上逐一彈出(pop)。
2、呼叫call,這將指令指標的當前值(call之後的下一條指令)壓棧,程式碼的控制流轉向被呼叫的函式。被呼叫的過程負責管理幀指標ebp,需要執行下列步驟:
前一個幀指標壓棧,因而棧指標下移。
將棧指標的當前值copy給幀指標,標記當前執行函式的棧區的起始位置。
執行當前函式的程式碼。
在函式結束時,儲存的舊幀指標位於棧的底部。其值從棧彈出到幀指標暫存器(ebp),使之指向前一個函式的棧區起始位置。現在,對當前函式執行call指令時壓棧的返回地址位於棧低。
呼叫return,將返回地址從棧彈出。cpu轉移到返回地址,程式碼的控制流也返回到呼叫函式。
具體C 語言例子分析
初看起來,這種方法似乎有些混亂,因此,我們先看一個簡單的C語言例子:
在IA-32系統上,彙編程式碼本身必須是AT&T表示法給出。
AT&T彙編語法總結為以下5條規則,就足夠了。
1.暫存器通過在名稱前加百分號(%)字首引用。example:為使用eax暫存器,彙編程式碼中將使用%eax。(如果在C中內聯彙編的話,C程式碼必須指定兩個百分號,才能在轉給彙編器的輸出中形成一個百分號)。
2.源暫存器總是在目的暫存器之前指定。 example,在mov語句中,這意味著 mov a,b 將 暫存器a中的值 內容copy到暫存器b中。
3.運算元的長度由彙編語句的字尾指定。b代步byte,w代表word,l代表long。在IA-32上,將一個長整型從eax暫存器移動到ebx暫存器中,需要指定movl %eax,%ebx。
4.間接記憶體引用(指標反引用)需要將暫存器包含在括號中,example:movl(%eax),%ebx 將暫存器eax的值指向的記憶體地址中的長整型copy到ebx暫存器中。
5.offset(register)指定暫存器值與一個偏移量聯用,將偏移量加到暫存器的實際值上。example: 8(%eax)指定將eax+8用作一個運算元。該表示法主要用於記憶體訪問,例如指定與棧指標或幀指標的偏移量,以訪問某些區域性變數。
我們來分析一下 main.s 彙編程式碼:
1.從main 主函式開始分析. 在IA-32系統中,ebp暫存器用於幀指標(棧頂),pushl %ebp 將該ebp暫存器中的值壓入系統棧上最低位置,這導致棧頂指標向下移動4byte,這是因為IA-32系統上需要4byte來表示一個指標(pushl中的字尾l,在AT&T彙編中表示一個long型)。
2.第3行,movl %esp, %ebp 將esp(棧指標)暫存器 的值 copy到ebp(幀指標)暫存器中;把當前的棧指標作為本函式的幀指標。
3.第4行,subl $24,%esp 從棧指標減去0x18 byte,使得棧指標下移,將棧的空間增大了0x18=24byte;
調整棧指標,為區域性變數保留空間。區域性變數必須放置在棧上,在C程式碼中,a與b兩個區域性變數,兩者都是整型變數,在記憶體中都需要4個byte。
因為棧的前4個byte儲存了 幀指標的舊值(上一個活動記錄),編譯器將接下來的兩個 4byte記憶體分配給了這兩個區域性變數。
ebp - 0xC 存著區域性變數a的值 3 ; ebp - 0x8 存著區域性變數b的值 4 (這裡可以看到引數是從右到左 壓入棧的)。
4.第5行 ,第6行 movl $0x3, -0xC(%ebp) movl $0x4, -0x8(%ebp) : 為了向分配的記憶體空間設定初始值(對應C中 區域性變數的初始化),編譯器使用了處理器的指標反引用選項。 這兩天指令通知編譯器,引用“幀指標減12”得到的值 在記憶體中指向的位置。使用mov指令將值3 寫入該位置。
編譯器接下來用同樣的方法處理第2個區域性變數,其在棧的位置稍低,ebp - 0x8 (ebp - 8byte) 位置 ,值為4。
5.第7行,第8行設定第2個引數(b),第9行,第10行負責設定第1個引數(a)。 movl -8(%ebp), %eax ; movl %eax, 4(%esp) ; movl -12(%ebp), %eax; movl %eax, (%esp)
區域性變數a和b必須用作即將呼叫的add過程呼叫的引數。編譯器通過將適當的值放置在棧的末端來建立引數列表。
如前所述,第一個引數在最低部。棧指標用於查詢棧的末尾。
記憶體中對應的位置通過指標反引用確定。將棧上的兩個區域性變數的值分別讀入eax暫存器,然後將eax的值寫入引數列表中對應的位置。(一般情況)
6.上圖描述了 add()函式呼叫前後,棧的狀態。現在可以使用call 指令呼叫add()函式。call指令 將eip(指令指標暫存器)壓入棧,程式碼控制流在add例程的開始處恢復執行。
根據呼叫約定,例程首先將此前的幀指標(ebp)壓入棧,並將棧指標(esp)賦值給 幀指標(ebp)。
過程的引數可以根據幀指標(ebp)查詢。編譯器知道引數就在呼叫函式的活動記錄末尾,而在當前活動記錄開始處又儲存了兩個4byte的值(返回地址,舊幀指標)。因此引數可以通過反引用ebp+8和ebp+12訪問。
add 指令用於 加法,而eax暫存器用作工作空間。結果值就儲存在該暫存器中,使它可以傳遞給呼叫函式(這裡是main())。
為了返回到呼叫函式,需要執行以下兩個操作: 使用pop將儲存的幀指標(ebp)從棧彈出到ebp暫存器。棧幀的頂端重新恢復到main()的設定;ret將返回地址從棧彈出到 eip(指令指標)暫存器,控制流轉向該地址。
7.因為main()中還使用了另一個區域性變數(ret)來儲存add()函式的返回值,返回後需要將eax暫存器的值 copy 到ret在棧上的位置。
總結
關於AT&T彙編
enter指令
在AT&T彙編中,enter等效於以下彙編指令:
pushl %ebp # 將%ebp壓棧
movl %esp %ebp # 將%esp儲存到%ebp, 這兩步是函式的標準開頭
leave指令
在AT&T彙編中,leave等效於以下彙編指令:
movl %ebp, %esp
popl %ebp
call指令
在AT&T彙編中,call foo(foo是一個標號)等效於以下彙編指令:
pushl %eip
movl f, %eip
ret指令
在AT&T彙編中,ret等效於以下彙編指令:
popl %eip
(個人理解)彙編可以用一句話概括:彙編就是在(暫存器和暫存器)或 (暫存器和記憶體)之間來回move 資料;就是指:資料在記憶體和暫存器間來回流動,流動的越頻繁就代表程式越複雜,比如office這樣的大型軟體。
從C語言層面分析:
EBP-xx 一般 是區域性變數
EBP+xx 一般都是引數
EBP+4 返回地址 ,制高點, 很多攻擊都是攻擊這裡, 防毒軟體,這裡是重點會掃描。
C函式堆疊中分配的空間,並不會清零,所以在寫C程式碼的時候,區域性變數一定要初始化賦值。
引數的傳遞形式、傳遞順序已經棧平衡並不是固定的(不同的函式呼叫約定)。
關於 暫存器 與記憶體的區別:
暫存器位於cpu內部,執行速度快,但比較貴。
記憶體速度相對較慢,成本低,所以容量能做很大。
暫存器和記憶體沒有本質區別,都是用於儲存資料的容器,都是定寬的。
暫存器常用的8個通用暫存器 :EAX,ECX,EDX,EBX, ESP, EBP, ESI, EDI.
計算機中的幾個常用計量單位:BYTE, WORD, DWORD :BYTE(位元組) = 8bit ; WORD (字 ) = 16bit ; DWORD (雙字)=32bit;
記憶體的數量特別龐大,無法每個記憶體單元都命名一個名字,所以用編號來替代。
我們稱計算機CPU是32bit或者64bit,有很多書上說之所以叫32bit計算機是因為暫存器的寬度是32bit,這是不準確的,因為還有很多暫存器是大於32bit的。
相關文章
- 高階語言反彙編程式的函式呼叫過程 (轉)函式
- C語言的本質(32)——C語言與彙編之C語言內聯彙編C語言
- C語言編譯全過程C語言編譯
- gcc 從語言編譯全過程 預處理---->編譯---->彙編----->連結GC編譯
- 3- C語言編譯過程C語言編譯
- C語言編譯過程簡介C語言編譯
- 深入理解 C 語言的函式呼叫過程函式
- 深入理解C語言的函式呼叫過程C語言函式
- 【開發語言】PHP、Java、C語言的編譯執行過程PHPJavaC語言編譯
- C語言的編譯連結執行過程C語言編譯
- 組合語言-019(彙編程式與c\c++相互呼叫)組合語言C++
- C 語言宏 + 內聯彙編實現 MIPS 系統呼叫
- C語言編譯和連結過程簡介C語言編譯
- go語言編譯過程概述Go編譯
- C語言程式碼區錯誤以及編譯過程C語言編譯
- java開發C語言編譯器:為C語言提供API呼叫JavaC語言編譯API
- c語言if語句是如何變成彙編程式碼的?C語言
- 一段C語言和彙編的對應分析,揭示函式呼叫的本質C語言函式
- 一種面嚮物件語言的方法呼叫過程。物件
- 轉享:從Java或C++角度看 Objective-C 語言JavaC++Object
- go語言與c語言的相互呼叫GoC語言
- 09. C語言內嵌彙編程式碼C語言
- C與彙編程式的約定呼叫
- 教你在 C 語言上編寫自己的協程
- C語言_來了解一下GCC編譯器編譯C可執行指令碼的過程C語言GC編譯指令碼
- C語言轉寫成MIPS指令集彙編以及MIPS指令集彙編中函式呼叫時棧的變化C語言函式
- 從語言學角度看詞嵌入模型模型
- 從元語言角度評價華為倉頡
- C語言知識彙總 | 00-C語言知識彙總目錄C語言
- CMM編譯器和C編譯器過程呼叫實現的比較編譯
- c / c + + 呼叫mysql儲存過程MySql儲存過程
- c語言指標彙總C語言指標
- 程式語言的演化過程
- 從彙編角度來理解linux下多層函式呼叫堆疊執行狀態Linux函式
- 從巨集觀的角度看 Gradle 的工作過程Gradle
- C語言函式呼叫棧C語言函式
- C語言呼叫 Java(Linux)C語言JavaLinux
- JAVA JNI 呼叫C語言 DemoJavaC語言