动态线条
动态线条end

流程转移与子程序

转移综述

背景:一般情况下指令时顺序执行的,而在实际中,常需要改变程序的执行流程。

转移指令

1)可以控制CPU执行内存中某处代码的指令

2)可以修改IP,或同时修改CS和IP的指令

转移指令分类

1)转移行为

      段内转移:只修改IP,如:jmp ax

      段间颛臾:同时修改cs和IP,如jmp 1000:0

2)根据指令对IP修改的范围不同

      段内短转移:IP修改范围:-128-127

      段内长转移:IP修改范围:-32768-32767

3)转移指令

      无条件转移指令,如:jmp

      条件转移指令,如jcxz

      循环指令,如:loop

      过程

      中断

操作符OFFSET

格式:offset 标号

实例

assume cs:code
code segment
start:
	mov ax,offset start	;相当于mov ax,0
	s:mov ax,offset s	;相当于mov ax,3
	mov ax,4c00H
	int 21H
code ends
end start

利用offset指令,将s处的两条指令复制到s0处

assume cs:code
code segment
	s:mov ax,bx
	mov si,offset s
	mov di,offset s0
	mov ax,cs:[si]
	mov cs:[di],ax
	s0:nop		;空指令占位符
	nop			;空指令占位符
	
	mov ax,4c00H
	int 21H
code ends
end

jmp指令

jmp指令功能

无条件转移,可以值修改IP,也可以修改cs和ip

jmp指令要给出两种信息

1)转移的目的地址

2)jmp指令要给出两种信息

      段间转移(远转移):jmp 2000:1000

      段内短转移:jmp short 标号,IP的修改范围为-128-127,8位的位移

      段内近转移:jmp near ptr 标号,IP的修改范围为-32768-32767,16位的位移

观察地址偏移实例

assume cs:code
code segment
start:
	mov ax,0
	jmp short s
	add ax,1
	s:inc ax
	
	mov ax,4c00H
	int 21H
code ends
end start

image-20220111082208511

1)跳转到076A:0008的位置继续执行命令

2)观察跳转指令的机器码可以观察到03,是因为需要在IP原有的数值上进行更改,在执行跳转指令时,ip已经指向了跳转指令的下一条指令

3)IP的值也就是0005,在此基础上添加03,刚好是所要跳转的目的地0008

两种段内转移

短转移:jmp short 标号

功能:ip = ip + 8位位移

原理:

1)8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址

2)short 指明此处的位移为8位位移

3)8位位移的范围为-128~127,用补码表示

4)八位位移由编译程序在编译时算出

近转移:jmp near ptr 标号

功能:ip = ip + 16位位移

原理:

1)16位位移 = 标号处的地址 - jmp指令后的第一个字节的地址

2)near ptr 指明此处的位移为16位位移

3)8位位移的范围为-32768~32767,用补码表示

4)十六位位移由编译程序在编译时算出

远转移:jmp far ptr 标号

功能:直接指明了跳转的目的地址,包含了段号的段地址CS和偏移地址IP

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

1)段内转移

       功能:jmp word ptr 内存单元地址

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

2)段间转移

      功能:jmp dword ptr 内存单元地址

      原理:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的地址,低地址处是转移的目的偏移地址

jmp指令小结

jmp指令格式 示例
jmp 标号 段间转移(远转移):jmp far ptr 标号
段内短转移:jmp short 标号
段内近转移:jmp near ptr 标号
jmp 寄存器 jmp bx
jmp ax
等等
jmp 内存单元 段内转移:jmp word ptr 内存单元地址
段间转移:jmp dowrd ptr 内存单元地址

其他转移指令

jcxz指令

格式:jcxz 标号

功能:如果cx = 0,则转移到标号处,否则不进行跳转

jcxz是有条件转移指令

1)所有的有条件转移指令都是短转移

2)对IP的修改范围都是-128~127

3)在对应的机器码中包含转移的位移,而不是目的地址

同理:loop指令也是属于短程的跳转指令

call与ret指令

调用子程序:call

返回:ret

示例

