汇编语言的过程调用的几个问题
汇编语言的过程调用,如果需要传递参数,一般有2种方法,通过寄存器来“传递”,或是通过参数来传递。(还有将所有参数制成参数列表并压栈的传递方法,但较少用。)
通过寄存器来“传递”,不是真正意义上的传递,其只不过是事先在几个有限的CPU寄存器中设置相应的值后,再调用过程,过程再直接读取这些寄存器的内容。可想而知,此法犹如C语言中的全局变量,极易感染。
而如果通过参数来传递,又不得不面临手工维护堆栈框架(stack frame)的重担。堆栈框架动态地存放着参数、调用过程的返回地址、过程局部变量、过程内的压栈等内容,也是不好对付的。
一般情况下,一个普通的过程可能如下编写:
Sum PROC
push ebp
mov ebp, esp
......
pop ebp
ret
Sum ENDP
作为遵从C调用约定(Calling Convention)调用者,则需这样调用上述过程:
push 5 ; 压进第2个参数
push 8 ; 压进第1个参数
call Sum ; 调用过程
add esp, 4 * 2 ; 释放2个参数在堆栈上所占用的空间
最后一步不仅怪异,且易忘掉。
而如果遵从STDCALL调用约定,则:
Sum PROC
push ebp
mov ebp, esp
......
mov eax, [ebp + 12] ; 取第1个参数
add eax, [ebp + 8] ; 与第2个参数相加
......
pop ebp
ret 4 * 2 ; 别忘了释放参数占用的空间
Sum ENDP
也不轻松。
而如果再在过程中创建了局部变量呢?
Sum PROC
push ebp
mov ebp, esp
sub esp, 8 ; 在栈上创建2个局部变量
......
mov eax, [ebp + 12] ; 取第1个参数
add eax, [ebp + 8] ; 与第2个参数相加
add eax, [ebp - 4] ; 且与第1个局部变量相加
add eax, [ebp - 8] ; 再与第2个局部变量相加
......
mov esp, ebp ; 释放局部变量占用的空间
pop ebp
ret 4 * 2 ; 别忘了释放参数占用的空间
Sum ENDP
有点烦人。
而使用MASM过程的伪指令,能大大地减轻手工维护堆栈框架(stack frame)的任务。
主要是在被调用的过程内,分为3种情况:
1. 无参数,也无局部变量
2. 有参数
3. 有局部变量
当无参数且无局部变量时,堆栈中只是保存call语句的下一条语句的地址,可以很安全地返回。
而当有参数,使用PROC伪指令的接收参数的形式,MASM则会自动生成正确的返回代码。
而当有局部变量,使用LOCAL伪指令来定义局部变量,MASM也会自动地生成正确的返回代码。
但仍需注意,在将参数压栈时,仍需将其打包为32位的,否则,很可能会出错。
.data
val1 WORD 19 ; 16位的变量
.code
movzx eax, val1 ; 扩展为32位的数据后
push eax ; 再压栈
另一选择是,将用作argument的变量声明为DWORD,这样虽然无需打包,但会浪费一定的空间。
.data
val1 DWORD 19 ; 32位的变量
.code
push val1 ; 直接压栈
还有另一种方法,即,总是传递指针。
.data
val1 WORD 5
val2 WORD 10
val3 WORD ?
.code
main PROC
push OFFSET val2
push OFFSET val1
call Sum ; sum(5, 10)
mov val3, ax ; receive the return value of Sum
exit
main ENDP
Sum PROC,
pV1:PTR WORD,
pV2:PTR WORD,
mov esi, pV1
mov ax, word ptr [esi]
mov edi, pV2
add ax, word ptr [edi]
ret
Sum ENDP
这种方法在保留了我们可以声明仅需的变量类型的同时,也确保argument以32位的方易做图确压栈。
但这种方法需注意,即使声明了pV1和pV2是指向WORD的指针,但esi仍是32位的指针。因此,需使用word ptr [esi]取出正确的word变量的内容,这也是一个值得提倡的编程风格。
为什么不能直接使用
mov ax, word ptr [pV1]
来赋值?
因为只有esi及edi才是变址寻址方式的合格者。
3种方法,根据实际需要灵活选用。
补充:综合编程 , 其他综合 ,