吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 998|回复: 2

[汇编] 8086汇编-高级汇编语言技术

[复制链接]
啥都不会 发表于 2023-3-6 12:01 | 显示全部楼层 |阅读模式

本帖最后由 啥都不会 于 2023-3-6 16:45 编辑

前言:
这篇笔记主要学习 "杨季文《80x86汇编语言程序设计》第七章" --- 高级汇编语言技术。

第一部分:学习结构和记录,自定义数据结构;
第二部分:学习宏汇编,自定义宏指令,简化汇编程序;
第三部分:学习重复汇编和条件汇编伪指令,再次简化汇编程序;
第四部分:学习源程序的结合,通过将多个ASM文件进行合并,简化汇编程序。

注:这一章学习的大多内容都有些类似于高级语言。其中源程序的结合,就是高级语言的静态库,使用 INCLUDE 包含需要的文件,编译器会根据所需文件进行代码合并。

编程语言:
8086汇编

以下为主题内容:
结构和记录
为了使程序员能更方便、更有效地对数据进行组织和描述,宏汇编语言除了提供定义简单数据变量地伪指令(如DB和DW等)外,还提供了用于说明复杂数据类型地伪指令,利用这些伪指令能够说明复杂的数据类型,从而定义复杂的数据变量。
结构
结构类型的说明
在描述结构型数据或使用架构型变量之前,需要说明结构类型。用伪指令 STRUC 和 ENDS 把一系列数据定义语句包括起来就说明了一个结构体类型,一般格式如下:
[Asm] 纯文本查看 复制代码
结构名 STRUC
       数据定义语句序列
结构名 ENDS


案例:一个名为 PERSON 的结构类型:
[Asm] 纯文本查看 复制代码
PERSON STRUC
ID  dw ?
SCORE   db 0
PNAME   db 'ABCDEFGH'
PERSON ENDS


  • 组成结构的变量称为结构的字段,相应的变量称为字段名。
  • 一个结构中可以含有任意数目的字段,并且各字段可以有不同的长度(基本单位是字节),还可以独立地存取。结构中的字段也可以没有字段名。
  • 结构中的字段名代表了从结构的开始到相应字段的偏移。在说明结构体类型时,可以给字段赋初值,也可以不赋初值。
  • 如果字段是一个字符串,那么确保其初始值有足够的长度以适应可能最长的字符串。


如下面的 MESST 的结构类型:
[Asm] 纯文本查看 复制代码
MESST STRUC
MBUFF DB 100 DUP(?)
CRLF DB 0DH,0AH
ENDMARK DB 24H
MESST ENDS


结构 MESST 中的字段 MBUFF 和 CRLF 均含有多个值。在说明结构类型时,结构名必须是唯一的,各字段名也应该是唯一的。在说明结构类型时不进行任何存储分配,只有在定义结构变量时才进行存储分配。

注:标记一个结构类型结束的伪指令与标记一个段结束的伪指令用相同的助记符 ENDS,汇编程序通过上下文理解 ENDS 的含义,所以要确保每一个 SEGMENT 伪指令和每一个 STRUC 伪指令有各自对应的 ENDS 伪指令。


结构变量的定义
在说明了结构类型后,就可以定义相应的结构变量。结构变量定义的一般格式如下:
[Asm] 纯文本查看 复制代码
[变量名] 结构名 <[字段值表]>


  • 变量名就是当前定义的结构变量的名称,结构变量名也可以省略。如果省略,那么就不能直接通过符号名访问该结构变量。
  • 结构名是在说明结构类型时所用的名字。
  • 字段值表用来给结构变量的各字段赋初始值,其中各字段值的排列顺序及类型应与结构定义时的各字段相一致,中间以逗号分隔。
  • 如果某个字段采用在说明结构时所给定的缺省初值,那么可简单地用逗号表示;如果结构变量的所有字段均如此,那么可省去字段值表,但仍必须保留一对尖括号。

例如,设上述结构 PERSON,那么可定义如下结构变量:
[Asm] 纯文本查看 复制代码
STUDENT1 PERSON <103,88,'WANG'>    ;三个字段都重赋值
STUDENT2 PERSON <104,,'LIMING'>    ;字段 SCORE 仍用缺省初值
STUDENT  PERSON <>                 ;三个字段均用缺省初值
         PERSON 99 dup (<>)        ;定义99各结构变量,初值不变


对宏汇编程序 MASM 而言,如果某个字段有多个值,那么在定义结构变量时,就不能给该字段赋初始值。如上面说明地结构 MESST,不饿能给 MBUFF 字段和CRLF字段重赋初值。
下面定义结构变量的语句:
[Asm] 纯文本查看 复制代码
MESS1 MESST <>
MESS2 MESST <,,0>


结构变量及其字段的访问
通过结构变量名可直接存取结构变量。若要存取结构变量中的某一字段,则可采用如下形式:
[Asm] 纯文本查看 复制代码
结构变量名.结构字段名


结构变量与结构字段名中间用点号分隔,并且结构字段名所代表的字段必须是对应结构所具有的字段。这种形式表示的变量的地址偏移值是结构变量地址(起始地址)的偏移值与相应字段偏移值之和。

案例:演示结构变量的直接寻址和变址寻址(相对基址)
[Asm] 纯文本查看 复制代码
;程序名:T7-1.asm
;功能:演示结构变量的直接寻址和变址寻址(相对基址)
assume cs:code,ds:data

DATE STRUC
YEAR DW ?
MONTH DB ?
DAY DB ?
DATE ENDS

data segment
YESTERDAY DATE <1995,7,17>
TODAY DATE <1995,7,18>
TOMORROW DATE <1995,7,19>
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;直接寻址
    mov AL,YESTERDAY.DAY
    mov ah,TODAY.MONTH
    mov TOMORROW.YEAR,dx
    ;变址寻址
    mov bx,offset YESTERDAY
    mov al,[bx].MONTH
code ends
end start



                               
登录/注册后可看大图



                               
登录/注册后可看大图



                               
登录/注册后可看大图


例1:数据文件 SCORE.DAT 中一次存放着30个学生的成绩记录,文件(成绩)记录具有如下字段:
[Asm] 纯文本查看 复制代码
学号        整数    2字节
姓名        字符串  8字节
语文成绩    整数    1字节
数学成绩    整数    1字节
外语成绩    整数    1字节


写一个程序计算三门课程的总分,把学号和总分依次写到文件 SCORE.SUM 中。SCORE.SUM 文件记录两个字段,第一个字段是学号,第二个字段是总分(用2字节表示)。
实现流程是:
  • 打开文件 SCORE.DAT;
  • 循环处理每个学生的成绩,把学号和总分放到缓冲区中;
  • 关闭文件 SCORE.DAT
  • 新建文件 SCORE.SUM
  • 把缓冲区的内容写入文件 SCORE.SUM
  • 关闭文件 SCORE.SUM


[Asm] 纯文本查看 复制代码
;程序名:T7-2.asm
;功能:写一个程序计算三门课程的总分,把学号和总分依次写到文件 SCORE.SUM 中。SCORE.SUM 文件记录两个字段,第一个字段是学号,第二个字段是总分(用2字节表示)。
assume cs:code,ds:data

