当指定子程序的语言模式,或者使用.model中指定的语言模式时,如stdcall、pascal等,子程序的参数压栈方式是不同的,例如stdcall模式下,参数是从右向左压栈,而在pascal模式下,参数是从左向右压栈。
下面,以stdcall模式为例,说明调用一个子程序时,是如何压栈的,假设压栈前,esp的值为addr:
addr …… |
addr - 4 ebp + 16 参数三 |
addr - 8 ebp + 12 参数二 |
addr - 12 ebp + 8 参数一 |
addr - 16 ebp + 4 返回地址 |
addr - 20 ebp 保存原ebp值,并且mov ebp, esp |
addr - 24 ebp - 4 局部变量1 |
addr - 28 ebp - 8 局部变量2 |
…… |
如上表所示,如果栈是向下生长的,则在将参数和返回地址进栈后,需要保存当前的ebp,并且将当前的esp值赋予ebp,从而可以用ebp访问参数或者局部变量。同时,编译器在编译时,会在ret前,加上leave这条指令, 实现mov esp,ebp, pop ebp的功能。
而在子程序中如果要用uses或是pushad/popad对实现环境变量保存时,如果返回结果是保存在eax中,则一定要记得将返回结果保存起来,否则eax中的值会被重置为调用前的值,如下列代码所示:
.386 .model flat, stdcall include windows.inc include kernel32.inc includelib kernel32.lib include user32.inc includelib user32.lib include masm32.inc includelib masm32.lib include debug.inc includelib debug.lib .data ddResult dd ? .code calc proc one, two, three pushad mov eax, one add eax, two add eax, three mov ddResult, eax ;如果不在这里保存,返回的eax会是原来的值 PrintDec eax PrintLine popad PrintDec eax PrintLine ret calc endp start proc invoke calc, 1, 2, 3 PrintDec ddResult ; 6 PrintLine PrintDec eax ; ret start endp end start
输出结果: