灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:2481回复:0

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

楼主#
更多 发布于:2012-11-10 15:38

作者: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种方法,根据实际需要灵活选用。


喜欢0 评分0
游客

返回顶部