;定义常量
COUTN = 30

;原始成绩结构体 SCORE
SCORE STRUC
IDS dw ?             ;学号
SNAME db 8 dup (' ')    ;姓名
LANG db 0           ;语文成绩
MATH db 0           ;数学成绩
ENG  db 0           ;英语成绩
SCORE ENDS

;对应学号、姓名和总分的 ITEM 的定义
ITEM STRUC
IDD  dw ?            ;学号
SUM dw 0            ;总分,总分300会超过1字节空间。
ITEM ENDS

data segment
BUFFER SCORE <>              ;存放原始成绩的缓冲区
STABLE ITEM COUTN dup (<>)   ;预留存储总表的缓冲区
FNAME1 db 'SCORE.DAT',0     ;原始文件名
FNAME2 db 'SCORE.SUM',0     ;存储新的数据文件名
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    mov si,offset STABLE    ;STABLE 需要变址寄存器存储数据
    ;打开文件 SCORE.DAT
    mov dx,offset FNAME1
    mov ax,3d00h
    int 21h                 ;以只读方式打开 SCORE.DAT
    jc stop                 ;文件打开失败,终止程序

    ;一次性读13个字节,初始化各寄存器
    mov bx,ax
    mov cx,13
    mov dx,offset BUFFER
READ:
    mov ah,3fh
    int 21h
    ;判断有没有读取到字符,没有读到就说明已经读到文件尾了。
    cmp ax,cx
    jnz clsf1
    ;将结果存储到 STABLE 中
    mov ax,BUFFER.IDS
    mov [si],ax
    xor ax,ax
    mov al,BUFFER.LANG
    add al,BUFFER.MATH
    add al,BUFFER.ENG
    adc ah,0
    add si,TYPE STABLE.IDD
    mov [si],ax
    ;
    add si,TYPE STABLE.SUM
    jmp READ
clsf1:                      ;关闭文件 SCORE.DAT
    mov ah,3eh
    int 21h
    cmp word ptr STABLE,0
    jz stop                 ;如果缓冲区中未读取任何数据,结束程序
    ;新建 SCORE.SUM
    mov dx,offset FNAME2
    mov cx,0
    mov ax,3c00h
    int 21h
    ;写数据需要变址寄存器
    mov si,offset STABLE
    ;写数据
    mov bx,ax
    mov cx,4
WRITE:
    ;判断有没有写到缓冲区的最后一个字符
    cmp word ptr [si],0
    jz clsf2
    mov dx,si
    mov ah,40h
    int 21h
    add si,TYPE ITEM
    jmp WRITE
clsf2:
    mov ah,3eh
    int 21h
stop:
    mov ax,4c00h
    int 21h
code ends
end start



                               
登录/注册后可看大图



                               
登录/注册后可看大图


在程序 T7-2.asm 中,定义了两个结构 SCORE 和 ITEM,用来描述文件记录的字段组成。利用 SCORE 定义了存放原始成绩记录的缓冲区。利用 ITEM 定义了一张总表,然后借助指向总分表当前项的指针访问当前的学号和总分字段。此外还利用了 TYPE 得到结构的字节数。

例2:写一个求字符串长度的子程序。子程序调用过程如下:

[Asm] 纯文本查看 复制代码
;程序名:T7-3.asm
;功能:写一个求字符串长度的子程序。子程序调用过程如下:
;子程序名:STRLEN
;功   能:检测字符串长度
;入口参数:字符串首地址的段值和偏移在堆栈顶
;出口参数:AX=字符串长度
;说明:(1)字符串以0结尾;字符串长度不包括结尾标志。
;     (2)本过程是一个远过程

assume cs:code,ds:data

;堆栈结构体 PARM
PARM STRUC
BPREG DW ?      ;对应 bp 寄存器保存单元
RETADR DD ?     ;对应返回地址
STROFF DW ?     ;对应入口参数中的偏移
STRSEG DW ?     ;对应入口参数中的段值
PARM ENDS

data segment
string1 db 'Hello World!',0
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    mov ax,seg string1
    push ax
    mov ax,offset string1
    push ax
    call far ptr STRLEN
    mov ax,4c00h
    int 21h
;-------------------------------------------------------
;子程序名:STRLEN
;功   能:检测字符串长度
;入口参数:字符串首地址的段值和偏移在堆栈顶
;出口参数:AX=字符串长度
;说明:(1)字符串以0结尾;字符串长度不包括结尾标志。
;      (2)本过程是一个远过程
;--------------------------------------------------------
STRLEN PROC FAR
    PUSH BP
    MOV BP,SP
    PUSH DS
    PUSH SI
    MOV DS,[BP].STRSEG  ;取字符串首地址的段值
    MOV SI,[BP].STROFF  ;取字符串首地址的偏移
    XOR AL,AL
STRLEN1:
    CMP BYTE PTR [SI],AL
    JZ STRLEN2
    INC SI
    JMP STRLEN1
STRLEN2:
    MOV AX,SI
    SUB AX,[BP].STROFF
    POP SI
    POP DS
    POP BP
    RET
STRLEN  ENDP
code ends
end start



                               
登录/注册后可看大图



                               
登录/注册后可看大图


记录
记录类型为按二进制位存取数据或信息提供了方便。
记录类型的说明
在描述记录型数据或使用记录型变量之前,需要说明记录类型。伪指令 RECORD 用于说明记录类型,一般格式如下:

[Asm] 纯文本查看 复制代码
记录名 RECORD 字段 [,字段...]


记录名标识说明记录类型:字段表示构成记录的字段的名字、宽度和初值。每一字段的格式如下:
[Asm] 纯文本查看 复制代码
字段名:宽度[=表达式]


字段名是记录字段的名字。宽度表示相应字段所占的位数,宽度必须是常数,宽度最大为 16 位。表达式的值将作为相应字段的初值。如果初值的数据宽度大于宽度,则汇编时将会产生错误提示信息。如果某个字段没有初值,那么缺省的初值被置为0.

一个记录可以含有多个字段,字段间用逗号分隔。但在一般情况下各字段的宽度之和不超过 16。例如:
[Asm] 纯文本查看 复制代码
COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3


上述记录类型 COLOR 含四个字段(BLINK、BACK、INTENSE、FORE),各字段均没有初值,他们的宽度分别是1、3、1、3。这四个字段所占总宽度正好是 8 位,所以也称为字节记录类型。这四个字段的具体意义如下图所示:

                               
登录/注册后可看大图


BLINK(闪烁)占1各数据位,BACK(背景色)占3个数据位,INTENSE(亮度)占1个数据位,FORE(前景色)占3个数据位。
注:在说明记录类型时,不实际分配存储单元

如果一个记录中所有说明字段的总宽度大于 8,那么汇编程序会给对应的记录变量分配两字节,否则仅给对应的记录变量分配一字节。第一个字段放在记录左边的较高有效位,随后说明的字段放在右边后续位上,如果说明的字段总宽度不正好是8位或16位,那么向右对齐,记录高端未说明的位置为0。

例如:
[Asm] 纯文本查看 复制代码
ABCD RECORD AA:5=12,BB:3=6,CC:4=3


