8086CPU 有 14 个寄存器,每个寄存器有一个名称
这些寄存器分别是 AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW
AX 累加寄存器(accumulate register)
BX 基地址寄存器(based register)
CX 计数器 (count register)
DX 数据寄存器(data registered)
这 4 个为 16 位通用寄存器,都可以分别为两个独立的 8 位寄存器来用:
8086 CPU 有 4 个段寄存器CS、DS、SS、ES。当 8086 CPU 要访问内存时由这 4 个 段寄存器提供内存单元的段地址。
CS 代码段寄存器(code segment)
DS 数据段(data segment)
SS 栈段寄存器(stack segment)
ES 附加段寄存器(extra segment)
注: 段寄存器不能使用 ADD、SUB 指令进行修改
CS 为代码段寄存器 (段地址)
IP 位置指针寄存器 (偏移地址)
IP 又称为指令指针寄存器(instructor point),代码在运行时会自动修改 IP 寄存器的值
由于 8086 地址总线是有 20 位,可以传送 20 位地址,达到 1MB 寻址能力。 8086 CPU 又是 16 位结构,所以寻址 采用 CS*16 + IP 两个寄存器进行寻址。
而 IP 位置指针寄存器最小可指向 16位 地址,也就是 64KB的内存
CSS*16 + IP: CSS 左移4位 + IP
CSS | IP | 物理地址 |
---|---|---|
0001H | 0008H | (0001H<<4) + 0008H = 00018H |
0022H | 0008H | (0022H<<4) + 0008H = 00228H |
8000H | 0008H | (8000H<<4) + 0008H = 80008H |
CS: IP 指向的内存当中的内容会被当做指令来运行
声明: MOV 指令不能用于设置 CS、IP 寄存器中的值。
若想同时修改 CS、IP 的内容,可用形容:JMP 段地址: 偏移地址
JMP 2AE3H:3H ,执行后:CS=2AE3H,IP=0003H,CPU 将从 2AE33H 地址读取指令。
JMP 3H:0B16H ,执行后:CS=0003H,IP=0B16H,CPU 将从 00B46H 地址读取指令。
CPU 要读取一个内存单元的时候,必须给出这个内存单元的地址,在8086 中,内存地址由段地址和偏移地址组成。 8086 中有一个 DS 寄存器,通常用来存放要访问数据的段地址。
DS 寄存器的使用
mov ax, 0100H
; mov ds, 0100H # 错误用法,非法操作
mov ds, ax
mov al, [0]
说明:
栈有两种操作:入栈和出栈
入栈就是将一个元素放到栈顶,出栈就是从栈顶去除一个元素。栈顶的元素总是最后入栈,需要出栈时,又最新被从栈中取出。栈的这种操作被称为:LIFO(Last In First Out)
SS*16 +SP = 当前栈顶地址, 任意时刻,SS:SP 指向镇定元素
栈操作又出栈POP 和 入栈 PUSH, 入栈和出栈操作会修改 SP 堆栈指针寄存器
PUSH 入栈指令执行过程
入栈时栈顶从高地址想低地址方向负增长
POP 出栈指令执行过程
出栈时栈顶从低地址向高地址方向增长
SP 堆栈指针寄存器(tack point)
si 和 di 是 8086 中和 bx 功能相近的寄存器, si 和 di 不能分成两个 8 位寄存器来使用。
数据存取的方式:
mov ax, [bx+si+idata] 指令的含义:(ax) = ((ds*16) + (bx) + (si) + idata)
BX、SI、DI、DP 这四个寄存器可以用在 […] 中来进行单元的寻址。
正确的寻址方式:
错误的寻址方式:
BX、SI、DI、DP 这四个寄存器可以在 […] 中单个出现,或只能以 4 种组合出现:
idata 解释说明立即数
**BX、SI、DI、DP ** 寻址错误组合方式
寻址方式:
寻址方式 | 含义 | 名称 | 常用格式举例 |
---|---|---|---|
[idata] | EA = idata; SA = (ds) | 直接寻址 | [idata] |
[bx] | EA = (bx); SA=(ds) | 直接寻址 | [bx] |
[si] | EA = (si); SA=(ds) | ||
[di] | EA = (di); SA=(ds) | ||
[bp] | EA = (bp); SA=(ds) | ||
[bx+idata] | EA = (bx) + idata; SA=(ds) | 寄存器相对寻址 | 用于结构体:[bx].idata; 用于数组:idata[si]; 用于二维数组:[bx][idata] |
[si+idata] | EA = (si) + idata; SA=(ds) | ||
[bi+idata] | EA = (di) + idata; SA=(ds) | ||
[bp+idata] | EA = (bp) + idata; SA=(ds) | ||
[bx+si] | EA = (bx) + (si); SA=(ds) | 寄存器相对寻址 | 用于二维数组:[bx][si] |
[bx+di] | EA = (bx) + (di); SA=(ds) | ||
[bp+si] | EA = (bp) + (si); SA=(ds) | ||
[bp+si] | EA = (bp) + (di); SA=(ds) | ||
[bx+si+idata] | EA = (bx) + (si) + idata; SA=(ds) | 相对基址变址寻址 | 用于表格(结构)中的数组项:[bx].idata[si] 用于二维数组:[bx][si] |
[bx+di+idata] | EA = (bx) + (di) + idata; SA=(ds) | ||
[bp+si+idata] | EA = (bp) + (si) + idata; SA=(ds) | ||
[bp+di+idata] | EA = (bp) + (di) + idata; SA=(ds) |
CPU 内部的寄存器中,有一种特殊的寄存器(对于不同的处理机制,个数和结构都可能不同)具体以下 3 种作用。
flag 和 其它寄存器不一样,其它寄存器用来存放数据的,都是整个寄存器具有一个含义。而 flag 寄存器是按位起作用,也就是说,它的每一个位有专门的意义,记录特定的信息。
8086CPU 的 falg 寄存器的结构图
flag 的第 6 位是 ZF, 零标志位。它记录相关指令执行后,其结果是否为 0,如果结果为 0, 那么 zf = 1;如果结果不为 0,那么 zf = 0。
对于 zf 的值,我们可以这样看,zf 标记相关指令的计算结果是否为 0,如果为 0,则 zf 要记录下 是0 这样的肯定信息。在计算机中 1 表示逻辑真,表示肯定,所以当结果为 0 的时候 zf = 1,表示” 结果是 0 “。如果结果为 0,则 zf 要记录下” 不是 0 “ 这样的否定信息。在计算机中 0 表示逻辑假,表示否定,所以当结果不为 0的是否 zf = 0,表示 “结果不是 0”。
举例:
mov ax, 1
dec ax ; 执行后结果为 0, 则 zf = 1, 表示结果为 0
mov ax, 0
inc ax ; 执行后结果为 1, 则 zf = 0, 表示结果为 1
注意:
在 8086CPU 的指令集中,有的指令的执行是影响标志寄存器的,比如 add、sub、mul、div、inc、or、and、dec 等,它们大都是运算指令(进行逻辑或算术运算);有的指令执行对标志寄存器没有影响,比如 mov、push、pop 等,它们大都是传送指令。
flag 的第 2 位是 PE,奇偶标志位。它记录相关指令执行后,其结果的所有 bit 位中 1 的个数是否位偶数。如果 1 的个数是偶数, pf = 1,反之 pf = 0;
举例:
mov ax, 01B
and ax, 10B ; 执行结果11B,则 pf = 1,表述执行结果 1 的数量位偶数
dec ax ; 执行结果10B,则 pf = 0,表述执行结果 1 的数量位奇数
flag 的第 7 位是 SF,符号标志位。它记录相关指令后,其结果是否位负数。如果结果为负, sf =1,如果非负数,fs = 0。
计算机中通常用补码表示符号数据。计算机中的额一个数据可以看作是有符号数,也可以看成无符号数。
00000001B,可以看作无符号数 1,也可以看作有符号数 +1;
10000001B,可以看作无符号数129,也可以看作有符号数 -127(-127 = ~10000001 + 1)。
flag 的 第 0 位 是 CF,进位标志。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高位有效位向高位的进位制,或从高位的借位值。
对于位数为 N 的无符号数来说,其对应的二进制信息的最高位,即第 N-1 位,就是它的最高有效位,而假想存在的第 N 位,就是相对于最高有效位的更高位
mov 指令有以下几种形式
传送方式 | 操作说明 | 示例 |
---|---|---|
mov 寄存器, 数据 | 将数据传送到寄存器 | mov ax, 08H |
mov 寄存器1, 寄存器2 | 将寄存器2中的数据传送到寄存器1中 | mov ax, bx |
mov 寄存器, 内存单元 | 将内存单元中的数据传送到寄存器中 | mov ax, [0] |
mov 内存单元, 寄存器 | 将寄存器中的数据传送到内存中 | mov [0], ax |
mov 段寄存器, 寄存器 | 将寄存器中的数据传送到段寄存器中 | mov ds, ax mov 指令不能修改 cs 寄存器 |
mov 寄存器, 段寄存器 | 将段寄存器中的数据传送到寄存器中 | mov ax, cs mov ax, ds |
mov 寄存器
汇编指令 | 控制CPU完成额操作 | 用高级语言的语法描述 |
---|---|---|
MOV AX, 18 | 将 18 送入到寄存器 AX | AX = 18 |
MOV AH, 78H | 将 78H 送入到寄存器 AX | AX = 0x78 |
MOV BX, AX | 将 寄存器 AX 中的值送入到寄存器 AB | BX = AX |
MOV AL, [0] | 将 DS 地址段相对偏 0 个字节内存中的数据送入到 AL | AL = address[DS<<4+0] |
MOV AX, 0100H MOV DS, AX | 将 AX 寄存器中的值送入到 DS 寄存器中 错误写法:MOV DS, 1000H | DS = AX |
加法指令
汇编指令 | 控制CPU完成额操作 | 用高级语言语法描述 |
---|---|---|
MOV AX, 18 | 将 18 送入到寄存器 AX | AX = 18 |
ADD AX, 8 | 将寄存器 AX 中的值加上 8 | AX += 8 |
ADD AX, B | 将 AX 和 BX 中的数值相加,结果存放在 AX 中 | AX += BX |
adc 是带进位加法指令,它利用了 CF 位上记录的进位值。
指令格式:adc 操作对象1, 操作对象2
功能: 操作对象1 = 操作对象1 + 操作对象2 + CF
比如指令 adc ax, bx 实现的功能是:(ax) = (ax) + (bx) + CF
减法指令
汇编指令 | 控制CPU完成额操作 | 用高级语言的语法描述 |
---|---|---|
SUB AX, BX | 将 AX 减去 BX 寄存器中的值,然后送入到 AX寄存器中 | AX -= BX |
inc 增量 increment
inc 自减 decrement
inc ax ; ax +=1
dec ax ; ax -= 1
PUSH 和 POP 指令有以下几种形式
传送方式 | 操作说明 | 示例 |
---|---|---|
push 寄存器 | 入栈将寄存器中的值送入到栈顶 | [SS:SP] = 寄存器 SP += 2 |
push 段寄存器 | 入栈将寄存器中的值送入到栈顶 | [SS:SP] = 段寄存器 SP += 2 |
push 内存单元 | 入栈将内存单元字中的值送入到栈顶 | [SS:SP] = [01000H] SP += 2 |
push 数据 | 入栈将数据字值送入到栈顶 | [SS:SP] = [01000H] SP += 2 |
pop 寄存器 | 出栈将寄存器栈顶中的值传送到寄存器中 | 寄存器 = [SS:SP] SP -= 2 |
pop 段寄存器 | 出栈将寄存器栈顶中的值传送到段寄存器中 | [SS:SP] = 段寄存器 SP += 2 |
and 指令: 逻辑与指令,按位进行与运算
or 指令: 逻辑或指令,按位或指令
div 是除法指令,使用 div 做触发的时候因该注意以下问题。
格式如下:
div reg
div 内存单元
示例:
mul 指令是应用在做乘法的
mul 指令注意事项:
格式:
mul reg
mul 内存单元
内存单元可以用不同地 寻址方式给出,比如:
mul byte ptr ds:[0]
含义: (ax) = (al) * ((ds)*16 + 0);
mul word ptr ds:[bx+si+8]
含义:
(ax) = (ax) * ((ds)*16 +(bx) + (si) + 8) 结果的低 16 位
(ax) = (ax) * ((ds)*16 +(bx) + (si) + 8) 结果的高 16 位
示例:
; 8 位数据与 8 位数据相乘,结果存放在 ax中
mov al, 100 ; al = 100
mov ah, 25 ; ah = 25
mul ah ; ax = 100 * 25
; 16 位数据与 16 位数据相乘,结果存放在 dx 和 ax 中
mov ax, 1000 ; ax = 1000
mov bx, 2500 ; bx = 2500
mul bx ; ax = (1000 * 2500) & 0xFFFF
; dx = (1000 * 2500) >> 15
8086 中 转移行为分为以下几类:
由于转移指令对 IP 的修改范围不同,段转移又分为:短转移 和 近转移
操作符 offset 在汇编语言中时编译器处理的符号,它的功能时获取标号的偏移地址。
比如:
assume cs: codesg
codesg segment:
start:
mov ax, offset start ; 相当于 mov ax, 0
s:
mov ax, offset s ; 相当于 mov ax, 3
codesg ends
end start
无条件转移指令,又称为短转移,可以只修改 IP, 也可以同时修改 CS 和 IP。
jmp 指令要给出两种信息:
不同的给出目的的地址方法,和不同的转移地址,对应不同格式 jmp 指令
这种格式的 jmp 指令实现的时段内转移,它对 IP 的修改范围为 -128 ~ 127,也就是说,它向前转移最多越过 128 个字节,向后转移最多越过 127 个字节。short 说明指令进行短转移。 标号 指令了转移的目的地
示例:
assume cs: codesg
codesg segment
start: mov ax, 0
jmp short s
add ax, 1
s: inc ax
codesg ends
end start
上面的代码的执行结束后 ax 的值 为 1,因为代码执 jmp short s 结束后将改变 IP 寄存器指向 s 代码短的首地址,从而运行 inc ax
near ptr 标号实现的时段内近转移,对 IP 的操作: (IP) = (IP) + 16 位位移操作
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]
汇编指令 | 控制CPU完成额操作 | 用高级语言的语法描述 |
---|---|---|
JMP 2AE3H:3H | 将 2AE3H 送入CS寄存器中 将0003H 送入 IP 寄存器中 | CS = 2AE3H, IP = 0003H |
JMP AX | 将 AX 寄存器中的值送入到 IP 寄存器 | IP = AX |
jcxz 指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对应 IP 的修改范围都为: -128 ~ 127。
操作指令格式:jcxz 标号(如果(cx) = 0,转移到标号出执行。
操作:当 (cx) = 0 时, (IP) = (IP) + 8 位位移;
8 位位移 = 标号出的地址 -jcxz 指令后的第一个字节的地址;
8 位位移的范围位 -128 ~ 127,用补码表示;
8 位位移由编译器程序在编译时算出。
当 (cs) ≠ 0 时,什么也不做(程序向下执行)。
我们从 jcx 的功能可以看出, “ jcxz 标号”的功能相当于:
if( (cs) == 0 ) jmp short 标号;
assume cs:code
; @description 找到数据段 2000H 出内存首个数据为 0 的段地址偏移,
; 并存放到 dx 中
code segment
start: mov ax, 2000H
mov ds, ax
mov bx, 0
s: mov ch, 0
mov cl, [bx]
jcxz ok ; cx == 0 时,代码段短跳转到 ok 处
inc bx
jmp short s
ok: mov dx, bx
mov ax, 4c00H
int 21H
code ends
end start
loop 指令为循环指令,所有的循环指令都是 短转移,在对应的机器码中包含转移的位移,而不是目的地址。对 IP 的修改范围都是: -128 ~·127.
指令格式: loop 标号 ((cx)=(cx)-1) ,如果 (cx)≠0 ,转移到标号出执行。
loop 指令操作:
loop 指令格式是:loop 标号, CPU 执行 loop 指的时候,要进行两步操作
从 loop 的功能可以看出, loop 标号 的功能相当于:
(cx–)–;
if((cx) ≠ 0) jmp short 标号;
call 和 ret 指令都是转移指令,他们都修改 IP, 或同时修改 CS 和 IP。它们经常被共同用来实现子程序的设计
ret 指令用栈中的数据,修改 IP 的内容,从而实现近转移;
retf 指令用栈中的数据,修改 CS 和 IP 的内容,从而实现远转移。
CPU 执行 ret 指令时,进行下面两步操作:
CPU 执行 retf 指令时,进行下面 4 步操作:
assume cs: code
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, 0
push cs ; 将 (cs)入栈
push offset start ; 将 start 入栈
retf ; pop ip; pop cs; retf执行结束后将跳转到代码开始处
code ends
end start
CPU 执行 call 指令时,进行两步操作
call 指令不能实现短转移,除此之外,call 指令实现转移的方法和 jmp 指令的原理相同
call 标号(将当前的 IP 压栈后,转到标号处执行命令)
cpu 执行此处格式的 call 指令时,进行如下操作:
call far ptr 标号 实现的是段转移。
CPU 执行此种格式的 call 指令是,进行如下操作。
CPU 执行 call far ptr 标号 指令的时候相当于做了:
指令格式: call 16 位 reg
功能:
(sp) = (sp) - 2
((ss) * 16 + (sp)) = (IP)
(IP) = (16 位 reg)
CPU 执行 call 16 位 reg 指令的时候相当于做了:
示例:
mov ax, 6
call ax
指令格式: call word ptr 内存单元地址
功能:
(sp) = (sp) - 2
((ss) * 16 + (sp)) = (IP)
(IP) = (内存单元地址)
CPU 执行 call 16 位 reg 指令的时候相当于做了:
示例:
call word ptr [0005H]
指令格式:call dword ptr 内存单元地址
将 CS 和 IP 入栈,在进行内短端元地址的跳转
cpu 执行 call dword ptr 内存单元地址 指令时,相当于进行了
push CS
push IP
jmp dword ptr 内存单元地址
指令应用示例:
mov sp, 10H
mov ax, 0123H
mov ds:[0], ax
mov dword ptr ds:[2],0
call dword ptr ds:[0]
执行后, (CS)=0, (IP)=0123H, (SP)=0CH
用汇编语言写的源程序,包括汇编指令和伪指令,我们编程的最终目的时让计算机完成一定的任务。程序中的汇编指令组成了最终由计算机执行的程序,而源程序中的伪指令是由编译器来处理的,它们并不实现我们变成的最终目的。这里所说的程序就是指源程序中最终由计算机执行、处理的指令或数据。
注意,将程序文件总的所有内容称为源程序,将源程序中最终由计算机执行、处理的指令或数据,称为程序。程序最先以汇编指令的形式存储在源程序中,经编译、连接后转变为机器码,存储在可执行文件中。
segment 和 ends 是一对成对使用的伪指令,这是在编译器编译的汇编程序时,必须要用到的一对伪指令。segment 和 ends 的功能时定义一段,segment 说明一个段开始,ends 说明一个段结束。一个段必须有一个名称来标识,使用格式为:
段明 segment
.
.
.
段名 ends
示例:
assume cs: codesg
codesg segment
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00H
int 21H
codesg ends
一个汇编程序时有多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。一个源程序中所有将被计算机处理的信息:指令、数据、栈,被划分到不同的段中。
end 是一个汇编程序结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令 end,就结束对程序的编译。所以,在编写程序的时候,如果程序写完了,要在结尾加上伪指令 end。否则,编译器在编译程序时,无法知道程序在何处结束。
assume 这条伪指令的含义是 “假设” 。它假设某一段寄存器和程序中的某一个用 segment…ends 定义的段相关联。通过 assume 说明这个种关联,在需要的情况下,编译程序可以将段寄存器和某一段具体相联系。assume 并不是一条非要深入理解不可的伪指令,以后编译时,记着用 assume 将有特定用途的段和相关的段寄存器关联起来即可。
dw 指令:定义子类型,dw 即 define word。
db 指令:定义字节变量的定义符,db 即 define byte
dd 指令:定义双字节变量, dd 即 double word
dup 指令: 该指令与 dw、db、dd 伪指令配合使用,用来进行数据重复。
; 初始数据段
data segment
; 定义 8 个字型数据
dw 0123H, 0456H, 0798H, 0abcH, 0edfH, 0fedH, 0ebaH, 0987H
; 定义 16 个字节数据类型
db 'welcome to masm!'
; 定义 1 个双字数据类型
dd 1
; 定义 32 个字类型数据, 初始值都是 0
dw 32 dup (0)
; 定义 9 个字节数据类型,初始值为('abcabcabc')
db 3 dup ('abc')
ptr 既 point 的缩写
word ptr :指明了指令寄存器访问的内存单元是一个字单元
byte ptr :指明了指令寄存器访问的内存单元是一个字节单元
示例:
assume cs: code, ds: data
data segment
string db 'Hello World!', 13, 10, '$'
data ends
code segment
start:
mov ax, data
mov ds, ax
lea dx, string
mov ah, 9
int 21H
mov ax, 4c00H
int 21H
code ends
end start
计算 data 段第一组数据的 3 次方,结果存放在后面一组 dword 单元中
assume cs:code, ds: data
data segment
dw 1, 2, 3, 4, 5, 6, 7, 8
dd 16 dup (0)
code segment
start: mov ax, data
mov ds, ax
mov si, 0 ; ds:si 指向第一组 word 单元
mov di, 16 ; ds:di 指向的二组 dword 单元
mov cx, 8
s: mov bx, [si]
call cube
mov [di], ax
mov [di+2], dx
add si, 2
add di, 4
loop s
mov ax, 4c00H
int 21H
cube: mov ax, bx
mul bx
mul bx
ret
code ends
end start
assume cs:code, ds: data
; 定义数据段
data segment
db 'conversation'
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov cx, 12
call capital
mov ax, 4c00H
int 21H
; 将小写字母转为大写字母
capital:
and byte ptr [si], 11011111B ; 转大写
inc si
loop capital
ret
code ends
end start
全称
段寄存器:
特殊功能寄存器:
sp——stack point——堆栈指针寄存器
bp——base point——基础指针
si——source index——源变址寄存器
di——destination index——目的变址寄存器
psw——program state word——程序状态字
Psw的常用标志:
OF(11位-overflow flag-溢出标志位)——OV(overflow-溢出)——NV(not overflow-没溢出)
DF(10位-direction flag-方向标志位)——DN(down-下方)——UP(up-上方)
IF(9位-interrupt flag-中断标志位)——EI(enable interrupt-允许中断)——DI(disabled interrupt-不允许中断)
TF(8位-trap flag-陷阱标志位)——
SF(7位-sign flag-负号标志位)——NG(negative-负)——PL(plus-正)
ZF(6位-zero flag-零值标志位)——ZR(zero-为零)——NZ(not zero-不为0)
AF(4位-auxiliary carray flag-辅助进位标志位)——AC(auxiliary carry-有辅助进位)NA(not auxiliary carry-没有辅助进位)
PF(2位-parity flag-奇偶标志位)——PE(parity even-偶)——PO(parity odd-奇)
CF(0位-carry flag-进位标志位)——CY(carried-有进位)——NC(not carried-没进位)