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

8086CPU 转移行为:

  1. 段内转移:只修改 IP,例:jmp ax。
  2. 段间转移:同时修改 CS 和 IP,例:jmp 1000:0。

段内转移分短转移和近转移。

  1. 短转移 IP 的修改范围:-128 ~ 127
  2. 近转移 IP 的修改范围为:-32768 ~ 32767

8086CPU 转移指令分类:

  1. 无条件转移指令,例:jmp
  2. 条件转移指令
  3. 循环指令,例:loop
  4. 过程
  5. 中断

操作符 offset

offset 是由编译器处理的符号,用于取得标号偏移地址

1
2
3
4
5
6
assume cs:codesg
codesg segment
start: mov ax, offset start
s: mov ax, offset s
codesg ends
end start

offset 操作符取得标号 start、s 的偏移地址 0 和 3。

jmp 指令

jmp 为无条件转移指令,可以只修改 IP 也可以同时修改 CS 和 IP。

jmp 指令要给出两种信息:

  1. 转移的目的地址
  2. 转移的距离

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

转到标号处执行指令:

1
jmp short 标号

实现的是段内短转移,对 IP 的修改范围为 -128 ~ 127,向前转移可以最多越过 128 个字节,向后转移可以最多越过 127 个字节。

short 符号表示进行的是短转移,「标号」指的是代码段中的标号,转移指令结束后,CS:IP 应该指向标号处的指令。

1
2
3
4
5
6
7
8
assume cs:codesg
codesg segment
start: mov ax, 0
jump short s
add ax,1
s: inc ax
codesg ends
end start

jmp short 标号指令的机器码中不包含转移的目的地址,包含的是 CPU 要转移的位移。位移是编译器根据汇编指令中的标号计算出来的。

jmp short 标号的功能为:(IP) = (IP) + 8 位位移。

  1. 8 位位移 = 标号处的地址 - jmp 指令后第一个字节的地址。(因为运行了 jmp 指令后 IP 会指向下一条指令的第一个字节)
  2. short 指明此处的位移为 8 位位移
  3. 8 位位移的范围是 -128 ~ 127,用补码表示
  4. 8 位位移由编译程序在编译时算出

段内近转移指令格式 jmp near ptr 标号

功能为:(IP) = (IP) + 16 位位移。

  1. 16 位位移 = 标号处的地址 - jmp 指令后第一个字节的地址。(因为运行了 jmp 指令后 IP 会指向下一条指令的第一个字节)
  2. near ptr 指明此处的位移为 16 位位移,进行的是段内近转移
  3. 16 位位移的范围是 -32768 ~ 32767,用补码表示
  4. 16 位位移由编译程序在编译时算出

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

jmp far ptr 标号实现的是段间转移,又称为远转移

功能为:(CS) = 标号所在段的段地址;(IP) = 标号所在段的偏移地址。

far ptr 指明了指令用标号的段地址和偏移地址修改 CS 和 IP。

示例程序:

1
2
3
4
5
6
7
8
9
10
assume cs:codesg
codesg segment
start: mov ax, 0
mov bx, 0
jmp far ptr s; 远转移
db 256 dup (0)
s: add ax, 1
inc ax
codesg ends
end start

jmp far ptr s 对应的机器码包含转移的目的地址

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

指令格式:jmp 16 位 reg

功能:(IP) = (16 位 reg)

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

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

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

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

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

(CS) = (内存单元地址 + 2)
(IP) = (内存单元地址)

jcxz 指令

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

指令格式:jcxz 标号(如果 (cx) = 0 则跳转到标号处执行。)
操作:当 (cx) = 0 时,(IP) = (IP) + 8 位位移。

  1. 8 位位移 = 标号处的地址 - jcxz 指令后第一个字节的地址。(因为运行了 jmp 指令后 IP 会指向下一条指令的第一个字节)
  2. 8 位位移的范围是 -128 ~ 127,用补码表示
  3. 8 位位移由编译程序在编译时算出

当 (cx) ≠ 0 时,程序继续向下运行,什么也不做。