上述记录类型 ABCD 含三个字段,这三个字段所占各位如下图所示:

                               
登录/注册后可看大图

注:一共是 12 位,因不满 16 位,在最高位补 4 个 0


记录变量的定义
定义记录变量的一般格式如下:
[Asm] 纯文本查看 复制代码
[变量名] 记录名 <[字段值表]>


  • 变量名就是当前定义的记录变量的名称,记录变量名可以省略,如果省略,那么就不能直接通过符号名访问该记录变量。
  • 记录名是在说明记录类型时所用的名字。
  • 字段值表用来记录变量的各字段初值,各字段值的排列顺序及大小应与记录说明时的各字段相一致,中间已逗号分隔。如果某个字段采用在说明记录时所给定的初值,那么可简单使用逗号表示;如果记录变量的所有字段均如此,那么可省去字段值表,但仍必须保留一对尖括号。

例如:定义上述记录类型 COLOR,那么可定义如下结构变量:
[Asm] 纯文本查看 复制代码
WARING  COLOR <1,0,1,4>     ;该字节值是 8CH
        COLOR <,3,,110B>    ;该字节是 36H
COLORST COLOR 32 dup(<>)    ;32 个字节



注:如果有 7 位宽时,可定义为一字符


记录专用操作符
操作符 WIDTH 和 MASK 仅与记录一起使用,得到已说明记录的不同方面的常数值。

(1)操作符 WIDTH
操作符 WIDTH 返回记录或记录中字段以位为单位的宽度。一般格式如下:
[Asm] 纯文本查看 复制代码
WIDTH 记录名
或者
WIDTH 记录字段名



案例:
[Asm] 纯文本查看 复制代码
;程序名:T7-4.asm
;功能:记录变量操作演示

assume cs:code,ds:data

COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3

data segment
WARING  COLOR <1,2,1,4>
data ends

code segment
start:
    xor ax,ax
    mov al,WARING
    ;
    sub al,WIDTH COLOR
    mov DH,WIDTH BACK
    ADD BH,WIDTH INTENSE
code ends
end start




                               
登录/注册后可看大图


(2)操作符 MASK
一般格式:
[Asm] 纯文本查看 复制代码
MASK 记录名
或者
MASK 记录字段


操作符 MASK 返回一个 8 位或 16 位二进制数,指定字段各个位置为1,其余位为0。如果记录是字节记录类型,那么就是一个 8 位二进制数,如果记录类型是字记录类型,那么就是一个 16 位二进制数。
[Asm] 纯文本查看 复制代码
;程序名:T7-4.asm
;功能:记录变量操作演示

assume cs:code,ds:data

COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3
ABCD RECORD AA:5=12,BB:3=6,CC:4=3

data segment
WARING  COLOR <1,2,1,4>
data ends

code segment
start:
    xor ax,ax
    mov al,WARING
    ;WIDTH
    sub al,WIDTH COLOR
    mov DH,WIDTH BACK
    ADD BH,WIDTH INTENSE
    ;MASK
    mov al,MASK BLINK       ;BLINK 1位置1,其余为0
    or al,MASK FORE         ;FORE 3位置1,其余为0
    and dx,MASK ABCD        ;ABCD 置1
code ends
end start



                               
登录/注册后可看大图


(3)记录字段
记录字段名作为一个特殊的操作符,它不带操作数,直接返回该字段移到所在记录的最右端所需移动的位数。
[Asm] 纯文本查看 复制代码
mov al,BLINK
mov cl,INTENSE



                               
登录/注册后可看大图



记录及其字段的访问

由于 8086/8088没有位操作指令,记录类型和记录操作符只能够提供访问记录中字段的便利。
[Asm] 纯文本查看 复制代码
;程序名:T7-5.asm
;功能:记录及其字段的访问

assume cs:code,ds:data

COLOR RECORD BLINK:1,BACK:3,INTENSE:1,FORE:3    ;说明记录类型

data segment
CHAR db 'A'
ATTR COLOR <0,0,1,7>        ;定义记录变量
data ends

code segment
start:
    mov bp,1 shl WIDTH BACK     ;置循环计数器
next:
    mov ah,9                    ;在当前光标位置显示字符
    mov bh,0
    mov al,CHAR
    mov bl,ATTR
    mov cx,1
    int 10h
    mov al,ATTR                 ;取显示属性(记录变量)
    mov ah,al
    and al,not MASK BACK        ;析出除背景的其他位
    mov cl,BACK
    shr ah,cl                   ;把背景字段移至右端
    inc ah                      ;调整背景色
    shl ah,cl                   ;再向左移到原位
    and ah,MASK BACK            ;屏蔽除背景位的其他位
    or ah,al                    ;和其他原值合并
    mov ATTR,ah                 ;保存属性
    ;接收键盘输入
    mov ah,0
    int 16h
    dec bp
    jnz next
    ;
    mov ax,4c00h
    int 21h
code ends
end start



宏是宏汇编语言的主要特征之一。在汇编语言源程序中,若某程序片段需要多次使用,为了避免重复书写,那么可以把他定义为一条宏指令。在写源程序时,程序员用宏指令来表示程序片段;在汇编时,汇编程序用对应的程序片段代替宏指令。

宏指令的定义和使用
宏指令在使用之前要先定义。宏定义一般格式如下:
[Asm] 纯文本查看 复制代码
宏指令 MACRO [形式参数]
      .
      .
      ENDM


其中,MACRO 和 ENDM 是一对伪指令,在宏定义中,它们必须成对出现,表示宏定义的开始和宏定义的结束。MACRO 和 ENDM 之间的内容称为宏定义体,可以是由指令、伪指令和宏指令构成的程序片段。宏指令名由用户指定,使用一般标号命名规则。可选的形式参数表可由若干个参数组成,各形式参数间用逗号分隔。

例如:把将 AL 寄存器内的低4位转换为对应十六进制数 ASCII 码的程序定义为一个宏:
[Asm] 纯文本查看 复制代码
HTOASC AMCRO
    AND AL,0FH
    ADD AL,90H
    DAA
    ADC AL,40H
    DAA
    ENDM


比如把 DOS 的 1 功能调用从键盘读一个字符的程序片段定义为一个宏
[Asm] 纯文本查看 复制代码
GETCH MACRO
    MOV AH,1
    INT 21H
    ENDM



在定义宏指令后,就可以使用宏指令来表示对应的程序片段,这称为宏调用。宏调用一般格式如下:
[Asm] 纯文本查看 复制代码
宏指令名    [实参数表]


其中,实参数表中的实参数应该与宏定义时的形式参数表中的形式参数相对应。

下面的程序调用了两个刚定义的两个宏:
[Asm] 纯文本查看 复制代码
;程序名:T7-6.asm
;功能:演示宏汇编代码

assume cs:code

HTOASC MACRO
    AND AL,0FH
    ADD AL,90H
    DAA
    ADC AL,40H
    DAA
    ENDM

GETCH MACRO
    MOV AH,1
    INT 21H
    ENDM

code segment
start:
    GETCH
    mov ah,al
    shr al,1
    shr al,1
    shr al,1
    HTOASC
    xchg ah,al
    HTOASC
    ;
    mov ax,4c00h
    int 21H
