函式調用約定

00401036 00401038 00401053

函式調用約定的含義

當一個函式被調用時,函式的參數會被傳遞給被調用的函式和返回值會被返回給調用函式。函式的調用約定就是描述參數是怎么傳遞和由誰平衡堆疊的,當然還有返回值。

函式調用約定的幾種類型

__stdcall__cdecl__fastcall,__thiscall,__nakedcall,__pascal

函式調用約定的主要約束事件

一、參數傳遞順序

1.從右到左依次入棧:__stdcall,__cdecl,__thiscall
2.從左到右依次入棧:__pascal,__fastcall

二、調用堆疊清理

1.調用者清除棧。
2.被調用函式返回後清除棧。

幾種常用函式調用約定的描述

(1)__cdecl調用約定

1、參數是從右向左傳遞的,也是放在堆疊中。
2、堆疊平衡是由調用函式來執行的(在call B,之後會有add esp x,x表示參數的位元組數)。
3、函式的前面會加一個前綴_(_sumExample)
下面來看看具體的反彙編代碼,這是從VC反彙編的代碼截取的一部分代碼。
10: int c = 0;
00401088 C7 45 FC 00 00 00 00 mov dword ptr [EBP-4],0
11: c = sumExample(2, 3);
0040108F 6A 03 push 3
00401091 6A 02 push 2
從上面的兩個push操作我們就可以知道參數是從右向左傳遞的了。另外這裡也回答前面的問題為什麼參數會被擴展為4個位元組,因為堆疊的操作都是對一個字進行操作的,所以參數都是4個位元組的。
00401093 E8 7C FF FF FF call @ILT+15(_Max) (00401014)
這裡就是調用函式操作了。在進行call操作之後,會自動將call的下一條語句作為函式的返回地址保存在棧中,也就是下面的(00401098)。在地址00401014處我們可以看到這樣的一小段代碼
@ILT+0(_Max):
00401005 E9 26 00 00 00 jmp _sumExample (00401030)
這裡就可以知道程式在編譯之後會在函式前面加上前綴_
00401098 83 C4 08 add esp,8
這裡就是平衡堆疊操作了。可以看出是在調用者進行的。
0040109B 89 45 FC mov dword ptr [ebp-4],eax
保存值是由eax暫存器返回的,從這裡就可以看出了。
12:
13: return 0;
0040109E 33 C0 xor eax,eax
14: }
3: int __cdecl sumExample(int a, int b)
4: {
00401030 55 push ebp
00401031 8B EC mov ebp,esp
00401033 83 EC 40 sub esp,40h 為局部變數預留空間
00401036 53 push ebx
00401037 56 push esi
00401038 57 push edi
00401039 8D 7D C0 lea edi,[ebp-40h]
0040103C B9 10 00 00 00 mov ecx,10h
00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401046 F3 AB rep stos dword ptr [edi]
這上面的一段代碼就是函式的開端了。也就是function prolog。通過將一些暫存器來對它們進行保存,也就像中斷髮生後,需要保護現場一樣。
5: return (a + b);
00401048 8B 45 08 mov eax,dword ptr [ebp+8]
0040104B 03 45 0C add eax,dword ptr [ebp+0Ch]
6: }
0040104E 5F pop edi
0040104F 5E pop esi
00401050 5B pop ebx
00401051 8B E5 mov esp,ebp
00401053 5D pop ebp
00401054 C3 ret
這裡就是函式收尾,也就是function epilog
經過上面的分析,相信你對__cdecl調用約定有了比較清晰的認識了。但是這裡我們應該想想為什麼不在被調函式內進行堆疊平衡呢?在這裡我們應該要考慮類似於像scanf和printf這樣的函式,這裡我們應該明白這兩個函式的參數都是可變的,如果參數不固定的話,在被調用函式內就無法知道參數究竟使用了多少個位元組,所以為了實現可變參數,我們必須要在被調函式執行之後我們才知道參數究竟用了多少位元組,所以我們在調用者來進行堆疊平衡操作。在後面我們將要對printf函式內部是怎么實現做一些探究。

(2)__stdcall調用約定

