综述
一般情况下, 指令顺序执行, 实际中, 常需要改变程序的执行流程
这就用到转移指令
- 可以控制cpu 执行内存中某处代码的指令
- 可以修改ip, 或同时修改cs 和ip 的指令
分类
按行为分类
- 段内转移: 只修改ip, 如
jmp ax
- 段间转移: 同时修改cs 和ip, 如
jmp 1000:0
根据指令对ip 修改范围不同分类
- 段内短转移: ip 修改范围-128~127
- 段内近转移: ip 修改范围-32768~32767
按转移指令分类
- 无条件转移, jmp
- 条件转移指令, jcxz
- 循环指令, loop
- 过程
- 中断
操作符offset
用offset 取得标号的偏移地址
格式: offset 标号
assume cs:code
code segment
start:
mov ax,offset start ;相当于mov ax,0
s: movax,offset s ;相当于, mov ax,3
code ends
end start
jmp 指令
无条件转移, 可以只修改ip, 也可以同时修改cs和ip
jmp 指令要给出两种信息
- 转移目的地址
- 转移距离
- 段间转移(远转移), jmp 2000:1000
- 段内短转移: jmp short 标号, ip 修改范围-128~127, 8 位的位移
- 段内近转移: jmp near ptr, ip 修改范围-32768~32767, 16 位的位移
jmp 指令是依据位移进行转移的
jmp short 指令中, 包含的是跳转到指令的相对位置, 而不是转移的目标地址
assume cs:code
code segment
start:
mov ax,0
jmp short s
add ax,1
nop
nop
s:inc ax
code ends
end
jmp根据位移转移
执行顺序
- (ip)=0003, cs:ip 指向
EB 05
, jmp 的机器码 - 读取指令码
EB 05
进入指令缓冲器 - (ip)=(ip)+多读取指令长度=(ip)+2=0005, cs:ip 指向
add ax,0001
- cpu 指向指令缓冲器里的指令
EB 05
- 指令
EB 05
执行后, (ip)=(ip)+05=000AH, cs:ip 指向inc ax
两种段内转移
短转移
jmp short 标号
功能: (ip)=(ip)+8 位位移
原理:
- 8 位位移="标号" 处的地址-jmp指令后的第一个字节的地址
- short 指明此处的位移为8 位位移
- 8 位位移的范围-128~127, 用补码表示
- 8 位位移由编译程序在编译时算出
近转移
jmp near ptr 标号
功能: (ip)=(ip)+16位位移
原理:
- 16 位位移="标号" 处的地址-jmp指令后的第一个字节的地址
- near ptr 指明此处的位移为16 位位移, 进行的是段内近转移
- 16 位位移的范围-32768~32767, 用补码表示
- 16 位位移由编译程序在编译时算出
远转移
远转移jmp far ptr 标号 | 近转移jmp near ptr 标号 |
---|---|
far ptr 指明了跳转的目的地址, 包含了标号的段地址cs 和偏移地址ip | near ptr 指明了相对于当前ip 的转移位移, 而不是转移的目的地址 |
far ptr转移 jmp转移地址 地址010B指令 | near ptr转移 jmp 位移地址 位移转移 |
转移地址在寄存器中的jmp 指令
格式 jmp 16 位寄存器
功能: ip=(16位寄存器)
例如: jmp ax
jmp word ptr 内存单元地址 | jmp dword ptr 内存单元地址 |
---|---|
从内存单元地址处开始存放着一个字, 是转移的目的偏移地址 | 从内存单元地址处开始存放两个字, 搞地质是转移地址的目的段地址, 低地址存放偏移地址 |
一个字的偏移地址 | 两个字的偏移地址 |
jmp 指令小结
格式 | 示例 |
---|---|
jmp 标号 | - 段间转移(远转移): jmp far ptr 标号 - 段内短转移: jmp short 标号, 8 位的位移 - 段内近转移: jmp near ptr 标号, 16 位的位移 |
jmp 寄存器 | jmp bx; 16 位的位移 |
jmp 内存单元 | 段内转移: jmp word ptr 内存单元地址; jmp word ptr [bx] 段间转移: jmp dword ptr 内存单元地址; jmp dword ptr [bx] |
其他转移指令
jcxz 指令
格式: jcxz 标号
功能:
如果(cx)=0, 则转移到标号处执行
当(cx)≠0, 程序向下执行
jcxz 是有条件的转移指令
所有的有条件转移指令都是短转移
对ip 的修改范围都为-128~127
在对应的机器码中包含转移的位移, 而不是目的地址
loop 指令
格式: loop 标号
操作:
-
(cx)=(cx)-1
-
当(cx)≠0, 转移到标号处执行
(cx)=0, 程序向下执行
如果(cx)≠0, (ip)=(ip)+8位位移
- 8 位位移="标号" 处的地址 - loop 指令后的第一个字节地址
- 8 位范围为-128~127, 补码表示
- 8 位位移由编译程序在编译时算出
call 指令和ret 指令
调研子程序: call 指令
返回: ret 指令
mov ax,0
call s
mov ax,4c00h
int 21h
s:add ax,1
ret
实质: 流程转移指令, 它们都修改ip, 或同时修改cs 和ip
call 指令
实质: 流程转移, 实现转移的方法和jmp 指令的原理类似
过程:
- 将当前的ip 或cs 和ip 压入栈中
- 转移到标号处执行指令
call 标号
- 16 位位移="标号" 处地址 - call 指令后的第一个字节地址
- 16 位范围-32768~32767, 补码表示
- 位移由编译程序编译时计算
call far ptr 标号, 实现段间转移
-
(sp)=(sp)-2
((ss)*16+(sp))=(cs)
(sp)=(sp)-2
((ss)*16+(sp))=(ip)
-
(cs)=标号所在段地址
(ip)=标号所在偏移地址
相当于:
push cs
push ip
jmp far ptr 标号
"call 标号" 类似于"jmp near ptr 标号", 对应的机器指令中为相对于当前ip 的转移位移, 而不是转移的目的地址, 实现段内转移. "call far ptr 标号" 实现的是段间转移
转移地址在寄存器中的call 指令
格式: call 16位寄存器
功能:
- (sp)=(sp)-2
- ((ss)*16+(sp))=(ip)
- (ip)=(16位寄存器)
相当于
- push ip
- jmp 16位寄存器
转移地址在内存中的call 指令
call word ptr 内存单元地址
相当于:
- push ip
- jmp word ptr 内存单元地址
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
执行后, (ip)=0123h, (sp)=0Eh
call dword ptr 内存单元地址
相当于:
- push cs
- push ip
- jmp dword ptr 内存单元地址
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]
执行后, (cs)=0, (ip)=0123h, (sp)=0Ch
返回指令, ret 和retf
ret | retf | |
---|---|---|
功能 | 用栈中的数据, 修改ip 内容, 从而实现近转移 | 用栈中的数据, 修改cs 和ip 内容, 从而实现远转移 |
相当于 | pop ip | pop ip pop cs |
举例 | ret指令 | retf指令 |
举例:
assume cs:code,ss:stack
stack segment
db 8 dup(0)
db 8 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,1000
call s
mov ax,4c00h
int 21h
s:
add ax,ax
ret
code ends
end start
执行过程1
执行过程2
mul 乘法指令
格式:
mul 寄存器
mul 内存单元
8位乘法 | 16位乘法 | |
---|---|---|
被乘数(默认) | al | ax |
乘数 | 8位寄存器或内存字节单元 | 16位寄存器或内存单元 |
结果 | ax | dx高位和ax低位 |
例 | mul bl; --(ax)=(al)(bl) mul byte ptr ds:[0]; --(ax)=(al) * ((ds)16+0) |
mul word ptr [bx+si+8]; --(ax)=(ax) * ((ds)*16+(bx)+(si)+8)结果的低16位; (dx)=(ax) * ((ds) * 16+(bx)+(si)+8)结果的高16位 |
模块化程序设计
- 调用子程序: call 指令
- 返回: ret 指令
- 子程序: 根据提供的参数处理一定的事务, 处理后, 将结果(返回值) 提供给调用者
参数和结果传递的问题
例如, 计算N 的3 次方
参数N 存放位置, 计算结果放在哪里?
方案
- 寄存器传递参数
- 内存单元传递参数
- 用栈传递参数
寄存器来存储参数和结果是最常用的方法
- 参数存放到bx 中, (bx)=N
- 子程序中用多个mul 指令计算N 的3 次方
- 将结果放到dx 和ax 中, (dx:ax)=
遇到的问题, 如果传递更多参数, 寄存器不够了怎么办?
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
s:
mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2
add di,4
loop s
cube:
mov ax,bx
mul bx
mul bx
ret
mov ax,4c00h
int 21h
code ends
end start
内存单元批量传递数据
- 将普亮数据放到内存中, 然后将他们坐在内存空间的首地址放在寄存器中, 传递给子程序
- 对于具有批量数据的返回结果, 也可以用同样的方法
assume cs:code
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
用栈传递参数
原理: 由调用者将徐亚传递给子程序的参数压入栈中, 子程序从栈中取得参数
任务: 计算(a-b)^3, 将a 和b 为word 型数据
- 进入子程序前, 参数a, b 入栈
- 调用子程序, 将使栈顶存放ip
- 结果(dx:ax)=(a-b)^3
计算(a-b)^3
用栈传递参数执行过程计算器冲突问题
将conversation
转换为大写, 需要将循环次数设置为cx,12
, 每次不固定, 可以写为db 'conversation',0
做如下循环
capital:
mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short capital
ok:
ret
有例子如下将下面字符串装换为大写
assume cs:code
data segment
db 'word',0
db 'unix',0
db 'wind',0
db 'goog',0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,0
mov cx,4
s:
mov si,bx
call capital
add bx,5
loop s
mov ax,4c00h
int 21h
capital:
push cx
push si
change:
mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ret
ok:
pop si
pop cx
ret
code ends
end start
在以上代码中使用了栈来存储参数, 另一种方式是使用cx 来直接存储循环次数, 部分代码如下
s:
mov si,bx
call capital
add bx,5
loop s
mov ax,4c00h
int 21h
capital:
mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short capital
ret
ok:
ret
在内循环中也同样使用cx, 会造成冲突, 重复使用cx 一个寄存器
而使用栈来存储, 就不用关心子程序使用了哪些寄存器, 也不会造成寄存器冲突
子程序标准框架如下
子程序开始:
子程序中使用的寄存器入栈
子程序内容
子程序使用的寄存器出栈
返回
在子程序的开始, 将要用到的所有寄存器中的内容都保存起来, 在子程序返回前再恢复
标志寄存器
PSW/FLAGS
别称, 程序状态字
结构:
- flag 寄存器是按位起作用的, 就是说, 它的每一位都有专门的含义, 记录特定信息
- 8086 中没有使用flag 的1, 3, 5, 12~15 位, 这些位不具有任何含义
作用:
- 用来存储相关指令的某些执行结果
- 为cpu 执行相关指令提供行为依据
- 控制cpu 相关工作方式
直接访问标志寄存器的方法
- pushf: 将标志寄存器的值压栈
- popf: 栈中弹出数据, 送入到标志寄存器
ZF 零标志(zero flag)
标记相关指令计算结果是否为0
- zf=1, 表示结果为0, 1表示逻辑真
- zf=0, 表示结果不是0, 0 表示逻辑假
指令 | 结果 |
---|---|
mov ax,1 and ax,0 |
zf=1, 表示结果是0 |
mov ax,1 or ax,0 |
zf=0, 结果非0 |
- 在8086 指令集中, 有的指令执行是影响标志寄存器的, 比如:
and
,sub
,mul
,div
,inc
,or
,add
, 大都是运算指令, 进行逻辑或算术运算 - 有的指令执行对标志寄存器没有影响, 比如,
mov
,push
,pop
, 大都是传送指令 - 使用一条指令的时候, 要注意指令的全部功能, 其中敖阔执行结果对标志寄存器的哪些标志位造成影响
PF 奇偶标志(parity flag)
指令执行后, 结果的所有二进制中1 的个数
- 1 的个数为偶数, pf=1
- 1 的个数为奇数, pf=0
指令 | 结果 |
---|---|
mov al,1 add al,10 |
结果为0000 1011B = 0000 0001B + 0000 1010B; 其中有3(奇数)个1, 则PF=0 |
mov al,1 or al,2 |
结果为00000011B = 0000 0001B or 0000 0010B; 其中有2(偶数)个1,则PF=1 |
SF 符号标志(sign flag)
指令执行后, 将结果视为有符号数
- 结果为负, sf=1
- 结果为非负, sf=0
指令 | 结果 |
---|---|
mov al,10000001B add al,1 |
al 为10000010B; 为负数,则SF=1 |
sub ax,ax | ax为0,为非负数, sf=0 |
该标志是cpu 对有符号数运算结果的一种记录, 将数据当做有符号数来运算的时候, 通过sf 可知结果的正负, 将数据当做无符号数来运算, sf 的值则没有意义, 虽然相关的指令影响了它的值
CF 进位标志(carry flag)
进行无符号数运算的时候, cf 记录了运算结果的最高有效位向更高位的进位值, 或从更高位的借位值
执行指令后
- 有进位或借位, cf=1
- 无进位或借位, cf=0
指令 | 结果 |
---|---|
mov al,98H add al,al |
(al)=30H, CF=1, CF记录了最高有效位向更高位的进位值 |
add al,al | (al)=60H,CF=0, CF记录了最高有效位向更高位的进位值 |
sub al,98H | (al)=C8H, CF=1, CF记录了向更高位的借位值 |
OF 溢出标志(overflow flag)
进行有符号数运算的时候, 如结果超过了机器所能表示的范围称为溢出
指令执行后
- 有溢出, of=1
- 无溢出, of=0
指令 | 结果 |
---|---|
mov al,98 add al,99 |
(al)=197,超出了8位有符号数的范围(-128~127), OF=1 |
mov al,0F0H add al,88H |
(al)=(-16)+(-120)=-136, 有溢出, OF=1 |
cf 和of 区别
- cf 是对无符号数运算有意义的进/借位标志位
- of 是对有符号数运算有意义的溢出标志位
带进(借) 位加减法
adc 带进位加法指令
adc 利用cf 位上记录的进位值
格式: adc 操作对象1,操作对象2
功能: 操作对象1=操作对象1+操作对象2+cf
例如: adc ax,bx 实现的功能, (ax)=(ax)+(bx)+cf
指令 | mov al, 98h add al,al adc al,3 |
mov ax,1 add ax,ax adc ax,3 |
mov ax,2 mov bx,1 sub bx,ax adc ax,1 |
---|---|---|---|
结果 | (ax)=34h | (ax)=5 | (ax)=4 |
解释 | adc 执行后, 计算为(ax)+3+cf=30h+3+1=34h | (ax)+3+cf=2+3+0=4 | (ax)+1+cf=2+1+1=4) |
adc 大数相加
adc大数相加例子两个128 位数据相加, 逆序相加
data segment
dw 0A452H,0A8F5H,78E6H,0A8EH,8B7AH,54F6H,0F04H,671EH
dw 0E71EH,0EF04H,54F6H,8B7AH,0A8EH,78E6H,58F5H,0452H
data ends
128位数据相加
其中使用了sub ax,ax
, 因为可以将标志寄存器的值置为默认, inc di
不影响进位标志位
sbb 带借位减法指令
格式: sbb 操作对象1,操作对象2
功能: 操作对象1=操作对象1-操作对象2-cf
例如: sbb ax,bx; (ax)=(ax)-(bx)-cf
cmp 指令
格式: cmp 操作对象1,操作对象2
功能: 计算操作对象1–操作对象2
应用: 其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果
指令 | cmp ax,ax | mov ax,8 mov bx,3 vmp ax,bx |
---|---|---|
功能 | 做(ax)–(ax)的运算, 结果为0, 但并不在ax中保存, 仅影响flag的相关各位 | (ax)=8,(bx)=3 |
标志寄存器 | ZF=1 PF=1 SF=0 CF=0 OF=0 | ZF=0 PF=1 SF=0 CF=0 OF=0 |
无符号数比较与标志位取值
通过cmp 指令执行后相关标志位的值, 可以看出比较结果
cmp ax,bx
有符号数比较与标志位取值
cmp ah,bh
条件转移指令
条件转移指令表举例
jxxx 系列指令和cmp 指令配合, 构造条件转移指令
- 不必再考虑cmp 指令对相关标志位的影响和jxxx 指令对相关标志位的检测
- 可以直接考虑cmp 和jxxx 配合使用表现出来的逻辑含义
配合使用实现高级语言中的if 语句功能
1 如果(ah)=(bh), 则(ah)=(ah)+(ah), 否则(ah)=(ah)+(bh)
cmp ah,bh
je s
add ah,bh
jmp short ok
s:
add ah,ah
ok:
ret
====================
if(a==b){
a=a+a
}
else{
a=a+b
}
2 如果(ax)=0, 则(ax)=(ax)+1
;ax 获得值
add ax,0
jnz s
inc ax
s:...
====================
if(a==0){
a++;
}
条件转移指令应用
指令: jxxx--je/jna/jae..
可以根据某种条件, 决定是否转移重新执行流程, "转移"=修改ip
如何检测
- 通过检测标志位, 由标志位体现条件
- 条件转移指令通常都和cmp 相配合使用, cmp 指令改变标志位
示例
data segment
db 8,11,8,1,8,5,63,38
data ends
统计数值为8 的字节个数
思路:
初始设置(ax)=0, 然后使用循环依次比较每个字节的值, 找到一个和8 相等的数将ax 的值加1
assume cs:code
data segment
db 8,11,8,1,8,5,63,38
data ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,0
mov bx,0
mov cx,8
s:
cmp byte ptr [bx],8
jne next
inc ax
next:
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end start
DF 标志和传送指令
方向标志位(Direction Flag)
功能
- 在串处理指令中, 控制每次操作后si, di的增减
- DF = 0: 每次操作后si, di递增
- DF = 1: 每次操作后si, di递减
对df 位进行设置的指令
-
cld指令: 将标志寄存器的DF位设为0(clear)
-
std指令: 将标志寄存器的DF位设为1(setup)
转送指令1: movsb
以字节为单位传送
-
((es)×16 + (di)) = ((ds) ×16 + (si))
-
如果DF = 0则:
(si) = (si) + 1
(di) = (di) + 1
如果DF = 1则:
(si) = (si) - 1
(di) = (di) - 1
传送指令2: movsw
功能, 以字为单位传送
-
((es)×16 + (di)) = ((ds) ×16 + (si))
-
如果DF = 0则:
(si) = (si) + 2
(di) = (di) + 2
如果DF = 1则:
(si) = (si) - 2
(di) = (di) - 2
data segment
db 'Welcome to masm!'
db 16 dup(0)
start:
mov ax,data
mov ds,ax
mov si,0
mov es,ax
mov di,16
cld
mov cx,16
s:
movsb
loop s
mov ax,4c00h
int 21h
code ends
end start
rep指令
rep 指令常和串传送指令搭配使用
功能: 根据cx 的值, 重复执行后面的指令
rep movsb == s: movsb, loop s
rep movsw==s: movsw, loop s
例如
用串传送指令, 将F000H 段中的最后16 个字符复制到data 段中, 最后一个字符位置: F000:FFFF
assume cs:code,ds:data
data segment
db 16 dup(0)
data ends
code segment
start:
mov ax,0f000h
mov ds,ax
mov si,0ffffh
mov ax,data
mov es,ax
mov di,15
mov cx,16
std
rep movsb
mov ax,4c00h
int 21h
code ends
end start
移动最后16位字符到data段
网友评论