作者:tanghw
汇编语言的过程调用,如果需要传递参数,一般有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种方法,根据实际需要灵活选用。