汇编语言Macro宏库详解

< 上一页宏的特性 实例:封装器下一页 >

本教程提供的示例程序包含了一个小而实用的 32 位链接库,只需要在程序的 INCLUDE 后面添加如下代码行就可以使用该链接库:

INCLUDE Macros.inc

有些宏封装在了 Irvine32 链接库的过程中,这样传递参数就更加容易。其他宏则提供新的功能。下表详细介绍了每个宏。

宏名 形式参数 说明
mDump varName, useLabel 用变量名和默认属性显示一个变量
mDumpMem abbress, itemCount, componentsize 显示内存区域
mGotoxy X,Y 将光标位置设置在控制台窗口缓冲区
mReadString varName 从键盘读取一个字符串
mShow itsName, format 用各种格式显示一个变量或寄存器
mShowRegister itsName, regValue 显示32位寄存器名,并用十六进制显示其内容
mWrite text  向控制台窗口输出一个字符串文本
mWriteSpace count 向控制台窗口输出一个或多个空格
mWriteString buffer 向控制台窗口输岀一个字符串变量的内容

1) mDumpMem

宏 mDumpMem 在控制台窗口显示一个内存区域。向其传递的第一个实参为包含待显示内存偏移量的常数、寄存器或者变量,第二个实参应为待显示内存中存储对象的数量,第三个实参为每个存储对象的大小。

宏在调用mDumpMem库过程时,分别将这三个实参分配给 ESI、ECX 和 EBX。现假设有一数据定义如下:

.data
array DWORD 1000h, 2000h, 3000h, 4000h

下面的语句按照默认属性显示数组:

mDumpMem OFFSET array, LENGTHOF array, TYPE array

输出为:

Dump of offset 00405004
------------------------------
00001000  00002000  00003000  00004000

下面的语句则将同一个数组显示为字节序列:

mDumpMem OFFSET array, SIZEOF array, TYPE BYTE

输出为:

Dump of offset 00405004
------------------------------
00 10 00 00 00 20 00 00 00 30 00 00 00 40 00 00

下面的代码把三个数值压入堆栈,并设置好 EBX、ECX 和 ESI,然后调用 mDumpMem 显示堆栈:

mov eax,0AAAAAAAAh
push eax
mov eax,0BBBBBBBBh
push eax
mov eax,OCCCCCCCCh
push eax
mov ebx,1
mov ecx,2
mov esi,3
mDumpMem esp, 8, TYPE DWORD

显示出来的结果堆栈区域表明,宏已经先把 EBX、ECX 和 ESI 压入了堆栈。这些数值之后是在调用 mDumpMem 之前入栈的 3 个整数:

Dump of offset 0012FFAC
------------------------------
00000003 00000002 00000001 CCCCCCCC BBBBBBBB AAAAAAAA 7C816D4F
0000001A

实现宏代码清单如下:
mDumpMem MACRO address:REQ, itemCount:REQ, componentsize:REQ
;用 DumpMem 过程显示一个内存区域。
;接收:内存偏移量、显示对象的数量,以及每个存储对象的大小。
;避免用 EBX、ECX 和 ESI 传递实参。
    push ebx
    push ecx
    push esi
    mov esi, address
    mov ecx, itemCount
    mov ebx, componentSize
    call DumpMem
    pop esi
    pop ecx
    pop ebx
ENDM

2) mDump

宏 mDump 用十六进制显示一个变量的地址和内容。传递给它的参数有:变量名和(可选的)一个字符以表明在该变量之后应显示的标号。显示格式自动与变量的大小属性(BYTE、WORD 或 DWORD)匹配。

下面的例子展示了对 mDump 的两次调用::

.data
diskSize DWORD 12345h
.code
mDump diskSize           ; no label
mDump diskSize,Y        ; show label

代码执行后,产生的输出如下所示:

Dump of cffset 00405000
------------------------
00012345
Variable name: diskSize
Dump of offset 00405000
------------------------
00012345

下面是宏 mDump 的代码清单,它反过来又调用了 mDumpMem。代码用一个新的伪指令 IFNE (若不为空)来发现主调者是否向第二个形参传递了实参:
;-----------------------------------------------
mDump MACRO varName:REQ, useLabel
;用其已知属性显示一个变量。
;接收:varName为变量名。
;如果 useLabel 不为空,则显示变量名。
;-----------------------------------------------
    call Crlf
    IFNB <useLabel>
        mWrite "Variable name: &varName"
    ENDIF
    mDumpMem OFFSET varName, LENGTHOF varName, TYPE varName
ENDM
&varName 中的符号 & 是替换操作符,它允许将 varName 形参的值插入到字符串文本中。

3) mGotoxy

宏 mGotoxy 把光标定位在控制台窗口缓冲区内指定的行列上。可以向其传递 8 位立即数、内存操作数和寄存器值:

mGotoxy    10,20                  ;立即数
mGotoxy    row, col              ;内存操作数
mGotoxy    ch,cl                  ;寄存器值

实现 下面是宏的源代码清单:
;-----------------------------
mGotoxy MACRO X:REQ, Y:REQ
;设置光标在控制台窗口的位置。
;接收:X和Y坐标(类型为BYTE)。避免用DH和DL传递实参。
;-----------------------------
    push edx
    mov dh,Y
    mov dl,X
    call Gotoxy
    pop edx
ENDM
若宏的实参是寄存器,它们有时可能会与宏内使用的寄存器发生冲突。比如,调用 mGotoxy 时用了 DH 和 DL,那么就不会生成正确的代码。为了说明原因,现在来查看上述参数被替换后展开的代码:

1  push edx
2  mov dhr dl     ;;行
3  mov dl,dh      ;;列
4  call Gotoxy
5  pop edx

