2020.02.10-2020.02.16

汇编学习

offset

功能是取得标号的偏移地址

1
2
3
4
5
6
7
8
assume cs:codesg
codesg segment
start:mov ax,offset start
<!-- 相当于mov ax,0 -->
s:mov ax,offstet s
<!-- 相当于mov ax,3 -->
codesg ends
end start

offset取得了标号start和s的偏移地址:0和3

jmp

依据位移进行转移的jmp指令

1.jmp short 标号

实现段内短转移,对ip修改范围为:-128~127字节
例:

1
2
3
4
5
6
7
....
start:mov ax,0
jmp short s
add ax,1
s:inc ax
....

执行后ax=1

“依据位移进行转移”:指令对应的机器码中不包含转移的目的地址,而是转移的位移,位移由编译器根据汇编指令计算

jmp short 标号 == (ip)+=8位位移

  • 8位位移=标号处的地址 - jmp指令后第一个字节的地址
  • short 指明位移为8位
  • 8位位移在编译时算出,在机器码中用补码表示

2.jmp near ptr 标号

与jump short 标号 相似
功能为:ip+16位位移

转移的目的地址在指令中的jmp指令

1.jmp far ptr 标号

用标号的段地址和偏移地址修改cs和ip
实现的是段间转移(远转移)
功能为:
 (cs)=标号所在段地址 
(ip)=标号所在偏移地址
机器码:EA0B01BD0B
对应:jmp 0BBD:0B01

转移地址在寄存器中的jmp指令

1.jmp 16位寄存器

(ip)=(16位寄存器)

转移地址在内存中的jmp指令

1.jmp word ptr 内存单元地址(段内转移)

内存单元地址处开始存放的一个字作为偏移地址
内存单元地址可用寻址方式的任一格式给出

2.jmp dword ptr 内存单元地址(段间转移)

高地址处的字是转移到目的地段地址,低地址处是转移到目的偏移地址:
 (cs)=(内存单元地址+2)
 (ip)=(内存单元地址)

如:

1
2
3
4
mov ax,0123
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]

(cs)=0,(ip)=0123

jcxz

为有条件转移指令

所有有条件的转移指令都是短转移,在机器码中包含的是位移而不是目的地址,ip修改范围为:-128~127

格式:jcxz 标号
相当于:

1
2
if((cx)==0)
jmp short 标号

loop

所有循环指令都是短转移在机器码中包含的是位移而不是目的地址,ip修改范围为:-128~127
格式:loop 标号
相当于:

1
2
3
(cx)--;
if((cx)!=0)
jmp short 标号;

根据位移进行转移的意义

在机器码中包含的是位移而不是目的地址—这种设计方便了程序段在内存中的浮动装配,在不同位置都可正确执行

call和ret指令

call和ret都是转移指令,修改ip或同时修改cs和ip

ret和retf

ret用栈中的数据修改ip实现近转移,相当于:
pop ip
retf用栈中的数据修改cs和ip实现远转移,相当于:
 pop ip
 pop cs

call

CPU执行call时:
1.将ip或cs和ip压入栈
2.实现长转移
call不能实现短转移

1.依据位移进行转移的call指令

call 标号
执行时进行如下操作:

1
2
3
(sp)=(sp)-2
((sp)*16+(sp))=(ip)
(ip)=(ip)+16位位移

将当前ip压栈后,转到标号处
相当于

1
2
push ip
jmp near ptr 标号

2.转移的目的地址在指令中的call指令

call far ptr 标号
执行时进行如下操作:

1
2
3
4
(sp)=(sp)-2
((ss)*16+(sp))=(cs)
(sp)=(sp)-2
((sp)*16+(sp))=(ip)

相当于:

1
2
3
push cs
push ip
jmp far ptr 标号

3.转移地址在寄存器中的call指令

call 16位寄存器
相当于

1
2
push ip
jmp 16位寄存器

4.转移地址在内存中的call指令

4.1.call word ptr 内存单元地址

1
2
push ip
jmp word ptr 内存单元地址

4.2.call dword ptr 内存单元地址

1
2
3
push cs
push ip
jmp dword ptr 内存单元地址

call 和 ret配合使用

实现子程序,用call指令执行子程序,再用ret指令转到call指令后的代码:

1
2
3
4
5
标号:
....
指令
....
ret

mul指令

乘法指令

注意:

  • 两个相乘的数要么都是8位,要么都是16位
     1.如果是8位,一个默认放在al中,另一个放在8位寄存器或内存字节单元中。结果默认放在ax中
     2.如果是16位,一个默认在ax中,另一个放在16位寄存器或内存字单元中。结果高位默认放在dx中,低位2放在ax中

格式:

1
mul 寄存器/内存单元

如:
1.100*10

1
2
3
mov al,100
mov bl,10
mul bl

结果:(ax)=1000

2.100*10000

1
2
3
mov ax,100
mov bx,10000
mul bx

结果:(ax)=4240h (dx)=000fh
(f4240h=1000000)

