| 61.187.54.* | 2樓
調用處 push?1 push?2 call?function add?esp,8?注意:這里調用者在恢復堆棧 被調用函數_function處 push?ebp?保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數退出 時恢復 mov?ebp,esp?保存堆棧指針 mov?eax,[ebp?+?8H]?堆棧中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp?+8指向 a add?eax,[ebp?+?0CH]?堆棧中ebp?+?12處保存了b mov?esp,ebp?恢復esp pop?ebp ret?注意,這里沒有修改堆棧
MSDN中說,該修飾自動在函數名前加前導的下劃線,因此函數名在符號表中被記錄為_f unction,但是我在編譯時似乎沒有看到這種變化。
由于參數按照從右向左順序壓棧,因此最開始的參數在最接近棧頂的位置,因此當采用 不定個數參數時,第一個參數在棧中的位置肯定能知道,只要不定的參數個數能夠根據 第一個后者后續的明確的參數確定下來,就可以使用不定參數,例如對于CRT中的sprin tf函數,定義為:
int?sprintf(char*?buffer,const?char*?format,...)
由于所有的不定參數都可以通過format確定,因此使用不定個數的參數是沒有問題的。
fastcall fastcall調用約定和stdcall類似,它意味著:
函數的第一個和第二個DWORD參數(或者尺寸更小的)通過ecx和edx傳遞,其他參數通過 從右向左的順序壓棧 被調用函數清理堆棧 函數名修改規則同stdcall 其聲明語法為:int?fastcall?function(int?a,int?b)
thiscall thiscall是唯一一個不能明確指明的函數修飾,因為thiscall不是關鍵字。它是C++類成 員函數缺省的調用約定。由于成員函數調用還有一個this指針,因此必須特殊處理,th iscall意味著:
參數從右向左入棧 如果參數個數確定,this指針通過ecx傳遞給被調用者;如果參數個數不確定,this指針 在所有參數壓棧后被壓入堆棧。 對參數個數不定的,調用者清理堆棧,否則函數自己清理堆棧 為了說明這個調用約定,定義如下類和使用代碼:
class?A { public: int?function1(int?a,int?b); int?function2(int?a,...); }; int?A::function1?(int?a,int?b) { return?a+b; } #include int?A::function2(int?a,...) { va_list?ap; va_start(ap,a); int?i; int?result?=?0; for(i?=?0?i?<?a?i?++) { result?+=?va_arg(ap,int); } return?result; } void?callee() { A?a; a.function1?(1,2); a.function2(3,1,2,3); }
callee函數被翻譯成匯編后就變成:
//函數function1調用 0401C1D?push?2 00401C1F?push?1 00401C21?lea?ecx,[ebp-8] 00401C24?call?function1?注意,這里this沒有被入棧 //函數function2調用 00401C29?push?3 00401C2B?push?2 00401C2D?push?1 00401C2F?push?3 00401C31?lea?eax,[ebp-8]?這里引入this指針 00401C34?push?eax 00401C35?call?function2 00401C3A?add?esp,14h
可見,對于參數個數固定情況下,它類似于stdcall,不定時則類似cdecl
naked?call 這是一個很少見的調用約定,一般程序設計者建議不要使用。編譯器不會給這種函數增 加初始化和清理代碼,更特殊的是,你不能用return返回返回值,只能用插入匯編返回 結果。這一般用于實模式驅動程序設計,假設定義一個求和的加法程序,可以定義為:
__declspec(naked)?int?add(int?a,int?b) { __asm?mov?eax,a __asm?add?eax,b __asm?ret }
注意,這個函數沒有顯式的return返回值,返回通過修改eax寄存器實現,而且連退出函 數的ret指令都必須顯式插入。上面代碼被翻譯成匯編以后變成:
mov?eax,[ebp+8] add?eax,[ebp+12] ret?8
注意這個修飾是和__stdcall及cdecl結合使用的,前面是它和cdecl結合使用的代碼,對 于和stdcall結合的代碼,則變成:
__declspec(naked)?int?__stdcall?function(int?a,int?b) { __asm?mov?eax,a __asm?add?eax,b __asm?ret?8?//注意后面的8 }
至于這種函數被調用,則和普通的cdecl及stdcall調用函數一致。
函數調用約定導致的常見問題 如果定義的約定和使用的約定不一致,則將導致堆棧被破壞,導致嚴重問題,下面是兩 種常見的問題:
函數原型聲明和函數體定義不一致 DLL導入函數時聲明了不同的函數約定 以后者為例,假設我們在dll種聲明了一種函數為:
__declspec(dllexport)?int?func(int?a,int?b);//注意,這里沒有stdcall,使用的是 cdecl 使用時代碼為:
typedef?int?(*WINAPI?DLLFUNC)func(int?a,int?b); hLib?=?LoadLibrary(...); DLLFUNC?func?=?(DLLFUNC)GetProcAddress(...)//這里修改了調用約定 result?=?func(1,2);//導致錯誤
由于調用者沒有理解WINAPI的含義錯誤的增加了這個修飾,上述代碼必然導致堆棧被破 壞,MFC在編譯時插入的checkesp函數將告訴你,堆棧被破壞了。 |