8086汇编

First Post:

Last Update:

仅作自己学习参考,内容仅记录自己认为比较重要的,对于知识体系来说不全

放在博客这格式就有点不对劲,代码块明明设置的assembly,但是显示的是text,高亮也显示不了,也许是这个主题的问题,但本地显示都没问题,不管了

本文参考(40条消息) 王爽《汇编语言》笔记(详细)_汇编语言笔记_洋葱汪的博客-CSDN博客

一.基础知识

1.指令

机器指令:CPU能直接识别并执行的二进制编码

汇编指令:汇编指令是机器指令的助记符,同机器指令一一对应。

指令:指令通常由操作码和地址码(操作数)两部分组成

指令集:每种CPU都有自己的汇编指令集。

编译器:够将汇编指令转换成机器指令的翻译程序每一种CPU都有自己的汇编指令集。

2.存储器

随机存储器(RAM)在程序的执行过程中可读可写,必须带电存储

只读存储器(ROM)在程序的执行过程中只读,关机数据不丢失

3.总线

总线是连接各个部件的信息传输线,是各个部件共享的传输介质。

根据传送信息的不同,系统总线从逻辑上分为3类,地址总线、控制总线和数据总线。

二.寄存器

1.寄存器

CPU由运算器、控制器、寄存器等器件构成,这些器件靠片内总线相连。

运算器进行信息处理;控制器控制各种器件进行工作;寄存器进行信息存储;

8086CPU有14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW都是16位

8086采用小端模式:高地址存放高位字节,低地址存放低位字节

2.通用寄存器

通常用来存放一般性的数据,有AX、BX、CX、DX,它们可分为两个可独立使用的8位寄存器

16位 高八位 低八位
AX AH AL
BX BH BL
CX CH CL
DX DH DL

在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的

一个8位寄存器所能存储的数据范围是0 ~ 28-1。

3.8086CPU给出物理地址的方法

8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。

当8086CPU要读写内存时:

CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
地址加法器将两个16位地址合成为一个20位的物理地址;
地址加法器采用物理地址 = 段地址×16 + 偏移地址的方法用段地址和偏移地址合成物理地址。

例如,8086CPU要访问地址为123C8H的内存单元,1230H左移一位(空出4位)加上00C8H合成123C8H

4.段寄存器

我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元,可以用分段的方式来管理内存。

用一个段存放数据,将它定义为“数据段”;

用一个段存放代码,将它定义为“代码段”;

用一个段当作栈,将它定义为“栈段”。

注意:

一个段的起始地址一定是16的倍数;
偏移地址为16位,变化范围为0-FFFFH,所以一个段的长度最大为64KB。
CPU可以用不同的段地址和偏移地址形成同一个物理地址。
段寄存器:8086CPU有4个段寄存器:CS、DS、SS、ES,提供内存单元的段地址。

1.CS和IP

CS为代码段寄存器,IP为指令指针寄存器,

CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,

CPU将CS:IP指向的内容当作指令执行。(即PC)

8086CPU的工作过程简要描述

  1. 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
  2. IP=IP+所读取指令的长度,从而指向下一条指令;
  3. 执行指令。转到步骤1,重复这个过程

2.DS 和 [address]

DS寄存器:通常用来存放要访问数据的段地址

[address]表示一个偏移地址为address的内存单元,段地址默认放在ds中

通过数据段段地址和偏移地址即可定位内存单元。

3.SS 和 SP

在基于8086CPU编程的时候,可以将一段内存当作栈来使用。

栈段寄存器SS,存放段地址,SP寄存器存放偏移地址,任意时刻,SS:SP指向栈顶元素

8086CPU中,入栈时,栈顶从高地址向低地址方向增长。

push ax表示将寄存器ax中的数据送入栈中,由两步完成。

SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。

pop ax表示从栈顶取出数据送入ax,由以下两步完成。

  1. 将SS:SP指向的内存单元处的数据送入ax中;
  2. SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

一看到这就想到脱壳了……

三.第一个程序

汇编程序从写出到执行的过程:

在这里插入图片描述

