汇编开发(六):程序进阶

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
3110861-bfe8e865a8efc559.png
值传递.png
  • 引用传递
    向栈中压入变量的地址
push OFFSET val2
push OFFSET val1
call Swap  
3110861-b36cbbda2727f286.png
引用传递.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
3110861-ad787f287cba7760.png
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
3110861-4615605c09f7690e.png
Stack frame for the MySub procedure.png
5). 局部变量

局部变量在运行栈上创建,通常在基址(EBP)之下。

3110861-b137a306a72dde59.png
Local Variables.png
MySub 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
3110861-e0d3eb5cd3bdb04d.png
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
3110861-20530406cf689ea3.png
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 
3110861-0b28b3d924cd4065.png
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
3110861-f4d825e51b8f0894.png
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]
3110861-801610caffcc9de5.png
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]

3110861-17762054ad793873.png
Attributes field in the PROC directive.png
  • 参数列表
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 
3110861-b1bf19644baf268c.png
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 
3110861-95c30bf536e41495.png
效果.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 
3110861-38b6c665e72ccaa4.png
效果.png
4. 创建多模块程序

一个很大的源文件管理起来困难并且编译起来比较慢,可以将单个文件拆分成多个文件,但是对任何源文件的修改仍然需要完整组装所有文件。更好的方法是将程序划分为模块(组合单元)。每个模块都是独立组装的,因此更改一个模块的源代码只需要重新组装单个模块。 链接器将所有已组装的模块(OBJ文件)组合成一个可执行文件很快。 链接大量对象模块所需的时间远远少于组装相同数量的源代码文件所需的时间.
有两种方法去创建多模块程序:第一个是传统的,使用EXTERN指令,这或多或少可以在不同的x86汇编程序中移植。第二种是使用微软的高级指令INVOKEPROTO,隐藏更多的低级细节。

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
  • 使用INCLUDEEXTERNDEF

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). 示例:数组求和

模块图结构:

3110861-7ac37c60ffcd3583.png
Structure chart, ArraySum program.png
5). 使用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

运行效果:

3110861-156c8014fa67abb3.png
效果.png
6). 使用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位中使用较多。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值