code ends
end start


对源程序汇编时,汇编程序把源程序中的宏指令替换成对应的宏定义体,这称为宏展开或宏扩展。


                               
登录/注册后可看大图


注:宏汇编指令不能放在程序结尾 END START 之后。


宏指令的用途
缩短源代码
若在源程序中要多次使用到某个程序片段,那么就可以把此程序片段定义为一条宏指令。例如,把使光标另起一行的程序片段写成如下宏:
[Asm] 纯文本查看 复制代码
CRLF MACRO
    XOR BH,BH
    MOV AH,14
    MOV AL,0DH
    INT 10H
    MOV AL,0AH
    INT 10H
    ENDM



扩充指令集
CPU 的指令集是确定的,但利用宏能在汇编语言中在形式上崔指令进行扩充。扩充后的指令集是机器指令集与宏指令集的并集。这不仅能方便源程序的编写,而且便于理解源程序。
例如,把8个通用寄存器全部压入堆栈的功能:
[Asm] 纯文本查看 复制代码
PUSHA MACRO
    PUSH AX
    PUSH BX
    PUSH CX
    PUSH DX
    PUSH SP
    PUSH BP
    PUSH SI
    PUSH DI
    ENDM



改变某些指令助记符的意义

宏指令可以与指令助记符或伪操作指令名相同,在这种情况下,宏指令的优先级最高,而同名的指令或伪操作指令就失效了。利用宏指令的这一点,可以改变指令助记符的意义。
例如,在定义如下宏指令后,助记符 LODSB 所表示指令的意义就变化了:
[Asm] 纯文本查看 复制代码
LODSB MACRO
    MOV AH,[SI]
    INC SI
    ENDM


注:定义与指令同名的宏指令后,根据编译器的不同,可能会出现警告信息。

宏指令中参数的使用
宏指令可以不带参数,如上面定义的宏指令 GETCH 和 PUSHA 等。但往往带参数的宏指令更具有灵活性。

宏指令的参数很灵活
(1)宏指令的参数可以是常数、寄存器和存储单元,也可以是表达式。
例1:在逻辑左指令 SHL 的基础上定义一条宏指令 SHLN,它能实现指定次数的左移。
[Asm] 纯文本查看 复制代码
SHLN MACRO REG,NUM
    PUSH CX
    MOV CL,NUM
    SHL REG,CL
    POP CX
    ENDM


宏指令调用:
[Asm] 纯文本查看 复制代码
SHLN BL,5
SHLN SI,9
SHLN AX,CL


(2)宏指令的参数可以是操作码
例2:下面的宏指令 MANDM 有三个参数,第一个参数 OPR 作为操作符使用:
[Asm] 纯文本查看 复制代码
MANDM MACRO OPR,X,Y
    MOV AX,X
    OPR AX,Y
    MOV X,AX
    ENDM



宏调用参数个数可以与定义时不一致
一般来说,宏调用时使用的实参个数应该与宏定义时的形参一致,但汇编程序并不要求它们必须相等。
  • 若实参个数多余形参个数,那么多余的实参被忽略。
  • 若实现的个数少于形参个数,那么多余的形参用 “空” 代替。
  • 另外必须注意,宏展开后即实参取代形参后,所得的语句必须是有效的,否则汇编程序将会提示出错。

例如,使用宏指令
[Asm] 纯文本查看 复制代码
MANDM SUB,var1,var2,var3
后,多余的参数 VAR3 被忽略了。

                               
登录/注册后可看大图


特殊的宏运算
为了方便宏的定义和调用,汇编程序还支持特殊的运算符,它们适用于宏的定义或调用,还适用于重复块。支持的特殊运算符如下表所示:

                               
登录/注册后可看大图


强迫替换运算符 “&”
在宏定义中,若参数紧跟在其它在字符前或后,或者参数出现带引号的字符串中时,就必须使用该运算符,以区分参数。

例1:宏指令 jump 中,参数 CON 作为操作码的部分。
[Asm] 纯文本查看 复制代码
;程序名:T7-8.asm
;功能:演示 强迫替换运算符& 的使用

assume cs:code

JUMP MACRO CON,LAB
    J&CON LAB
    ENDM

code segment
start:
    JUMP NZ,next
    ;
next:
    mov ax,4c00h
    int 21H
code ends
end start


197099102-939d1e82-6ac8-46f3-8e5c-8dd3a9d543e2.png

197099102-939d1e82-6ac8-46f3-8e5c-8dd3a9d543e2.png

例2:下面定义的宏 MSGGEN 中,两个参数合并成标号,一个参数用在字符串中。
[Asm] 纯文本查看 复制代码
;程序名:T7-8-1.asm
;功能:演示 强迫替换运算符& 的使用

assume cs:code,ds:data

MSGGEN MACRO LAB,NUM,XYZ
    LAB&NUM db 'HELLO MR.&XYZ',0DH,0AH,24H
    ENDM

data segment
MSGGEN MSG,1,TAYLOR
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ah,9
    mov dx,offset MSG1
    int 21h
next:
    mov ax,4c00h
    int 21H
code ends
end start



197100299-3095ce8d-489c-4445-9119-7bb8cafa1642.png

字符串原样传递运算符 “<>”
字符串原样传递运算符是一对尖括号,在宏调用、重复块和条件汇编中,由它括起的内容作为一个字符串。在宏调用时,若实参包含逗号或空格等间隔符,则必须使用该运算符,以保证实参的完整性。若实参是某个有特殊意义的字符,为了使它只表示字符本身,也可以使用该运算符。
[Asm] 纯文本查看 复制代码
;程序名:T7-9.asm
;功能:演示 字符串原样传递运算符 “<>” 的使用

assume cs:code,ds:data

DFMESS MACRO MESS
    db '&MESS',0DH,0AH,0
    ENDM

data segment
DFMESS <This is a example>
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图



文字字符运算符 “!”
该运算符使其后的一个字符只作为一般字符。在宏调用时,如果实参中含有一些特殊字符,为了使这些字符作为一般字符来处理,那就必须在其前写上该字符。

例如:利用上述的宏 DFMESS 定义字符串 "Can not enter > 99"。
[Asm] 纯文本查看 复制代码
;程序名:T7-10.asm
;功能:演示 文字字符运算符 “!” 的使用

assume cs:code,ds:data

DFMESS MACRO MESS
    db '&MESS',0DH,0AH,0
    ENDM

data segment
DFMESS <Can not enter > 99>
DFMESS <Can not enter !> 99>
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图


表达式运算符 “%”
在宏调用时,使用该运算符能把其后表达式的结果作为实参替换,而非表达式自身。
[Asm] 纯文本查看 复制代码
;程序名:T7-11.asm
;功能:演示 表达式运算符 “%” 的使用

assume cs:code,ds:data

DFMESS MACRO MESS
    db '&MESS',0DH,0AH,0
    ENDM

data segment
DFMESS %(12+3-4)
DFMESS 12+3-4
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图


宏注释
在宏定义中,如果注释以两个分号引导,那么扩展时该注释不会出现。