加载后,CPU的CS:IP指向程序的第一条指令(即程序的入口)

四.[bx] 和 loop指令

1.[bx] 和 loop指令

[bx] 的含义:[bx]同样表示一个内存单元,它的偏移地址在bx中,段地址默认在ds中

loop指令的格式是:loop 标号,CPU执行loop指令的时候,要进行两步操作:

(cx) = (cx) - 1;

判断 cx 中的值,不为零则转至标号处执行程序,如果为零则向下执行。

例如:计算2的12次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code 

code segment
mov ax, 2

mov cx, 11 ;循环次数
s: add ax, ax ;2+2 4+4 8+8
loop s ;在汇编语言中,标号代表一个地址,标号s实际上标识了一个地址,
;这个地址处有一条指令:add ax,ax。
;执行loop s时,首先要将(cx)减1,然后若(cx)不为0,则向前
;转至s处执行add ax,ax。所以,可以利用cx来控制add ax,ax的执行次数。

mov ax,4c00h
int 21h
code ends
end

loop 和 [bx] 的联合应用

计算ffff:0 ~ ffff:b单元中的数据的和,结果存储在dx中

问题分析:

这些内存单元都是字节型数据范围0 ~ 255 ,12个字节数据和不会超过65535,dx可以存下
对于8位数据不能直接加到 dx
解决方案:

用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器a中,再将ax中的数据加到dx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code 

code segment
mov ax, 0ffffh ;在汇编源程序中,数据不能以字母开头,所以要在前面加0。
mov ds, ax ;段地址默认在ds中
mov bx, 0 ;初始化ds:bx指向ffff:0
mov dx, 0 ;初始化累加寄存器dx,(dx)= 0

mov cx, 12 ;初始化循环计数寄存器cx,(cx)= 12
s: mov al, [bx]
mov ah, 0
add dx, ax ;间接向dx中加上((ds)* 16 +(bx))单元的数值
inc bx ;ds:bx指向下一个单元
loop s

mov ax, 4c00h
int 21h
code ends
end

2.段前缀

1
2
3
4
5
6
mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
mov ax, ss:[0]
mov ax, cs:[0]

这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址
的“ds:”,“cs:”,“ss:”,“es:”,在汇编语言中称为段前缀。

3.段前缀的使用

将内存ffff:0 ~ ffff:b单元中的数据复制到0:200 ~ 0:20b单元中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code 

code segment
mov ax, 0ffffh
mov ds, ax ;(ds)= 0ffffh
mov ax, 0020h
mov es, ax ;(es)= 0020h 0:200 等效于 0020:0
mov bx, 0 ;(bx)= 0,此时ds:bx指向ffff:0,es:bx指向0020:0

mov cx,12 ;(cx)=12,循环12次
s: mov dl,[bx] ;(d1)=((ds)* 16+(bx)),将ffff:bx中的字节数据送入dl
mov es:[bx],dl ;((es)*16+(bx))=(d1),将dl中的数据送入0020:bx
inc bx ;(bx)=(bx)+1
loop s

mov ax,4c00h
int 21h
code ends
end

五.包含多个段的程序

在代码段中使用数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
;计算 8 个数据的和存到 ax 寄存器
assume cs:code

code segment

dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;define word 定义8个字形数据

start: mov bx, 0 ;标号start
mov ax, 0

mov cx, 8
s: add ax, cs:[bx]
add bx, 2
loop s

mov ax, 4c00h
int 21h
code ends
end start ;end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方
;用end指令指明了程序的入口在标号start处,也就是说,“mov bx,0”是程序的第一条指令。

在代码段中使用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
;利用栈,将程序中定义的数据逆序存放。
assume cs:codesg

codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ; 0-15单元
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 16-47单元作为栈使用

start: mov ax, cs
mov ss, ax
mov sp, 30h ;将设置栈顶ss:sp指向栈底cs:30。 30h = 48d
mov bx, 0

mov cx, 8
s: push cs:[bx]
add bx, 2
loop s ;以上将代码段0~15单元中的8个字型数据依次入栈

mov bx, 0