jcxz 标号 的功能相当于:if((cx) == 0) jmp short 标号

loop 指令

loop 指令为循环指令,所有的循环指令都是短转移,对应的机器码中包含转移的位移,而不是目的地址。对 IP 的修改范围都为:-128 ~ 127。

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

操作:

  1. (cx) = (cx) - 1
  2. 如果 (cx) ≠ 0,(IP) = (IP) + 8 位位移。

如果 (cx) = 0,程序向下执行。

loop 标号相当于:

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

根据位移进行转移的意义

jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号

几种汇编指令对 IP 的修改是根据转移目的地址和转移起始地址之间的位移来进行的。机器码中不包含转移的目的地址,包含的是到目的地址的位移。方便程序段在内存中的浮动装配。

编译器对转移位移超界的检测

根据位移进行转移的指令,如果在源程序中出现了转移范围超界的问题,编译的时候,编译器将报错。

形如 jmp 2000:0100 的转移指令只能在 debug 里面使用,汇编编译器并不认识,在源程序中使用的话编译器会报错。

分析一个奇怪的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:codesg
codesg segment
mov ax,4c00h
int 21h
start: mov ax,0 ;从这里开始运行
s: nop ;空一个字
nop ;空一个字

mov di,offset s ;记录 s 标号的地址
mov si,offset s2 ;记录 s2 标号的地址
mov ax,cs:[si] ;将 s2 标号内存处一个字写入 ax 寄存器
mov cs:[di],ax ;将 ax 寄存器的一个字写入 s 标号处的内存
s0: jmp short s
s1: mov ax,0
int 21h
mov ax,0
s2: jmp short s1
nop

codesg ends
endstart

这个程序说明了短转移指令的机器码中包含的是位移,不包含目的地址,所有的转移根据位移来进行。s2 处的机器码为 BEF6,BE 代表转移,F6 是位移,补码,相当于 -10,表示向前转移 10 个字节

当程序执行到 jmp short s 时,会跳转到 s 标号处执行,执行的机器指令是 BEF6,含义是向前转移 10 个字节,转移到了 codesg 段,执行终止退出命令,所以这个程序能正常退出。

在显示器上输出彩色字符

80 x 25 彩色字符模式显示缓冲区的结构:

向内存空间 B8000H ~ B8FFFFH 写入数据,写入的内容会立即出现在显示器上。

显示缓冲区分为 8 页,每页 4KB,显示器可以显示任意一页的内容,一般显示第 0 页的内容,B8000H ~ B8F9FH 中的 4000 个字节内容将出现在显示器上。

一个字符占两个字节存储空间,低位字节存储字符的 ASCII 码,高位字节存储字符的属性。

属性字节的格式:

7 6 5 4 3 2 1 0
含义 闪烁 背景色 R 背景色 G 背景色 B 高亮 前景色 R 前景色 G 前景色 B
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
assume cs:codesg
datasg segment
db 'w',2,'e',2,'l',2,'c',2,'o',2,'m',2,'e',2,' ',2,'t',2,'o',2,' ',2,'m',2,'a',2,'s',2,'m',2,'!',2
db 'w',36,'e',36,'l',36,'c',36,'o',36,'m',36,'e',36,' ',36,'t',36,'o',36,' ',36,'m',36,'a',36,'s',36,'m',36,'!',36
db 'w',113,'e',113,'l',113,'c',113,'o',113,'m',113,'e',113,' ',113,'t',113,'o',113,' ',113,'m',113,'a',113,'s',113,'m',113,'!',113
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov cx,16
mov bx,0
mov ax,47228 ;屏幕中央位置
mov es,ax
s: mov dx, ds:[bx]
mov es:[bx], dx
inc bx
inc bx
loop s
mov cx,16
s1: mov dx, ds:[bx]
mov es:[bx+160-32], dx
inc bx
inc bx
loop s1
mov cx,16
s2: mov dx, ds:[bx]
mov es:[bx+320-64], dx
inc bx
inc bx
loop s2
mov ax,4c00h
int 21h
codesg ends
end start

显示器输出字符