宏与子程序的区别
采用宏和子程序这两种方法均能达到简化源程序的目的。但是这两者之间存在质的不同。
  • 宏调用是通过宏指令名进行的,在汇编时,由汇编程序把宏展开,有多次宏调用,就有相应次的宏扩展,因此并不简化目标程序;子程序调用是在程序执行期间执行 CALL 指令进行的,子程序代码只在目标程序中出现一次,所以目标程序也得到相应的简化。
  • 宏调用时的参数由汇编程序通过实参替换形参的方式实现传递,所以参数很灵活;子程序调用时的参数需通过寄存器,堆栈或约定的内存单元传递。
  • 宏调用是在汇编时完成,所以不需要额外的时间开销;子程序调用和子程序返回均需要时间,且涉及堆栈。

总之,当程序片段不长,速度是主要矛盾时,通常采用宏只能够的方法简化源程序;当程序片段较长,额外操作所附加的时间就不明显,而节约存储空间是主要矛盾时,通常采用子程序的方法简化源程序和目标程序。

与宏有关的伪指令

局部变量说明伪指令 LOCAL
在宏定义体中可以使用标号。例如:
[Asm] 纯文本查看 复制代码
HTOASC MACRO
    AND AL,0FH
    CMP AL,9
    JBE ISDECM
    ADD AL,7
ISDECM:
    ADD AL,30H
    ENDM


如果在程序中多次调用上述宏 HTOASC,汇编时将出现重复定义错误,如下图所示:

                               
登录/注册后可看大图


原因是每次展开宏 HTOASC 都得到一个标号 ISDECM。为此,汇编提供了伪指令 LOCAL,供程序员说明宏的局部标号。

伪指令 LOCAL 的一般格式如下:
[Asm] 纯文本查看 复制代码
LOCAL 标号表


标号表由标号构成,标号间用逗号隔开。汇编程序在每次展开宏时,总把由 LOCAL 伪指令说明的标号用唯一的符号(??0000至??FFFF)代替,从而避免标号重定义错误。例如:
[Asm] 纯文本查看 复制代码
;程序名:T7-12.asm
;功能:演示 局部变量说明伪指令 LOCAL 的使用

assume cs:code

HTOASC MACRO
    LOCAL ISDECM
    AND AL,0FH
    CMP AL,9
    JBE ISDECM
    ADD AL,7
ISDECM:
    ADD AL,30H
    ENDM

code segment
start:
    xor ax,ax
    mov al,7
    HTOASC
    mov al,5
    HTOASC
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图



                               
登录/注册后可看大图


注:LOCAL 伪指令用在宏定义体内,而且必须是伪指令 MACRO 后的第一条语句,在 MACRO 和 LOCAL 伪指令之间不允许有注释和分号标志。

清除宏定义伪指令 PURGE
伪指令 PURGE 的作用是告诉汇编程序取消某些宏。取一般格式如下:
[Asm] 纯文本查看 复制代码
PURGE 宏名表


宏名表由宏名构成,宏名之间用逗号分隔。汇编程序在遇到 PURGE 伪指令后,就取消由宏名表所列出的宏定义,此后不再扩展这些宏。
例如:定义如下宏
[Asm] 纯文本查看 复制代码
;程序名:T7-13.asm
;功能:演示 清除宏定义伪指令 PURGE 的使用

assume cs:code

LODSB MACRO
    MOV AH,[SI]
    INC SI
    ENDM

code segment
start:
    LODSB
    PURGE LODSB
    LODSB
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图



                               
登录/注册后可看大图


注:微软的汇编器不允许这样用,因此编译之后原 LODSB 指令是没有的。



终止宏扩展伪指令 EXITM
伪指令 EXITM 通知汇编程序结束当前宏调用的扩展。一般格式如下:
[Asm] 纯文本查看 复制代码
EXITM


当遇到 EXITM 时,汇编程序立即退出宏,在宏剩下的语句不被扩展。如果在一嵌套的宏内遇到伪指令 EXITM,则退出到外层宏。
伪指令 EXITM 通常与条件伪指令一起使用,以便在规定的条件跳过宏内的最后的语句。



宏定义的嵌套
宏定义体中调用宏
宏汇编语言允许在宏定义体中使用宏调用,其限制条件仍是:必须先定义后调用。
如下宏 WHTOASC 的定义体内就调用了宏 HTOASC
[Asm] 纯文本查看 复制代码
;程序名:T7-14.asm
;功能:演示 嵌套宏 的使用

assume cs:code

HTOASC MACRO
    AND AL,0FH
    ADD AL,90H
    DAA
    ADC AL,40H
    DAA
    ENDM

WHTOASC MACRO
    MOV AH,AL
    SHR AL,1
    SHR AL,1
    SHR AL,1
    SHR AL,1
    HTOASC
    XCHG AH,AL
    HTOASC
    ENDM

code segment
start:
    MOV AX,0102H
    WHTOASC
    ;
    mov ax,4c00h
    int 21H
code ends
end start



宏定义体中定义宏指令

宏定义体中还可含有宏定义,但只有在调用外层的宏后,才能调用内层的宏。原因是只有在调用了外层的宏后,内层的宏定义才有效。

下面的宏 DEFMAC 含有一个宏定义,并且外层的宏参数 MACNAME 是内层的宏指令名:
[Asm] 纯文本查看 复制代码
;程序名:T7-14-1.asm
;功能:演示 嵌套宏 的使用

assume cs:code,ds:data

DEFMAC MACRO MACNAME,OPERATOR
    MACNAME MACRO X,Y,Z
    PUSH AX
    MOV AX,X
    OPERATOR AX,Y
    MOV Z,AX
    POP AX
    ENDM
    ENDM

data segment
var1 dw 1
var2 dw 1
result dw 0
data ends

code segment
start:
    DEFMAC ADDITION,ADD
    ADDITION var1,var2,result
    ;
    DEFMAC SUBTRACT,SUB
    ;
    DEFMAC LOGOR,OR
    mov ax,4c00h
    int 21H
code ends
end start


197241380-ae668eb4-2a44-4f65-bb62-99813b9e97cf.png

注:宏指令 DEFMAC 的参数 MACNAME 作为内部嵌套宏的指令名,从而实现 DEFMAC 执行之后同时声明了宏 ADDITION 指令。


重复汇编
有时程序中会连续地重复执行完全相同或几乎相同地一组语句时,当出现这种情况时,可考虑重复伪指令定义地重复块,以简化源程序。
重复块是允许建立在重复语句块的宏的一种特殊形式。它们与宏的不同之处在于它们没有被命名,并因而不能被调用。但像宏一样,它们可以有参数,且在汇编过程中参数可被变量替换;宏运算符、用伪指令 LOCAL 说明的符号等可用在重复块中;重复块总是由伪指令 ENDM 结束。

伪指令 REPT
伪指令 REPT 用于创建重复块,重复块的重复次数由一数值表达式给定。一般格式如下:
[Asm] 纯文本查看 复制代码
REPT 表达式
需要重复的语句组
ENDM



1. 宏汇编程序会把 “需要重复的语句组” 连续地重复汇编,由表达式值决定重复此时。
2. 表达式必须可求出数值常数。
3. 任何有效汇编程序语句均可安排在 “需重复语句组” 中。