mov cx, 8
s0: pop cs:[bx]
add bx,2
loop s0 ;以上依次出栈8个字型数据到代码段0~15单元中

mov ax,4c00h
int 21h
codesg ends
end start ;指明程序的入口在start处

六.更灵活的定位内存地址的方法

1.and 和 or

and指令:逻辑与指令,按位进行与运算。

mov al, 01100011B
and al, 00111011B

执行后:al=00100011B即都为1才为1

or指令:逻辑或指令,按位进行或运算。

mov al, 01100011B
or al, 00111011B
执行后:al=01111011B 即只要有一个为1就为1

2.[bx+idata]

[bx+idata]表示一个内存单元, 例如:mov ax, [bx+200]
该指令也可以写成如下格式:

1
2
3
4
5
mov ax, [200+bx]

mov ax, 200[bx]

mov ax, [bx].200

用[bx+idata]的方式进行数组的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assume cs:codesg,ds:datasg 

datasg segment
db 'BaSiC';转为大写
db 'MinIx';转为小写
datasg ends

codesg segment
start:
mov ax, datasg
mov ds, ax
mov bx, 0 ;初始ds:bx

mov cx, 5
s: mov al, 0[bx] ;注意这种写法,有三种等效写法
and al, 11011111b ;转为大写字母
mov 0[bx], al ;写回
mov al, 5[bx] ;[5 + bx]
or al, 00100000b ;转为小写字母
mov 5[bx], al
inc bx
loop s

mov ax, 4c00h
int 21h
codesg ends
end start

用c语言表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
char a[] = "BaSic";
char b[] = "MinIX";

int i = 0;

do
{
a[i] = a[i] & 0xDF;
b[i] = b[i] | 0x20;
i++;
} while(i < 5);

return 0;
}

3.SI 、DI 与 寻址方式的灵活应用

1.si 、di

si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
assume cs: codesg, ds: datasg 

datasg segment
db 'welcome to masm!';用si和di实现将字符串‘welcome to masm!"复制到它后面的数据区中。
db '................'
datasg ends

codesg segment
start: mov ax, datasg
mov ds, ax
mov si, 0

mov cx, 8
s: mov ax, 0[si] ;[0 + si]
mov 16[si], ax ;[16 + si] 使用[bx +idata]方式代替di,使程序更简洁
add si, 2
loop s

mov ax, 4c00h
int 21h
codesg ends
end start

2.[bx + si] 和 [bx + di]

[bx+si]和[bx+di]的含义相似

[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)

指令mov ax, [bx + si]的含义:将一个内存单元字数据的内容送入ax,段地址在ds中

该指令也可以写成如下格式:mov ax, [bx] [si]

3.[bx+si+idata]和[bx+di+idata]

[bx+si+idata]表示一个内存单元,它的偏移地址为(bx)+(si)+idata

指令mov ax,[bx+si+idata]的含义:将一个内存单元字数据的内容送入ax,段地址在ds中

