文章目录
第九章 转移指令的原理
可以修改IP,或同时修改CS和IP的指令统称为转移指令
8086CPU的转移行为有以下几类
- 只修改IP时,称为段内转移,比如:
jmp ax
。 - 同时修改cs和ip时,称为段间转移,比如:
jmp 1000:0
。
由于转移指令对P的修改范围不同,段内转移又分为:短转移和近转移。
-
短转移IP的修改范围为-128~127。
-
近转移P的修改范围为-32768~32767。
8086CPU的转移指令分为以下几类。
- 无条件转移指令(如:jmp)
- 条件转移指令
- 循环指令(如:loop)
- 过程
- 中断
操作符 offset
操作符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
依据位移进行转移的jmp
指令
jmp short 标号 ;段内短位移,8位位移,-128~127
jmp near ptr 标号 ;段内近位移,16位位移,-32768~32767
编译后的机器码不含有目标地址,而是含有从当前位置到目标位置的位移
cs不改变,ip改变
目的地址在指令中的jmp
指令
jmp far ptr 标号
"jmp far ptr 标号"实现的是段间转移,又称为远转移.
转移地址在寄存器中的jmp
指令
jmp 16位寄存器
转移地址在内存中的jmp
指令
-
jmp word ptr 偏移地址
-
jmp dword ptr 地址
地址的低地址处的字存储偏移地址, 高地址处的字存储段地址,因此第二种方式是段间转移.
jcxz
指令
jcxz
指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围为:-128~127。
jcxz 标号
如果(cx)=0, 才执行转移.
loop
指令
1oop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128~127。
loop 标号
(cx) = (cx) - 1,如果(cx) != 0, 转移到标号执行
[!NOTE]
根据位移进行转移的设计方便了程序段在内存中的浮动装配,
因为使用的是相对位移,所以即使程序段的位置发生变化,也能保证程序正确执行.
[!WARNING]
如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将报错.
实验8
assume cs:codesg
codesg segment
mov ax,4c00h
int 21h
start: mov ax,0
s:nop
nop
mov di,offset s
mov si,offset s2
mov ax,cs:[si]
mov cs:[di],ax
s0: jmp short s
s1: mov ax,0
int 21h
mov ax,0
s2: jmp short s1
nop
codesg ends
end start
这个实验演示了jmp short
的特性
11~14行代码把22行的命令"移到了"第8行
我们可能期待着它同样能跳转到s1
但是jmp short
采用相对位移进行跳转, 而且在编译时计算并且写入机器码, 不会因为命令位置的改变而改变.
所以实际上jmp short s1
在运行时代表向前移动8个字节
很巧的是,第八行执行后转移到了第4行, 而这正是程序退出命令.
实验9
编程:
在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串’welcome to masm!'。
代码:
assume es:data,cs:code,ss:stack
data segment
db 'welcome to masm' ;要打印的内容
data ends
stack segment
db 0,0,0,0,0,0,0,0;存储临时变量
db 1,2h,24h,71h ;索引、绿色、绿底红色、白底蓝色
stack ends
code segment
start:
mov ax,0b800h ;初始段
mov ds,ax ;
mov ax,data ;
mov es,ax ;
mov ax,stack ;
mov ss,ax ;
mov sp,8 ;
mov cx,3 ;共打印三行
mov di,0 ;每行自增160
s0: ;外层,打印一行
mov bx,0 ;每个字符自增2
mov si,0 ; 自增1
push cx
mov cx,15 ;共15个字符
s: ;内层,打印一个字符
mov al,es:[si]
mov [di+bx],al ;ascii字符
push bx ;获取属性索引
mov bh,0 ;
mov bl,ss:[8] ;
mov ax,ss:[8+bx] ;获取属性
pop bx
mov [di+bx+1],ax ;打印字符
add bx,2
add si,1
loop s
add di,00a0h ;进入下一行
mov ax,ss:[8] ;修改索引,切换下一种颜色
inc ax ;
mov ss:[8],ax ;
pop cx
loop s0
mov ax,4c00H ;程序退出
int 21H
code ends
end start
运行结果:
反思:
- 编译错误:
inc ss:[8]
mov [di+bx],es:[si]
;都是错误写法
-
逻辑错误: 运行过程中发现的两个bug都是由于使用16字节寄存器读取或写入一个字节, 导致错误, 应该使用对应的8字节寄存器
-
可优化的写法: 当寄存器不足或冲突时, 可以尝试栈内存
比如本程序把当前颜色索引、用到的颜色都使用了栈存储
可改进的方向:
- 打印任意字符串(需要计算字符串长度) PS:不得多于80个字符
- 打印任意行数,更多的颜色
;计算字符数
cnt: mov ch, 0
mov cl, es:[bx]
jcxz cnt_OK
inc bx
jmp short cnt
cnt_OK:
mov ss:[13],bx ;记录字符数
通过以上代码,只要修改data区内的字符串就直接可以打印出来了。
但是这暴露了新的问题,我把字符数记录在了stack段,字符数和颜色,临时变量之间强耦合,如果增加颜色,第八行的代码要改,如果增大临时区,它俩都要修改,目前还没有太好的解决办法,应该可以定义多个段,用哪个段时把它mov到ss上。因为这太麻烦了我就放到一个段了
;重写了获取属性,增加了取余,行数大于颜色数时颜色循环
push bx ;获取属性索引
mov bh,0 ;
mov bl,ss:[8] ;
mov ax,bx ;索引除以颜色数
push bx ;
mov bx,ss[14] ;没错,我又往栈里放了一个颜色数...
div bl ;8位除法,余数在ah中
pop bx ;
mov bl,ah ;
mov ax,ss:[9+bx] ;获取属性
现在随便更改打印行数也不会出错了,因为颜色会循环使用,缺点是越来越屎山了…
使用总结:
修改字符串直接在data段里修改,打印行数和颜色在stack段修改(颜色数需要自行修改)
目前无法实现的功能:
读取终端输入,打印特定行数和特定字符串,目前只能修改代码data和stack段实现
汇编代码编写难度大概是随代码量成指数增长,最初的版本很快就写完了,也只碰到几个bug,然而写这个改进版本时就暴露了代码结构混乱和汇编功底浅薄的问题。
问题关键是它没有一个简易可行的编程范式,编程中要使用的常量、变量和它们的载体(寄存器、内存)都需要自己管理,访问或者修改时稍有不慎就会导致严重后果,而且debug困难。
所以以后的实验还是以实现基本功能为主,防止浪费时间。
改进后的代码:
assume es:data,cs:code,ss:stack
data segment
db 'hello,world!'
data ends
stack segment
db 0,0,0,0,0,0,0,0
db 0,2h,24h,71h,7h,0fh,3h ;索引,颜色
db 0,6,15 ;字符数,颜色数,打印行数
stack ends
code segment
start:
mov ax,0b800h
mov ds,ax
mov ax,data
mov es,ax
mov ax,stack
mov ss,ax
mov sp,8
;计算字符数
cnt: mov ch, 0
mov cl, es:[bx]
jcxz cnt_OK
inc bx
jmp short cnt
cnt_OK:
mov ss:[15],bl ;记录字符数
mov ch,0 ;打印行数
mov cl,ss:[17] ;
mov di,0 ;每行自增160
s0: mov bx,0 ;每个字符自增2
mov si,0 ; 自增1
push cx
mov ch,0 ;打印字符数
mov cl,ss:[15] ;
s: mov al,es:[si]
mov [di+bx],al ;ascii字符
push bx ;获取属性索引
mov bh,0 ;
mov bl,ss:[8] ;
mov ax,bx ;对这个索引进行取余颜色数
push bx ;
mov bl,ss:[16] ;
div bl ;8位除法,余数在ah中
pop bx ;
mov bl,ah ;
mov ah,0 ;获取属性
mov al,ss:[9+bx] ;
pop bx
mov [di+bx+1],ax
add bx,2
add si,1
loop s
add di,00a0h ;进入下一行
mov ah,0
mov al,ss:[8] ;下一种颜色
inc ax
mov ss:[8],al
pop cx
loop s0
mov ax,4c00H
int 21H
code ends
end start