mov ax,0
call s
mov ax,4c00H
int 21H

s:add ax,1
ret

call指令

字面意思:调用子程序

实质:流程转移,call指令实现转移的方法与jmp指令的原理相似

格式一:call 16位寄存器

cpu执行call指令,进行两部操作:

      1)将当前的IP压入栈中

sp = sp - 2
ss * 16 + sp = ip

      2)ip = 标号所在的偏移地址

call标号:

      1)16位位移 = 标号处地址 - call指令后的第一个字节的地址

      2)16位位移的范围为-32768~32767,用补码表示

      3)16位位移由编译程序在编译时算出

格式二:call far ptr 标号

cpu执行call指令,进行两部操作:

      1)将当前的cs与ip压栈

sp = sp - 2
ss * 16 + sp = cs
sp = sp - 2
ss * 16 + sp = ip

      2)cs = 标号所在段的段地址

            ip = 标号所在的偏移地址

格式三: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指令
功能 用栈中的数据,修改IP的内容,从而实现近转移 用栈中的数据,修改CS和IP的内容,从而实现远转移
相当于 pop IP pop IP
pop CS

ret实例

assume cs:code,ss:stack
stack segment
	db 16 dup (0)
stack ends

code segment
	moc ax,4c00H
	int 21H
start:
	mov ax,stack
	mov ss,ax
	mov sp,16
	mov ax,0
	push ax
	mov bx,0
	ret
code ends
end start

retf实例

assume cs:code,ss:strack
strack segment
	db 16 dup (0)
strack ends
code segment
	mov ax,4c00H
	int 21H
	
start:
	mov ax,strack
	mov ss,ax
	mov sp,16
	mov ax,0
	push cs
	push ax
	mov bx,0
	retf

code ends
end start

call和ret配合使用

assume cs:code
code segment
main:
	....
	call sub1		;调用子程序
	....
	mov ax,4c00H
	int 21H
	
sub1:
	....
	call sub2		;调用子程序
	...
	ret
	
sub2:
	....
	ret
	
code ends
end main

计算2N(以三次方为例)

assume cs:code
code segmeng
main:
	mov ax,1
	mov cx,3
	call s
	mov bx,ax
	mov ax,4c00H
	int 21H
s:
	add ax,ax
	loop s
	ret
	
code ends
end main

上述程序在运行时,使用的栈段不是我们人为定义的,是在内存中随机开辟的空间,因此具有一定的危险性。

mul惩乘法指令

命令格式

mul 寄存器

mul 内存单元

八位乘法 十六位乘法
被乘数 AL AX
乘数 八位寄存器或内存字节单元 十六位寄存器或内存字单元
结果 AX DX(高位)和AX(低位)

计算100 * 10

mov al,100
mov bl,10
mul bl
;结果:ax = 03E8H

计算100 * 10000

mov ax,100
mov bx,10000
mul bx
;结果:dx = 00FH,ax = 4240H

汇编语言的模块化程序设计

模块化程序设计

assume cs:code
code segment
main:
	....
	call sub1		;调用子程序
	....
	mov ax,4c00H
	int 21H
	
sub1:
	....
	call sub2		;调用子程序
	...
	ret
	
sub2:
	....
	ret
	
code ends
end main

子程序:根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。

参数和结果传递的问题

问题:提供的N,计算N的三次方

考虑:我们将参数N存储到什么地方,计算得到的值,存储到什么地方

方案:

      1)用寄存器传递参数

      2)用内存单元进行参数传递

      3)用栈传递参数

用寄存器传递参数

问题:根据提供的N,计算N的3次方

考虑:将参数N存储在什么地方?计算得到的数字数值,存储在什么地方?

用寄存器传递参数

      参数存放到bx中,即bx = N

      子程序中用多个mul指令计算N3

      将结果放到dx和ax中:dx:ax = N3

编程任务

assume cs:code,ds:data
data segment
	dw 1,2,3,4,5,6,7,8
	dd 8 dup (0)
data ends
code segment
main:
	mov ax,data
	mov ds,ax
	mov si,0		;将源地址指针寄存器指向数据段
	mov di,16		;将目的地址指针寄存器指向存储段
	mov cx,8		;设置循环次数
	
l:
	mov bx,[si]
	call cube
	mov [di],ax
	mov [di + 2],dx
	add si,2
	add di,4
	loop l
	
	mov ax,4c00H
	int 21H
cube:				;计算三次幂的子程序
	mov ax,bx
	mul bx
	mul bx
	ret
	
code ends
end main

上述传递方法的缺点,由于寄存器的个数是有限的,因此不能一次性传递多个数据。

用内存单元批量传递数据

方案:

1)将批量数据放到内存中,然后将他们所在内存空间的首地址放到寄存器中,传递给需要的子程序

2)对于具有批量数据的返回结果,也可用同样的放大

编程:将data段中的字符串转化为大写

assume cs:code,ds:data
data segment
	db 'conversation'	;十二个字符,占用空间12字节
data ends
code segment
main:
	mov ax,data
	mov ds,ax
	mov si,0
	mov cx,12
	call fun
	
	mov ax,4c00H
	int 21H
	
fun:
	and byte ptr [si],11011111b
	inc si
	loop fun
	ret
	
code ends
end main

上述程序完成了将小写字母变为大写字,并且可以发现,call跳转与loop跳转可以跳转到同一标志符上。

用栈传递参数

原理:有调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数

任务:计算(a-b)^3,a、b为word型数据

      1)进入子程序前,参数a、b入栈

      2)调用子程序,将使栈顶存放IP

      3)结果:(dx:ax) = (a - b)^3

例:设a = 3,b = 1,计算:(a - b)^3

code segment
main:
	mov ax,1
	push ax
	mov ax,3
	push ax
	call difcube
	mov ax,4c00H
	int 21H
	
difcube:
	push bp
	mov bp,sp
	mov ax,[bp + 4]
	sub ax,[bp+6]
	mov bp,ax
	mul bp
	mul bp
	pop bp
	ret 4
code ends

寄存器冲突的问题

会造成冲突的汇编代码

assume cs:code,ds:data
data segment
	db 'asdasd',0
	db 'zxczcx',0
	db 'cvbcv',0
	db 'sdfsdfsd',0
data segment

code segment
main:
	mov ds,ax
	mov bx,0
	mov cx,4
l:	
	mov si,bx
	call capital
	add bx,5
	loop l			;<=======================
	mov ax,4c00H
	int 21H
capital:
	mov cl,[si]
	mov ch,0
	jcxz ok
	and byte ptr [si],11011111n
	inc si
	jmp short capital
ok: ret
code ends
end main

在最后一次循环结束时,cx的值已经变为0,但是在第18行代码处,进行loop循环时,先将cx自行减一,然后判断cx是否为0,由于当时的cx已经为0,再次减一就变为了FFFF,那么将进入错误的逻辑。

解决冲突的基本操作

子程序框架:在子程序的开始,将要用到的所有寄存器中的内容都保存起来,在子程序返回前再恢复

子程序开始:
	子程序使用的寄存器入栈
	子程序内容
	子程序使用的寄存器出栈
	返回(ret、retf)

标志寄存器(PWD)

标志寄存器的结构

1)flag寄存器是按位起作用的,也就是说,他的每一位都有专门的含义,记录特定的信息

2)8086CPU中没有使用flag的1,3,5,12,13,14,15位,这些位不具有任何意义。

标志寄存器的作用

1)用来存储相关指令的某些执行结果

2)用来为CPU执行相关指令提供行为依据

3)用来控制CPU的相关工作方式

观察寄存器的值

image-20220112094714757

寄存器名称 标志 值为1时 值为0时 意义 所在位
overFlow OF OV NV 溢出 11
direction DF DN UP 方向 10
sign SF NG PL 符号 7
zero ZF ZR NZ 零值 6
parity PF PE PO 奇偶 2
carry CF CY NC 进位 0

ZF标志

1)ZF标记记录了相关指令的计算结果是否为0

      ZF = 1,表示结果是0

      ZF = 0,表示结果不是0

