2.12 彙編眼中的函數
為什麼學習本節內容?因為我們要深刻理解彙編函數的形式以及函數調用的過程。
本節必須掌握的知識點:
掌握調用函數的兩種形式
熟練運用函數調用
我們的程序是由成千上萬行指令組合起來的,之前介紹了簡單的指令,並沒有把它們組合起來,接下來介紹把我們前面幾節中介紹的彙編指令組合在一起實現具體的功能。在介紹實現具體功能之前先介紹一個知識點。這個知識點是函數。
2.12.1【函數】
相信大家對函數這個名詞不會太陌生,彙編的世界中的函數是一系列指令的集合,為了完成某個重複使用的特定功能。我們用例子分析彙編眼中的函數是什麼樣子的。
例1:向寄存器中賦值
假如我們需要將1存到四個寄存器中,需要編寫下面四行代碼:
MOV EAX,1
MOV ECX,1
MOV EDX,1
MOV EBX,1
我們把以上四條指令看成一個整體,從某個角度來說,這四條指令就是一個函數,為了實現向寄存器中賦值的功能。
知道了函數是什麼樣子,具體怎麼執行一個函數呢?執行函數有2種方式,執行函數也叫函數調用。
執行方式:
1、用JMP指令執行函數
2、用CALL指令執行函數
【JMP指令執行函數】
JMP指令是無條件跳轉指令。
第一步:在DTDebug.exe軟件中打開飛鴿軟件,如圖2-12-1所示。
第二步:輸入例1中的函數,如圖2-12-1所示。
看圖2-12-2中,我們已經把函數寫入到彙編窗口中,當前函數體開始的地址為0x77068E51,當前EIP寄存器存儲的數據為0x77068E34,我們知道EIP寄存器是存儲下一行將要執行指令的地址,我們怎麼能讓它直接運行到函數地址0x77068E51呢,那就是第三步要做的事情了。
第三步:JMP指令是無條件跳轉指令,那麼我們只需要用JMP指令跳到函數體開始的內存地址就可以了,輸入JMP 0x77068E51,如圖2-12-3所示。
第四步:按F8執行並觀察數據變化,如圖2-12-4所示。
看圖2-12-4中所示,黑色定位光標顯示在0x77068E51,EIP寄存器存儲的數據由0x77068E34變為了0x77068E51說明已經跳轉成功。
第五步:按F8執行並觀察數據變化,看是否實現了函數想要做的事情,如圖2-12-5所示。
圖2-15-5中,已經實現了把0x00000001存入了相對應的寄存器中。
以上是JMP指令調用函數的步驟,對JMP指令調用函數的總結:我們調用函數時只要JMP函數體的地址,然後跳轉到函數體開始的位置就可以了。
【CALL指令執行函數】
第一步:在DTDebug.exe軟件中打開飛鴿軟件,如圖2-12-6所示
第二步:輸入例1中的函數,如圖2-12-7所示。
看圖2-12-7中,彙編窗口中的黑色定位光標定位在0x77068E34,函數體開始的地址為0x77068E51,EIP存儲的數據為0x77068E34。
第三步:用CALL指令調用函數,輸入CALL 0x77068E51,當前彙編窗口中的黑色定位在0x77068E34,如圖2-12-8所示。
第四步:按F7執行並觀察數據變化。
看圖2-12-9中所示,按F7執行後,彙編窗口中的黑色定位光標定位在了0x77068E51,也是函數體開始的內存地址,EIP存儲的數據為0x77068E51,說明已經成功跳到函數體開始的內存地址,並將自己下一行指令的內存地址壓入堆棧中。
第五步:按F8執行完函數,如圖2-12-10所示。
看圖2-12-10中,已經實現了把0x00000001存入了相對應的寄存器中。
如果我想接著從CALL指令的下一行開始執行怎麼辦哪?
可以用RETN指令,在執行CALL指令時已經將CALL指令下一行指令地址壓入到了堆棧中。
第一步:輸入RETN指令,如圖2-12-11。
第二步:按F8執行並觀察數據變化,如圖2-12-12所示。
看圖2-12-12,執行完RETN指令,可以總結CALL指令一共做了兩件事:第一件事是修改EIP的值,第二件事是把它下一行的地址壓入堆棧中。CALL始終把它下一行地址壓入堆棧,這是一個相對值,保證了函數的可複用性。可以這樣說,CALL就是為函數調用所生的。
為了更好的讓大家理解函數體現是實現具體的功能,我們來編寫一個例子
例:編寫一個函數,實現任意兩個整數相加。
實現:
第一步:在DTDebug.exe軟件中打開飛鴿軟件,如圖2-12-13所示。
第二步:因為要實現任意2個整數相加,這裡需要用到2個寄存器,假設這兩個整數分別為1,2,這裡1和2分別代表參數,則輸入
MOV ECX 1
MOV EDX 2
這裡是函數體,實現任意2個整數相加的函數
ADD ECX,EDX (ECX存儲的數據+EDX存儲的數據的值保存到ECX中)
MOV EAX,ECX (所得到的結果放到EAX中)
如圖2-12-14所示
第三步:輸入CALL指令,CALL 0x77068E51,如圖2-12-15所示。
第四步:輸入RETN指令,如圖2-12-16。
以上是實現任意兩個數的彙編函數編寫,接下來我們驗證是否能實現該功能。
驗證:
第一步:按兩次F8,將黑色光標定位到CALL指令那一行,當前ESP存儲的數據為0x0019FFF0如圖2-12-17所示。
看圖2-12-17中,按兩次F8執行完
MOV ECX,1
MOV EDX,2
把1移動到ECX寄存器的過程我們叫傳遞參數,把2移動到EDX寄存器的過程我們也是傳遞參數,
第二步:按F7執行,並觀察數據變化,如圖2-12-18所示。
圖2-12-18彙編窗口中,黑色定位光標顯示在了0x77068E51,該內存地址也是函數體開始的地方,且將自己下一行地址壓入到堆棧中,ESP存儲的數據由0x0019FFF0變為了0x0019FFEC,棧頂為0x0019FFEC存儲的數據正是CALL指令下一行地址0x77068E43。
第三步:按F8兩次運行到RETN指令處並觀察數據變化,如圖2-12-19。
圖2-12-19彙編窗口中,黑色定位光標已經顯示在了RETN指令那一行,寄存器窗口中,EAX也保存了ECX存儲的數據加上EDX存儲的數據,運行到這裡,說明我們編寫的程序正確實現了兩個數相加的功能。
第四步:按F8執行並觀察數據變化,如圖2-12-20所示。
看圖2-12-20彙編窗口中,F8執行完後,返回到了CALL指令下一行。
總結:在實現任意兩個數相加函數時遇到了幾個知識點,第一個是參數,第二個是調用函數,第三個是參數傳遞,第四個是返回值。
參數:看實現第二步中,
MOV ECX,1
MOV EDX,2
1和2就是例題的參數。
參數傳遞:看驗證第一步中,執行完。
MOV ECX,1
MOV EDX,2
把1保存到ECX的過程叫參數傳遞,把2保存到EDX的過程也是參數傳遞。
調用函數:看驗證第一步、第二步中,先將黑色定位光標定位到CALL指令那一行,CALL調用函數體開始的內存地址0x77068E51,按F7執行完後,執行到函數體開始的地方,整個過程叫函數調用。
返回值: 看驗證第二步、第三步中,執行完。
ADD ECX,EDX
MOV EAX,ECX
把EDX存儲的數據與ECX存儲的數據相加的結果保存到ECX中,接著把ECX中保存的結果移動到EAX中。一般情況下EAX用來存儲返回值的。
下一節介紹堆棧傳參。
練習:
1、本節知識介紹了CALL指令能重複調用函數,那JMP指令可以重複調用函數嗎?
2、編寫一個函數,能夠實現任意三個整數的加法運算。
3、如果要傳遞的參數比較多,比如10個,該如何實現呢?
---摘自本人拙著:編程達人內部教材《彙編、C語言基礎教程》
閱讀更多 愛達人編程達人 的文章