日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

【软件开发底层知识修炼】二十四 ABI之函数调用约定

發(fā)布時間:2023/12/10 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【软件开发底层知识修炼】二十四 ABI之函数调用约定 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
  • 上一篇文章學習了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ù)的反匯編代碼
void __attribute__((__cdecl__)) func_1(int i) {80483d5: 55 push %ebp80483d6: 89 e5 mov %esp,%ebp }80483d8: 5d pop %ebp80483d9: c3 ret
  • func_2的反匯編代碼
080483da <func_2>:void __attribute__((__stdcall__)) func_2(int i) {80483da: 55 push %ebp80483db: 89 e5 mov %esp,%ebp }80483dd: 5d pop %ebp80483de: c2 04 00 ret $0x4
  • func_3的反匯編代碼
080483e1 <func_3>:void __attribute__((__fastcall__)) func_3(int i) {80483e1: 55 push %ebp80483e2: 89 e5 mov %esp,%ebp80483e4: 83 ec 04 sub $0x4,%esp80483e7: 89 4d fc mov %ecx,-0x4(%ebp) }80483ea: c9 leave 80483eb: c3 ret
  • 從以上三個函數(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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。