2)示例

;结果为0
mov ax,1
and ax,0

;结果非零
mov ax,1
or ax,0

3)小总结

  • 在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如:add、sub、mul、div、inc、or、and等,他们大都是运算之灵,进行逻辑或算数运算
  • 有的指令的执行对寄存器是没有影响的,比如:mov、push、pop等,他们大都是传送指令
  • 使用一条指令的时候,要注意这条指令的全部功能,其中包括执行结果对标记寄存器的那些标志位造成影响

PF奇偶标志

1)PF记录指令执行后,结果的所有二进制位中1的个数

      1的个数为偶数,PF = 1

      1的个数为奇数,PF = 0

2)示例

;PF = 0
mov al,1
add al,10

;PF = 1
mov ,1
or al,2

SF符号标志

1)SF记录指令执行后,将结果视为有符号数

      结果为负,SF = 1

      结果非负,SF = 0

2)SF标志是CPU对有符号数运算结果的一种记录。将数据当做有符号数来运算的时候,通过SF可知结果的正负;将数据当做无符号数来运算的时,SF的值则没有意义,虽然相关的指令影响了它的值。

CF进位标志

1)在进行无符号数运算的时候,CF记录了运算结果的最高有效位向更高位的进位值,或从更高为的借位值。

2)CF记录指令执行后

      有进位或借位:CF = 1

      无进位或借位:CF = 0

3)示例

;运算结果:al = 30H,CF = 1
mov al,98H
add al,al

;运算结果:al = 60H,CF = 0
add al,al

;运算结果:al = C8H,CF = 1
sub al,98H

OF溢出标志

1)在进行有符号运算的时候,如果结果超出了机器所能表示的范围,称为溢出

2)OF记录有符号数操作指令执行后

      有溢出:OF = 1

      无溢出:OF = 0

3)注意:溢出只是对有符号数运算而言

OF与CF的区别

1)OF是对有符号数运算有意义的溢出标志位

2)CF是对无符号数运算有意义的进/借为标志位

1.最高位有进位,一定发生溢出。

错!
如8位加法:
FF+02=01
(CF)=1,(OF)=0

如理解为符号加法,则为-1+2=1,(CF)=1表示有进位,(OF)=0表示无溢出,此例可推翻命题
如理解为无符号加法,则为127+2=1,(CF)=1表示有进位,亦表示溢出,(OF)无意义,

2.发生了溢出,最高位一定有进位。
错
8位加法:
40+40=80
(CF)=0,(OF)=1
如理解为符号加法,则为64+64=-128,(CF)=0表示无进位,(OF)=1表明结果溢出,此例可推翻命题
如理解为无符号加法,则为64+64=128,(CF)=0表示无进位,亦表示无溢出,(OF)无意义,

测试表格

指令 CF OF SF ZF PF
sub al,al 0 0 0 1 1
mov al,10H 0 0 0 0 0
add al,90H 0 1 1 0 1
mov al,80H 1 0 0 0 0
add al,80H 0 1 1 0 1
mov al,0FCH 1 0 1 0 1

带进/借位的加减法

adc带进加法指令

adc是带进位加法指令,它利用了CF为上记录的进位值

      格式:adc 操作对象1,操作对象2

      功能:操作对象1 + 操作对象2 + CF

adc指令应用:大数相加

问题:8086CPU提供add指令,完成8位或16位加法,有更大的数相加时,如何做

例子:编程实现1EF000H + 201000H,结果存放在ax(高16位)和bx(低16位)中

mov ax,001EH
mov bx,0F00H
add bx,1000H
adc ax,0020H

更大数值的进位同理

sub ax,ax与mov ax,0的区别

前者可影响CF标志位,后者对标志位无影响,如果需要将CF清零,需要用sub命令

inc di与add di,2的区别

前者不影响标志位,后者运算结束后影响标志位

sbb指令:带借位减法指令

1)格式:sbb 操作对象1 操作对象2

2)功能:操作对象1 = 操作对象1 - 操作对象2 - CF

