談到函式,一般首先要分析一下各種函式呼叫約定,比如_cdecl、 _stdcall等。這兩種呼叫約定呼叫時都是最右側的引數先進棧,棧最上面的就是函式的第乙個引數。不同之處在於,_cdecl由呼叫者清理引數占用的 棧空間,而_stdcall由被呼叫者清理引數占用的棧空間。很明顯,對於接受可變引數的函式,如printf,被呼叫函式是無法知道到底有幾個引數的, 所以只能採用由呼叫者清理引數棧的方式。_stdcall呼叫方式生成的**會小一點。 下面的分析採用vc++6.0進行。
一.函式內部的彙編**
view plain
void func()
int main()
對應的彙編**是:
1: void func()
2:
00401038 pop edi
00401039 pop esi
0040103a pop ebx
0040103b mov esp,ebp
0040103d pop ebp
0040103e ret
5: int main()
6:
0040106f pop edi
00401070 pop esi
00401071 pop ebx
00401072 add esp,40h
00401075 cmp ebp,esp
00401077 call __chkesp (00401090)
0040107c mov esp,ebp
0040107e pop ebp
0040107f ret
一般來說,函式開頭的**如下:
push ebp //儲存ebp
mov ebp,esp //將esp的值送ebp,在函式內部可能還會使用push、pop等操作,這時esp的值會不斷變化,如果採用esp來定址局 部變數或者引數的話,可能要不斷修正偏移量,而採用ebp定址變數就方便很多。當然,這樣會浪費乙個暫存器,編譯器可以優化。
sub esp,40h //為區域性變數分配空間,這裡的40h大小是vc預設分配的。
而結尾**如下:
mov esp,ebp //恢復esp,其實相當於把棧裡區域性變數的空間**了
pop ebp
ret
可以看到,即使函式沒有定義任何區域性變數,編譯器仍然為我們非配了0x40大小的空間,並全部初始化0xcccccccc。有時候我們會見到燙燙€這樣的字串資訊,就是這部分內存在作怪。
二 帶引數和區域性變數的函式
view plain
#include
void func(int a,int b)
int main()
反彙編**:
main中呼叫func的**是:
00401088 push 20h //32壓棧
0040108a push 10h //16壓棧
0040108c call @ilt+10(func) (0040100f)
00401091 add esp,8 //呼叫完畢後清理引數占用的棧空間,這裡採用的是_cdecl呼叫約定。
再看func的**:
2: void func(int a,int b)
3:
……00401047 mov esp,ebp
00401049 pop ebp
0040104a ret
函式中通過ebp加偏移量的方式來定址區域性變數,很容易看出,棧布局如下圖所示:
圖中,位址從上到下增加。因為這裡是near呼叫,所以沒有儲存ecs暫存器的內容。
函式呼叫約定
函式呼叫約定有多種,這裡簡單說一下 1 stdcall 呼叫約定相當於16位動態庫中經常使用的 pascal 呼叫約定。在32位的vc 5.0中pascal呼叫約定不再被支援 實際上它已被定義為 stdcall。除了 pascal外,fortran和 syscall也不被支援 取而代之的是 stdc...
函式呼叫約定
函式呼叫約定1.stdcall是pascal程式的預設呼叫方式,通常用於win32 api中,函式採用從右到左的壓棧方式,自己在退出時清空堆疊。vc將函式編譯後會在函式名前面加上下劃線字首,在函式名後加上 和引數的位元組數。2 c呼叫約定 即用 cdecl關鍵字說明 按從右至左的順序壓引數入棧,由呼...
函式呼叫約定
1.stdcall是pascal程式的預設呼叫方式,通常用於win32 api中,函式採用從右到左的壓棧方式,自己在退出時清空堆疊。vc將函式編譯後會在函式名前面加上下劃線字首,在函式名後加上 和引數的位元組數。2 c呼叫約定 即用 cdecl關鍵字說明 按從右至左的順序壓引數入棧,由呼叫者把引數彈...