5 流程转移和子程序
5.1 转移
本质是修改ip和cs的值,改变命令执行的顺序
分类
按行为:段内转移和段间转移
按修改范围:短转移(-128-127)和长转移(-32768-32767)
按指令:
- 无条件转移:jmp
- 有条件转移:jcxz和jcxnz
- 循环指令:loop
- 过程
- 中断:int
5.2 JMP指令汇总
JMP 标号
标号也就是名称,如s:inc bx,中s为标号
jmp short label,段内短转移,8位,机器码是位移jmp near ptr label,段内近转移,16位,机器码是位移jmp far ptr label,段间转移,机器码是label的内存地址CS:IP,按小端存储,ip地址低jmp far ptr statr ;假设start的位置是076A:010B,那么这条的机器码就是EA0B016A07
JMP 寄存器
jmp bx,bx是目标指令的ip地址
JMP 立即数
jmp 00,ip = 00
jmp 00:00,cs = 00, ip = 00
JMP 内存单元
jmp word ptr [address],段内转移,转到[address]所存储的值的位置jmp dword ptr [address],段间转移,CS=2-3[address],IP=0-1[address],dword是双字,4B
基于位移进行的JMP指令
在机器码中的位移是jmp指令指向的地址 - jmp指令后一条的地址
mov ax, 0 ;假设存在076A:0000中
jmp short s ;存在076A:0003中,对应机器码EB05,EB是jmp short的代码,05 = 0A - 05
add ax, 1 ;存在076A:0005中
nop ;空指令,占1B,076A:0008
nop ;076A:0009
s: inc ax ;076A:000A
为什么会是减去后一条?
程序执行流程:
- IP指向jmp,jmp指令进入指令缓冲器
- IP += last length,指向下一条
- jmp执行,IP += (jmp指向 - 目前指向)=(jmp指向 - jmp下一条)
当要加的数值超过限定时,会报错
转移地址在内存中的jmp指令
mov ax, 0123
mov ds:[0], ax
jmp word ptr ds:[0] ;执行后,IP = 0123
内存地址的内容是IP的值
mov ax, 0123
mov ds:[0], ax
jmp dword ptr ds:[0] ;执行后,CS=0000,IP=0123
存放两个字,高字是CS,底字是IP
有条件转移:JCXZ
jcxz label
jmp when cx == zero
当cx寄存器的值为0时,执行jmp指令(对应jcxnz)
机器码是label的地址 - 这条指令的下一条地址(位移),对IP的修改范围是**-128 - 127**
循环转移:LOOP
s: add ax, ax ;设地址为076A:0006
loop s ;地址为076A:0008,机器码为E2FC
mov ax, 4c00 ;地址为076A:000A
FC是-4的补码,表示IP -= 4,LOOP指令的机器码是位移,一般是负的
用位移转移的好处
无论label的位置如何,IP转移的位移是固定的,适用于调用函数指针时动态装载
5.3 OFFSET指令
s: mov ax, offset s
将s的偏移地址(ip)存入ax中,一定是一个16b寄存器
例:下面是一个将s处的指令复制到s0处的代码
s: mov ax, bx
mov si, offset s
mov di, offset s0
mov ax, cs:[si]
mov cs:[di], ax
s0: nop
s0原来是空的
指令也是数据,由机器码形式储存,也可以mov add等,一条mov ax, bx指令长2B,可以用一个16b寄存器存储
5.4 模块化设计程序(函数)
CALL指令
main:
mov ax, 0123
call func1
mov ax, 4c00
int 21
fun1:
call func2
ret
fun2:
mov bx, 2
ret
call指令的实质是修改IP或IP和CS
CPU执行call的流程:将当前的ip或cs和ip入栈(先入cs后入ip),转移到label处执行
call的机器码是label地址 - call后第一个指令地址,也是位移,范围是-32768 - 32767
类似于jmp near ptr label,用risc-V指令表示为
addi sp, sp, -4
lw cs, 2(sp) ;只有call far ptr有
lw ip, 0(sp)
;jmp near ptr label 这是cisc
同理还有call far ptr,实现段间转移,机器码为目标地址,小端存储,先ip后cs
但是,在执行过程中是CS先入栈,其实都是IP的地址更低
转移地址在寄存器中的call
mov ax, 0123
call ax
相当于
push ip
jmp near ptr ax ;ip = ax
转移地址在内存中的call
mov sp, 10H
mov ax, 0123H
mov ds:[0], ax
call word ptr ds:[0]
执行后,ip=0123H,cs不变,sp=0EH,sp -= 2
mov sp, 10H
mov ax, 0123H
mov ds:[0], ax
mov ds:[2], 0000H
call dword ptr ds:[0]
执行后,ip=0123H, cs=0000H,ip=0CH,高地址是cs,低地址是ip,sp -= 4
返回指令ret/retf
后者就是ret far,搭配call far ptr使用
fun2:
mov bx, 2
ret ;相当于 pop ip
fun2:
mov bx, 2
retf ;相当于 pop ip, pop cs
在实际过程中,一般要先开辟栈段
assume ss:stack, cs:code
stack segment
db 8 dup (0)
db 8 dup (0)
stack ends
code segment
mov ax, stack
mov ss, ax
mov sp, 16
开辟了一个16b的栈段(ss:0000H到ss:0015H)
ret n指令
ret 4等价于
pop ip
add sp, 4
如果函数中使用了某寄存器,在函数的开头要先将原始值入栈,在结束前对这些寄存器出栈
如果不是对寄存器入栈,而是一些数,则可以直接ret n,重新归位SP
mov ax, 1
push ax
mov ax, 3
push ax
call func
func: push bp
mov bp, sp
;codes
pop bp
ret 4 ;栈中还有0001H,0003H这四个字节,要归位
5.5 寄存器冲突问题
在处理字符串时,为了能够自动处理而不设置cx = n(循环次数),会在字符串末尾加上一个0
db 'conversation', 0
;格式省略
func: mov si, 0H
mov cl, [si]
mov ch, 0H
jcxz over
inc si
jmp short func
over: ret
但是这样有一个问题,cx是循环控制的寄存器,这样就不能控制外层循环了
所以在编写的程序中必须对使用到的所有寄存器进行入栈出栈操作
代码修改为
func: push cx
push si
func1: mov si, 0
mov cl, [si]
mov ch, 0H
jcxz over
inc si
jmp short func1
over: pop si
pop cx
ret
5.6 标志位寄存器
PSW/FLAGS,是一个东西,程序状态字/标志位寄存器
是一个16bit寄存器,按位起作用,在8086中为

