前面了解了 8086CPU 下的工作原理和相应的汇编指令、语法,已基本上可以编写很多复杂的功能程序。但对于汇编指令和语法,除了前面提到过的外,还有许多其它的常用指令和语法,下面将对部分进行介绍。
指令部分
-
和传送数据有关的指令
LEA 16位通用寄存器, 字型内存单元
该指令把内存单元对应的偏移地址传送到指定寄存器中。
如:
assume cs:code, ds:data data segment data_1 db 0,1,2,3 data_2 db 4 data ends code segment start: mov ax, data mov ds, ax lea ax, data_2 ;把data2的偏移地址0004H放入ax中 lea bx, ds:[0001h];把偏移地址0001H传送到bx中 mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看 AX, BX 的运行结果如下:
LDS 16位通用寄存器,双字内存单元
该指令把双字内存单元中的高字节作为段地址传送到 DS 中,低字节作为偏移地址传送到指定通用寄存器中。
如:
assume cs:code, ds:data data segment data_1 db 0,1,2,3 data ends code segment start: mov ax, data mov ds, ax lds ax, dword ptr data_1;把高字节0302H传送到ds中,低字节0100H传送到ax中 mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看 DS, AX 的运行结果如下:
LES 16位通用寄存器,双字内存单元
该指令和LDS指令功能一样,只是把高字节段地址传送到ES中。
XCHG 操作对象1, 操作对象2
该指令表示交换两个对象的内容,可以实现通用寄存器与通用寄存器或内存单元的数据交换,不可使用段寄存器和立即数。
如:
assume cs:code code segment start: mov ax, 1010H mov bx, 2020H xchg ax, bx ;交换ax和bx的内容 mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看 AX, BX 的运行结果如下:
-
和标志寄存器有关的指令
对于标志寄存器,前面有介绍过以下指令:
PUSHF,POPF,STD,CLD,STI,CLI
以上指令分别控制所有标志位、DF标志位、IF标志位。
下面介绍其它标志位的控制指令。
CLC ;设置 CF 标志位为0 STC ;设置 CF 标志位为1 CMC ;CF标志位取反
该指令对标志位 CF 进行操作。
如:
assume cs:code code segment start: mov al, 0ffh add al, 1 CLC ;置CF为0 STC ;置CF为1 CMC ;CF取反,当前CF为1,执行该指令后,CF为0 mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看 CF 标志位的运行结果如下:
LAHF ;把标志寄存器中的低 8 位标志位 ( SF, ZF, AF, PF, CF ) 传送至 AH 的相应位,其它位无意义,不去关心 SAHF ;把 AH 中的数据传入标志寄存器的低 8 位对应的标志位 ( SF, ZF, AF, PF, CF ) 中,其它位无意义,不去关心
该指令对标志寄存器低 8 位标志位进行操作。
如:
assume cs:code code segment start: lahf ;低8位标志位传入AH mov ah, 11111111B ;全部置1 sahf ;修改标志寄存器低8位标志位 mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看 SF, ZF, AF, PF, CF 标志位的运行结果如下:
-
和运算相关的指令
NEG 操作对象
该指令为取补指令,相当于用零减操作对象,所以和减法一样,会影响相应的标志位。
如:
assume cs:code, ds:data data segment data_1 db 1 data ends code segment start: mov ax, data mov ds, ax mov al,1 neg al neg data_1 mov al, data_1 mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看取补后的运行结果如下:
另外,该指令需要注意的是,当对字节类型的-128或字类型的-32768操作时,不会改变操作数的内容,但OF会被置为1。IDIV 操作对象 IMUL 操作对象
该指令功能和 DIV、MUL 功能一致,只是进行的是有符号的乘除法运算。
-
和位操作相关的指令
CBW ;字节扩展为字,把寄存器 AL 中的符号位扩展到 AH 中,即 AL 符号位为 1 时,AH 为0FFH,符号位为 0 时,AH 为 00H CWD ;字扩展为双字,把 AX 中的符号位扩展到 DX 中。即 AX 符号位为 1 时,DX 为 0FFH,符号位为 0 时,DX 为 00H
该指令为符号扩展指令。
如:assume cs:code code segment start: mov ax,0080H cbw ;扩展后 AH = 0FFH cwd ;扩展后 DX = 0FFFFH mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看扩展后的运行结果如下:
另外,还有些位运算相关的指令,可以自己动手查看运行结果:NOT 操作对象 ;按位取反并把结果放入操作对象中,该指令对标志位没有影响 AND 操作对象1, 操作对象2 ;按位与运算并把结果放入操作对象1中,影响标志位 OR 操作对象1, 操作对象2 ;按位或运算并把结果放入操作对象1中,影响标志位 XOR 操作对象1, 操作对象2 ;按位异或运算并把结果放入操作对象1中,影响标志位 TEST 操作对象1,操作对象2 ;按位与运行,结果不改变操作对象的内容,但影响标志位,同时把CF和OF置0。
同时,还有些移位相关的指令:
移位指令: SHL或SAL 操作对象, 1或CL ;向左移位,当移位大于1时,需使用cl,移出的最高位放入CF中。 SHR 操作对象, 1或CL ;逻辑右移,当移位大于1时,需使用cl,移出的最低位放入CF中 SAR 操作对象, 1或CL; 算术右移,当移位大于1时,需使用cl,符号位不变,即移动的过程中左边补符号位。 循环移位指令: ROL 操作对象,1或CL ;循环左移,移出的最高位移入最低位中,同时移出的最高位进入CF ROR 操作对象,1或CL ;循环右移,移出的最低位移入最高位中,同时移出的最低位进入CF RCL 操作对象,1或CL ;带进位循环左移,移出的最高位进入CF,同时CF移入最低位 RCR 操作对象,1或CL ;带进位循环右移,移出的最低位进入CF,同时CF移入最高位
-
和取值有关的指令
获取数值表达式的高八位或低八位数据:
HIGH 数值表达式 ; 获取数值表达式结果的高8位 LOW 数值表达式 ; 获取数值表达式结果的低8位
如:
assume cs:code code segment start: mov al, high (4080h + 1) ;高8位即40h送入al mov al, high 4080h + 1 ;high优先级高于+,即把41H送入al mov al, low 4080h ;低8位即80H送入al mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看 AL 的运行结果如下:
析值操作符:SEG 变量名或标号 ;获取段地址 OFFSET 变量名或标号 ;获取偏移地址 TYPE 变量名或标号 ;获取类型占用的字节数,near、far 等没实际的物理意义。如字节时为 1,字型为 2,双字为 4,near 为 -1,far 为 -2 LENGTH 变量名 ;获取 dup 定义时的重复值,没有使用 dup 定义时为 1,嵌套使用时返回最外层的重复值。 SIZE 变量名 ;获取 dup 定义时占用的字节数,相当于 length * type
如:
assume cs:code, ds:data data segment buff db 1,2,3 varw dw 1,2,3 dupvar db 15 dup(0) data ends code segment start: mov ax, data mov ds, ax mov ax, seg buff ;段地址 mov ax, offset start ;偏移地址 mov al, type buff ;字节,占用1一个字节 mov al, type varw ;字,占用2个字节 mov al, length varw ;未使用dup,默认为1 mov al, length dupvar ;重复指为15 mov al, size dupvar ;length * type = 15 * 1 = 15 mov al, size varw ;length * type = 1 * 2 = 2 mov ax, 4c00h int 21h code ends end start
感兴趣的同学可以自己通过debug来查看运行结果。
属性操作符:
类型 PTR 地址表达式
该指令指定访问的内存单元地址的类型,类型可为byte、word、dword、near、far。前面一直在用该种方式指定访问内存单元的类型,不再对该指令进行详细的说明。
THIS 类型
该指令返回一个具有指定类型的存储器操作数,但不分配存储单元,所返回的存储器操作数地址的段地址和偏移地址就是下一个将分配的存储单元的段地址和偏移地址。类型可为byte、word、dword、near、far等。
THIS 指令常用于符号定义语句中,从而定义一个具有类型、段地址和偏移地址三属性的表示存储器操作数的符号,如:
assume cs:code, ds:data data segment byte_info equ this word ;equ,符号定义符,稍后会介绍到。 next_info db 1, 2 data ends code segment start: mov ax, data mov ds, ax mov ax, seg byte_info ;段地址,和next_info段地址一样 mov ax, seg next_info ;段地址 mov ax, offset byte_info ;偏移地址和next_info一样 mov ax, offset next_info ;偏移地址 mov al, type byte_info ;字型为2 mov ax, byte_info ;byte_info代表下一个分配的内存单元,把0201H送入ax mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看运行结果如下:
语法部分
-
符号定义语句
等价语句:
符号名 EQU 表达式
该语句用符号名来表示对应的表达式,即相当于别名。
如:用COUNT表示100: COUNT EQU 100 用LEN表示 2 * COUNT: LEN EQU 2 * COUNT 用MOVE代替MOV使用: MOVE EQU MOV 用CX_代替CX使用: CX_ EQU CX
等价语句定义的符号名不能重复定义,也不给符号分配存储单元。
等号语句:
符号名 = 数值表达式
该指令用符号表示一个常数,数值表达式应该是可以计算出数值的表达式,所以表达式中不能用之后出现的符号名,符号可以重复定义使用。
如:XX = 10 YY = 20 + 300/4 XX = YY
定义符号名语句:
符号名 LABEL 类型
该语句用来定义指定类型的符号名,该符号的段地址和偏移地址和下一个紧挨着的存储单元的段地址和偏移地址一致。
如:BUFFER LABEL WORD ;BUFFER 指定字型类型,且段地址和偏移地址和 BUFF 一致 BUFF DB 100 DUP(0) QUIT LABEL FAR ;BUFFER指定 FAR 类型,且段地址和偏移地址和 EXIT 一致 EXIT: MOV AX, 4C00H INT 21H
调整地址计数器指令语句:
ORG 数值表达式
汇编程序在对源程序编译的过程中,使用地址计数器来保存当前正在汇编的指令或变量的地址偏移,通常地址计数器逐步递增,可以通过该指令语句来改变地址计数器的当前值。
如:assume cs:code code segment org $ + 100h; 下一个存储单元的偏移地址等于 100H start: jmp begin;对于指令:jmp 标号,因为还不知道标号的位置,还不知道转移位移是8位还是16位 ;编译器会在此指令后添加一个nop指令,占用一个字节来防止偏移位移为16位的内存占用 ;所以该指令会占用3个字节 bbb dw 1, 2,$,$ + 3, $ + 4;该定义数据偏移地址为0103H, 因为 $ 是动态变化的,所以$=0107,$+3=010ch, $+4=010fh begin: mov ax, word ptr ($ + 10);把 cs:[$ + 10] 内容放入 ax 中 mov ax, 4c00h int 21h code ends end start
通过 dosbox 下的 debug 装载运行,可以查看运行结果如下:
过程定义语句:在汇编子程序中,通常使用过程来定义子程序,通过 CALL 过程名 的方式来进行调用。格式如下:
过程名 proc [near | far] 程序功能内容 过程名 endp
用 CALL 过程名 进行调用时,和我们前面说的 CALL 功能一致。
对于过程中的 near 或 far 类型,near 指定该过程的近类型,只能被所在段的程序进行调用,使用 call 进行调用时,为段内转移。far 指定该过程的远类型,可以被不同段的程序进行调用,使用 call 进行调用时,为段间转移。更详细的会在以后的更新中介绍到。
在定义子程序时,为了方便知道子程序的功能等其它的信息,一般要养成做好注释说明的习惯,如下功能:
对所输入的字符转化为大写并显示在屏幕的中间行,当输入的不是字母时,退出程序:
assume cs:code code segment start: mov ax, 0b800h mov ds, ax mov si, 12 * 160 begin: mov ah, 0 int 16h call upper cmp ah, 0 ;转换的为0时,退出 je exit ;显示转换后的字符 mov byte ptr ds:[si], ah mov byte ptr ds:[si + 1], 2 add si, 2 jmp short begin exit: mov ax, 4c00h int 21h ;子程序名:upper ;功能说明:把字母字符转换为大写 ;入口参数:al = 需要转换的字母 ;出口参数:ah = 转换完成后的字母,若不是字母,为0 ;其它说明: upper proc ;未指定类型时,默认为near cmp al, 'A' jb zero_transfer ;不是字母,不进行转换,ah = 0 cmp al, 'Z' jna no_transfer ;A~Z,本身为大写字母,不用转换 cmp al, 'a' jb zero_transfer ;不是字母,不进行转换,ah = 0 cmp al, 'z' ;a~z,转换大写 jna transfer zero_transfer: mov ah, 0 jmp short return no_transfer: mov ah, al jmp short return transfer: mov ah, al and ah, 11011111b return: ret upper endp code ends end start
通过 dosbox 下 debug 装载程序运行,可以用键盘进行输入并转换大写显示,如下所示: