前言
记录汇编语言的基础知识。
寄存器
通用寄存器
又叫作数据寄存器,用于存放一般性的数据,例如操作数和运算结果,通常有四个:
- 64位:RAX、RBX、RCX、RDX
- 32位:EAX、EBX、ECX、EDX
- 16位:AX、BX、CX、DX
其中四个通用寄存器的一般作用如下
- AX:累加寄存器,常用于运算;
- BX:基址寄存器,常用于地址索引;
- CX:计数寄存器,常用于计数;
- DX:数据寄存器,常用于数据传递;
变址寄存器
主要用于存放存储单元在段内的偏移量,有两个:
- 64位:RSI、RDI
- 32位:ESI、EDI
- 16位:SI、DI
SI(Source Index):源变址寄存器,可用来存放相对于DS段的源变址指针;
DI(Destination Index):目的变址寄存器,可用来存放相对于ES段的目的变址指针;
SI、DI不能够分成两个8位寄存器来使用;
指针寄存器
主要用于存放堆栈内存储单元的偏移量 ,用它们可实现多种存储器操作数的寻址方式,有两个:
64位:RBP、RSP
32位:EBP、ESP
16位:BP、SP
BP为基指针(BasePointer)寄存器,用它可直接存取堆栈中的数据;
SP为堆栈指针(StackPointer)寄存器,用它只可访问栈顶,存储栈顶的偏移地址,SS:SP指向栈顶;
段寄存器
段寄存器是根据内存分段的管理模式而设置的;
内存单元的物理地址由段寄存器的值和一个偏移量值组合而成的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址;
段寄存器包括:
CS代码段寄存器(CodeSegmentRegister),其值为代码段的地址
DS数据段寄存器(DataSegmentRegister),其值为数据段的地址
SS堆栈段寄存器(StackSegmentRegister),其值为堆栈段的段地址
ES附加段寄存器(ExtraSegmentRegister),其值为附加数据段的地址
FS附加段寄存器(ExtraSegmentRegister),其值为附加数据段的地址
GS附加段寄存器(ExtraSegmentRegister),其值为附加数据段的地址
指令指针寄存器
存放下次将要执行的指令在代码段的偏移量,配合CS寄存器一起使用CS:IP,只有一个:
- 64位:RIP
- 32位:EIP
- 16位:IP
标志寄存器(flag)
标志寄存器的作用:
- 用来存储相关指令的某些执行结果;
- 用来为CPU执行相关指令提供行为依据;
- 用来控制CPU的相关工作方式;
flag寄存器按位起作用,每一位都有专门的含义
ZF标志位
第6位,零标志位;
它记录相关指令执行后,其结果是否为0:
- 如果结果为0,那么zf=1
- 如果结果不为0,那么zf=0
例如:
1
2
3mov ax,1
sub ax,1
; 执行后结果为0,zf=1
PF标志位
第2位,奇偶标志位;
它记录相关指令执行后,其结果的所有bit位中1的个数是否位偶数:
- 如果1的个数为偶数,pf=1;
- 如果为奇数,那么pf=0;
例如:
1
2
3mov ax,1
add al,10
; 执行后,结果为00001011B,其中有3个1,则pf=0
SF标志位
第7位,符号标志位;
它记录相关指令后,其结果是否为负:
- 如果结果为负,sf=1;
- 如果不为负,sf=0;
计算机通常用补码来表示有符号数据。计算机中的一个数据可以看做是有符号数,也可以看做成无符号数。
比如:
00000001B,可以看做无符号数1,或有符号数+1;
10000001B,可以看做无符号数129,也可以看作有符号数-127;
不管我们如何看待,CPU在执行add等指令的时候,就已经包含了两种含义,也将得到同一种信息来记录的两种结果。关键在于程序需要哪一种结果;例如:
1
2
3
4
5
6mov al,10000001B
add al,1
; 执行后,结果为10000010B,sf=1,表示:如果指令进行的是有符号数运算,那么结果为负;
mov al,100000001B
add al,011111111B
; 执行后,结果为0,sf=0,表示:如果指令进行的是有符号数运算,那么结果为非负;某些指令将影响寄存器中的多个标记位,这些被影响的标记位比较全面地记录了指令的执行结果,为相关的处理提供了所需的依据。比如指令
sub al,al
执行后,ZF、PF、SF等标志位都要受到影响,它们分别为:1,1,0;
CF标志位
第0位,进位标志位;
一般情况下,在进行无符号运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位;
例如:
1
2
3
4
5
6
7
8; 进位
mov al,98H
add al,al ; 执行后:(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al,al ; 执行后:(al)=60H,CF=0
; 借位
mov al,97H
sub al,98H ; 执行后(al)=FFH,CF=1,CF记录了向更高位的借位值
sub al,al ; 执行后(al)=0,CF=0,CF记录了向更高位借位值
OF标志位
第11位,溢出标志位;
一般情况下,OF记录了有符号数运算的结果是否发生了溢出:
- 如果发生溢出,OF=1;
- 如果没有,OF=0;
例如:
1
2
3
4mov al,F0H
add al,88H
; 执行后:CF=1,OF=0.对于无符号运算,F0H+78H有进位,CF=1;
; 对于有符号数运算,F0H+78H不发生溢出,OF=0
AF标志位
- 第四位,辅助标志位;
- 反应运算结果低四位产生进位或错位的情况;
DF标志位
- 第10位,方向标志位;
- 在串处理指令中,控制每次操作后si,di的增减:
- df=0,每次操作后si、di递增;
- df=1,每次操作后si、di递减;
TF标志位
- 第8位,单步标志位;
- 用于程序跟踪调试:
- 当TF=1,CPU进入单步方式;
IF标志位
- 第9位,中断允许位
- 当IF=1时,CPU为开中断;
- 当IF=0时,CPU为关中断;
寻址方式
立即寻址
操作数包含在指令中,它作为指令的一部分,跟在操作码后存放在代码段;
1 | mov ax,idata |
寄存器寻址
指令所要的操作数已存储在某寄存器中,直接对寄存器进行操作;
1 | mov 寄存器1,寄存器2 |
直接寻址
操作数在存储器中,指令直接包含有操作数的有效地址;
由于操作数一般存放在数据段,所以操作数的地址由DS加上指令中给出的16位偏移得到;
1 | mov 寄存器,[idata] |
寄存器间接寻址
操作数在存储器中,操作数有效地址在SI、DI、BX、BP这四个寄存器之一中;
在一般情况下:
- 如果有效地址在SI、DI和BX中,则以DS段寄存器的内容为段值;
- 如果有效地址在BP中,则以SS段寄存器的内容为段值;
1 | [bx] |
寄存器相对寻址
操作数在存储器中,操作数的有效地址是一个基址寄存器(BX、BP)或变址寄存器的(SI、DI)内容加上指令中给定的8位或16位偏移量之和;
1 | [bx+idata] |
基址变址寻址
操作数在存储器中,操作数的有效地址由基址寄存器之一的内容与变址寄存器之一的内容相加得到;
1 | [bx+si] |
相对基址变址寻址
操作数在存储器中,操作数的有效地址由基址寄存器之一的内容与变址寄存器之一的内容及指令中给定的偏移量相加得到;
1 | [bx+si+idata] |
指令
CPU指令执行过程
- 从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲区;
- (IP) = (IP)+所读取指令的长度(8位地址,因为范围是-128~127),从而指向下一条指令;
- 执行指令;
- 转到步骤1,重复这个过程;
mov
指令
MOV指令可以在CPU内或CPU和存储器之间传送字或字节;
它传送的信息可以从
- 寄存器到寄存器,
mov ax,bx
- 立即数到寄存器,
mov ax,8
- 立即数到存储单元,
mov [0],8
- 从存储单元到寄存器,
mov ax,[0]
- 从寄存器到存储单元,
mov [0],ax
- 从寄存器或存储单元到除CS外的段寄存器 (注意立即数不能直接送段寄存器),
mov ds,ax
、mov ds,[0]
- 从段寄存器到寄存器或存储单元,
mov ax,ds
、mov [0],ds
注意:
- MOV指令中的源操作数绝对不能是立即数和代码段CS寄存器;
- MOV指令中绝对不允许在两个存储单元之间直接传送数据;
- MOV指令中绝对不允许在两个段寄存器之间直接传送数据;
- MOV指令不会影响标志位;
add
指令
用于实现数据加法运算,操作和要求与mov
相同;
sub
指令
用于实现数据减法运算,操作和要求与mov
相同;
div
指令
div是除法指令,使用div做除法的时候应注意以下问题:
除数:有8位和16位两种,在一个寄存器或者内存中。
被除数:默认放在AX或(DX和AX)中,如果除数为8位,被除数为16位,被除数默认在AX中存放,如果除数为16位,被除数为32位,被除数则在(DX和AX)中存放,DX存放高16位,AX存放低16位。
结果:如果除数是8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数是16位,则AX存储除法操作的商,DX存储除法操作的余数。
被除数 除数 商 余数 AX reg/mem8 AL AH DX:AX reg/mem16 AX DX EDX:EAX reg/mem32 EAX EDX 1
2
3
4
5
6
7
8
9; 8位无符号除法
mov ax, 0083h ; 被除数
mov bl, 2 ; 除数
div bl ; AL = 41h, AH = Olh
; 16位无符号除法
mov dx, 0 ; 清除被除数高16位
mov ax, 8003h ; 被除数的低16位
mov ex, 100h ; 除数
div ex ; AX = 0080h, DX = 0003h
mul
指令
mul 是乘法指令;
两个相乘的数:两个相乘的数,要么都是 8 位,要么都是 16 位;
- 如果是 8 位,一个默认放在 AL 中,另一个放在 8 位寄存器或内存字节单元中;
- 如果是 16 位,一个默认在 AX 中,另一个放在 16 位寄存器或内存字单元中;
结果:如果是 8 位乘法,结果默认放在 AX 中;如果是 16 位乘法,结果高位默认在 DX 中存放,低位在 AX 中存放;
格式:
1
2mul reg
mul 内存单元内存单元可以用不同的寻址方式给出
1
2
3
4
5mul byte ptr ds:[0];含义:(ax)=(al)*((ds)*16+0)
mul word ptr [bx+si+8]
; 含义:(ax)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位
; (dx)=(ax)*((ds)*16+(bx)+(si)+8)结果的高16位示例
计算 100*10:
100 和 10 小于 255,可以做 8 位乘法;
1
2
3
4mov al,100
mov bl,10
mul bl
; 结果(ax)=1000(03E8H)100*10000
100 小于 255,10000 大于 255,要做 16 位乘法;
1
2
3
4mov ax,100
mov bx,10000
mul bx
; 结果 (ax)=4240H,(dx)=000FH,(1000000=F4240H)
push
、pop
指令
push指令和pop指令用于实现寄存器/内存单元和栈空间之间传送数据,push用于数据入栈,pop用于数据出栈;
具体指令为
1 | push 寄存器/段寄存器/内存空间 |
- push指令执行步骤:
- SP=SP-2;
- 向SS:SP指向的字单元送入数据;
- pop指令执行步骤:
- 从SS:SP指向的字单元中读取数据;
- SP=SP+2;
补充栈知识:
- 从高地址向低地址存数据,即入栈时,栈顶从高地址向低地址增长;
- 当栈为空时,SP指向栈的最底部单元的上一个单元(高地址在底部);
- 栈顶地址通过SS:SP获取;
- 栈越界包括PUSH越界和POP越界;
loop
指令
作用是循环执行某段指令;
格式如下
1 | mov cx, 循环的次数 # 当遇到Loop标号时 cx就代表循环的次数 |
loop指令执行流程:
-
cx=cx-1
; - 判断cx的值:
- 如果不为零,就执行标号处的代码,然后执行第一步;
- 如果为零, 执行loop后面的代码;
loop的实现其实就是判断cx>0然后jump到标号所在地址;
1 | mov ax, ffffh |
and
指令
逻辑与指令,按位进行逻辑与运算;
1 | mov al,01100011B |
结果为al=00100011B
;
or
指令
逻辑或指令,按位进行逻辑或运算;
1 | mov al,01100011B |
结果为al=01111011B
;
db
/dw
/dd
指令
伪指令,由编译器识别处理;
db
定义字节型数据,define byte
;
dw
定义字型数据,define word
;
dd
定义双字型数据,define double word
;
1 | ; example |
在data段定义了3个数据:
第一个数据为01H
,在data:0
,占1个字节;
第二个数据为0001H
,在data:1
,占1个字,即2个字节;
第三个数据为00000001H
,在data:3
,占2个字,即4个字节;
dup
指令
伪指令,与dd,dw,db配合使用,用来重复定义数据,由编译器识别处理;
1 | db 3 dup (0) ; 定义了三个字节,它们的值都是0,相当于:db 0,0,0 |
dup
使用格式如下:
1 | db 重复的次数 dup (重复的字节型数据) |
offset
指令
伪指令,由编译器识别处理;
用于返回数据标号的偏移地址([bx,bp,si,di]),这个偏移地址按字节计算,表示的是该数据标号距离数据段起始地址的距离;
1 | ; 该程序在运行中将s处的一条指令复制到s0处 |
jmp
指令
依据位移进行转移的jmp
指令
无条件转移,可以只修改IP,也可以同时修改CS和IP;
jmp指令要给出两种信息:
- 转移的目的地址
- 转移的距离(段间转移、段内短转移,段内近转移)
段内短转移
格式:jmp short 标号(转到标号处执行指令)
这种格式的 jmp 指令实现的是段内短转移,它对IP的修改范围为 -128~127,也就是说,它向前转移时可以最多越过128个字节,向后转移可以最多越过127个字节;
short
符号说明指令进行的是短转移;
标号是指代码段中的标号,指明了指令要转移的目的地址,指令转移结束后,CS:IP应该指向标号处的指令;
1 | assume cs:codesg |
程序执行后,ax的值为11h,因为执行jmp short s
后,直接越过mov ax,0h
,IP
指向了标号s处的inc ax
;
实际上,jmp short 标号
的功能为:(IP)=(IP)+8位位移
8位位移=标号后处的地址-jmp指令后第一个字节的地址;
short指明此处的位移是8位位移;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出,如下图;
机器码
EB03
中的03
即上述提到的8位位移;
段内近转移
格式:jmp near ptr 标号(转到标号处执行指令)
,与段内短转移用法基本相同;
功能为:(IP)=(IP)+16位位移
,对IP的修改范围为-32768~32767;
- 16位位移=标号后处的地址-jmp指令后第一个字节的地址;
-
near ptr
指明此处的位移是16位位移,进行的是段内近转移; - 16位位移的范围为-32768~32767,用补码表示;
- 16位位移由编译程序在编译时算出;
转移的目的地址在指令中的jmp
指令
前面的两个jmp指令对应的机器指令中并没有转移的目的地址,而是相对于当前IP的转移位移。
段间转移
格式:jmp far ptr 标号(转到标号处执行指令)
,实现段间转移,又称为远转移;
功能:(CS)=标号所在段的段地址;(IP)=标号所在段中的偏移地址;far ptr
指明了指令用标号的段地址和偏移地址修改CS和IP;
机器码EA0B012B0B
中的0B012B0B
即CS、IP的内容;
转移地址在寄存器中的jmp
指令
格式:jmp 16位reg
功能:(IP)=(16位reg)
1 | mov ax,2233H |
转移地址在内存中的jmp
指令
段内转移
格式:jmp word ptr 内存单元地址
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址,内存地址的表示需要段地址和偏移地址;
1 | mov ax,2233h |
段间转移
格式:jmp dword ptr 内存单元地址
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的地址,低地址处是转移的目的偏移地址
cs=内存单元地址+2
ip=内存单元地址
1 | mov ax,0123h |
jcxz
指令
有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址;
对 IP 的修改范围都为: -128~127;
格式:jcxz 标号
(如果 (cx)=0 ,转移到标号处执行)
操作: 当 (cx)=0
时 ,(IP)=(IP)+8位位移
8位位移 = 标号处的地址 - jmp指令后的第一个字节地址;
8位位移的范围-128~127,用补码表示;
8位位移是编译程序时在编译时算出的;
当 (cx)≠0
时 ,什么也不做(程序向下执行)
jcxz 标号
的功能相当于:if((cx)=0) jmp short 标号
;
ret
/retf
指令
调用栈中的数据;
ret
指令用栈中的数据,修改IP的内容,从而实现近转移;CPU执行
ret
指令时,进行下面两步操作:-
(IP)=((SS)*16+(SP))
-
(SP)=(SP)+2
-
相当于执行
pop ip
下面程序
ret
执行后,(IP)=0
,CS:IP
指向代码段的第一条指令;retf
指令用栈中的数据,修改CS和IP的内容,从而实现远转移;CPU执行
retf
指令时,进行下面四步操作:-
(IP)=((SS)*16+(SP))
-
(SP)=(SP)+2
-
(CS)=((SS)*16+(SP))
-
(SP)=(SP)+2
-
相当于执行
pop ip
pop cs
下面程序
ret
执行后,(IP)=0
,CS:IP
指向代码段的第一条指令;
call
指令
- CPU执行
call
指令时,进行两步操作:- 将当前的IP或CS和IP压入栈中;
- 转移;
-
call
指令与ret
/retf
配合使用实现压栈和出栈; -
call
指令不能实现短转移,除此之外,call
指令实现转移的方法和jmp
指令的原理相同;
依据位移进行转移的call
指令
格式:
call 标号
(将当前的IP压栈后,转到标号处执行指令)CPU执行此种格式的call指令时,进行如下的操作:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(IP)+16位位移
16位位移 = 标号处的地址 - call指令后的第一个字节地址;
16位位移的范围-32768~32767,用补码表示;
16位位移是编译程序时在编译时算出的;
call执行
call 标号
时,相当于进行:push IP
jmp near ptr 标号
转移目的地址在指令中的call
指令
格式:
call far ptr 标号
,实现的是段间转移.CPU执行此种格式的call指令时,进行如下的操作:
(sp)=(sp)-2
((ss)*16+(sp))=(CS)
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(cs)=标号所在段的段地址
(IP)=标号在段中的偏移地址
CPU执行
call far ptr 标号
时,相当于进行:push CS
push IP
jmp far ptr 标号
转移地址在寄存器中的call
指令
格式:
call 16位reg
CPU执行此种格式的call指令时,进行如下的操作:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)
CPU执行
call 16位reg
时,相当于:push IP
jmp 16 位reg
转移地址在内存中的call
指令
转移地址在内存中的call指令有两种格式;
call word ptr 内存单元地址
CPU执行时,相当于:
push IP
jmp word ptr 内存单元地址
call dword ptr 内存单元地址
CPU执行时,相当于:
push CS
push IP
jmp word ptr 内存单元地址
adc
指令
带进位加法指令,它利用了CF位上记录的进位值;
指令格式: adc 操作对象1,操作对象2
功能:操作对象1 = 操作对象1 + 操作对象2 + CF
例如:指令adc ax,bx
实现的功能是(ax)=(ax)+(bx)+CF
1 | mov ax,3 |
sbb
指令
带借位减法指令,它利用了CF位上记录的借位值;
指令格式:sbb 操作对象1,操作对象2
;
功能:操作对象1=操作对象1-操作对象2-CF
;
例如,指令sbb ax,bx
实现的功能是(ax)=(ax)-(bx)-CF
sbb指令执行后,将对CF进行设置;
1 | ; 计算003E1000H-00202000H,结果放在ax,bx中 |
cmp
指令
cmp是比较指令,cmp的功能相当于减法指令(sub)。它不保存结果,只是影响相应的标志位,其他的指令通过识别这些被影响的标志位来得知比较结果;
指令格式: cmp 操作对象1,操作对象2
;
功能:计算操作对象1-操作对象2
,但不保存结果,只是根据结果修改相应的标志位;
条件转移指令
此处的条件转移指令根据无符号数的比较结果进行转移;
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | zf=1 |
jne | 不等于则转移 | zf=0 |
jb | 低于则转移 | cf=1 |
jnb | 不低于则转移 | cf=0 |
ja | 高于则转移 | cf=0 & zf=0 |
jna | 不高于则转移 | cf=1 | zf=1 |
j——jump
e——equal
b——below
a——above
n——not
movsb
指令
格式:movsb
;
功能:将ds:si
指向的内存单元中的字节送入es:di
中,然后根据标志寄存器df位的值,将si和di递增或递减;
表示操作如下
1 | ((es)*16+(di)) = ((ds)*16+(si)) |
movsw
指令
格式:movsb
;
功能:将ds:si
指向的内存单元中的字送入es:di
中,然后根据标志寄存器df位的值,将si和di递增2或递减2;
表示操作如下
1 | mov es:[di],word ptr ds:[si] |
rep
指令
按照计数寄存器CX中指定的次数重复执行字符串指令;
1 | rep movsb |
cld
/std
指令
cld
:将标志寄存器的df位置0;
std
:将标志寄存器的df位置1;
pushf
、popf
指令
pushf
:将标志寄存器的值压栈;
popf
:从栈中弹出数据送入标志寄存器;
pushf
和popf
为直接访问标志寄存器提供了一种方法;
iret
指令
中断返回,中断服务程序的最后一条指令;
iret
指令将推入堆栈的段地址和偏移地址弹出,使程序返回到原来发生中断的地方,其作用是从中断中恢复中断前的状态;
iret
指令的功能用汇编语言描述为
1 | pop IP |
int
指令
引发中断,调用“中断例程”(interrupt routine);
格式:int n
,n为中断类型码;
指令执行过程如下:
取中断类型码n;
标志寄存器入栈
pushf
,IF=0,TF=0
(重置中断标志位);CS、IP入栈;
查中断向量表,
(IP)=(n*4)
,(CS)=(n*4+2)
;从此处转去执行n号中断的中断处理程序;
in
/out
指令
CPU对外设的操作通过专门的端口读写指令来完成;
读端口用in
指令;
写端口用out
指令;
例如:
1 | IN AL,21H ; 表示从21H端口读取一字节数据到AL |
shl
/shr
指令
shl
是逻辑左移指令;
功能为:
- 将一个寄存器或内存单元中的数据向左移位;
- 将最后移出的一位写入CF中;
- 最低位用0补充;
例如:
1 | mov al,01001000b |
注意:如果移动位数大于1时,必须将移动位数放在cl中;
例如:
1 | mov al,00110001b |
shr
是逻辑右移指令,和shl所进行的操作刚好相反;
字/字节操作判断
通过寄存器名判断
ax表示字操作;
al表示字节操作;
通过操作符
X ptr
判断X
在汇编指令中可以是word
和byte
;1
2mov word ptr ds:[0],1
add byte ptr [bx],2默认访问
有些指令默认访问字单元或者字节单元;
比如push和pop只进行字操作;
中断
中断信息:任何一个通用 CPU 都具备一种能力, 可以在执行完当前正在执行的指令之后, 检测到从 CPU 外部发送过来的或者内部产生的一种特殊信息, 并且可以立即对所接受到的信息进行处理. 这种特殊的信息称为:中断信息。中断意味着 CPU 不再继续向下执行, 而是转去处理这个特殊的信息.
内中断
CPU 内部产生的中断称为内中断
中断类型码
中断类型码是中断来源信息的编码,在8086CPU中使用一个字节的长度来编码中断源;
对于 8086CPU, 有四种中断信息,类型码表示如下;
中断原因 中断类型码 除法错误 0 单步执行 1 执行 into 指令 4 执行 int 指令 n(int n,n为字节型立即数) 中断处理程序
CPU 在收到中断信息之后,需要对中断进行处理,中断处理程序就是用来处理对应中断的程序,CPU 在收到中断信息之后, 就会转去执行对应的中断处理程序,中断处理程序由程序员编写;
中断处理程序的常规步骤:
- 保存用到的寄存器;
- 处理中断;
- 恢复用到的寄存器;
- 用 iret 指令返回;
中断向量
中断程序的入口地址;
中断向量表
- 记录中断向量的列表;
- 中断向量表在内存中保存, 存放着 256 个中断源所对应的中断处理程序的入口地址;
- CPU 根据中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址;
- 对于 8086PC 机,中断向量表存放在0000:0000~0000:03FF所在的内存中,每个表项占用两个字节的大小,高地址字存放段地址,低地址字存放偏移地址;
中断过程
CPU进行中断处理
- 从中断信息中获取中断类型码;
- 标志寄存器的值入栈;
- 设置标志寄存器的第8位TF和第9位IF的值为0;
- CS的内容入栈;
- IP的内容入栈;
- 从内存地址为中断类型码*4和中断类型码*4+2的两个单元中读取中断处理程序的入口地址放入IP和CS中;
更简洁的描述中断过程如下:
- 取得中断码N;
-
pushf
-
TF=0,IF=0
-
push CS
-
Push IP
-
(IP)=(N*4),CS=(N*4+2)
外中断
由外部设备发生的事件引发的中断;
外中断分为两大类:不可屏蔽中断、可屏蔽中断;
- 不可屏蔽中断
- 不可屏蔽中断是CPU必须响应的中断;
- 当CPU检测不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程;
- 对8086CPU不可屏蔽中断的中断类型码固定为2;
- 可屏蔽中断
- 可屏蔽中断是CPU可以不响应的外中断;
- CPU是否响应可屏蔽中断,要看标志寄存位IF位的设置;
- 当CPU检测到可屏蔽中断信息:
- 如果IF = 1,CPU在执行完当前指令后响应中断,引发中断过程;
- 如果IF = 0,不响应可屏蔽中断;
- 不可屏蔽中断
几乎所有由外设引发的中断,都是可屏蔽中断,如键盘输入、打印机请求;
不可屏蔽中断在系统中有必须处理的紧急请求情况发生时来通知CPU中断信息,比如机器突然掉电;
外中断处理过程如下
可屏蔽中断引发的中断处理过程:
取中断类型码n;
- 可屏蔽中断信息来自CPU外部,中断类型码是通过数据总线送入CPU;
- 对比内中断:中断类型码是在CPU内部产生;
标志寄存器入栈,
IF=0 ,F=0
;- 将IF置为0的原因:进入中断处理程序后,禁止其他可屏蔽中断;
- 如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置为1;
CS,IP
入栈;(IP)=(n*4),CS=(n*4+2)
;由此转去执行中断处理程序;
不可屏蔽中断的中断过程:
中断值固定为2,不必取中断码
标志寄存器入栈,
IF= 0,TF = 0
CS,IP
入栈(IP) = (8),CS = (0AH)
直接定址表
标号
在汇编代码中,可以用标号表示该段代码的内存地址;
标号格式为标号名:冒号
例如start:
;
1 | start:mov ax,0 |
数据标号
普通的标号只能表示内存地址;
数据标号既可以表示内存地址,也可以表示内存单元的长度;
使用数据标号可以以更加简洁的方式访问内存中的数据;
数据标号的格式为:
标号名
,没有冒号;例如:使用数据标号,实现数值的累加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
start:mov si,0
mov cx,8
s:mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start- 在上面的代码中,a、b为数据标号;
- 可以使用类似
a[si]
的方式获取标号a处偏移量为si的值的数据; - 注意数据标号的类型,例如上面的a是字节型数据单元,不能使用字型单元的mov指令;
在其他段使用数据标号
普通的标号只能在代码段中使用,例如标号
s:
出现在数据段中会报错;一般情况下会将数据段和代码段分开,并且在数据段中保存数据,在代码段中保存代码,普通标号无法满足访问数据的需要;
使用数据标号可以解决标号无法在其它段中的问题;
例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20assume cs:code,ds:data
data segment
a db 1,2,3,4,5
b dw 0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov cx,5
s: mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start- 如果想要在代码段中使用标号访问数据段,需要使用伪指令assume将段寄存器和标号所在段关联;
- assume的关联是为了让编译器可以识别标号所在段的段地址在哪个寄存器中,实际代码中还需要将段寄存器指向对应的段;
- 比如:上面的代码中,想要使用数据标号a、b,用assume伪指令关联ds和data;
- 但assume只是将关联告诉了编译器,实际的代码段中还需要用mov ax,data和mov ds,ax指令来将ds指向data;
使用数据标号储存两个数据标号的段地址和偏移地址
1
2
3
4
5data segment
a db 1,2,3,4,5
b dw 0
c dd a,b
data ends相当于:
1
2
3
4
5data segment
a db 1,2,3,4,5
b dw 0
c dw offset a,seg a,offset b,seg b
data endsseg功能为取得某一标号的段地址;
直接定址表
在编程时常会遇到一些比如用给出的数据通过计算来获得结果的问题;
例如:将一个数转换成十六进制显示;
- 当一次需要转换的数字很多时,需要多次进行重复的计算转换;
- 为了简化操作,可以将问题转换为用给出的数据作为查表的依据,通过查表获得结果;
- 具体操作是:
- 建一张数据和结果映射表;
- 用数据为条件,计算结果在表中的位置也就获取了结果;
- 例如:将0~F对应的ascii码放入一个数据段中,根据所给数值的大小来计算该数值对应的ascii在数据段中的地址;这样就不用每次都做将数值转换成ascii的操作;
这种可以依据数据,直接计算出所要找的元素的位置的表称为直接定址表;
参考:《汇编语言》-王爽