4.不同的寻址方式的灵活应用

  • [idata]用一个常量来表示地址,可用于直接定位一个内存单元;
  • [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
  • [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
  • [bx+si]用两个变量表示地址;
  • [bx+si+idata]用两个变量和一个常量表示地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

;将datasg段中每个单词改为大写字母
assume cs:codesg,ds:datasg,ss:stacksg

datasg segment
db 'ibm ' ;16
db 'dec '
db 'dos '
db 'vax ' ;看成二维数组
datasg ends

stacksg segment ;定义一个段,用来做栈段,容量为16个字节
dw 0, 0, 0, 0, 0, 0, 0, 0
stacksg ends

codesg segment
start: mov ax, stacksg
mov ss, ax
mov sp, 16
mov ax, datasg
mov ds, ax
mov bx, 0 ;初始ds:bx

;cx为默认循环计数器,二重循环只有一个计数器,所以外层循环先保存cx值,再恢复,我们采用栈保存
mov cx, 4
s0: push cx ;将外层循环的cx值入栈
mov si, 0
mov cx, 3 ;cx设置为内层循环的次数
s: mov al, [bx+si]
and al, 11011111b ;每个字符转为大写字母
mov [bx+si], al
inc si
loop s

add bx, 16 ;下一行
pop cx ;恢复cx值
loop s0 ;外层循环的loop指令将cx中的计数值减1

mov ax,4c00H
int 21H
codesg ends
end start

七、数据处理的两个基本问题

1. bx、si、di和bp

在8086CPU中,只有这4个寄存器可以用在“[…]”中来进行内存单元的寻址。

在[ ]中,这4个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。

只要在[……]中使用寄存器bp,而指令中没有显性地给出段地址, 段地址就默认在ss中

2.机器指令处理的数据在什么地方

数据处理大致可分为3类:读取、写入、运算。

在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置。指令在执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口

在这里插入图片描述

3.汇编语言中数据位置的表达

汇编语言中用3个概念来表达数据的位置

  • 立即数(idata)
1
2
3
4
5
mov ax, 1                 ;对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中)
add bx, 2000h ;在汇编语言中称为:立即数(idata)
or bx, 00010000b
mov al, 'a'

  • 寄存器
1
2
3
4
5
6
7
mov ax, bx     ;指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。
mov ds, ax
push bx
mov ds:[0], bx
push ds
mov ss, ax
mov sp, ax
  • 段地址(SA)和偏移地址(EA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
;指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中。
mov ax, [0]
mov ax, [di]
mov ax, [bx+8]
mov ax, [bx+si]
mov ax, [bx+si+8] ;以上段地址默认在ds中

mov ax, [bp]
mov ax, [bp+8]
mov ax, [bp+si]
mov ax, [bp+si+8] ;以上段地址默认在ss中

mov ax, ds:[bp]
mov ax, es:[bx]
mov ax, ss:[bx+si]
mov ax, cs:[bx+si+8] ;显式给出存放段地址的寄存器

寻址方式

在这里插入图片描述

4.指令要处理的数据有多长

8086CPU的指令,可以处理两种尺寸的数据,byte和word

  • 通过寄存器名指明要处理的数据的尺寸。
    例如: mov al, ds:[0] 寄存器al指明了数据为1字节
  • 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte。
    例如:mov byte ptr ds:[0], 1 byte ptr 指明了指令访问的内存单元是一个字节单元
  • 有些指令默认了访问的是字单元还是字节单元
    例如,push [1000H],push 指令只进行字操作。

5.寻址方式的综合应用

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
mov ax, seg 
mov ds, ax
mov bx, 60h ;确定记录地址,ds:bx

mov word ptr [bx+0ch], 38 ;排名字段改为38 [bx].0ch
add word ptr [bx+0eh], 70 ;收入字段增加70 [bx].0eh
mov si, 0 ;用si来定位产品字符串中的字符
mov byte ptr [bx+10h+si], 'V' ;[bx].10h[si]
inc si
mov byte ptr [bx+10h+si], 'A'
inc si
mov byte ptr [bx+10h+si], 'X'

C语言描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*定义一个公司记录的结构体*/
struct company
{
char cn[3];/*公司名称*/
char hn[9];/*总裁姓名*/
int pm;/*排名*/
int sr;/*收入*/
char cp[3];/*著名产品*/
};
//sizeof (struct company) == 24

int main()
{
/*定义一个公司记录的变量,内存中将存有一条公司的记录*/
struct company dec = {"DEC", "Ken Olsen", 137, 40, "PDP"};

int i;

dec.pm = 38;
dec.sr = dec.sr + 70;

i = 0;
dec.cp[i] = 'V'; //mov byte ptr [bx].10h[si], 'V'
i++;
dec.cp[i] = 'A';
i++;
dec.cp[i] = 'X';

return 0;
}


6.div指令、dd、dup、mul指令

div是除法指令

除数:有8位和16位两种,在一个寄存器内存单元中。

被除数:默认放在AXDX和AX中,
如果除数为8位,被除数则为16位,默认在AX中存放;
如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。

结果:
如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;
如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。

1
2
3
4
5
6
7
8
9
10
11
12
;利用除法指令计算100001/100。
;100001D = 186A1H
mov dx, 1;高16位
mov ax, 86A1H ;(dx)*10000H+(ax)=100001 低16位
mov bx, 100;除数
div bx ;div 除数 被除数默认在dx和ax中存放

;利用除法指令计算1001/100 这个是8位的除法
mov ax, 1001
mov bl, 100
div b1

伪指令dd

db和dw定义字节型数据和字型数据。

dd是用来定义dword(double word,双字)型数据的伪指令

操作符dup

dup在汇编语言中同db、dw、dd等一样,也是由编译器识别处理的符号。
它和db、dw、dd等数据定义伪指令配合使用,用来进行数据的重复

1
2
3
4
db 3 dup (0)       ;定义了3个字节,它们的值都是0,相当于db 0,0,0。
db 3 dup (0, 1, 2) ;定义了9个字节,它们是0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2。
db 3 dup ('abc', 'ABC') ;定义了18个字节,它们是abcABCabcABCabcABCC,相当于db 'abc', 'ABC' ,'abc' , 'ABC, 'abc', 'ABC'。

mul 指令

mul是乘法指令,使用 mul 做乘法的时候:相乘的两个数:要么都是8位,要么都是16位。

8 位: AL中和 8位寄存器或内存字节单元中;

16 位: AX中和 16 位寄存器或内存字单元中。

结果

8位:AX中;

16位:DX(高位)和 AX(低位)中。

格式:==mul 寄存器== 或 ==mul 内存单元==

1
2
3
4
5
6
7
;计算100*10
;100和10小于255,可以做8位乘法
mov al,100
mov bl,10
mul bl ;默认其中一个就是在al中(八位)

;结果: (ax)=1000(03E8H)
1
2
3
4
5
6
7
;计算100*10000
;100小于255,可10000大于255,所以必须做16位乘法,程序如下:
mov ax,100
mov bx,10000
mul bx

;结果: (ax)=4240H,(dx)=000FH (F4240H=1000000)

八、转移指令的原理

可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括地讲,转移指令就是可以控制CPU执行内存中某处代码的指令。

8086CPU的转移行为有以下几类。

只修改IP时,称为==段内转移==,比如:==jmp ax==。
同时修改CS和IP时,称为==段间转移==,比如:==jmp 1000:0==。

由于转移指令对IP的修改范围不同,段内转移又分为:==短转移和近转移==

短转移IP的修改范围为==-128 ~ 127==。
近转移IP的修改范围为==-32768 ~ 32767==。

8086CPU的转移指令分为以下几类

  • 无条件转移指令(如:jmp)
  • 条件转移指令
  • 循环指令(如:loop)
  • 过程
  • 中断

1.操作符offset

操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;将s处的一条指令复制到s0处
assume cs:codesg
codesg segment
s: mov ax, bx ;(mov ax,bx 的机器码占两个字节)
mov si, offset s ;获得标号s的偏移地址
mov di, offset s0 ;获得标号s0的偏移地址

mov ax, cs:[si]
mov cs:[di], ax
s0: nop ;(nop的机器码占一个字节)
nop
codesg ends
ends

2.jmp指令

jmp为无条件转移,转到标号处执行指令可以只修改IP,也可以同时修改CS和IP;

jmp指令要给出两种信息:

  • 转移的目的地址
  • 转移的距离(段间转移、段内短转移,段内近转移)

jmp short 标号, jmp near ptr 标号, jcxz 标号, loop 标号 等几种汇编指令,它们对 IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是到目的地址的位移距离。

依据位移进行转移的jmp指令
jmp short 标号==(段内短转移)==

指令“jmp short 标号”的功能为(IP)=(IP)+8位位移,转到标号处执行指令

(1)8位位移 = “标号”处的地址 - jmp指令后的第一个字节的地址;

(2)short指明此处的位移为8位位移;

(3)8位位移的范围为-128~127,用补码表示

(4)8位位移由编译程序在编译时算出。

1
2
3
4
5
6
7
8
9
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s ;s不是被翻译成目的地址
add ax, 1
s:inc ax ;程序执行后, ax中的值为 1
codesg ends
end start

CPU不需要这个目的地址就可以实现对IP的修改。这里是依据==位移进行转移==

jmp short s指令的读取和执行过程:

(CS)=0BBDH,(IP)=0006,上一条指令执行结束后CS:IP指向EB 03(jmp short s的机器码);
读取指令码EB 03进入指令缓冲器;
(IP) = (IP) + 所读取指令的长度 = (IP) + 2 = 0008,CS:IP指向add ax,1;
CPU指行指令缓冲器中的指令EB 03;
指令EB 03执行后,(IP)=000BH,CS:IP指向inc ax

jmp near ptr 标号 ==(段内近转移)==

指令“jmp near ptr 标号”的功能为:**(IP) = (IP) + 16位位移**。

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

jmp far ptr 标号==(段间转移或远转移)==

指令jmp far ptr 标号 功能如下:

  • (CS) = 标号所在段的段地址;
  • (IP) = 标号所在段中的偏移地址。
  • far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
1
2
3
4
5
6
7
8
9
10
11
assume cs:codesg
codesg segment
start: mov ax, 0
mov bx, 0
jmp far ptr s ;s被翻译成转移的目的地址0B01 BD0B
db 256 dup (0) ;转移的段地址:0BBDH,偏移地址:010BH
s: add ax,1
inc ax
codesg ends
end start

3.转移地址在寄存器或内存中的jmp指令

jmp 16位寄存器 功能:IP =(16位寄存器)

转移地址在内存中的jmp指令有两种格式:

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

功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。

1
2
3
4
mov ax, 0123H
mov ds:[0], ax
jmp word ptr ds:[0]
;执行后,(IP)=0123H
  • jmp dword ptr 内存单元地址(段间转移)

功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。

  1. (CS)=(内存单元地址+2)
  2. (IP)=(内存单元地址)
1
2
3
4
5
6
7
8
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。

4.jcxz指令和loop指令

jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移

在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为-128~127。

指令格式:jcxz 标号(如果(cx)=0,则转移到标号处执行。)

当(cx) = 0时,(IP) = (IP) + 8位位移

8位位移 = “标号”处的地址 - jcxz指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
当(cx)!=0时,什么也不做(程序向下执行)

loop指令

loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。

对IP的修改范围都为-128~127。

指令格式:loop 标号 ((cx) = (cx) - 1,如果(cx) ≠ 0,转移到标号处执行)。

(cx) = (cx) - 1;如果 (cx) != 0,(IP) = (IP) + 8位位移。

8位位移 = 标号处的地址 - loop指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
如果(cx)= 0,什么也不做(程序向下执行)。

九、call和ret指令

call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。

1.ret 和 retf

  • ret指令用栈中的数据,修改IP的内容,从而实现转移;

  • retf指令用栈中的数据,修改CS和IP的内容,从而实现转移。

CPU执行ret指令时,相当于进行: pop IP:

(1)(IP) = ( (ss) * 16 + (sp) )

(2)(sp) = (sp) + 2

CPU执行retf指令时,相当于进行:pop IP, pop CS:

(1)(IP) = ( (ss) * 16 + (sp) )

(2)(sp) = (sp) + 2

(3)(CS) = ( (ss) * 16 + (sp) )

(4)(sp) = (sp) + 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assume cs:code 
stack seqment
db 16 dup (0)
stack ends

code segment
mov ax, 4c00h
int 21h
start: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, 0
push ax ;ax入栈
mov bx, 0
ret ;ret指令执行后,(IP)=0,CS:IP指向代码段的第一条指令。可以push cs push ax retf
code ends
end start

2.call 指令

call指令经常跟ret指令配合使用,因此CPU执行call指令,进行两步操作:

(1)将当前的 IP 或 CS和IP 压入栈中;

(2)转移(jmp)。

call指令不能实现短转移,除此之外,call指令实现转移的方法和 jmp 指令的原理相同。

  • call 标号(近转移)

CPU执行此种格式的call指令时,相当于进行 push IP jmp near ptr 标号

  • call far ptr 标号(段间转移)

CPU执行此种格式的call指令时,相当于进行:push CS,push IP jmp far ptr 标号

  • call 16位寄存器

CPU执行此种格式的call指令时,相当于进行: push IP jmp 16位寄存器

  • call word ptr 内存单元地址

CPU执行此种格式的call指令时,相当于进行:push IP jmp word ptr 内存单元地址

  • call dword ptr 内存单元地址

CPU执行此种格式的call指令时,相当于进行:push CS push IP jmp dword ptr 内存单元地址

3.call 和 ret 的配合使用

示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
start: mov ax,1
mov cx,3
call s ;(1)CPU指令缓冲器存放call指令,IP指向下一条指令(mov bx, ax),执行call指令,IP入栈,jmp

mov bx,ax ;(4)IP重新指向这里 bx = 8
mov ax,4c00h
int 21h
s: add ax,ax
loop s;(2)循环3次ax = 8
ret;(3)return : pop IP
code ends
end start

十、标志寄存器

(这部分不太好记,那就不记了,实战过程中慢慢积累吧,看着看着就记住了)

1.标志寄存器

CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。

(1)用来存储相关指令的某些执行结果;

(2)用来为CPU执行相关指令提供行为依据;

(3)用来控制CPU的相关工作方式。

这种特殊的寄存器在8086CPU中,被称为标志寄存器(flag)。

8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW-Program Status Word)

flag寄存器是按位起作用的,它的每一位都有专门的含义,记录特定的信息。

在这里插入图片描述

在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令

1.零标志位 (ZF)

零标志位(Zero Flag)。它记录相关指令执行后,其结果是否为0。

如果结果为0,那么zf = 1(表示结果是0);如果结果不为0,那么zf = 0。

1
2
3
4
5
6
mov ax, 1
sub ax, 1 ;执行后,结果为0,则zf = 1

mov ax, 2
sub ax, 1 ;执行后,结果不为0,则zf = 0

2.奇偶标志位 (PF)

奇偶标志位(Parity Flag)。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。

如果1的个数为偶数,pf = 1,如果为奇数,那么pf = 0。

1
2
3
4
5
6
mov al, 1
add al, 10 ;执行后,结果为00001011B,其中有3(奇数)个1,则pf = 0;

mov al, 1
or al, 2 ;执行后,结果为00000011B,其中有2(偶数)个1,则pf = 1;

3.符号标志位(SF)

符号标志位(Symbol Flag)。它记录相关指令执行后,其结果是否为负。

如果结果为负,sf = 1;如果非负,sf = 0。

计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。

00000001B,可以看作为无符号数1,或有符号数+1;
10000001B,可以看作为无符号数129,也可以看作有符号数-127。

对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算

CPU在执行add等指令的时候,就包含了两种含义:可以将add指令进行的运算当作无符号数的运算,也可以将add指令进行的运算当作有符号数的运算

SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值

1
2
3
4
5
6
mov al, 10000001B 
add al, 1 ;执行后,结果为10000010B,sf = 1,表示:如果指令进行的是有符号数运算,那么结果为负;


mov al, 10000001B
add al, 01111111B ;执行后,结果为0,sf = 0,表示:如果指令进行的是有符号数运算,那么结果为非负

4.进位标志位(CF)

进位标志位(Carry Flag)。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值

5.溢出标志位(OF)

溢出标志位(Overflow Flag)。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。

如果发生溢出,OF = 1;如果没有,OF = 0。

CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位

CPU在执行add等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。

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

1
2
3
4
5
6
7
8
9
10
11
12
mov al, 98
add al, 99 ;执行后将产生溢出。因为进行的"有符号数"运算是:(al)=(al)+ 99 = 98 + 99=197 = C5H 为-59的补码
;而结果197超出了机器所能表示的8位有符号数的范围:-128-127。
;add 指令执行后:无符号运算没有进位CF=0,有符号运算溢出OF=1
;当取出的数据C5H按无符号解析C5H = 197, 当按有符号解析通过SP得知数据为负,即C5H为-59补码存储,

mov al,0F0H ;F0H,为有符号数-16的补码 -Not(F0 - 1)
add al,088H ;88H,为有符号数-120的补码 -Not(88- 1)
;执行后,将产生溢出。因为add al, 088H进行的有符号数运算结果是:(al)= -136
;而结果-136超出了机器所能表示的8位有符号数的范围:-128-127。
;add 指令执行后:无符号运算有进位CF=1,有符号运算溢出OF=1

2.adc指令和sbb指令

adc指令

adc是带进位加法指令,它利用了CF位上记录的进位值。

指令格式:adc 操作对象1, 操作对象2

功能:操作对象1 = 操作对象1 + 操作对象2 + CF

1
2
3
4
5
mov ax, 2
mov bx, 1
sub bx, ax ;无符号运算借位CF=1,有符号运算OF = 0
adc ax, 1 ;执行后,(ax)= 4。adc执行时,相当于计算:(ax)+1+CF = 2+1+1 = 4。

在这里插入图片描述

1
2
3
4
5
6
7
;计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。
;将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。
mov ax, 001EH
mov bx, 0F000H
add bx, 1000H
adc ax, 0020H

sbb指令

sbb是带借位减法指令,它利用了CF位上记录的借位值。

指令格式:sbb 操作对象1, 操作对象2

功能:操作对象1 = 操作对象1 - 操作对象2 - CF

1
2
3
4
5
6
;计算 003E1000H - 00202000H,结果放在ax,bx中,程序如下:
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H

3.cmp指令

cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。

其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

cmp指令格式:cmp 操作对象1,操作对象2

例如:
指令cmp ax, ax,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。
指令执行后:zf=1,pf=1,sf=0,cf=0,of=0。

CPU在执行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

上面的表格可以正推也可以逆推

如果用cmp来进行有符号数比较时:
SF只能记录实际结果的正负,发生溢出的时候,实际结果的正负不能说明逻辑上真正结果的正负。
但是逻辑上的结果的正负,才是cmp指令所求的真正结果,所以我们在考察SF的同时考察OF,就可以得知逻辑上真正结果的正负,同时就知道比较的结果。

1
2
3
4
5
6
mov ah, 08AH  ; -Not(8A-1) = -118  即当成有符号数时为-118
mov bh, 070H ; 有符号数时最高位为0为正数, 70H = 112
cmp ah, bh ;(ah)-(bh)实际得到的结果是1AH
; 在逻辑上,运算所应该得到的结果是:(-118)- 112 = -230
; sf记录实际结果的正负,所以sf=0

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

可以根据某种条件,决定是否修改IP的指令

jcxz它可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。

所有条件转移指令的转移位移都是[-128,127]。

多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改IP

这些条件转移指令通常都和cmp相配合使用,它们所检测的标志位,都是cmp指令进行无符号数比较的时记录比较结果的标志位

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

这个挺重要的:

指令 含义 检测的相关标志位
je 等于则转移 zf = 1
jne 不等于则转移 zf = 0
jb 低于则转移 cf = 1
jnb 不低于则转移 cf = 0
ja 高于则转移 cf = 0 且 zf = 0
jna 不高于则转移 cf = 1 且 zf = 1

贴张图,需要魔法上网才可以看:

5.DF标志和串传送指令

方向标志位。在串处理指令中,控制每次操作后si、di的增减。

df = 0每次操作后si、di递增;
df = 1每次操作后si、di递减。

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

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

格式:rep movsb
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用,
功能:rep的作用是根据cx的值,重复执行后面的串传送指令

8086CPU提供下面两条指令对df位进行设置。

cld指令:将标志寄存器的df位置0
std指令:将标志寄存器的df位置1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
;将data段中的第一个字符串复制到它后面的空间中。
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends

mov ax, data
mov ds, ax
mov si, 0 ;ds:si 指向data:0
mov es, ax
mov di, 16 ;es:di指向data:0010

mov cx, 16 ;(cx)=16,rep循环16次
cld ;设置df=0,正向传送
rep movsb

6.pushf和popf

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

pushf和popf,为直接访问标志寄存器提供了一种方法。


接下来还有一些内容,但是目前来说是用不到的,就先不学了,什么时候用到了再说