一个公式

 将可能产生溢出的除法运算转变为多个不会产生溢出的除法运算(商小于65536)

1
x/n=int (h/n)*65536+[ rem(h/n)*65536+l]/n  

x : 被除数(0,ffffffff)
n : 除数(0,ffff)
h : x高16位
l : x低16位
int() : 取商
rem() : 取余

标志寄存器

标志寄存器作用:
1.用来储存相关指令的执行结果
2.用来为CPU执行相关指令提供行为依据
3.用来控制CPU的相关工作方式
8086CPU有16位,其中储存的信息被称为程序状态字(psw)
flag是按位起作用的,每一位都有专门的含义,记录特定的信息

flag寄存器各位示意图

flag的1、3、5、12、13、14、15位在8086CPU中没有使用,其他位都有特殊含义

影响标志寄存器的大都是运算指令,没有影响的大都是传送指令

ZF标志

零标志位
记录相关指令执行后结果是否为0,如果为0那么ZF=1,如果不为0那么ZF=0

1
2
mov ax,1
sub ax,1

执行后zf=1

1
2
mov ax,2
sub ax,1

执行后zf=0

PF标志

奇偶标志位
记录相关指令执行后结果的所有bit位中 1 的个数是否为偶数,如果为偶数PF=1,如果不为偶数PF=0

1
2
mov al,1(10)
add al,10(10)

结果为00001011B,∴PF=0

1
2
mov al,1
or al,2

结果为00000011B,∴PF=1

SF标志

符号标志位
记录相关指令执行后结果是否为负,如果负sf=1,如果非负sf=0

计算机中通常用补码表示有符号数据,一个数据可以看作是 有符号数,也可以看成无符号数。不管如何看待,CPU在执行指令的时候就已经包含了两种含义,也将得到两种结果,关键在于程序需要哪种结果
sf标志是对于有符号数运算的一种记录,记录了数据的正负
将数据当作有符号数运算时,可以通过 sf 知道结果的正负
将数据当作无符号数运算时, sf 值无意义,虽然相关指令影响了它的值

1
2
mov al,10000001B
add al,1

结果为10000010,sf=1,表示:如果指令进行的是有符号数的运算,那么结果为负。

1
2
mov al,10000001B
add al,01111111B

结果为0,sf=0,表示如果指令进行的是有符号数运算,那么结果为非负。

单纯地考查sf的值不能知道结果的正负,因为sf记录的只是可以在计算机中存放的相应位数的结果的正负(如果发生溢出)

CF标志

进位标志位
进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值

对于位数为N的无符号数来说,N-1位为它的最高有效位,假想存在的第N位就是相对于最高有效位的更高位

当两个数据相加的时候,可能产生从最高有效位向更高位的进位。CPU不丢弃这个高位进位值,而是保存在CF上

OF标志

溢出标志位
在进行有符号数运算时,如果结果超过了机器能表达的范围称为溢出
记录了有符号数运算的结果是否发生了溢出,如果发生溢出OF=1,如果没有OF=0

CF是对无符号数运算有意义的标志位,OF是对有符号数运算有意义的标志位。
对于无符号数运算,CPU用CF来记录是否产生进位;对于有符号数,CPU用OF来记录是否产生溢出,还要用SF来记录结果的符号

1
2
mov al,98
add al,99

执行后 CF=0,OF=1

1
2
mov al,0f0H
add al,78H

执行后CF=1,OF=0

adc指令

带进位加法指令,利用了CF位上记录的进位值
格式:adc 操作对象1,操作对象2
操作对象1=操作对象1+操作对象2+CF
比add指令多加了一个CF位的值

1
2
3
4
add ax,bx
==
add al,bl
adc ah,bh

adc指令执行后也可能产生进位值,所以也会对CF位进行设置
add指令和adc指令配合 可以对更大的数据进行加法运算

例:
计算1EF0001000H+2010001EF0H,结果放在ax,bx,cx中

1.将低16位相加,CF中记录相加的进位值
2.将次高16位和CF相加,CF中记录相加的进位值
3.高16位和CF相加,CF中记录相加的进位值

sbb指令

带借位减法指令,利用了CF位上的借位值
格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1-操作对象2-CF
可以对任意大的数据进行减法运算,思路同adc指令

cmp指令

比较指令,功能相当于减法指令,只是不保存结果,仅仅根据计算结果对标志寄存器进行设置
格式:cmp 操作对象1,操作对象2
cmp可以对无符号数进行比较,也可以对有符号数进行比较
通过cmp指令执行后,相关标志位的值可以看出比较的结果:

进行无符号数比较时:

1
cmp ax,bx

如果(ax)=(bx)则(ax)-(bx)=0,所以zf=1
如果(ax)!=(bx)则(ax)-(bx)!=0,所以zf=0
如果(ax)<(bx)则(ax)-(bx)将产生借位,所以cf=1
如果(ax)>=(bx)则(ax)-(bx)将不必借位,所以cf=0
如果(ax)>(bx)则(ax)-(bx)=0不必借位且结果不为0,所以cf=0,zf=0
如果(ax)<=(bx)则(ax)-(bx)=0可能借位,结果可能为0,所以cf=1或zf=1

