5 流程转移和子程序

学习笔记
作者: MingXiao

5.1 转移

本质是修改ipcs的值,改变命令执行的顺序

分类

按行为:段内转移和段间转移

按修改范围:短转移(-128-127)和长转移(-32768-32767

按指令:

  1. 无条件转移:jmp
  2. 有条件转移:jcxz和jcxnz
  3. 循环指令:loop
  4. 过程
  5. 中断:int

5.2 JMP指令汇总

JMP 标号

标号也就是名称,如s:inc bx,中s为标号

  1. jmp short label,段短转移,8位,机器码是位移

  2. jmp near ptr label,段近转移,16位,机器码是位移

  3. jmp far ptr label,段转移,机器码是label的内存地址CS:IP,按小端存储,ip地址低

    jmp far ptr statr        ;假设start的位置是076A:010B,那么这条的机器码就是EA0B016A07
    

JMP 寄存器

jmp bx,bx是目标指令的ip地址

JMP 立即数

jmp 00ip = 00

jmp 00:00cs = 00, ip = 00

JMP 内存单元

  1. jmp word ptr [address],段转移,转到[address]存储的值的位置
  2. 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

为什么会是减去后一条?

程序执行流程:

  1. IP指向jmp,jmp指令进入指令缓冲器
  2. IP += last length,指向下一条
  3. 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:0000Hss: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, andinc指令不影响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符号位,在负数时为1
  • jc,carry进/借位,存在进/借位时为1
  • jp,parity奇偶位,偶数为1
  • jo,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 = 0std表示将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


Comments