假设 DL 传递的是 Y 值,DH 传递的是 X 值,代码行 2 会在代码行 3 有机会把列值复制 到DL之前就替换了 DH的原值。

提示:只要有可能,宏定义应该用注释说明哪些寄存器不能用作实参。

4) mReadString

宏 mReadSrting 从键盘读取一个字符串,并将其存储在缓冲区。在这个宏的内部封装了一个对 ReadString 库过程的调用。需向其传递缓冲区名:

.data
firstName BYTE 30 DUP(?)
.code
mReadString firstName

下面是宏的源代码:
;-----------------------------------------   
mReadString MACRO varName:REQ
;从标准输入读到缓冲区。
;接收:缓冲区名。避免用 ECX 和 EDX 传递实参。
;-----------------------------------------   
    push ecx
    push edx
    mov edx,OFFSET varName
    mov ecx,SIZEOF varName
    call Readstring
    pop edx
    pop ecx
ENDM

5) mShow

宏 mShow 按照主调者选择的格式显示任何寄存器或变量的名字和内容。传递给它的是寄存器名,其后可选择性地加上一个字母序列,以表明期望的格式。字母选择如下:H = 十六进制,D = 无符号十进制,I 二有符号十进制,B 二二进制,N = 换行。

可以组合多种输出格式,还可以指定多个换行。默认格式为“HIN”。mShow 是一种有用的辅助调试工具,经常被 DumpRegs 库过程使用。可以把mShow当作调试工具,显示重要寄存器或变量的值。

【示例】下面的语句将 AX 寄存器的值显示为十六进制、有符号十进制、无符号十进制和二进制:

mov ax, 4096
mShow AX        ;默认选项:HTN
mShow AX,DBN    ;无符号十进制,二进制,换行

输出如下:

AX = 1000h +4096d
AX = 4096d 0001 0000 0000 0000b

【示例】下面的语句在同一行上,用无符号十进制格式显示 AX, BX, CX 和 DX:

;插入测试数值,显示4个寄存器:
mov ax, 1
mov bx, 2
mov cx, 3
mov dxz 4
mShow AX, D
mShow BX, D
mShow CX,D
mShow DX, DN

相应输出如下:

AX = Id BX = 2d CX = 3d DX = 4d

【示例】下面的代码调用 mShow,用无符号十进制格式显示 mydword 的内容,并换行:

.data
mydword. DWORD ?
.code
mS how mydword,DN

实现 mShow的实现代码太长不便在这里给岀,不过可以在本书安装文件夹(C : \Irvine)内的Macros.inc文件中找到完整代码。在编写mShow时,需要注意在寄存器被宏 自身的内部语句修改之前显示其当前值。

6) mShowRegister

宏 mShowRegister 显示单个 32 位寄存器的名称,并用十六进制格式显示其内容。传递给它的是希望被显示的寄存器名,其后紧跟寄存器本身。下面的宏调用指定了被显示的名称为 EBX:

mShowRegister EBX, ebx

产生的输出如下:

EBX=7FFD9000

下面的调用使用尖括号把标号括起来,其原因是标号内有一个空格:

mShowRegister <Stack Pointer>, esp

产生输出如下:

Stack Pointer=0012FFC0

实现宏的源代码如下:
;------------------------------------
mShowRegister MACRO regName, regValue
LOCAL tempStr
;显示寄存器名和内容。
;接收:寄存器名,寄存器值
;------------------------------------
    .data
    tempStr BYTE " &regName=",0
    .code
    push eax
    ;显示寄存器名
    push edx
    mov edx,OFFSET tempStr
    call WriteString
    pop edx
    ;显示寄存器内容
    mov eax,regValue
    call WriteHex
    pop eax
ENDM

7) mWriteSpace

宏 mWriteSpace 向控制台窗口输出一个或多个空格。可以选择性地向其传递一个整数形参,以指定空格数 ( 默认为一个 )。例如,下面的语句写了 5 个空格:

mWriteSpace 5

实现mWriteSpace的源代码如下:
;-------------------------------------------
mWriteSpace MACRO count:=<1>
;向控制台窗口输出一个或多个空格。
;接收:一个整数以指定空格数。
;默认个数为l。
;-------------------------------------------
LOCAL spaces
.data
spaces BYTE count DUP('    '),0
.code
    push edx
    mov edx,OFFSET spaces
    call WriteString
    pop edx
ENDM

8) mWriteString

宏 mWriteSrting 向控制台窗口输出一个字符串变量的内容。从宏的内部来看,它通过在同一语句行上传递字符串变量名简化了对 WriteString的调用。例如:

.data
str1 BYTE "Please enter your name: ", 0
.code
mWriteString str1

mWriteString 的实现如下,它将 EDX 保存到堆栈,然后把字符串偏移量赋给 EDX,在过程调用后,再从堆栈恢复 EDX 的值:
;------------------------------
mWriteString MACRO buffer:REQ
;向标准输出写一个字符串变量。
;接收:字符串变量名。
;------------------------------
    push edx
    mov    edx,OFFSET buffer
    call WriteString
    pop edx
ENDM
< 上一页宏的特性 实例:封装器下一页 >

编程帮,一个分享编程知识的公众号。跟着站长一起学习,每天都有进步。

通俗易懂,深入浅出,一篇文章只讲一个知识点。

文章不深奥,不需要钻研,在公交、在地铁、在厕所都可以阅读,随时随地涨姿势。

文章不涉及代码,不烧脑细胞,人人都可以学习。

当你决定关注「编程帮」,你已然超越了90%的程序员!

编程帮二维码
微信扫描二维码关注