【软件开发底层知识修炼】二十四 ABI之函数调用约定
- 上一篇文章學習了Linux環(huán)境下的函數(shù)棧幀的形成與摧毀。點擊鏈接查看相關(guān)文章:軟件開發(fā)底層知識修煉】二十三 ABI-應用程序二進制接口三之深入理解函數(shù)棧幀的形成與摧毀
- 本篇文章繼續(xù)學習ABI接口相關(guān)的內(nèi)容。函數(shù)調(diào)用約定
文章目錄
- 1 函數(shù)參數(shù)如何入棧,返回值在哪里
- 2 函數(shù)調(diào)用約定的編程實驗
- 2.1 使用gdb調(diào)試代碼證明eax存的值是函數(shù)返回值
- 2 .2 查看程序的反匯編文件來說明調(diào)用約定的不同
- 3 總結(jié)
1 函數(shù)參數(shù)如何入棧,返回值在哪里
前面學過的文章中,已經(jīng)深入的了解了函數(shù)調(diào)用過程中函數(shù)的棧幀的形成與摧毀。在發(fā)生函數(shù)調(diào)用時,首先入棧的是函數(shù)的參數(shù)。但是我們知道一般函數(shù)的參數(shù)都是會比較多。在參數(shù)比較多的時候,函數(shù)的參數(shù)是以什么樣的順序入棧的呢?函數(shù)返回時是誰來將參數(shù)彈出棧呢?
并且,在函數(shù)執(zhí)行完之后,返回的時候,返回值在哪里?通過什么方式將返回值傳遞給調(diào)用者?
首先給出在C語言中默認的調(diào)用約定(cdecl):
- 調(diào)用函數(shù)時,參數(shù)從右往左入棧
- 函數(shù)返回時,調(diào)用者負責將參數(shù)彈出棧。這里說是調(diào)用者,實際上是編譯器在編譯的時候為調(diào)用者添加了相應的指令
- 函數(shù)返回值保存在eax寄存器中。前提是函數(shù)返回值是基礎(chǔ)數(shù)據(jù)類型,如果是結(jié)構(gòu)體這種的類型,就另說。
上面的調(diào)用約定是C語言默認的函數(shù)調(diào)用約定。我們平常所熟知的也就是上面的默認的調(diào)用約定。當然,或許大多數(shù)人是不知道的吧!
- 下面的表格給出了其他各種調(diào)用約定
注意:以上三個調(diào)用約定,只需要注意__thiscall__約定。它一般是C++中的成員函數(shù)的約定,C++成員函數(shù)又由隱藏的this指針。如果函數(shù)的參數(shù)是確定個數(shù)的,則this存放于ECX寄存器,函數(shù)自身清理棧中的參數(shù)。如果成員函數(shù)是可變參數(shù),那么久相當于前面的__cdecl__調(diào)用約定
在上面的函數(shù)調(diào)用約定中,還有幾點需要注意:
- 只有使用的__cdecl__約定的函數(shù),才支持可變參數(shù)。使用其他調(diào)用約定的,不支持可變參數(shù)
- 在C++中,當類的成員函數(shù)為可變參數(shù)時,調(diào)用約定自動變?yōu)開_cedcl__
- 調(diào)用約定定義義了函數(shù)被編譯后,對應的在符號表中的最終的符號名稱的樣子
2 函數(shù)調(diào)用約定的編程實驗
2.1 使用gdb調(diào)試代碼證明eax存的值是函數(shù)返回值
convention.c
#include <stdio.h>int test(int a, int b, int c) //默認的__cdecl__調(diào)用約定 {return a + b + c; } //__cdecl__調(diào)用約定 void __attribute__((__cdecl__)) func_1(int i) { } //__stdcall__調(diào)用約定 void __attribute__((__stdcall__)) func_2(int i) { } //__fastcall__調(diào)用約定 void __attribute__((__fastcall__)) func_3(int i) { }int main() {int r = test(1, 2, 3);printf("r = %d\n", r);return 0; }- 上述代碼很簡單,分別將幾個函數(shù)強制設定為相應的調(diào)用約定,并可以通過查看變量r的內(nèi)容與寄存器eax的內(nèi)容來證明函數(shù)返回值是存儲在eax寄存器中的。下面就來通過運行該程序,并使用GDB進行查看相應的內(nèi)存的值。
編譯運行程序,并且記性gdb調(diào)試
- gcc -g convention.c -o test.out
- gdb test.out
- start
- break convention.c:6
- continue
- info registers
- continue
因為gdb在前面的文章中已經(jīng)使用過很多次,并且使用了各種動態(tài)圖展示gdb的使用。這里就直接給出相應的命令了。
上述第一個contiue后,運行到convention.c的第6行就停止了。此時再info registers。可以看到如下信息:
可以看到在函數(shù)test即將返回時。寄存器eax存的值為6 。 這正好等于test函數(shù)的返回值這不是巧合。因為eax寄存器就是用來保存函數(shù)的返回值的。當然,后面我們還可以通過查看反匯編文件來分析。
最后的執(zhí)行continue命令導致整個程序的運行結(jié)束
2 .2 查看程序的反匯編文件來說明調(diào)用約定的不同
上面的gdb調(diào)試方法沒有證明出函數(shù)調(diào)用約定的不同,下面我么使用查看可執(zhí)行代碼的反匯編文件進行說明。
使用命令
- objdump -S test.c > test.s
生成可執(zhí)行文件的反匯編代碼test.s。
在該文件中可以找到
- func_1函數(shù)的反匯編代碼
- func_2的反匯編代碼
- func_3的反匯編代碼
- 從以上三個函數(shù)的反匯編代碼可以看出,它們的前言和后序是由區(qū)別的,這是因為它們分別使用了不同的函數(shù)調(diào)用約定。使用了不同的 函數(shù)調(diào)用預定,匯編層面肯定是不一樣的。
3 總結(jié)
學會了以下內(nèi)容
- 函數(shù)返回值如果是整形的,通過eax寄存器傳遞返回值給調(diào)用者(下一篇文章講解返回值是結(jié)構(gòu)體的類型個時是如何傳遞給調(diào)用者的)
- 學會三種不同的調(diào)用約定對應的區(qū)別(__cdecl__調(diào)用約定,__stdcall__調(diào)用約定,__fastcall__調(diào)用約定)
總結(jié)
以上是生活随笔為你收集整理的【软件开发底层知识修炼】二十四 ABI之函数调用约定的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java常用设计模式(面试常考)
- 下一篇: knx智能照明控制系统电路图_智能照明控