Win32 API函式絕大部分都是採用__stdcall調用約定的。WINAPI其實也只是__stacall的一個別名而已。
#define WINAPI __stdcall
還是與上面一樣,我們在函式的面前用__stdcall作為修飾符。此時函式將會採用__stdcall調用約定
int __stdcallsumExample (int a, int b);
__stdcall調用約定的主要特徵是:
1、參數是從右往左傳遞的,也是放在堆疊中。
2、函式的堆疊平衡操作是由被調用函式執行的。
3、在函式名的前面用下劃線修飾,在函式名的後面由@來修飾並加上棧需要的位元組數的空間(_sumExample@8)。
main函式
push 3
push 2
這兩個push可以說明函式的參數是由右向左傳遞的。
call _sumExample@8 //調用函式
mov dword ptr [c], eax //eax暫存器保存函式的返回值,此時將返回值賦值給局部變數c。
再來看看函式的代碼。
函式的開端與__cdecl調用約定是相同的
mov eax, dword ptr [a]
add eax, dword ptr [b]
函式的收尾也是和__cdecl調用約定是相同的
另外在最後面將對堆疊進行平衡操作。
ret 8 //兩個4位元組的參數
上面的是文章本來的說明,但在VC中卻好像有點區別。
main函式
0040108F 6A 03 push 3
00401091 6A 02 push 2
00401093 E8 81 FF FF FF call @ILT+20(_sumExample) (00401019)
00401098 89 45 FC mov dword ptr [ebp-4],eax
sumExample函式
5: return (a + b);
00401048 8B 45 08 mov eax,dword ptr [ebp+8]
0040104B 03 45 0C add eax,dword ptr [ebp+0Ch]
00401054 C2 08 00 ret 8 //堆疊平衡操作
因為棧的清理(堆疊平衡操作)是由被調用函式執行的。所以使用__stdcall調用約定生成的執行檔要比__cdecl的要小,因為在每次的函式調用都要產生堆疊清理的代碼。函式具有可變參數像我wsprintf這個函式,與前面的prinf一樣,都必須使用__cdecl調用約定,因為只有調用者才知道參數的數量在每一次的函式調用,因此也只有調用者才能夠執行堆疊清理操作。

(3)__fastcall調用約定

__fastcall見名知其意,其特點就是快。__fastcall函式調用約定表明了參數應該放在暫存器中,而不是在棧中,VC編譯器採用調用約定傳遞參數時,最左邊的兩個不大於4個位元組(DWORD)的參數分別放在ecx和edX暫存器。當暫存器用完的時候,其餘參數仍然從右到左的順序壓入堆疊。像浮點值、遠指針和__int64類型總是通過堆疊來傳遞的。
下面來看看使用測試的原始碼
#include <stdio.h>
int __fastcall sumExample(int a, int b, int c)
{
return (a + b + c);
}
double __fastcall sumExampled(double a, double b)
{
return (a + b);
}
int main()
{
int c = 0;
double d = 0.0;
c = sumExample(2, 3, 5);
d = sumExampled(2.3, 2.5);
return 0;
}
15: int c = 0;
004010C8 C7 45 FC 00 00 00 00 mov dword ptr &#91;ebp-4&#93;,0
16: double d = 0.0;
004010CF C7 45 F4 00 00 00 00 mov dword ptr &#91;ebp-0Ch&#93;,0
004010D6 C7 45 F8 00 00 00 00 mov dword ptr &#91;ebp-8&#93;,0
17: c = sumExample(2, 3, 5);
004010DD 6A 05 push 5
004010DF BA 03 00 00 00 mov edx,3
004010E4 B9 02 00 00 00 mov ecx,2
004010E9 E8 26 FF FF FF call @ILT+15(@sumExample@8) (00401014)
004010EE 89 45 FC mov dword ptr &#91;ebp-4&#93;,eax
18:
19: d = sumExampled(2.3, 2.5);
004010F1 68 00 00 04 40 push 40040000h
004010F6 6A 00 push 0
004010F8 68 66 66 02 40 push 40026666h
004010FD 68 66 66 66 66 push 66666666h
00401102 E8 FE FE FF FF call @ILT+0(@sumExampled@16) (00401005)
00401107 DD 5D F4 fstp qword ptr &#91;ebp-0Ch&#93;

相關詞條

熱門詞條

聯絡我們