在win32asm中主要包含了两种参数传递方式:
1 stdcall
这种是windows api中使用的标准的参数传递(除了wsprintf这个函数),使用从右到左的传递顺序,堆栈清空由被调用函数完成
2 c
这种也是采用了从右到左的传递顺序,但是堆栈的清空由调用者清空
这里的第9行,可以看到把esp寄存器值+16了,因为该函数使用了4个参数,每个参数占了 4个字节,所以在清空堆栈时要把esp恢复到初始值 就要加16。为什么这样那?我自己的估计是这个函数参数不定 ,所以要做到清空堆栈恢复esp的初始值调用者是最清楚的,就交给他恢复,而其他的win api由于参数个数固定 ,被调用函数自身可以知道使用的堆栈的大小,就可以自己清空 。
上次没写完,今天继续
这个表中可以看到常用的参数调用的特性,syscall和stdcall中可以用VARARG,但是用的时候必须调用者清除堆栈,也就是相当于C调用了。
在使用堆栈时主要使用的寄存器就是EBP和ESP两个了。一般ESP存放栈顶指针,这个自不用说。
那EBP呢?
其实EBP的作用 体现在函数调用的时候,在调用了一个函数后,堆栈中的内容一般是
参数n,参数n-1,参数n-2,。。。参数1,函数返回地址,当前EBP的值,本地参数1,本地参数2。。本地参数n
例子:
MyProc proc
push ebp ;这就是为什么有当前EBP的值,用于现场保护。为什么要那?想想如果嵌套调用函数
;会怎么样呢?
mov ebp,esp
1 stdcall
这种是windows api中使用的标准的参数传递(除了wsprintf这个函数),使用从右到左的传递顺序,堆栈清空由被调用函数完成
1 ; Example of calling a stdcall function:
2 ; MessageBeep prototype:
3 ; int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
4
5 push MB_OK ; uType
6 push offset szCaption ; lpCaption
7 push offset szText ; lpText
8 push [hWnd] ; hWnd
9 call MessageBoxA
2 ; MessageBeep prototype:
3 ; int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
4
5 push MB_OK ; uType
6 push offset szCaption ; lpCaption
7 push offset szText ; lpText
8 push [hWnd] ; hWnd
9 call MessageBoxA
2 c
这种也是采用了从右到左的传递顺序,但是堆栈的清空由调用者清空
1 ; Example of calling a C function:
2 ; wsprintf prototype: int cdecl wsprintf(LPTSTR lpOut, LPCTSTR lpFmt, );
3
4 push 345h ; vararg 2
5 push 1 ; vararg 1
6 push offset szFormat ; lpFmt
7 push offset szOutput ; lpOut
8 call wsprintf
9 add esp, 16 ; clean stack
2 ; wsprintf prototype: int cdecl wsprintf(LPTSTR lpOut, LPCTSTR lpFmt, );
3
4 push 345h ; vararg 2
5 push 1 ; vararg 1
6 push offset szFormat ; lpFmt
7 push offset szOutput ; lpOut
8 call wsprintf
9 add esp, 16 ; clean stack
上次没写完,今天继续
C | SysCall | StdCall | Basic | Fortran | Pascal | |
参数从左到右 | 是 | 是 | 是 | |||
参数从右到左 | 是 | 是 | 是 | |||
调用者清除堆栈 | 是 | |||||
允许使用:VARARG | 是 | 是 | 是 |
这个表中可以看到常用的参数调用的特性,syscall和stdcall中可以用VARARG,但是用的时候必须调用者清除堆栈,也就是相当于C调用了。
在使用堆栈时主要使用的寄存器就是EBP和ESP两个了。一般ESP存放栈顶指针,这个自不用说。
那EBP呢?
其实EBP的作用 体现在函数调用的时候,在调用了一个函数后,堆栈中的内容一般是
参数n,参数n-1,参数n-2,。。。参数1,函数返回地址,当前EBP的值,本地参数1,本地参数2。。本地参数n
例子:
MyProc proc
push ebp ;这就是为什么有当前EBP的值,用于现场保护。为什么要那?想想如果嵌套调用函数
;会怎么样呢?
mov ebp,esp
sub esp,8
mov eax,dword ptr [ebp + 8]
sub eax,dword ptr [ebp + c]
add esp,8 ;为什么是8?因为例子里假设函数中有2个本地变量
pop ebp
ret 8 ;为什么是8?因为例子里假设函数传递了2个参数,
;而且使用了stdcall,被调用函数要恢复堆栈
MyProc endp
每次都这样写多麻烦啊?不是要写死?
办法是人想的嘛,减少重复性劳动才是王道啊。。
于是就有了
enter xxx,yyy
leave这一对双胞胎了。。
enter的两个参数xxx表示函数所有本地参数占用的字节数,yyy表示嵌套级数,一般为0
enter 8,0替代程序黄色部分
leave替代程序蓝色部分
是不是简单多了?还是烦?。。。这个。。。。
masm提供了local宏,使用这个后函数可以简单的写为
MyProc proc Var1,Var2
local lVar1,lVar2
mov eax,Var1
sub eax,Var2
ret ;这里可没8哦,local帮你搞定了
MyProc endp
这下简单了不?嘿嘿。是不是有点眼熟,有点象高级语言了吧。
有一个要注意,在一个函数内部中最好不要改变EBP的值,万不得已时一定要保护好现场,并尽快恢复。
另外还有几个知识点
声明函数
MyProc proto :DWORD,:DWORD
使用函数
invoke MyProc x,y
loop's blog