剩下的都是没用的
使用
pushf ;将FLAGS的值压栈
popf ;将栈顶值赋给FLAGS,弹栈
直接对FLAGS进行操作,此外没有办法直接操作
只有运算指令和部分指令的操作结果会对FLAGS产生影响,如add, sub, mul, div, or, and,inc指令不影响CF
大部分传送指令对FLAGS无影响,如mov, push, pop等
ZF,零标志
记录指令的结果是否为零,即zf = (result == 0)
sub ax, ax
执行后ZF = 1
PF,奇偶标志
记录指令的结果的二进制表示的1的个数,pf = (numOf1 == even)
mov ax, 0
add ax, 3
执行后PF = 1
SF,符号标志
将所有结果都视为有符号数,SF = 符号位,即负数为1,正数和0为0
mov ax, 1H
add ax, F000H
执行后SF = 1
如果数据是无符号数,那么SF没有意义,别看就行
CF,进位标志
只有在进行无符号数运算时,记录了结果的MSB向更高位的进位/借位,CF = (有进位/借位)
如果是有符号数运算,也会记录,但是没用,不看这个就行
mov al, 98H
add al, al
执行后CF = 1
Nbit无符号数,其最高位为N-1,注意有第0位
OF,溢出标志
与CF对应,这是仅针对有符号数的,OF = (有溢出)
mov al, 98
add al, 99
执行后OF = 1
5.7 带进/借位的加减法
abc指令是带进位的加法,abc ax, bx实现了ax = ax + bx + CF,用于超过16bit的数的相加
这个例子实现了1EF000H + 201000H
mov ax, 1EH
mov bx, F000H
add bx, 1000H
adc ax, 20H
将后四位给了bx,前四位给了ax
sbb指令是带借位的减法,sbb ax, bx实现了ax = ax - bx - CF
这个例子实现了003E1000H - 00202000H
mov ax, 003EH
mov bx, 1000H
sub bx, 2000H
sbb ax, 0020H
5.8 cmp指令和条件转移指令
cmp是比较指令,相当于减法,但是不保存结果,cmp ax, bx运算ax - bx,但是不对reg造成改变,会改变FLAGS
jxxx指令
没有提到寄存器(cx)的就是只对FLAGS进行判断
js,sign符号位,在负数时为1jc,carry进/借位,存在进/借位时为1jp,parity奇偶位,偶数为1jo,overflow溢出位,溢出为1
cmp和条件转移的配合
cmp ah, bh
je s ;je表示ZF=1就执行
add ah, bh
jmp short over
s: add ah, ah
over: ret
相当于
if (ah == bh){
ah += ah;
}
else{
ah += bh;
}
jxxx不一定配合cmp,可以单独使用,反正都是只看FLAGS的值
5.9 DF标志位和串传送指令
DF是方向标志,DF = 0,每次串操作后si++, di++;DF = 1, si--, di--
设置DF的指令
cld表示将DF clear,DF = 0;std表示将DF set,DF = 1
串传送指令
movsb以字节传送,功能是[es:di]=[ds:si],注意是es
data segment
db 'Welcom'
db 6 dup (0)
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov di, 6
mov cx, 6
mov es, ax
cld
s: movsb
loop s
mov ax, 4c00H
int 21H
code ends
end start
movsw以字传送,串操作后di+=2,si+=2
rep指令
根据cx的值,重复执行后面的指令,上面的s指令可以改成
rep movsb