1. 堆栈框架
1). 栈参数
之前使用寄存器传递参数,现在我们将使用运行栈在子程序中传递参数。堆栈框架(或激活记录)是为传递的参数,子程序返回地址,局部变量和已保存的寄存器预留的堆栈区域。栈框架的创建步骤:
I. 如果有要传递的参数,则压入栈中。
II. 调用子程序,使子程序返回地址被压入堆栈。
III. 子程序开始执行时,EBP被压入栈中。
IV. EBP等于ESP。 从这一点开始,EBP充当所有子例程参数的基础参考。
V. 如果存在局部变量,则ESP递减以为堆栈上的变量保留空间。
VI. 如果需要保存任何寄存器,则将它们压入堆栈。
2). 寄存器参数的缺点
缺点:相同的寄存器用于保存数据值,例如计算中的循环计数器和操作数。 因此,在程序之前必须首先将用作参数的任何寄存器压入堆栈
调用,分配过程参数的值,然后在过程返回后恢复为原始值。例如:
push ebx ; save register values
push ecx
push esi
mov esi,OFFSET array ; starting OFFSET
mov ecx,LENGTHOF array ; size, in units
mov ebx,TYPE array ; doubleword format
call DumpMem ; display memory
pop esi ; restore register values
pop ecx
pop ebx
不仅所有额外的推送和弹出都会造成代码混乱,它们往往会通过避免寄存器参数来消除我们希望获得的性能优势! 此外,程序员必须非常小心每个寄存器的PUSH与其适当的POP匹配,即使代码中存在多个执行路径。
两种参数类型被压入栈中产生的子程序调用方式有:值传递、引用传递。
- 值传递
向栈中存入变量的值
.data
val1 DWORD 5
val2 DWORD 6
.code
push val2
push val1
call AddTwo
值传递.png
- 引用传递
向栈中压入变量的地址
push OFFSET val2
push OFFSET val1
call Swap
引用传递.png
- 传递数组
高级语言通过引用传递向子程序传递数组。
.data
array DWORD 50 DUP(?)
.code
push OFFSET array
call ArrayFill
3). 接收栈参数
- 基址偏移寻址
我们使用基址偏移地址接收栈参数,EBP是一个基础的寄存器并且偏移量是一个常量。
AddTwo PROC
push ebp
mov ebp,esp ; base of stack frame
mov eax,[ebp + 12] ; second parameter
add eax,[ebp + 8] ; first parameter
pop ebp
ret
AddTwo ENDP
Accessing Stack Parameters.png
- 显示堆栈参数
显示堆栈参数:栈参数使用表达式引用。
y_param EQU [ebp + 12]
x_param EQU [ebp + 8]
AddTwo PROC
push ebp
mov ebp,esp
mov eax,y_param
add eax,x_param
pop ebp
ret
AddTwo ENDP
- 清理堆栈
当子程序返回后,我们必须从堆栈中移除参数。否则,会早晨内存泄漏和栈损坏。
4). 32位调用约定
- C调用约定
C调用约定通常在C和C++程序中使用,子程序参数以相反的顺序被压入堆栈,因此进行函数调用的C程序将首先将B推入堆栈,然后按A。C调用约定解决清理堆栈的方法是:当程序调用子程序时,它在CALL指令后面跟一个语句,该语句向堆栈指针(ESP)添加一个等于子程序参数组合大小的值。
Example1 PROC
push 6
push 5
call AddTwo
add esp,8 ; remove arguments from the stack
ret
Example1 ENDP
- STDCALL调用约定
当程序使用RET时,在RET后跟一个整型参数,整型参数的值为该子程序参数的大小。
AddTwo PROC
push ebp
mov ebp,esp ; base of stack frame
mov eax,[ebp + 12] ; second parameter
add eax,[ebp + 8] ; first parameter
pop ebp
ret 8 ; clean up the stack
AddTwo ENDP
- 保存和恢复寄存器
子程序经常在修改寄存器前保存他们当前的内容。这是一个好方法,因为原数据在子程序返回之后可以被恢复。
MySub PROC
push ebp ; save base pointer
mov ebp,esp ; base of stack frame
push ecx
push edx ; save EDX
mov eax,[ebp+8] ; get the stack parameter
.
.
pop edx ; restore saved registers
pop ecx
pop ebp ; restore base pointer
ret ; clean up the stack
MySub ENDP
Stack frame for the MySub procedure.png
5). 局部变量
局部变量在运行栈上创建,通常在基址(EBP)之下。
Local Variables.pngMySub PROC
push ebp
mov ebp,esp
sub esp,8 ; create locals
mov DWORD PTR [ebp�4],10 ; X
mov DWORD PTR [ebp�8],20 ; Y
mov esp,ebp ; remove locals from stack
pop ebp
ret
MySub ENDP
Stack frame after creating local variables.png
- 局部变量符号
为了阅读更简便,可以定义符号为每个局部变量偏移地址。
X_local EQU DWORD PTR [ebp�4]
Y_local EQU DWORD PTR [ebp�8]
MySub PROC
push ebp
mov ebp,esp
sub esp,8 ; reserve space for locals
mov X_local,10 ; X
mov Y_local,20 ; Y
mov esp,ebp ; remove locals from stack
pop ebp
ret
MySub ENDP
6). 引用参数
引用参数通常使用基址偏移地址(EBP)被程序所访问。因为每个引用参数是一个指针,它通常被加载到寄存器中以用作间接操作数。
- 数组填充实例
; 数组填充
INCLUDE Irvine32.inc
.data
count = 100
array WORD count DUP(?)
.code
main PROC
push OFFSET array
push count
call ArrayFill
call Crlf
call WaitMsg
exit
main ENDP
ArrayFill PROC
push ebp
mov ebp, esp
pushad ; 保存寄存器
mov esi, [ebp + 12] ; 数组地址
mov ecx, [ebp + 8] ; 数组长度
cmp ecx, 0 ; ECX == 0?
je L2 ; 如果是,则结束循环
L1:
mov eax, 10000 ; 从0——10000获取随机数
call RandomRange ; 从Irvine32库中
mov [esi], ax ; 插入数组
add esi, TYPE WORD ; 移动到下一个结点
LOOP L1
L2:
popad
pop ebp
RET 8 ; 清除堆栈
ArrayFill ENDP
END
7). LEA 指令
LEA指令返回间接操作数的地址。因为间接操作数包含一个或多个寄存器,它们的偏移量在运行时计算。
makeArray PROC
push ebp
mov ebp,esp
sub esp,32 ; myString is at EBP�30
lea esi,[ebp–30] ; load address of myString
mov ecx,30 ; loop counter
L1: mov BYTE PTR [esi],'*' ; fill one position
inc esi ; move to next
loop L1 ; continue until ECX = 0
add esp,32 ; remove the array (restore ESP)
pop ebp
ret
makeArray ENDP
8). ENTER 和 LEAVE 指令
- ENTER 指令
ENTER 指令调用程序时自动创建一个堆栈框架。它为局部变量保留堆栈空间并将EBP保存在堆栈中。具体来说它执行三个操作:
I. 将EBP压入栈中(push ebp)
II. 设置EBP为堆栈框架的基地址(mov ebp, esp)
III. 为局部变量预留空间(sub esp, numbytes)
格式
ENTER numbytes, nestinglevel
第一个参数是一个长廊,为局部变量预留的字节空间。第二个参数指定过程的词法嵌套级别。这两个操作数都是立即数。numbytes是4的倍数。在我们的程序中,nestinglevel总是0.
示例:
MySub PROC
enter 8,0
; 类似于
MySub PROC
push ebp
mov ebp,esp
sub esp,8
Stack frame before and after ENTER has executed.png
- LEAVE 指令
LEAVE指令终止进程的堆栈帧。 它通过将ESP和EBP恢复为调用过程时分配的值来反转前一个ENTER指令的操作。
格式
MySub PROC
enter 8,0
.
.
leave
ret
MySub ENDP
9). LOCAL 指令
LOCAL 定义一个或多个局部变量名并分配它们的类型。
格式
LOCAL varlist
; 其中每个变量类似于label:type
示例:
MySub PROC
LOCAL TempArray[10]:DWORD
BubbleSort PROC
LOCAL temp:DWORD, SwapFlag:BYTE
2. 递归
递归子程序是指直接或间接的调用它自身。在处理具有重复模式的数据结构时,递归(调用递归子例程的实践)可以是一个强大的工具。
- 无穷的递归
; 无穷的递归
INCLUDE Irvine32.inc
.data
endlessStr BYTE "This recursion never stops",0
.code
main PROC
call Endless
call Crlf
call WaitMsg
exit
main ENDP
Endless PROC
mov edx,OFFSET endlessStr
call WriteString
call Crlf
call Endless
ret ; never executes
Endless ENDP
END
1). 递归计算和
有用的递归子例程始终包含终止条件。 当终止条件变为真时,当程序执行所有挂起的RET指令时,堆栈将展开。
; 递归计算和
INCLUDE Irvine32.inc
.code
main PROC
mov ecx, 5 ; 设置count = 5
mov eax, 0 ; 设置sum = 0
call CalcSum ; 计算和
L1:
call WriteDec ; 显示EAX
call Crlf
call WaitMsg
exit
main ENDP
;----------------------------------------------
; 计算一串数字之和
; Receives: ECX = count
; Returns: EAX = sum
;----------------------------------------------
CalcSum PROC
cmp ecx, 0 ; 检查counter
jz L2 ; 如果为0, 则退出程序
add eax, ecx ; 否则求和
dec ecx ; counter递减
call CalcSum ; 递归
L2:
ret
CalcSum ENDP
END
Stack Frame and Registers (CalcSum).png
2). 计算因子
; 递归相乘
INCLUDE Irvine32.inc
.code
main PROC
push 5 ; 计算5!
call Factorial ; 计算因子
call WriteDec ; 显示EAX
call Crlf
call WaitMsg
exit
main ENDP
;----------------------------------------------
; 计算因子乘积
; Receives: [ebp + 8] = n, 计算
; Returns: EAX = 乘积
;----------------------------------------------
Factorial PROC
push ebp
mov ebp, esp
mov eax, [ebp + 8] ; 获取n
cmp eax, 0 ; 判断 n > 0?
ja L1 ; 继续L1
mov eax, 1 ; 如果不是,则返回1
jmp L2 ; 返回结果
L1:
dec eax
push eax
call Factorial
ReturnFact:
mov ebx, [ebp + 8] ; 获取n
mul ebx ; EDX:EAX = EAX * EBX
L2:
pop ebp
ret 4
Factorial ENDP
END
Recursive calls to the factorial function.png
3. INVOKE, ADDR, PROC, and PROTO
在32位模式下,INVOKE,PROC和PROTO指令为定义和调用过程提供了强大的工具。除了这些指令外,ADDR运算符也是必不可少的工具
定义程序参数。
1). INVOKE 指令
INVOKE指令,仅在32位模式下可用,将参数压入栈中和调用程序。INVOKE是CALL指令的一个方便替代品,因为它允许您使用一行代码传递多个参数。
- 格式
INVOKE procedureName [, argumentList]
Argument types used with INVOKE.png
EAX,EDX重写: 如果将小于32位的参数传递给程序,INVOKE会经常重写EAX和EDX,因此在使用INVOKE调用程序时,最好使用保存和恢复EAX、DEX的方式。
- 替换代码
push TYPE array
push LENGTHOF array
push OFFSET array
call DumpArray
; call 四行代码等价于 INVOKE 一行代码
INVOKE DumpArray, OFFSET array, LENGTHOF array, TYPE array
2). ADDR 操作
ADDR 操作,32位模式下可用,使用INVOKE指令时可以传递指针参数。
- 格式
INVOKE FillArray, ADDR myArray
3). PROC 指令
- PROC 指令语法
label PROC [attributes] [USES reglist], parameter_list
其中label是用户自定义标签,attributes可选值为[distance] [langtype] [visibility] [prologuearg]
- 参数列表
label PROC [attributes] [USES reglist],
parameter_1,
parameter_2,
.
.
parameter_n
; 其中每个parameter_n的格式为paramName:type
- 由PROC修改的RET指令
push ebp
mov ebp,esp
.
.
leave
ret (n*4)
- 指定参数传递协议
Example1 PROC C,
parm1:DWORD, parm2:DWORD
Example1 PROC STDCALL,
parm1:DWORD, parm2:DWORD
4). PROTO 指令
在64位中,我们可以使用PROTO指令标识外部的程序。
ExitProcess PROTO
.code
mov ecx,0
call ExitProcess
在32位中,PROTO是一个更强大的功能,因为它可以包含一个列表
程序参数。
; 类型于C语言中的函数声明
MySub PROTO ; procedure prototype
.
INVOKE MySub ; procedure call
.
MySub PROC ; procedure implementation
.
.
MySub ENDP
- 程序声明的方法
I> 将PROC改为PROTO
II> 移除USES操作和寄存器列表
; 程序 PROTO 声明:
ArraySum PROTO,
ptrArray:PTR DWORD, ; points to the array
szArray:DWORD ; array size
; 程序实现
ArraySum PROC USES esi ecx,
ptrArray:PTR DWORD, ; points to the array
szArray:DWORD ; array size
; (remaining lines omitted...)
ArraySum ENDP
-
编译时参数检查
PROTO 指令编译时比较程序声明和程序实现的参数。 -
MASM 检测错误
如果参数超出声明参数的大小,MASM将生成一个错误。 -
MASM 不检测的错误
如果参数小于声明参数的大小,MASM将不生成错误。 -
数组求和示例
; 数组求和
INCLUDE Irvine32.inc
.data
array DWORD 10000h, 20000h, 30000h, 40000h, 50000h
theSum DWORD ?
.code
ArraySum PROTO,
ptrArray: PTR DWORD, ; points to the array
szArray:DWORD ; array size
main PROC
INVOKE ArraySum,
ADDR array, ; 数组首地址
LENGTHOF array ; 数组元素个数
call WriteHex
call Crlf
call WaitMsg
exit
main ENDP
ArraySum PROC USES esi ecx,
ptrArray: PTR DWORD, ; points to the array
szArray:DWORD ; array size
mov esi, ptrArray ; 数组地址
mov ecx, szArray ; 数组长度
mov eax, 0 ; 设置和为0
cmp ecx, 0 ; 数组长度是否为0
je L2 ; 如果等于则quit
L1:
add eax, [esi] ; 求和
add esi, 4 ; 移动到下一个数组元素
LOOP L1 ; 循环
L2:
ret ; sum 在EAX寄存器中
ArraySum ENDP
END
ArraySum.png
5). 参数分类
过程参数通常根据调用程序和被调用过程之间的数据传输方向进行分类:
Input -> 输入参数是由调用程序传递给过程的数据.
Output -> 当调用程序将变量的地址传递给过程时,将创建输出参数.
Input-Output -> 输入输出参数与输出参数相同,但有一个例外:被调用过程期望参数引用的变量包含一些数据。
6). 示例:交换两个整数
; 交换两个数
INCLUDE Irvine32.inc
Swap PROTO, pValX: PTR DWORD, pValY: PTR DWORD
.data
Array DWORD 10000h, 20000h
.code
main PROC
; 交换之前显示数组
mov esi, OFFSET Array
mov ecx, 2 ; 数组长度
mov ebx, TYPE Array
call DumpMem ; 显示数组数据
INVOKE Swap, ADDR Array, ADDR[Array + 4]
; 显示交换后的数据
call DumpMem
call Crlf
call WaitMsg
exit
main ENDP
;--------------------------------------------
; 交换两个32位整型
; Return: 无
;--------------------------------------------
Swap PROC USES eax esi edi,
pValX: PTR DWORD,
pValY: PTR DWORD
mov esi, pValX ; 获取指针
mov edi, pValY
mov eax, [esi] ; 获取第一个整数
xchg eax, [edi] ; 交换两个数
mov [esi], eax ; 替换第一个整数
RET
Swap ENDP
END
效果.png
7). 调试提醒
常见错误:
- 参数大小不匹配
- 传递指针类型错误
- 传递即时值
8). WriteStackFrame 程序
; 交换两个数
INCLUDE Irvine32.inc
WriteStackFrame PROTO,
numParam:DWORD, ; 传递参数个数
numLocalVal: DWORD, ; 本地变量
numSavedReg: DWORD ; 保存寄存器的个数
.code
myProc PROC USES eax ebx,
x: DWORD, y: DWORD
LOCAL a: DWORD, b: DWORD
PARAMS = 2
LOCALS = 2
SAVED_REGS = 2
mov a, 0AAAAh
mov b, 0BBBBh
INVOKE WriteStackFrame, PARAMS, LOCALS, SAVED_REGS
RET
myProc ENDP
main PROC
mov eax, 0EAEAEAEAh
mov ebx, 0EBEBEBEBh
INVOKE myProc, 1111h, 2222h;
call Crlf
call WaitMsg
exit
main ENDP
END
效果.png
4. 创建多模块程序
一个很大的源文件管理起来困难并且编译起来比较慢,可以将单个文件拆分成多个文件,但是对任何源文件的修改仍然需要完整组装所有文件。更好的方法是将程序划分为模块(组合单元)。每个模块都是独立组装的,因此更改一个模块的源代码只需要重新组装单个模块。 链接器将所有已组装的模块(OBJ文件)组合成一个可执行文件很快。 链接大量对象模块所需的时间远远少于组装相同数量的源代码文件所需的时间.
有两种方法去创建多模块程序:第一个是传统的,使用EXTERN
指令,这或多或少可以在不同的x86汇编程序中移植。第二种是使用微软的高级指令INVOKE
和PROTO
,隐藏更多的低级细节。
1). 隐藏和导出程序名称
默认情况下,MASM让所有的程序时public
,它们可以被任意模块被调用。我们可以覆盖这个行为,使用PRIVATE
修饰符,mySub PROC PRIVATE
。
-
OPTION PROC:PRIVATE指令
:源程序模块之外的程序总是隐藏。所有的程序默认的访问修饰符变为私有的。然后我们必须使用PUBLIC
指令标识它们。
OPTION PROC:PRIVATE
PUBLIC mySub
2). 调用外部程序
EXTERN
指令,在调用当前模块外部的过程时使用,标识过程的名称和堆栈帧大小。
INCLUDE Irvine32.inc
EXTERN sub1@0:PROC
.code
main PROC
call sub1@0
exit
main ENDP
END main
3). 跨模块边界使用变量和符号
- 导出变量和符号
在一个闭合的模块中,变量和符号默认是私有的。我们可以使用PUBLIC
指令导出特殊的名字。
PUBLIC count, SYM1
SYM1 = 10
.data
count DWORD 0
- 接收外部变量 和符号
我们可以使用EXTERN
指令接收定义在其他模块的变量和符号
EXTERN name : type
- 使用
INCLUDE
和EXTERNDEF
vars.inc
; vars.inc
EXTERNDEF count:DWORD, SYM1:ABS
sub1.asm
; sub1.asm
.386
.model flat,STDCALL
INCLUDE vars.inc
SYM1 = 10
.data
count DWORD 0
END
main.asm
.386
.model flat,stdcall
.stack 4096
ExitProcess proto, dwExitCode:dword
INCLUDE vars.inc
.code
main PROC
mov count,2000h
mov eax,SYM1
INVOKE ExitProcess,0
main ENDP
END main
4). 示例:数组求和
模块图结构:
Structure chart, ArraySum program.png5). 使用EXTERN
创建模块
_prompt.asm
; 提示用户输入整数
INCLUDE Irvine32.inc
.code
;------------------------------------------
PromptForIntegers PROC
; 提示用户输入整数数组
; Receives:
; ptrPrompt: PTR BYTE ; 提示信息
; ptrArray: PTR DWORD ; 数组首地址
; arraySize: DWORD ; 数组大小
; Returns: 无
;------------------------------------------
arraySize EQU [ebp + 16]
ptrArray EQU [ebp + 12]
ptrPrompt EQU [ebp + 8]
enter 0,0
pushad ; 保存所有寄存器
mov ecx, arraySize
cmp ecx, 0 ; 判断数组长度是否小于等于0
jle L2 ; 如果是则退出
mov edx, ptrPrompt ; 提示信息的地址
mov esi, ptrArray
L1:
call WriteString ; 显示字符串
call ReadInt ; 从EAX读取一个整数
call Crlf ; 换行
mov [esi], eax ; 存储到数组
add esi, 4 ; 下一个整数
LOOP L1
L2:
popad ; 恢复所有寄存器
leave ; 恢复栈
ret 12
PromptForIntegers ENDP
END
_arraysum.asm
; 数组求和
INCLUDE Irvine32.inc
.code
;------------------------------------------
ArraySum PROC
; 计算数组元素之和
; Receives:
; ptrArray ; 数组首地址
; arraySize ; 数组大小
; Returns: EAX = sum
;------------------------------------------
ptrArray EQU [ebp + 8]
arraySize EQU [ebp + 12]
enter 0,0
push ecx ; 不要push EAX
push esi
mov eax, 0 ; 设置和为0
mov esi, ptrArray
mov ecx, arraySize
cmp ecx, 0 ; 数组大小是否小于0
jle L2 ; 如果是则退出
L1:
add eax, [esi] ; 添加一个整数到sum
add esi, 4 ; 指向数组下一个元素
LOOP L1 ; 循环
L2:
pop esi
pop ecx ; 返回sum到EAX中
leave
ret 8 ; 恢复栈
ArraySum ENDP
END
_display.asm
; 显示程序
INCLUDE Irvine32.inc
.code
;------------------------------------------
DisplaySum PROC
; 在控制台显示数组和
; Receives:
; ptrPrompt ; 提示信息首地址
; theSum ; 数组和
; Returns: 无
;------------------------------------------
theSum EQU [ebp + 12]
ptrPrompt EQU [ebp + 8]
enter 0, 0
push eax
push edx
mov edx, ptrPrompt ; 指向提示信息
call WriteString
mov eax, theSum
call WriteInt ; 显示EAX
call Crlf
pop edx
pop eax
leave
ret 8
DisplaySum ENDP
END
Sum_main.asm
; 数组求和 总模块
; 使用到的模块:_arraysum.asm, _display.asm, _prompt.asm
INCLUDE Irvine32.inc
EXTERN PromptForIntegers@0:PROC
EXTERN ArraySum@0:PROC, DisplaySum@0:PROC
; 重定义方法名,为了使用方便
ArraySum EQU ArraySum@0
PromptForIntegers EQU PromptForIntegers@0
DisplaySum EQU DisplaySum@0
; 数组长度
Count = 3
.data
prompt1 BYTE "Enter a signed integer: ",0
prompt2 BYTE "The sum of the integers is: ",0
array DWORD Count DUP(?)
sum DWORD ?
.code
main PROC
call Clrscr
; 提示用户输入数据
push Count
push OFFSET array
push OFFSET prompt1
call PromptForIntegers
; 计算数组之和
push Count
push OFFSET array
call ArraySum
mov sum, eax
; 显示和
push sum
push OFFSET prompt2
call DisplaySum
call Crlf
call WaitMsg
exit
main ENDP
END
运行效果:
效果.png6). 使用INVOKE and PROTO 创建模块
sum.inc
; (sum.inc)
INCLUDE Irvine32.inc
PromptForIntegers PROTO,
ptrPrompt:PTR BYTE, ; prompt string
ptrArray:PTR DWORD, ; points to the array
arraySize:DWORD ; size of the array
ArraySum PROTO,
ptrArray:PTR DWORD, ; points to the array
arraySize:DWORD ; size of the array
DisplaySum PROTO,
ptrPrompt:PTR BYTE, ; prompt string
theSum:DWORD ; sum of the array
__prompt.asm
; 提示用户输入整数
INCLUDE sum.inc ; 获取方法原型
.code
;------------------------------------------
PromptForIntegers PROC,
ptrPrompt: PTR BYTE, ; 提示信息
ptrArray: PTR DWORD, ; 数组首地址
arraySize: DWORD ; 数组大小
; 提示用户输入整数数组
; Returns: 无
;------------------------------------------
pushad ; 保存所有寄存器
mov ecx, arraySize
cmp ecx, 0 ; 判断数组长度是否小于等于0
jle L2 ; 如果是则退出
mov edx, ptrPrompt ; 提示信息的地址
mov esi, ptrArray
L1:
call WriteString ; 显示字符串
call ReadInt ; 从EAX读取一个整数
call Crlf ; 换行
mov [esi], eax ; 存储到数组
add esi, 4 ; 下一个整数
LOOP L1
L2:
popad ; 恢复所有寄存器
ret
PromptForIntegers ENDP
END
__arraysum.asm
; 数组求和
INCLUDE sum.inc
.code
;------------------------------------------
ArraySum PROC,
ptrArray:PTR DWORD, ; 数组首地址
arraySize:DWORD ; 数组大小
; 计算数组元素之和
; Returns: EAX = sum
;------------------------------------------
push ecx ; 不要push EAX
push esi
mov eax, 0 ; 设置和为0
mov esi, ptrArray
mov ecx, arraySize
cmp ecx, 0 ; 数组大小是否小于0
jle L2 ; 如果是则退出
L1:
add eax, [esi] ; 添加一个整数到sum
add esi, 4 ; 指向数组下一个元素
LOOP L1 ; 循环
L2:
pop esi
pop ecx ; 返回sum到EAX中
ret ; 恢复栈
ArraySum ENDP
END
__display.asm
; 显示程序
INCLUDE Irvine32.inc
.code
;------------------------------------------
DisplaySum PROC,
ptrPrompt: PTR BYTE, ; 提示信息首地址
theSum:DWORD ; 数组和
; 在控制台显示数组和
; Returns: 无
;------------------------------------------
push eax
push edx
mov edx, ptrPrompt ; 指向提示信息
call WriteString
mov eax, theSum
call WriteInt ; 显示EAX
call Crlf
pop edx
pop eax
ret
DisplaySum ENDP
END
Sum__main.asm
; 数组求和 总模块
; 使用到的模块:_arraysum.asm, _display.asm, _prompt.asm
INCLUDE sum.inc
; 数组长度
Count = 3
.data
prompt1 BYTE "Enter a signed integer: ",0
prompt2 BYTE "The sum of the integers is: ",0
array DWORD Count DUP(?)
sum DWORD ?
.code
main PROC
call Clrscr
INVOKE PromptForIntegers, ADDR prompt1, ADDR array, Count
INVOKE ArraySum, ADDR array, Count
mov sum, eax
INVOKE DisplaySum, ADDR prompt2, sum
call Crlf
call WaitMsg
exit
main ENDP
END
注:这两种方式,使用较多的为EXTERN
创建多模块方式,第二种在32位中使用较多。
网友评论