3)与sub区别:利用CF为上记录的借位值

4)比如:sbb ax,bx

5)实现功能:ax = ax - bx - CF

cmp和条件转移指令

CMP指令

1)格式:cmp 操作对象1,操作对象2

2)功能:计算操作数对象1 - 操作对象2

cmp是比较指令,功能相当于减法指令,只是不保存结果

cmp指令执行后,将对标志寄存器产生影响

比较关系 ax ? bx ax - bx 特点 标志寄存器
等于 ax == bx ax - bx = 0 ZF = 1
不等于 ax != bx ax - bx != 0 ZF = 0
小于 ax < bx ax - bx 将产生错位 CF = 1
大于等于 ax >= bx ax - bx不必错位 CF = 0
大于 ax > bx ax - bx既不借位,结果又不为0 CF = 0且ZF = 0
小于等于 ax <= bx ax - bx 或者借位,或者结果为0 CF = 1 或 ZF = 1

条件转移指令

应用格式

cmp oper1,oper2
jxxx 标号
指令 含义 测试条件
je/jz 相等/结果为0 ZF = 1
jne/jnz 不等/结果不为0 ZF = 0
js 结果为负 SF = 1
jns 结果非负 SF = 0
jo 结果溢出 OF = 1
jno 结果溢出 OF = 0
jp 奇偶位为1 PF = 1
jnp 奇偶位为0 PF = 0
jb/jnae/jc 低于/不高于等于/有借位 CF = 1
jnb/jae/jnc 不低于/高于等于/无借位 CF = 0

上述指令代表的含义:

简写 全写 简写 全写
J Jump G Greater
E Equal S Sign
N Not C Carry
B Below P Parity
A Above O Overflow
L Less Z Zero

利用cmp配合跳转指令实现高级语言中的IF

例:如果ah = bh,则ah = ah ,否则ah = ah + bh

	cmp ah,bh
	je s
	add ah,bh
	jmp short ok
s:
	add ah,ah
ok:
	ret

条件转移指令的应用

统计数据域中数值为8的字节的个数

code segment
main:
	mov ax,data
	mov ds,ax
	mov bx,0
	mov axx,0
	mov cx,8
s:
	cmp byte ptr [by],8
	jne next
	inc ax
next:
	inc bx
	loop s
	
	mov ax,4c00H
	int 21H
code ends

DF标志和串传送指令

DF方向标记位

功能:

1)在串处理指令中,控制每次操作后si,di的增减

2)DF = 0:每次操作后si、di递增

3)DF = 1:没出操作后si、di递减

对DF位进行设置

1)cld指令:将标志寄存器的DF设为0(clear)

2)std指令:将标志寄存器的DF设为1(setup)

串传送指令

传送指令1:movsb

功能:(以字节为单位传送)

1)se * 16 + di = ds * 16 + si

2)如果df = 0

      si = si + 1

      di = di + 1

       如果df = 1

      si = si - 1

      di = di - 1

传送指令2:movsw

功能:(以字为单位传送)

1)se * 16 + di = ds * 16 + si

2)如果df = 0

      si = si + 2

      di = di + 2

       如果df = 1

      si = si - 2

      di = di - 2

因此,将字符串赋值操作代码进行改进

assume cs:code,ds:data
data segment
	db 'welcome to masm!'
	db 16 dup (0)
data ends
code segment
main:
	
	;设置寄存器相关初值
	mov ax,data
	mov ds,ax
	mov si,0
	mov es,ax
	mov di,16
	cld			;将df标记位初始化为0
	
	;循环传送
	mov cx,16
l:
	movsb
	loop l
	
	mov ax,4c00H
	int 21H
code ends
end main

rep指令

1)rep指令常和串传送指令搭配使用

2)功能:根据cx的值,重复执行后面的指令

3)用法:rep movsb

	;循环传送
	mov cx,16
l:
	movsb
	loop l

修改后:

	mov cx,16
l
	rep movsb
	loop l
posted @ 2022-01-17 09:06  v1v1v1  阅读(195)  评论(0)    收藏  举报