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函數將告訴你,堆棧被破壞了。 |