鸿 网 互 联 www.68idc.cn

汇编语言的过程调用的几个小问题

来源:互联网 作者:佚名 时间:2015-09-03 08:16
汇编语言 的 过程 调用 的几个 问题 汇编语言 的 过程 调用 ,如果需要传递参数,一般有2种方法,通过寄存器来“传递”,或是通过参数来传递。(还有将所有参数制成参数列表并压栈的传递方法,但较少用。) 通过寄存器来“传递”,不是真正意义上的传递,其
汇编语言过程调用的几个问题

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

网友评论
<