例如:把字符 A 到 Z 的 ASCII 码填入数组 TABLE 中:
[Asm] 纯文本查看 复制代码
;程序名:T7-15.asm
;功能:演示 伪指令 “REPT” 的使用

assume cs:code,ds:data

CHAR = 'A'

data segment
TABLE LABEL BYTE
    REPT 26         ;重复块开始,规定重复次数
    DB CHAR         ;需要重复的语句1
CHAR = CHAR+1       ;需要重复的语句2
    ENDM            ;结束块重复
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图



伪指令 IRP
伪指令 IRP 用于创建重复块,重复次数和每次重复时使用的实参由实参数列表决定。一般格式如下:
[Asm] 纯文本查看 复制代码
IRP 形式参数,<实参1,实参2,....,实参n>
重复语句块
ENDM


  • 实参的个数决定了重复的次数。
  • 宏汇编程序会把 “需要重复的语句组” 连续地重复汇编规定的次数,并在每次重复时一次用相应位置的实参代替 “需重复语句组” 中的形式参数。
  • 实参数列表应放在一对尖括号内,若有多个实参数,则各实参数间用逗号分隔。

例如:把 0~9 的平方值存入数组 QUART 中
[Asm] 纯文本查看 复制代码
;程序名:T7-15-1.asm
;功能:演示 伪指令 IRP 的使用

assume cs:code,ds:data

data segment
;把 0~9 的平方值存入数组 QUART 中
QUART LABEL BYTE
    IRP x,<0,1,2,3,4,5,6,7,8,9>         ;重复块开始,尖括号中的参数规定重复次数
    DB x*x          ;需要重复的语句1
    ENDM            ;结束块重复
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,4c00h
    int 21H
code ends
end start



197312728-3e3b7605-3975-48fd-b8a4-90bb53128c59.png

再如:把若干个寄存器值压入堆栈
[Asm] 纯文本查看 复制代码
;程序名:T7-15-1.asm
;功能:演示 伪指令 IRP 的使用

assume cs:code,ds:data

data segment
;把 0~9 的平方值存入数组 QUART 中
QUART LABEL BYTE
    IRP x,<0,1,2,3,4,5,6,7,8,9>         ;重复块开始,尖括号中的参数规定重复次数
    DB x*x          ;需要重复的语句1
    ENDM            ;结束块重复
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;把若干个寄存器值压入堆栈
    IRP REG,<AX,BX,CX,DX>
    push REG
    ENDM
    ;
    mov ax,4c00h
    int 21H
code ends
end start


197313323-3a49da06-a77e-417b-a0ba-afa14bcfda75.png

伪指令 IRPC
伪指令 IRPC 与 伪指令 IRP 相似,但实参数列表是一个字符串,一般格式如下:
[Asm] 纯文本查看 复制代码
IRPC 形式参数,字符串
需要重复的语句组
ENDM


  • 字符串的长度规定了重复的次数。
  • 宏汇编程序会把 “需要重复的语句组” 连续地重复汇编规定地次数,并在每次重复时一次用 “字符串” 中的一个字符作为参数代替 “需要重复语句组” 中的形式参数。
  • 如果字符串含有空格、逗号等分隔符,那么字符串需要用尖括号括起来。

例如:把从 2 开始的 10 个偶数存入字数组 TABLE 中
[Asm] 纯文本查看 复制代码
;程序名:T7-15-2.asm
;功能:演示 伪指令 IRPC 的使用

assume cs:code,ds:data

data segment
;把从 2 开始的 10 个偶数存入字数组 TABLE 中
TABLE LABEL BYTE
    IRPC x,0123456789   ;重复块开始,字符串个数规定重复次数
    dw (x+1)*2          ;需要重复的语句1
    ENDM                ;结束块重复
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图


条件汇编
条件汇编语句提供根据某种条件决定是否汇编某段源程序的功能。
再源程序中使用条件汇编语句的主要目的是:
(1)通过在汇编前或汇编时改变某种体哦啊急啊 ,从而方便地产生不同地程序;
(2)增强宏定义能力,使得宏地适用范围更广
(3)改变汇编效率。

尽管条件汇编语句在形式上与高级语言中地条件语句相似,但本质上却完全不同。条件汇编语句是说明性语句,是由伪指令构成,它地功能是由编译器实现;一般高级语言的条件语句是执行语句,它的功能由目标程序实现。

条件汇编伪指令
条件汇编语句的一般格式如下:
[Asm] 纯文本查看 复制代码
IFxxxx 条件表达式
    语句组1
[ELSE
    语句组2]
ENDIF


IFxxxx 是条件伪指令助记符的一般形式,其中 xxxx 表示构成条件语句助记符的其他字符。完整的条件伪指令助记符如下:
[Asm] 纯文本查看 复制代码
IF   IFE   IFDEF   IF1   IF2
IFB  IFNB  IFIDN   IFDIF


注:一定要在条件语句最后安排伪指令 ENDIF。

条件汇编语句的意义为:如果条件伪指令要求的条件满足,那么就编译语句组 1,否则编译 ELSE 中的语句组 2。同时由于 “语句组1” 和 “语句组2” 可再含有条件语句,因此可以形成条件汇编语句的嵌套。一个嵌套的 ELSE 伪指令总是与最近但又没有 ELSE 的 IFxxxx 伪指令相匹配。

伪指令 IF 和 IFE

伪指令 IF 的一般格式如下:
[Asm] 纯文本查看 复制代码
IF 表达式


如果表达式的值不等于0,条件为真。表达式不能包含向前引用,其结果应为一个常数值。

伪指令 IFE 的一般格式如下:
[Asm] 纯文本查看 复制代码
IFE 表达式


如果表达式的值等于 0,条件为真。表达式不能包含向前引用,其结果应为一个常数值。

例1:在下面的条件语句中,如果 MFLAG 值不为 0,即条件满足,那么就编译 “语句组1”,否则编译 “语句组2”:
[Asm] 纯文本查看 复制代码
;程序名:T7-16.asm
;功能:演示 伪指令 IF 的使用

assume cs:code

MFLAG = 0

code segment
start:
    if MFLAG
    mov ah,0
    int 16h     ;当 MFLAG 值不为 0 时,编译此语句组
    else
    mov ah,1
    int 21h     ;当 MFLAG 值为 0 时,编译此语句组
    endif
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图


例2:在下面的条件语句中,条件表达式是一个关系表达式,根据关系表达式的求值方法,如果 PORT 值为 0,则关系表达式的值为1,不为0所以条件满足。
[Asm] 纯文本查看 复制代码
;程序名:T7-16-1.asm
;功能:演示 伪指令 IF 的使用

assume cs:code,ds:data

PORT = 0

data segment
    if PORT EQ 0
    PORTADDR = 3F8H
    IVECTN = 0BH
    IMASKV = 11110111B
    endif
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov ax,PORTADDR
    mov bx,IVECTN
    mov cx,IMASKV
    ;
    mov ax,4c00h
    int 21H
code ends
end start


197327807-cba28287-aeb1-4145-986d-2ff4401d8715.png

例3:如下定义的宏 SHIFTL 使用了重复块和结束宏扩展伪指令 EXITM
[Asm] 纯文本查看 复制代码
;程序名:T7-16-2.asm
;功能:演示 伪指令 IF 的使用

assume cs:code

SHIFT MACRO OP,N
COUNT = 0
    REPT N
    SHL OP,1
COUNT = COUNT+1
    if COUNT GE N       ;如果 COUNT >= N,就停止编译
    EXITM
    endif
    ENDM
    INC OP
    ENDM

code segment
start:
    SHIFT AX,1
    ;
    SHIFT BX,3
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图


伪指令 IFDEF 和 IFNDEF
伪指令 IFDEF 的一般格式如下:
[Asm] 纯文本查看 复制代码
IFDEF 符号


如果符号已定义或被说明成外部符号,则条件为真。

伪指令 IFNDEF 的一般格式如下:
[Asm] 纯文本查看 复制代码
IFNDEF 符号


如果符号未定义或未被说明成外部符号,则条件为真。

例如:在下面的条件语句中,如果已先定义符号 MLARGE,则条件满足,那么 AXINC 被定义为远过程,否则过程 AXINC 被定义成近过程。
[Asm] 纯文本查看 复制代码
;程序名:T7-17.asm
;功能:演示 伪指令 IFDEF 的使用

assume cs:code

MLARGE = 0


code segment
    ifdef MLARGE
AXINC PROC FAR          ;若已定义 MLARGE 则汇编此语句
    else
AXINC PROC NEAR         ;若未定义 MLARGE 则汇编此语句
    endif
    INC AX              ;不受影响
    RET                 ;不受影响
AXINC ENDP
start:
    call FAR ptr AXINC
    ;
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图


符号可以在源程序中定义,也可以在汇编命令行中定义。


伪指令 IF1 和 IF2
伪指令 IF1 的格式如下:
[Asm] 纯文本查看 复制代码
IF1

若是第一趟扫描则条件为真。

伪指令 IF2 的格式如下:
[Asm] 纯文本查看 复制代码
IF2

若是第二趟扫描则条件为真。

条件汇编与宏结合
条件汇编与宏相结合,能大大扩大宏的使用范围。
例如:如下定义的宏 ADDNUM 有两个参数,在对宏调用扩展时,能根据不同的参数扩展成不同的指令:
[Asm] 纯文本查看 复制代码
;程序名:T7-18.asm
;功能:演示 宏中使用条件汇编

assume cs:code

ADDNUM MACRO REG,NUM
    if (NUM GT 2) OR (NUM LE 0)
    ADD REG,NUM
    else
    INC REG
    if NUM EQ 2
    INC REG
    endif
    endif
    ENDM


code segment
start:
    ADDNUM AX,1
    ;
    ADDNUM BX,3
    mov ax,4c00h
    int 21H
code ends
end start



                               
登录/注册后可看大图


条件汇编伪指令
伪指令 IFB 和 IFNB
伪指令 IFB 和 IFBN
伪指令 IFB 一般用在宏定义内,格式如下:
[Asm] 纯文本查看 复制代码
IFB <参数>

如果在宏调用时没有使用实参来代替该形参,那么条件满足。注意,参数应该用尖括号起。

伪指令 IFNB 一般使用在宏定义,格式内,格式如下:
[Asm] 纯文本查看 复制代码
IFNB <参数>

如果在宏调用时使用实参来代替该形参,那么条件满足。注意,参数应该用尖括号括起。

例如:如下定义的宏 PRINT,若指定显示信息时,则显示,否则显示缺省信息
[Asm] 纯文本查看 复制代码
;程序名:T7-18-1.asm
;功能:演示 宏中使用条件汇编

assume cs:code,ds:data

PRINT MACRO MSG
    ifb <MSG>
    MOV SI,OFFSET DEFAULTMSG    ;如果没有实参,输出 DEFAULTMSG
    else
    MOV SI,OFFSET MSG           ;如果有实参,输出 MSG 参数信息
    endif
    CALL SHOWIT
    ENDM

data segment
DEFAULTMSG db 'Hello World!','$'
MESS1 db 'I LOVE MASM!','$'
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    PRINT MESS1
    mov ax,4c00h
    int 21H

;-------------------------------
SHOWIT PROC
    mov dx,si
    mov ah,9
    int 21h
    ret
SHOWIT ENDP
;------
code ends
end start



伪指令 IFIDN 和 IFDIF
伪指令 IFIDN 一般使用在宏定义内,格式如下:
[Asm] 纯文本查看 复制代码
IFIDN <参数1>,<参数2>
IDIDNI <参数1>,<参数2>

如果 字符串参数1 与 字符串参数2 相等,则条件满足。参数1 或 参数2 可以是宏定义中的形参,如果是形参,会由之前相应的实参代替。字符串是按字符逐个比较的,格式一对大小写有区别,格式二忽略大小写区别。注意,参数应用尖括号括起。

伪指令 IFDIF 一般使用在宏定义内,格式如下:
[Asm] 纯文本查看 复制代码
IFDIF <参数1>,<参数2>
IFDIFI <参数1>,<参数2>


如果字符串1 与 字符串2 不等,则条件满足。其他说明同上。
例如:定义的宏 RDWR 的第二个参数就决定了读写方式。
[Asm] 纯文本查看 复制代码
;程序名:T7-18-2.asm
;功能:演示 宏中使用条件汇编

assume cs:code,ds:data

RDWR MACRO BUFF,RWMODE
    LEA DX,BUFF
    ifidni <RWMODE>,<READ>
    CALL READIT
    endif
    ifidni <RWMODE>,<WRITE>
    CALL WRITEIT
    endif
    ENDM

data segment
BUFFER dw 0102h
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    RDWR BUFFER,READ
    ;
    RDWR BUFFER,WRITE
    RDWR BUFFER,READ
    mov ax,4c00h
    int 21H

;-------------------------------
READIT PROC
    mov si,dx
    add dx,word ptr [si]
    add dx,3030h
    xchg dh,dl
    mov ah,2
    int 21h
    ;
    xchg dh,dl
    int 21h
    ret
READIT ENDP

;--------------------------------
WRITEIT PROC
    mov si,dx
    mov word ptr [si],0304h
    ret
WRITEIT ENDP
code ends
end start
```

例如:如下定义的宏 GETCH 就有加强能力
```
GETCH MACRO CHAR
    MOV AH,1
    INT 21H
    ifnb <CHAR>             ;如果有参数,执行下面的语句。
    ifdifi <CHAR>,<AL>      ;如果有参数,就把AL的值传给形参 CHAR
    MOV CHAR,AL
    endif
    endif
    ENDM


源程序的结合
为了方便编辑源程序和对程序进行修改或维护,汇编程序允许把源程序存放在多个文本文件中,在汇编时结合到一起,同时参加汇编。

源程序的结合
存放在若干文本文件中的源程序的结合是利用伪指令 INCLUDE 完成的。它的一般格式如下:
[Asm] 纯文本查看 复制代码
INCLUDE 文件名


伪指令 INCLUDE 表示汇编程序将指定的文本文件从本行起加入汇编,直到该文本文件的最后一行汇编完成后,继续汇编随后的语句。

  • 文件名可带有盘符和路径,采用 DOS 有关规则表示。
  • 若文件名没有盘符或路径,则首先在汇编命令行参数 /I 所指定的目录中寻找该文件,然后再到当前目录中寻找该文件。

对于 MASM 而言,最后还会由环境变量 INCLUDE 所指定的目录中寻找该文件。对于 TAMS 而言,若文件名没有扩展名,则默认为扩展名 ASM

下列程序的功能是:接收一个字符串,然后按小写和大写形式重新显示字符串。
[Asm] 纯文本查看 复制代码
;程序名:T7-19.asm
;功能:接收一个字符串,然后按小写和大写形式重新显示字符串。

assume cs:code,ds:data

INCLUDE DATA.ASM

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    cld
    mov dx,offset MESS1     ;显示提示信息
    mov ah,9
    int 21H
    ;
    mov ah,10               ;接收字符串
    mov dx,offset BUFFER
    int 21H
    ;
    call NEWLINE            ;另起一行
    mov bl,BUFFER+1
    xor bh,bh
    mov BUFFER[BX+2],0
    mov si,offset STRBEG
    call STRLWR             ;转换成小写字符串
    mov SI,offset STRBEG
    call DISPMESS           ;显示信息
    call NEWLINE            ;换行
    ;
    mov si,offset STRBEG
    call STRUPR             ;转换成大写字符串
    mov si,offset STRBEG
    call DISPMESS
    call NEWLINE
EXIT:
    mov ax,4c00h
    int 21H

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
NEWLINE endp

;-------------------------------
DISPMESS PROC
;功能:显示以0结尾的字符串
;入口参数:SI=字符串首地址偏移
DISPME1:
    LODSB
    or al,al
    jz DISPME2
    mov dl,al
    mov ah,2
    int 21H
    jmp DISPME1
DISPME2:
    ret
DISPMESS ENDP

INCLUDE  STRING.ASM         ;结合含子程序的文件 STRING.ASM
code ends
end start


文本 DATA.ASM 内容如下:
[Asm] 纯文本查看 复制代码
;文件名:DATA.ASM
;内容:程序 T7-19.asm 的一部分
STRLEN = 128
DATA SEGMENT
BUFFER DB STRLEN,0,STRLEN DUP(0)
STRBEG = BUFFER+2
MESS1 db 'Please Enter string: ','$'
DATA ENDS



文本文件 STRING.ASM 内容如下:
[Asm] 纯文本查看 复制代码
;文件名:STRING.ASM
;内容:程序 T7-19.asm 的一部分

STRLWR PROC
;子程序名:STRLWR
;功能:把字符串转换为小写
;入口参数:SI=字符串起始地址偏移
    jmp STRLWR2
STRLWR1:
    sub al,'A'
    cmp al,'Z'-'A'
    ja STRLWR2
    add al,'a'
    mov [si-01],al
STRLWR2:
    LODSB
    and al,al
    jnz STRLWR1
    ret
STRLWR ENDP

STRUPR PROC
;子程序名:STRUPR
;功能:把字符串转换成大写
;入口参数:SI-=字符串起始地址偏移
    jmp STRUPR2
STRUPR1:
    sub al,'a'
    cmp al,'z'-'a'
    ja STRUPR2
    add al,'A'
    mov [si-01],al
STRUPR2:
    LODSB
    and al,al
    jnz STRUPR1
    ret
STRUPR ENDP



宏库的使用
通常程序员会把一组有价值和经常使用的宏定义集中存放在一个文本文件中,这样的文本文件称为宏库。有了宏库之后,只要在源程序首使用伪指令 INCLUDE,就能方便地调用宏库了。

例如:建立宏库 DOSBIO.MAC 内容如下
[Asm] 纯文本查看 复制代码
;接受一个字符串
GETSTR MACRO MBUFF
    MOV DX,MBUFF
    MOV AH,10
    INT 21H
    ENDM

;显示一个字符串
DISPSTR MACRO MBUFF
    MOV DX,MBUFF
    MOV AH,9
    INT 21H
    ENDM

;取得一个字符
GETCH MACRO CHAR
    MOV AH,1
    INT 21H
    ifnb <CHAR>             ;如果有参数,执行下面的语句。
    ifdifi <CHAR>,<AL>      ;如果有参数,就把AL的值传给形参 CHAR
    MOV CHAR,AL
    endif
    endif
    ENDM

;显示一个字符
ECHOCH MACRO CHAR
    IFNB <CHAR>
    IFDIFI <CHAR>,<DL>
    MOV DL,CHAR
    ENDIF
ENDIF
    MOV AH,2
    INT 21H
    ENDM


源程序代码如下:
[Asm] 纯文本查看 复制代码
;程序名:T7-19-1.asm
;功能:演示宏库的使用

assume cs:code,ds:data

INCLUDE DOSBIO.MAC          ;结合宏库 DOSBIO.MAC
;
INCLUDE DATA.ASM            ;含数据部分的文件 DATA.ASM

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    cld
    DISPSTR <OFFSET MESS1>      ;调用宏 DISPSTR
    GETSTR <OFFSET BUFFER>      ;调用宏 GETSTR
    call NEWLINE
    ;
    mov bl,BUFFER+1
    xor bh,bh
    mov BUFFER[BX+2],0
    mov si,offset STRBEG
    call STRLWR             ;转换成小写字符串
    mov SI,offset STRBEG
    call DISPMESS           ;显示信息
    call NEWLINE            ;换行
    ;
    mov si,offset STRBEG
    call STRUPR             ;转换成大写字符串
    mov si,offset STRBEG
    call DISPMESS
    call NEWLINE
EXIT:
    mov ax,4c00h
    int 21H

;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说    明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
    push ax
    push dx
    mov dl,0dh
    mov ah,2
    int 21h
    mov dl,0ah
    mov ah,2
    int 21h
    pop dx
    pop ax
    ret
NEWLINE endp

;-------------------------------
DISPMESS PROC
;功能:显示以0结尾的字符串
;入口参数:SI=字符串首地址偏移
DISPME1:
    LODSB
    or al,al
    jz DISPME2
    mov dl,al
    mov ah,2
    int 21H
    jmp DISPME1
DISPME2:
    ret
DISPMESS ENDP

INCLUDE  STRING.ASM         ;结合含子程序的文件 STRING.ASM
code ends
end start



                               
登录/注册后可看大图


197100299-3095ce8d-489c-4445-9119-7bb8cafa1642.png
197312728-3e3b7605-3975-48fd-b8a4-90bb53128c59.png
197327807-cba28287-aeb1-4145-986d-2ff4401d8715.png

评分

参与人数 9HB +10 THX +4 收起 理由
消逝的过去 + 1
花盗睡鼠 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
数羊到天亮 + 2
sjtkxy + 1 + 1
叶雨 + 1
DDK4282 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
boot + 2 + 1
zxjzzh + 2 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
学编程我不配 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
头像被屏蔽
sjtkxy 发表于 2023-3-20 04:59 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
Agni 发表于 2023-4-15 20:06 | 显示全部楼层

如果是易语言的置入代码   怎么加效验呢,
比如 用这个置入判断某个文本 成功返回1 失败返回-1
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表