1. 程式人生 > >函式呼叫的彙編實現淺析

函式呼叫的彙編實現淺析

一, 引子
       對於彙編的理解是思維的一個跳變, 指令語法的理解對語義的理解似乎毫無
幫助, 就好象我對設計模式的模糊理解使我看程式碼的時候總是困惑於其如此實現
的原因. 這也使我想起看英文書的時候, 經常是每一個單詞都認識, 但組合在一起
卻不知所云. 其理相通罷.
      區域性變數的儲存及子過程的呼叫均是用棧實現的,  這個資料結構被證明是最
有效的, 棧的實現如此重要以至CPU對它提供了內建的支援,  這裡提到的CPU是IA32,
實際上它包括兩種微架構, 但這不是我現在關心的, 因為這裡提到的約定對於這兩種微
架構來說是共同的. 幾個重要的約定是:
      地址編號從下往上增長, 位元位從右往左增長. 如果超過一個位元組的資料, 低
位元組放在低地址中, 此所謂 "little endian". 如:
31            23         15            7                0
+-----------+---------+----------+-------------+ 0Ch
|               |            |             |                 |
+-----------+---------+----------+-------------+ 08h
|               |            |             |                 |
+-----------+---------+----------+-------------+ 04h
|               |            |    12h    |      34h     |
+-----------+---------+----------+-------------+ 00h
      如果 00h 處存放的是一個字的話, 那麼它的值為 1234h. 初看起來, 這樣的表達方式
讓人不是太習慣, 雖然我們通常將地址想象成一個一維的序列, 但我們必須要理解這樣
的方式, 因為在CPU眼中, 資料是按照這種方式獲取的,  這有助於我們對資料對齊的理解.
 
二, 程式碼

     一個很簡單但比較典型的程式, 包含函式呼叫, 賦值, 運算, 返回.
 int add (int i1, int i2) {
  int val1 = i1;
  int val2 = i2;

  static int val3 = 0x20;
  ++val3;

  return val1 + val2 + val3;
 }

 int main () {
  int ret = 0x10;
  ret = add (0x1, 0x2);

  return 0;
 }
 

//////////////////////////////////////////////////////////////////////////////////////


三, 指令解析
    不同的編譯器, 程式碼會略有不同, 但主線是不變的, 這沒有唯一答案, 我需要了解
的只是它的思路.
 1, esp和ebp


      SS擁有當前程序執行堆疊的選擇符(selector), 而 esp 則永遠指向棧的頂端,
 請注意, 棧的頂端是棧中地址最小的地方, 記住地址是從下往上增長的.
  我們已有基址和指向棧中元素的指標, 為何還需要ebp? 設想 esp 總是在
 不斷變化, 我如何用正確的偏移引用棧中的區域性變數和引數呢? 於是ebp
 被用來作為當前活動記錄 (也叫棧幀) 的基指標, 它永遠指向函式返回地址
 即呼叫者的下一條指令的地址, 和被呼叫函式中第一個區域性變數的中間, 當然它
 是一個雙字 (32位). 
 
2, call
      call 指令呼叫一個過程, 但它有一個小動作, 在引數入棧以後, 被呼叫函式
 執行 之前, 它會將當前函式的下一條指令地址, 即EIP的值壓入. 

 3, 呼叫約定 ( calling convention)

      在 c/c++ 中, 函式的預設呼叫約定為 cdecl, 它約定引數從右到左入棧, 由呼叫
 者清理堆疊, 所謂清理, 即調整ESP的值, 使得原來的區域性資料不再屬於棧. 當然
 由此也可以理解 stdcall 的工作方式, 不過換成被調函式自己清理罷了. 
 
4, retn
     這稱之為 "near return", 在執行這個操作的時候, ESP已經指向函式的返回地址,
 ret 指令將彈出這個地址, 並將它存入 EIP, "near return" 意指 CS 的值沒有變化, 而
 "far return" 將修改 CS 的值. 
 
5, 靜態變數
     就象我早已經知道的, 靜態變數是在一個全域性儲存區, 不過那只是在概念中
 遠沒有看見程式碼這麼直觀, 在下面的程式碼中就可以看到, 這個變數並不在棧中出
 現, 事實上, 如果我不對它進行一下操作, 甚至感覺不到它的存在. 這就是為什麼
 靜態變數總能保持它的值的祕密.