进行有符号数比较时:

1
cmp ah,bh

如果(ah)=(bh)则(ah)-(bh)=0,所以zf=1
如果(ah)!=(bh)则(ah)-(bh)!=0,所以zf=0

如果sf=1,of=0,(ah)<(bh)
如果sf=1,of=1,(ah)>(bh)
如果sf=0,of=1,(ah)<(bh)
如果sf=0,of=0,(ah)>=(bh)

of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负
如果因为溢出导致了实际结果为负(正),那么逻辑上真正的结果必然为正(负)

*zf:记录结果是否为0。如果为0那么ZF=1,如果不为0那么ZF=0
*cf:记录了无符号数运算结果的最高有效位向更高位的进位值
*of:记录了有符号数运算的结果是否发生了溢出,如果发生溢出OF=1,如果没有OF=0。 OF=0,说明逻辑上真正结果的正负=实际结果的正负
*sf: 记录相关指令执行后结果是否为负,如果负sf=1,如果非负sf=0
*pf:记录相关指令执行后结果的所有bit位中 1 的个数是否为偶数,如果为偶数PF=1,如果不为偶数PF=0

检测比较结果的条件转移指令

与call和ret类似,通常和cmp配合使用
检测被cmp影响的,表示比较结果的标志位

根据无符号数的比较结果进行转移的条件转移指令检测zf、cf:

1
2
3
4
5
6
7
指令            含义                检测标志位
je 等于则转移 zf=1
jne 不等于则转移 zf=0
jb 低于则转移 cf=1
jnb 不低于则转移 cf=0
ja 高于则转移 cf=0&&zf=0
jna 不高于则转移 cf=1||zf=1

根据有符号数的比较结果进行转移的条件转移指令检测sf、of、zf

DF标志和串传送指令

DF:方向标志位,在串传送指令中,控制每次操作后si、di的增减
df=0,每次操作后si、di递增
df=1,每次操作后si、di递减

  1. movsb
    功能:将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值将si和di递增或递减

  2. movsw
    功能:将ds:si指向的内存单元中的送入es:di中,然后根据标志寄存器df位的值将si和di递增2或递减2

一般来说,movsb和movsw都和rep配合使用
格式: rep movsb
功能:

1
2
s:movsb
loop s

对df位进行设置的指令:
cld指令:将df位置0
std指令:将df位置1

使用串传送指令进行数据的传送,需要:

  1. 传送的原始位置:ds:si
  2. 传送的目的位置:es:di
  3. 传送的长度:cx
  4. 传送的方向:df  (正向/反向传送,si、di递增/递减)

pushf和popf

pushf是将标志寄存器的值压栈
popf是从栈中弹出数据送入标志寄存器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mov ax,0 ;ax清零
push ax ;ax入栈
popf ;把栈中数据弹出到PSW。至此,PSW已经被全置为: 0000 0000 0000 0000 B

mov ax,0fff0h
add ax,0010h

CF:假设这是无符号运算:FFF0h+0010h = 1111 1111 1111 0000b + 0000 0000 0001 0000b
=(进位1)0000 0000 0000 0000b,产生进位1,CF标志 = 1。

OF:假设这是有符号运算:FFF0h此处为补码形式,(FFF0h)原 = 1000 0000 0001 0000b。FFF0h+0010h=0,OF标志 = 0

pushf
pop ax
这两句把 ax的值设置为:(0000 00** 010* 0101)b

....

tips:

正加正得负,或负加负得正,肯定溢出

一个正数和一个负数相加不可能溢出

内中断

任何一个cpu都可以在执行完当前正在执行的指令后,检测到从cpu外部发送来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理,这种信息称为中断信息

内中断的产生

cpu内部有4种情况可以产生需要及时处理的中断信息
处理中断信息首先要知道接收到的信息的来源,所以中断信息中必须包含识别来源的编码。8086cpu用中断类型码来标识中断信息的来源
中断类型码为一个字节型数据,即可以表示256种中断信息的来源(简称中断源)

  1. 除法错误,如执行div指令产生的除法溢出 中断类型码:0
  2. 单步执行 中断类型码:1
  3. 执行into指令 中断类型码:4
  4. 执行int 指令 指令格式为int n,n为字节型立即数,中断类型码:n

中断向量表

中断处理程序入口地址的列表
cpu用8位的终端类型码,通过中断向量表,找到相应的中断处理程序的入口地址
中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口
cpu知道了中断类型码就可以将中断类型码作为中断向量表的表项号,定位相应的表项,从而得到程序的入口地址
如果使用8086cpu,中断向量表就必须存放在0000:0000~0000:03FF中,一个表项占两个字,高地址存放段地址,低地址字存放偏移地址

*一个字节:8位
*一个字==两个字节