子程序的压栈方式
当指定子程序的语言模式,或者使用.model中指定的语言模式时,如stdcall、pascal等,子程序的参数压栈方式是不同的,例如stdcall模式下,参数是从右向左压栈,而在pascal模式下,参数是从左向右压栈。
?
下面,以stdcall模式为例,说明调用一个子程序时,是如何压栈的,假设压栈前,esp的值为addr:
?
addr……addr - 4ebp + 16参数三addr?- 8ebp + 12参数二addr - 12ebp + 8参数一addr - 16ebp + 4返回地址addr - 20ebp保存原ebp值,并且mov ebp, espaddr - 24ebp - 4局部变量1addr - 28ebp - 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.incincludelib user32.libinclude 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
?输出结果:
