8086汇编知识点
基础指令
mov传送指令
mov ax,10h #(ax)=10h
mov ax,bx
mov ax,ds:[bx+si+5]
add加法指令
add ax,10h #(ax)=(ax)+10h
add ax,bx
add al,bl
sub减法指令
sub ax,10h #(ax)=(ax)-10h
sub ax,bx
sub al,bl
push入栈指令
push ax #sp=sp-2 (ss:sp)=(ax) 栈底数值最高,栈顶数值最低,栈的生长是从高位到低位
push ds
pop出栈指令
pop ds #(ds)=(ss:sp) sp=sp+2 先将值转给寄存器,在调整栈指针
pop ax
循环指令
loop指令
循环进行某操作
循环次数通常存放在cx寄存器中
注:汇编源程序中,数据不能以字母开头
例如,mov ax,0ffffh 其中必须在字母之前加零
8086汇编例程:
assume cs:code,ds:data,ss:stack #定义代码段,数据段,栈段
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
code segment
start: #将CS:IP指向代码段,否则将从先定义的数据段执行
mov ax,stack
mov ss,ax
mov sp,16
mov ax,data
mov ds,ax
mov bx,0
mov cx,8
s: push [bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start
内存寻址
[bx+idata]定位方式
mov ax,[5+bx]等价于mov ax,5[bx]
si、di寄存器
si,di与bx功能相近,主要用于作为内存寻址的偏移地址
si、di不能拆分为两个8位寄存器
[bx+si]、[bx+di]
mov ax,[bx][si]
即为(ax)=((ds)*16+(bx)+(si))
[bx+si+idata]、[bx+di+idata]
偏移地址为(bx)+(si)+idata
常用格式如下:
mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov [bx].200[si]
mov [bx][si].200
and和or指令及其特殊用法
and al,01010101b #按位与
or al,01010101b #按位或
- and可将数值为0的位对应的位数的值设置为零,1对应的位数的值保持不变
- or 可将数值为1的位对应的位数的值设置为一,0对应的位数的值保持不变
又由于ASCII码中大小写字母的码值相差32,即00100000b
因此
and al,11011111b
#将小写字母转换为大写字母,若本来为大写,则保持不变
or al,00100000b
#将大写字母转换为小写字母,若本来为小写,则保持不变
由此可见,使用and和or指令,能够在大小写转换中不必判断字母原来的情况,能够在位的角度轻松完成,这也启发我们在做事的时候应该从多方面思考,也许从另一个角度来看,问题会明了许多。
寻址方式
- 直接寻址,[idata]
- 寄存器间接寻址,[bx]
- 寄存器相对寻址,[bx+idata][bx].idataidata[bx]==[bx][idata]
- 基址变址寻址,[bx+si]==[bx][si]
- 相对基址变址寻址,[bx+si+idata]==[bx].idata[si]==idata[bx][si]
寄存器bx,si,di,bp可以单独出现
或以特定的组合出现
mov ax,[bx]
mov ax,[si]
mov ax,[di]
mov ax,[bp]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp+si]
mov ax,[bp+di]
mov ax,[bx+si+idata]
mov ax,[bx+di+idata]
mov ax,[bp+si+idata]
mov ax,[bp+di+idata]
即bx与bp不能同时出现,si与di不能同时出现
[bx]默认段寄存器为ds(数据段)
[bp]默认段寄存器为ss(栈段)
通常情况下,[bx+si+idata]的寻址方式主要用于访问结构体中的数据。bx用于定位结构体,idata用于定位结构体中的某一个数据项,si定位数组项中的某个元素可以用类c语言的书写方式,例如[bx].idata、[bx].idata[si]
指令处理数据的长度
- 通过寄存器名指明
- 操作符
- 栈操作(push、pop均只能进行字操作)
#寄存器名
mov ax,1 #字操作
mov al,1 #字节操作
#操作符X ptr
mov word ptr ds:[0],1 #字操作
inc byte ptr ds:[0] #字节操作
乘除指令
div指令
除法指令,除数可以为16位或8位,可以在寄存器或内存单元中。
被除数在AX寄存器或DX和AX寄存器中。
- 除数为8位,被除数为16位,在ax中存放,运算后al存储商,ah存储余数
- 除数为16位,被除数为32位,在dx、ax中存放,dx存放高16位,运算后,dx存储商,ax存储余数
注:在debug中插入汇编代码时,插入的数据默认是16进制的,比如mov bl,100就会提示error,这里的100指的是十进制的128
mul指令
乘法指令,两个相乘的数要么均为8位、要么均为16位。
- 均为8位,一个默认放在AL中,另一个放在8位寄存器(如bl)或内存字节单元中,结果放在AX中
- 均为16位,一个默认放在AX中,另一个放在16位寄存器或内存字单元中,结果高位放在DX、低位放在AX中
#例子:计算100x10
mov al,100
mov bl,10
mul bl
#例子:计算100x10000
mul ax,100
mov bx,10000
mul bx
dd伪指令、dup指令
-
db(define byte) 定义字节型数据
-
dw(define word) 定义字型数据
-
dd(define double word) 定义双字型(dword)数据
db 3 dup (0) #相当于db 0,0,0
db 3 dup (0,1,2) #相当于db 0,1,2,0,1,2,0,1,2
格式如下:
db 重复的次数 dup (重复的字节型数据)
dw 重复的次数 dup (重复的字型数据)
dd 重复的次数 dup (重复的双字型数据)
应用:
#定义一个容量200个字节的栈段
stack segment
db 200 dup(0)
stack ends
跳转指令
offset操作符
用于取得标号的偏移地址
assume cs:codesg
codesg segment
s: mov ax,bx
mov si,offset s #将mov ax,bx指令的偏移地址送入si中
mov di,offset s0 #将nop的偏移地址送入di中
mov ax,cs:[si]
mov cs:[di],ax
s0: nop
nop
codesg ends
end s
jmp指令
1.转移地址在目标指令中
- jmp short 标号(段内短转移,范围为-128~127,在机器码中由补码表示)
assume cs:codesg
codesg segment
start: mov ax,0
jmp short s
add ax,1
s: inc ax
codesg ends
end start
- jmp near ptr 标号(段内近转移,范围为-32768~32767,在机器码中由补码表示)
- jmp far ptr 标号(段间转移,又称远转移,在机器码中储存了转移位置的CS:IP值)
2.转移地址在寄存器中
- jmp 16位寄存器 如jmp ax
3.转移地址在内存中
- jmp word ptr 内存单元地址(段内转移)
mov ax,0123h
mov ds:[0],ax
jmp word ptr ds:[0]
- jmp dword ptr 内存单元地址(段间转移)
#从内存单元地址处开始的两个字,高地址处为转移的目的段地址,低地址处为转移的目的偏移地址
#(CS)=(内存单元地址+2)
#(IP)=(内存单元地址)
#例如
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2]:,0
jmp dword ptr ds:[0]
#执行后,(CS)=0,(IP)=0123h,CS:IP指向0000:0123
注:形如"jmp 2000:0100"的转移指令,是在Debug中使用的汇编指令,汇编编译器无法识别,在源程序中无法使用
jcxz指令
有条件转移指令,所有有条件转移指令都是短转移
Jmp if CX == Zero
- jcxz 标号 (如果(cx)=0,转移到标号处进行,反之,什么也不做)
jcxz 标号 等价于 if((cx)==0) jmp short 标号;
assume cs:code
code segment
start: mov ax,2000h
mov ds,ax
mov bx,0
s:mov cl,[bx]
mov ch,0
jcxz ok
inc bx
jmp short s
ok:mov dx,bx
mov ax,4c00h
int 21h
code ends
end start
注:段内转移、jcxz、loop等转移指令对IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的,这种设计,方便了程序段在内存中的浮动装配
函数调用、返回指令
ret和retf指令
- CPU执行ret指令,进行如下操作:
- (IP)=((ss)*16+(sp))
- (sp)=(sp)+2
- CPU执行retf指令,进行如下操作:
- (IP)=((ss)*16+(sp))
- (sp)=(sp)+2
- (CS)=((ss)*16+(sp))
- (sp)=(sp)+2
由此可见,ret、retf指令与SS、SP寄存器有关,即与栈段相关联。
ret指令,相当于:
pop IP
retf指令,相当于:
pop IP
pop CS
call指令
CPU执行call指令时,进行两步操作
- 将当前的IP或CS:IP压入栈中
- 转移
call指令实现转移的方法和jmp指令的原理相同
有如下应用形式
1.根据位移进行转移
形式:call 标号(将当前的IP压入栈,转到标号处执行指令)
相当于进行:
push IP
jmp near ptr 标号
注:call指令不能实现短转移
2.转移的目的地址在指令中
形式:call far ptr 标号(段间转移)
相当于进行:
push CS
push IP
jmp far ptr 标号
3.转移的目的地址在寄存器中
形式:call 16位寄存器
相当于进行:
push IP
jmp 16位寄存器
4.转移的目的地址在内存中
有两种形式
- call word ptr 内存单元地址,相当于进行
push IP
jmp word ptr 内存单元地址
- call dword ptr 内存单元地址
push CS
push IP
jmp dword ptr 内存单元地址
由以上应用形式可知,call无法进行短转移,call标号、call16位寄存器、callword大小的内存单元地址时,仅将ip入栈,而call far ptr 标号、call dword ptr 内存单元地址时,先将CS入栈(为栈中高地址)再将IP入栈(为栈中低地址)。这一点无论是在retf时还是在call、jmp时,CS均在高地址,IP均在低地址,与数值在内存中高位存放在高地址、低位存放在低地址类似
call和ret指令配合编写子程序
assume cs:code
code segment
main: :
:
call sub1 ;调用子程序1
:
:
mov ax,4c00h
int 21h
sub1: : ;子程序1
:
call sub2 ;调用子程序2
:
:
ret ;子程序1返回
sub2: : ;子程序2
:
:
ret ;子程序2返回
code ends
end main
IP的栈操作可表示为
push IP ;保存主程序IP
push IP1 ;保存子程序1IP
pop IP1 ;恢复子程序1IP
pop IP ;恢复主程序IP
为了避免出现寄存器冲突的可能性,例如在主程序中使用了cx来控制循环,子程序中用cx控制循环时会出现修改cx值导致主程序循环无法按预期进行的问题。我们需要在编写子程序(即函数)时,在子程序的开始将子程序用到的寄存器中的内容压入栈中,在子程序返回前,从栈中恢复寄存器在主程序中的数值。
codesg segment
main:
mov cx,4
s: : #主函数中的循环
:
call sub
loop s
:
mov ax,4100h
int 21h
sub:
push cx #cx寄存器的值入栈
mov cx,3
l: : #子函数中的循环
:
loop l
pop cx #cx寄存器内容出栈
ret
codesg ends
标志寄存器
结构
flag寄存器:每一位都有专门的含义,为空的位没有任何含义
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF | DF | IF | TF | SF | ZF | AF | PF | CF |
各标记位作用
1.ZF标志位
- 零标志位,zero
记录相关指令执行后,其结果是否为零,如果为0,zf=1,反之,zf=0
2.PF标志位
- 奇偶标志位
记录相关指令执行后,其结果的所有位中1的个数是否为偶数,如果为偶数,pf=1,反之,pf=0
3.SF标志位
- 符号标志位,sign
记录相关指令执行后,其结果是否为负,如果为负,sf=1,反之,sf=0
4.CF标志位
- 进位标志位,carry
mov al,90h
add al,al ;执行后:(al)=20h,CF=1,记录了最高有效位向更高位的进位值
mov al,91h
sub al,92h ;执行后:(al)=ffh,CF=1
mov al,91h
sub al,al ;执行后:(al)=0,CF=0
将CF设置为0的方法:
sub ax,ax
5.OF标志位
- 溢出标记位,overflow
记录有符号运算的结果是否发生了溢出,如果发生了溢出,OF=1,反之,OF=0
6.DF标志位
- 方向标志位,direction
在串传送指令中,控制每次操作后si、di的增减
- df=0 每次操作后si、di递增
- df=1 每次操作后si、di递减
详情见串传送指令
pushf和popf指令
- pushf:将标志寄存器的值压栈
- popf :从栈中弹出数据,送入标志寄存器中
带进位的加减指令
adc指令
带进位的加法指令,利用了cf位上记录的进位值
adc ax,bx #等价于(ax)=(ax)+(bx)+CF
#可以将add指令转换为add、adc指令的组合
add ax,bx
#即为
add al,bl
adc ah,bx
应用:
#计算1EF000H+201000H,结果存放在ax(高16位)、bx(低16位)中
mov ax,001EH
mov bx,0F000H
add bx,1000H ;低16位
adc ax,0020H ;高16位,加上进位值
sbb指令
带借位的减法指令,利用了cf位上记录的借位值
sbb ax,bx #等价于(ax)=(ax)-(bx)-CF
用法与adc类似
条件转移指令
cmp指令
比较指令,cmp的功能相当于减法指令,不过不保存结果
根据相关标志位的值可以看出比较结果
对于无符号数
无符号ax与bx大小比较 | 标志位情况 |
---|---|
(ax)=(bx) | zf=1 |
(ax)!=(bx) | zf=0 |
(ax)<(bx) | cf=1 |
(ax)>=(bx) | cf=0 |
(ax)>(bx) | cf=0 && zf=0 |
(ax)<=(bx) | cf=1 || zf=1 |
总结:相等于否决定zf,相减为负与否决定cf
对于有符号数
标记位值 | 实际结果的正负 |
---|---|
sf=1,of=0 | 无溢出,结果为负 |
sf=1,of=1 | 有溢出,结果为非负 |
sf=0,of=0 | 无溢出,结果为非负 |
sf=0,of=1 | 有溢出,结果为负 |
条件转移指令
条件转移指令通常配合cmp使用,通过检测标志寄存器,来决定是否修改IP
利用无符号数、有符号数(这里的结果为负或非负取决于上表中的sf、of值)比较结果的条件转移指令
指令 | 含义 | 无符号比较检测的相关标记位 | 有符号比较检测的相关标记位 |
---|---|---|---|
je | 等于则转移 | zf=1 | zf=1 |
jne | 不等于则转移 | zf=0 | zf=0 |
jb | 低于则转移 | cf=1 | 结果为负 |
jnb | 不低于则转移 | cf=0 | 结果为非负 |
ja | 高于则转移 | cf=0 && zf=0 | 结果为非负&&zf=0 |
jna | 不高于则转移 | cf=1 || zf=1 | 结果为负||zf=1 |
- e: equal
- b: below
- a: above
注:当条件转移指令不与cmp指令配合使用时,将根据标志寄存器当前的状态来决定是否转移
串传送指令
movsb、movsw指令
#将ds:si指向的内存单元中的字节送入es:di中,然后根据df的值将si和di递增或递减
movsb ;类似于mov es:[di],byte ptr ds:[si] 并无该指令!
#将ds:si指向的内存单元中的字送入es:di中,然后根据df的值将si和di递增2或递减2
movsw ;类似于mov es:[di],word ptr ds:[si] 并无该指令!
rep指令
rep指令的作用是根据cx的值,重复执行后面的串传送指令
rep movsb
#等价于
s:movsb
loop s
rep movsw
#等价于
s:movsw
loop s
cld指令、std指令
-
cld指令,将标志寄存器的df位置0
-
std指令,将标志寄存器的df位置1
| 结果为非负 |
| ja | 高于则转移 | cf=0 && zf=0 | 结果为非负&&zf=0 |
| jna | 不高于则转移 | cf=1 || zf=1 | 结果为负||zf=1 |
- e: equal
- b: below
- a: above
注:当条件转移指令不与cmp指令配合使用时,将根据标志寄存器当前的状态来决定是否转移
串传送指令
movsb、movsw指令
#将ds:si指向的内存单元中的字节送入es:di中,然后根据df的值将si和di递增或递减
movsb ;类似于mov es:[di],byte ptr ds:[si] 并无该指令!
#将ds:si指向的内存单元中的字送入es:di中,然后根据df的值将si和di递增2或递减2
movsw ;类似于mov es:[di],word ptr ds:[si] 并无该指令!
rep指令
rep指令的作用是根据cx的值,重复执行后面的串传送指令
rep movsb
#等价于
s:movsb
loop s
rep movsw
#等价于
s:movsw
loop s
cld指令、std指令
- cld指令,将标志寄存器的df位置0
- std指令,将标志寄存器的df位置1