四, 程式碼解析
 _main:
                push    ebp                               ;儲存原來的ebp
                mov     ebp, esp                        ;esp, ebp指向棧頂
                push    ecx                                ;int ret 只有一個區域性變數, 存在ecx中.
                mov     [ebp - 4], 10h                 ;ret = 0x10 賦值
                push    2                                    ;要呼叫add了, 先將引數壓棧
                push    1                                    ;
                call     add                                  ;呼叫, 先將EIP壓棧
                add     esp, 8                              ;清理堆疊, 兩個引數, 共8個位元組
                mov     [ebp - 4], eax                  ;返回值在eax中, 將它賦給 ret
                xor     eax, eax                           ;將 eax 清 0, main返回值是0
                mov     esp, ebp                         ;將main的區域性資料彈出
                pop     ebp                                 ;恢復ebp的值, 同時esp正好指向main的返回地址
                retn                                           ;將返回地址存入EIP, 轉移流程.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
 add:
                 push    ebp                                ;這兩行程式碼同main中的相同
                 mov     ebp, esp                         ;
                 sub     esp, 8                              ;有兩個區域性變數, 留出8個位元組空間
                 mov     eax, [ebp+8]                   ;用eax中轉, 將 i1 的值賦給val1
                 mov     [ebp-8], eax                    ;  val1 = i1;
                 mov     ecx, [ebp+0Ch]                ;用ecx中轉, 將 i2 的值賦給val2
                 mov     [ebp-4], ecx                    ;  val2 = i2;
                 mov     edx, dword_407030         ; static int val3 靜態變數並沒有被壓入棧
                 add     edx, 1                              ;
                 mov     dword_407030, edx          ; val3 = val3 +1;
                 mov     eax, [ebp-8]                     ;
                 add     eax, [ebp-4]                      ;
                 add     eax, dword_407030            ; val1 + val2 + val3 返回值放在在eax中
                 mov     esp, ebp                           ;丟棄區域性資料
                 pop     ebp                                  ;這兩行程式碼與main中的相同
                 retn                                            ;

 main 的 stack frame:
 +-------------------+<--------------high address
 |main返回地址    |
 +-------------------+
 |      ebp             |<-----------------ebp for main
 +-------------------+
 |      int ret          |         ebp-4
 +-------------------+
 |      int i2           |          ebp+0Ch
 +-------------------+
 |      int i1           |          ebp+8
 +-------------------+
 |  add返回地址     |
 +-------------------+
 |       ebp            |<------------------ebp for add
 +-------------------+
 |      int val2        |          ebp-4
 +-------------------+
 |      int val1        |          ebp-8
 +-------------------+
五, 尾聲
      順便提一下的是在 stdcall 呼叫約定中, 被呼叫函式, 如此處的add, 返回語句變為 "ret 8" ,
它在彈出返回地址後, 自己將ESP的指加了8個位元組, 此所謂被呼叫者清理堆疊.
 雖然現實世界遠比此複雜, 但這是一個好的開始, 很多看起來高不可攀的東西, 其實
對於我們來說, 也許就是差一把進入大門的鑰匙.

 

相關推薦

函式呼叫彙編實現淺析

一, 引子       對於彙編的理解是思維的一個跳變, 指令語法的理解對語義的理解似乎毫無幫助, 就好象我對設計模式的模糊理解使我看程式碼的時候總是困惑於其如此實現的原因. 這也使我想起看英文書的時候, 經常是每一個單詞都認識, 但組合在一起卻不知所云. 其理相通罷.   

一段C語言和彙編的對應分析,揭示函式呼叫的本質

一段C語言和彙編的對應分析,揭示函式呼叫的本質 2018年09月30日 13:32:19 sdulibh 閱讀數:17 本文作者周平,原創作品轉載請註明出處 首先對會涉及到的一些CPU暫存器和彙編的基礎知識羅列一下:   16位、32位、64

js將字串作為函式呼叫實現input文字框等form表單元素回車鍵統一事件響應

  通過給文字框<input enterKey=“fnName” />設定enterKey=“fnName”,頁面載入完後會自動繫結input的keydown事件,捕捉到回車鍵則呼叫fnName函式,如select等其它form元素也可以。要實現form表單元素回車鍵統一事件響應

memmove函式彙編實現

參考memmove原始碼 void* memmove(void* dst,const void* src,size_t count) { void* ret = dst; //dst <= src表示,如果dst在src的前面,從前往後複製不會覆蓋src中還沒有複

不用 eval 用查詢window物件屬性方式實現字串函式呼叫

  在上文《js將字串作為函式名呼叫,實現input文字框等form表單元素回車鍵統一事件響應》中提到,因為eval()的安全性問題,建議不使用eval(),而使用其它更安全的方式實現。那麼eval()到底有哪細不足,應該如何更安全地實現? 1、eval()是一個函式,看起來更像運算子1

獻給彙編初學者-函式呼叫堆疊變化分析

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

VBS 指令碼通過條件程式實現全域性函式呼叫

效果動畫演示   步驟一:變數字典建立 步驟二:新建“視窗0”畫面 1、變數==>動畫(文字內容) 關聯 “變數” 2、按鈕“呼叫函式”  事件  “左鍵單擊”事件 Var.中間變數= Not Var.中間變

函式呼叫實現過程詳解(棧空間解析)

轉自:函式呼叫棧 剖析+圖解   棧: 在函式呼叫時,第一個進棧的是主函式中函式呼叫後的下一條指令(函式呼叫語句的下一條可執行語句)的地址,然後是函式的各個引數,在大多數的C編譯器中,引數是由右往左入棧的,然後是函式中的區域性變數。注意靜態變數是不入棧的。 當本次函式

C語言彙編-函式呼叫堆疊的過程

本篇來分析函式呼叫的過程:通過下面一個簡單的例子來進入話題:#include<stdio.h>int sum(int a,int b){ int tmp=0; tmp=a+b; return tmp;}int main(){ int a=10; int b=20;

C語言彙編-函式呼叫

函式呼叫大家都不陌生,呼叫者向被呼叫者傳遞一些引數,然後執行被呼叫者的程式碼,最後被呼叫者向呼叫者返回結果,還有大家比較熟悉的一句話,就是函式呼叫是在棧上發生的,那麼在計算機內部到底是如何實現的呢? 對於程式,編譯器會對其分配一段記憶體,在邏輯上可以分為程式碼段,資料段,堆,棧 程式碼段:儲存程式文字,指令

彙編角度檢視C語言函式呼叫約定【非常有用】

轉自:https://blog.csdn.net/Holmofy/article/details/76094986   為了防止出現不必要的程式碼影響組合語言的檢視,所以程式中不使用任何庫函式,以保持彙編程式碼的簡潔。 這裡所使用的彙編是VC的MASM。 預設函式呼叫方式_

C語言函式呼叫過程的彙編分析

轉自:   http://www.cnblogs.com/xiaojianliu/articles/8733560.html   下面一段C程式: int bar(int c, int d) { int e = c + d; return e; }

sort函式的用法(C++排序庫函式呼叫)對陣列進行排序,在c++中有庫函式幫我們實現,這們就不需要我們自己來程式設計進行排序了。

對陣列進行排序,在c++中有庫函式幫我們實現,這們就不需要我們自己來程式設計進行排序了。 (一)為什麼要用c++標準庫裡的排序函式 Sort()函式是c++一種排序方法之一,學會了這種方法也打消我學習c++以來使用的氣泡排序和選擇排序所帶來的執行效率不高的問題!因為它使用

C函式呼叫過程及彙編分析

C程式碼: int fun(int para) {     int a=0;     return 0; } void main() {     fun(1); } 彙編程式碼: 1:

MongoDB自動增長id實現、自定義函式呼叫、與Spring整合

昨天同事問實現MongoDB主鍵自動增長有什麼好的辦法,雖然喜歡MongoDB客戶端驅動程式自動生成的id,不過還是來測試了一下,僅僅是測試哦 廢話少說 1、建立專案,新增依賴 <dependencies> <dependen

個人js學習例項-點選按鈕實現全選與反選,及封裝函式呼叫前後

原始: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="wid

簡單的api實現以及動態函式呼叫

實現一個簡單的api功能,環境python2.7 請求方法:curl http://ip:8000/?name={api中的方法名}|python -m json.tool 只需編寫api的方法即可 #!/usr/bin/env python #coding:utf-8 import

C#呼叫C帶回調函式方法的實現

1、C語言定義個回撥函式的註冊 typedef void(__stdcall *test_callback)(int* array, int size); __declspec(dllexport)

Masm(611) 呼叫 第三方c語言函式彙編呼叫第三方c語言函式庫)

一開始的程式碼: void print(){ printf("hello hairi"); } lib1的程式碼 include Lib1.lib data segment ;定義資料段 infon db 0dh,0ah,'Please input a year

淺析C++中的this指標及彙編實現

有下面的一個簡單的類: class CNullPointCall { public: static void Test1(); void Test2(); void Test3(int iTest); void Test4(); priv