第八章 高级过程
8.2 堆栈框架
汇编中,函数传参分为寄存器参数和堆栈参数(值参数和引用参数)
8.2.1 堆栈参数
传递值

TITLE test ;假设等价函数调用为int sum = AddTwo(val1,val2); INCLUDE Irvine32.inc AddTwo PROTO,a:DWORD,b:DWORD .data val1 DWORD 5 val2 DWORD 6 .code main Proc push val2 push val1 call AddTwo mov edx,eax call WriteDec call Crlf call WaitMsg exit main ENDP AddTwo PROC,a:DWORD ,b:DWORD mov eax,a add eax,b ret 8 AddTwo ENDP END main
TITLE test ;假设等价函数调用为int sum = AddTwo(val1,val2); INCLUDE Irvine32.inc .data val1 DWORD 5 val2 DWORD 6 .code main Proc push val2 push val1 call AddTwocall WriteDec call Crlf call WaitMsg exit main ENDP AddTwo PROC push ebp mov ebp,esp mov eax,[ebp+12];第二个参数 add eax,[ebp+8];第一个参数 pop ebp ret 8 AddTwo ENDP END main
传递引用
TITLE test ;假设等价函数调用为 AddTwo(val1,val2); INCLUDE Irvine32.inc .data val1 DWORD 5 val2 DWORD 6 .code main Proc push OFFSET val2 push OFFSET val1 call Swap mov eax,val1 call WriteDec call Crlf mov eax,val2 call WriteDec call Crlf call WaitMsg exit main ENDP Swap PROC push ebp mov ebp,esp mov edi,[ebp+12];第二个参数地址 mov esi,[ebp+8];第一个参数地址 mov ebx,[edi] mov edx,[esi] mov [edi],edx mov [esi],ebx pop ebp ret 8 Swap ENDP END main
传递数组
TITLE test ;假设等价函数调用为 AddTwo(val1,val2); INCLUDE Irvine32.inc .data array DWORD 50 DUP(?) .code main Proc push OFFSET array call FillArray mov eax,array[0] call WriteDec call Crlf call WaitMsg exit main ENDP FillArray PROC push ebp mov ebp,esp mov esi,[ebp+8] S: mov DWORD PTR [esi],55 add esi,TYPE [ebp+8] loop S pop ebp ret 4 FillArray ENDP END main
stdcall和cdlcall都是由被调用函数清理堆栈(平衡栈),cdecl由调用函数处清理堆栈。
在使用堆栈向过程传递长整数(由多个字节构成)时,可以先把高位字节压栈,然后再把低位字压栈,这样实际上式以一个小尾顺序把长整数压栈了。

USES操作符后跟要在过程开始时保存并在过程结束时恢复的寄存器列表。
MySub1 PROC USES ecx,edx ret MySub1 ENDP
相当于
push ecx push edx pop edx pop ecx ret
使用显示堆栈参数的过程应用应避免使用USES操作符,因为使用USES保存寄存器的值,在PUSH EBP之前,会导致显示访问[EBP+8]访问出问题。
8.2.2 局部变量
下面一段C++对应的汇编代码
void MySub() { int X = 10; int Y = 20; }
MySub PROC push ebp mov ebp, esp sub esp, 8 ;创建局部变量 mov DWORD PTR [ebp-4],10 ; X mov DWORD PTR [ebp-8],20 ; Y mov esp, ebp ;从堆栈中删除局部变量 pop ebp ret MySub ENDP

LEA指令返回间接操作数的偏移地址。
不能用 OFFSET 获得堆栈参数的地址,因为 OFFSET 只适用于编译时已知的地址。下面的语句无法汇编:
mov esi,OFFSET [ebp-30 ] ;错误
8.2.3 ENTER和LEAVE指令
ENTER指令自动为被调用过程创建堆栈框架。执行三个操作:
- 把 EBP 入栈 (push ebp)
 - 把 EBP 设置为堆栈帧的基址 (mov ebp, esp)
 - 为局部变量保留空间 (sub esp, numbytes)
 
ENTER numbytes, nestinglevel
这两个操作数都是立即数。Numbytes 总是向上舍入为 4 的倍数,以便 ESP 对齐双字边界,表示分配局部变量的字节数。Nestinglevel 确定了从主调过程堆栈帧复制到当前帧的堆栈帧指针的个数。
LEAVE 指令结束一个过程的堆栈帧。它反转了之前的 ENTER 指令操作:恢复了过程被调用时 ESP 和 EBP 的值。
8.2.4 LOCAL伪指令
LOCAL 声明一个或多个变量名,并定义其大小属性。(另一方面,ENTER 则只为局部变量保留一块未命名的堆栈空间。)如果要使用 LOCAL 伪指令,它必须紧跟在 PROC 伪指令的后面。
BubbleSort PROC LOCAL temp:DWORD, SwapFlag:BYTE
8.4 MODEL伪指令
.model flat STDCALL
STD对过程的命名:AddTwo-->_AddTwo@8
8.5 INVOKE,ADDR,PROC和PROTO
8.5.1 INVOKE指令
INVOKE 伪指令,只用于 32 位模式,将参数入栈(按照 MODEL 伪指令的语言说明符所指定的顺序)并调用过程。
push TYPE array push LENGTHOF array push OFFSET array call DumpArray
等同于
INVOKE DumpArray, OFFSET array, LENGTHOF array, TYPE array
8.5.2 ADDR符号操作符
ADDR 运算符同样可用于 32 位模式,在使用 INVOKE 调用过程时,它可以传递指针参数。
INVOKE FillArray, ADDR myArray
INVOKE mySub, ADDR [ebp+12 ] ;错误
mov esi, ADDR myArray ;错误
8.5.3 PROC伪指令
PROC 伪指令允许在声明过程时,添加上用逗号分隔的参数名列表。
每个参数的形式:
paramName: type
ParamName 是分配给参数的任意名称,其范围只限于当前过程(称为局部作用域(local scope))。同样的参数名可以用于多个过程,但却不能作为全局变量或代码标号的名称。
下例声明的过程采用了 C 调用规范:
Examplel PROC C, parm1:DWORD, parm2:DWORD
8.5.4 PROTO伪指令
PROTO 伪指令指定程序的外部过程。
MASM 要求 INVOKE 调用的每个过程都有原型。PROTO 必须在 INVOKE 之前首先岀现。换句话说,这些伪指令的标准顺序为:
MySub PROTO ;过程原型 . INVOKE MySub ;过程调用 . MySub PROC ;过程实现 .. MySub ENDP
还有一种情况也是可能的:过程实现可以出现在程序的前面,先于调用该过程的 INVOKE 语句。在这种情况下,PROC 就是它自己的原型:
MySub PROC ;过程定义 .. MySub ENDP . INVOKE MySub ;过程调用
假设已经编写了一个特定的过程,创建其原型也是很容易的,即复制 PROC 语句并做如下修改:
- 将关键字 PROC 改为 PROTO。
 - 如有 USES 运算符,则把该运算符连同其寄存器列表一起删除。
 
INCLUDE Irvine32.inc EXTERN sub1@0:PROC .code main PROC call subl@0 exit main ENDP END main
                    
                
                
            
        
浙公网安备 33010602011771号