流程转移与子程序

转移概念

一般情况下指令是顺序逐条执行,然而在实际情况中常常需要改变程序的执行流程。

转移指令:

  • 可以控制CPU执行内存中某处代码的指令
  • 可以修改IP,或者同时修改CS和IP的指令

转移指令按行为分为:

  • 段内转移:只修改IP,如jmp ax
  • 段间转移:同时修改CS和IP,如jmp 1000:0

根据指令对IP修改的范围不同分为:

  • 段内短转移: IP修改范围为-128~127
  • 段内近转移: IP修改范围为-32768~32767

按转移指令分为:

  • 无条件转移指令:jmp
  • 条件转移指令:jcxz
  • 循环指令:loop
  • 过程
  • 中断

offset偏移地址

格式

offset 标号
assume cs:codeseg
codeseg segment
start:  mov ax, offset start	; 相当于mov ax,0
	  s:mov ax, offset s		; 相当于mov ax,3
codeseg ends
end start

jmp指令,无条件转移

不仅可以修改IP,也可以同时修改CS和IP。

短转移

“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 far ptr 标号近转移jmp near ptr 标号
段间转移段内转移
far ptr指明跳转到的目的地址,即包含了标号的段地址CS和偏移地址IPnear ptr指明了相对于当前IP的转移位移,而不是转移的目的地址
assume cs:codesg
codesg segment
start:  mov ax,0
		mov bx,0
		jmp far ptr s
		db 256 dup(0)
	  s:add ax,1
	    inc ax
codesg ends
end start
转移地址在寄存器中的jmp指令

指令格式:jmp 16位寄存器

assume cs:codesg
codesg segment
start: 	mov ax,0
		mov bx,ax
		jmp bx
		mov ax,0123H
codesg ends
end start
转移地址在内存中的jmp指令
jmp word ptr内存单元地址jmp dword ptr内存单元地址
段内转移段间转移
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。
00 — (IP)
02 — (CS)

段内偏移

mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]	; (IP)=0123H

段间转移

mov ax,0123H
mov [bx],ax
mov word ptr [bx+2],0
jmp dword ptr [bx]
;执行后
; (CS) = 0
; (IP) = 0123H
; CS:IP指向0000:0123

jcxz指令

指令格式:jcxz 标号 (cx为cx寄存器,z为zero)

功能: 如果cx等于0,则转移到标号处执行,当cx不等于0时,则什么也不做,向下执行。

  • 当cx = 0时,(IP) = (IP) + 8位位移
    • 8位位移="标号"处的地址减去jcxz指令后的第一个字节的地址;
    • 8位位移的范围为-128~127,用补码表示;
    • 8位位移由编译程序在编译时算出;
  • jcxz是有条件转移指令
    • 所有的有条件转移指令都是短转移;
    • 对IP的修改范围都为-128~127;
    • 在对应的机器码中包含转移的位移,而不是目的地址;
assume cs:codesg
codesg segment
start:
	mov ax,2000H
	mov ds,ax
	mov bx,0
  s:mov cx,[bx]
  	jcxz ok
  	inc bx
  	inc bx
  	jmp short s
 ok:mov dx,bx
 	mov ax,4c00H
 	int 21H
 codesg ends
 end start

loop指令

指令格式: loop标号

指令操作:

  • (cx) = (cx) - 1;

  • 当(cx) != 0时,转移到标号处执行;

    当(cx) = 0时,程序向下执行;

assume cs:codesg
codesg segment
start:
	mov cx,6h
	mov ax,10h
  s:add ax,ax
  	loop s
  	mov ax,4c00h
  	int 21h
codesg ends
end start

模块化设计

call指令

调用子程序,call指令实现转移的方式与jmp指令的原理相似。

格式:call 标号

CPU执行call指令:

  • 将当前IP或CS和IP压入栈中
  • 转移到标号处执行指令

call 标号:

  • 16位位移="标号"处的地址减去call指令后的第一个字节的地址;
  • 16位位移的范围为-32768~32767,用补码表示;
  • 16位位移由编译程序在编译时算出;
mov ax,0
call s
mov ax,4c00h
int 21h

s: add ax,1
	ret
指令"call for ptr 标号"-不同段间转移
  • (sp) = (sp) - 2

    ((ss) * 16 + (sp)) = (cs)

    (sp) = (sp) - 2

    ((ss) * 16 + (sp)) = (ip)

  • (cs) = 标号所在的段地址

    (IP) = 标号所在的偏移地址

该指令相当于

push cs
push ip
jmp far ptr 标号
mov ax,0
call far ptr s
; ...
mov ax,4c00h
int 21h

s:add ax,1
	ret
转移地址在内存中的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)=OEH

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 word ptr ds:[0]
; 执行结果为:(CS) = 0,(IP) = 0123H,(sp) = 0CH

返回指令ret和retf

ret指令采用栈中的数据,修改IP的内容,从而实现近转移。相当于pop IP。

assume cs:codesg,ss:stack
stack segment
	db 16 dup(0)
stack ends
codesg segment
	mov ax,4c00h
	int 21h
start: mov ax,stack
	   mov ss,ax
	   mov sp,16
	   mov,ax,0
	   push ax		; 返回的ax值(IP)
	   mov bx,0
	   ret
codesg ends
end start

retf采用栈中的数据,修改CS和IP的内容,从而实现远转移。相当于:

  • pop IP
  • pop CS
assume cs:codesg,ss:stack
stack segment
	db 16 dup(0)
stack ends
codesg segment
	mov ax,4c00h
	int 21h
start: mov ax,stack
	   mov ss,ax
	   mov sp,16
	   mov,ax,0
	   push cs		; 返回的cs值(CS)
	   push ax		; 返回的ax值(IP)
	   mov bx,0
	   ret
codesg ends
end start

call 和 ret的配合使用

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

mul乘法指令

  • mul寄存器
  • mul内存单元
8位乘法16位乘法
被乘数ALAX
乘数8位寄存器或内存字节单位16位寄存器或内存字单元
结果AXDX(高位)和AX(低位)
mul bl
mul byte ptr ds:[0]
mul word ptr [bx+si+8]
;ax存放结果的低16位
dx存放结果的高16位

示例

; 100 * 10
mov al,100
mov bl,10
mul bl
; 结果: (ax) = 1000 (03E8H)
; 100 * 10000
mov ax,100
mov bx,10000
mul bx
; 结果(bx) = 000FH (ax) = 4240H
; F4240H = 1000000

模块化程序设计

采用寄存器存储参数和结果
  • 参数放置在bx中,即(bx) = N
  • 子程序中采用多个指令进行运算
  • 将结果放置在dx和ax中
; 计算data段中第一组数据的3次方,结果保存在后一组dword单元中
assume cs:code, ds:data
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
	   	
	   	mov ax,4c00h
	   	int 21h
	    
cube:	mov ax,bx
		mul bx
		mul bx
		ret
code ends
end start
采用内存单元批量传递数据
  • 将批量数据存放在内存中,随后将它们所在内存空间的首地址放置在寄存器中,传递给需要的子程序。

  • 对于具有批量数据的返回结果,可用同样的方式。

示例如下:

; 将data段中的字符串转换为大写
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
采用栈传递参数

将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。

需要特别注意指令"ret n"的作用

其它数据
返回点IP
a参数
b参数
pop ip
add sp,n	; 舍弃n个参数直接偏移到栈首

示例描述: 计算(a-b)^3,a,b为word型数据。

  • 进入子程序前,参数a,b入栈
  • 调用子程序,使栈顶存放IP
  • 结果: (dx:ax) = (a - b) ^ 3
assume cs:code
code segment
start: 	mov ax,1
		push ax
		mov ax,3
		push ax
		call difcube
		mov ax,4c00h
		int 21h
difcube:push bp			; 将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

子程序编写标准框架

  • 子程序开始: 子程序中使用的寄存器入栈

    ​ 子程序内容

    ​ 子程序使用的寄存器出栈

    ​ 返回(ret,retf)

在子模块开始时,将要用到的寄存器内容全都保存,在子程序返回前在恢复。

assume cs:code
data segment
	db 'word',0
	db 'unix',0
	db 'wind',0
	db 'good',0
data ends

code segment
start:  mov ax,data
		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
			ok: pop si
				pop cx
				ret
code ends
end start

标志寄存器

flag寄存器是按位标记,每一个都有不同的含义,记录特定的信息。

  • 存储相关指令的某些执行结果
  • 为CPU执行相关指令提供行为依据
  • 控制CPU的相关工作方式
标志值为1值为0意义
OverflowOFOVNV溢出
DirectionDFDNUP方向
SignSFNGPL符号
ZeroZFZRNZ零值
ParityPFPEPO奇偶
CarryCFCYNC进位
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"
PF-奇偶标志(Parity Flag)

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

  • 1的个数为偶数,PF = 1
  • 1的个数为奇数,PF = 0
SF-符号标志(Sign Flag)

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

  • 结果为负数,SF = 1
  • 结果为非负,SF = 0

示例

指令执行结果
mov al,10000001
add al,1
结果al为10000010
为负数,则SF = 1
sub ax,ax结果ax为0,为非负数,SF = 0

SF标志是CPU对有符号数运算结果的一种记录,将数据当作有符号数运算时,通过SF可知结果的正负,将数据当作无符号数运算时,SF没有意义。

CF-进位标志(Carry Flag)

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

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记录有符号数操作指令执行之后的状态

  • 有溢出,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是对有符号数运算有意义的溢出标志位
指令执行结果
mov al,0F0H
add al,88H
CF = 1, OF = 1,当无符号数运算有进位,当有符号数运算有溢出。

adc带进位加法指令

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

  • 格式: adc 操作对象1,操作对象2
  • 功能: 操作对象1 = 操作对象1 + 操作对象2 + CF
指令mov al,98h
add al,al
adc al,3
mov ax,1
add ax,ax
adc ax,3
结果(ax) = 34H(ax) = 5
adc执行时,相当于计算
(ax)+3+CF=30H+3+1 = 34H
adc执行时,相当于计算
(ax)+3+CF=2+3+0=5

sbb带借位减法指令

  • 格式:sbb 操作对象1, 操作对象2
  • 功能: 操作对象1 = 操作对象1 - 操作对象2 - CF
  • 利用CF位上记录的借位值
  • (ax) = (ax) - (bx) - CF
; 计算003E1000H - 00202000H结果放在ax,bx中
; 对任意大的数据进行减法运算
mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H

cmp指令

cmp是比较指令,功能相当于减法指令,不保存结果,但指令执行后,将对标志寄存器产生影响。

  • 格式: cmp 操作对象1, 操作对象2
  • 功能: 计算操作对象1减去操作对象2
指令cmp ax,axmov ax,8
mov bx,3
com ax,bx
功能做(ax) - (ax)的运算,结果为0,但并不在ax中保存,仅影响flag的标志位(ax) = 8, (bx) = 3
标志寄存器ZF = 1,PF = 1,SF = 0,CF = 0,OF = 0ZF = 0, PF = 1,SF = 0, CF = 0, OF = 0
无符号数比较与标志位取值

通过cmp指令执行后查看相关标志位的值,

比较关系(ax) ? (bx)(ax) - (bx)特点标志寄存器
等于(ax) = (bx)(ax) - (bx) = 0ZF = 1
不等于(ax) != (bx)(ax) - (bx) != 0ZF = 0
小于(ax) < (bx)(ax) - (bx) 将产生借位CF = 1
大于等于(ax) >= (bx)(ax) -(bx) 不必借位CF = 0
大于(ax) > (bx)(ax) - (bx)既不借位,结果也不为0CF = 0 且 ZF = 0
小于等于(ax) <= (bx)(ax) - (bx)或借位,或结果为0CF = 1 或 ZF = 1
有符号数比较与标志位取值

通过cmp进行有符号数比较(cmp ah bh)。

结果需要(SF)配合是否溢出(OF)得出结论。

比较关系(ax) ? (bx)(ax) - (bx)特点标志寄存器
等于(ah) = (bh)(ah) - (bh) = 0ZF = 1
不等于(ah) != (bh)(ah) - (bh) != 0ZF = 0
小于(ax) < (bx)(ax) - (bx)为负,且不溢出SF = 1且OF = 0
大于(ax) > (bx)(ax) - (bx)为负,且溢出SF = 1且OF = 1
大于等于(ax) >= (bx)(ax) - (bx)为非负,且无溢出SF = 0且OF = 0
小于等于(ax) <= (bx)(ax) - (bx)为非负,且有溢出SF = 0或OF = 1
条件转移指令

根据单个标志位转移的指令

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

根据无符号数比较结果进行转移的指令

指令含义测试条件
jb/jnae/jc低于则转移CF = 1
jnb/jae/jnc低于则转移CF = 0
jna/jbe不高于则转移CF = 1或ZF = 1
ja/jnbe高于则转移CF = 0且ZF = 0

根据有符号数比较结果进行转移的指令

指令含义测试条件
jl/jnge小于则转移SF = 1且OF = 0
jnl/jge不小于转移SF = 0且OF = 0
jle/jng小于等于则转移SF = 0或OF = 1
jnle/jg不小于等于则转移SF = 1且OF = 1

j = Jump e = Equal n = Not b = Below a = Above l = Less g = Greater s = Sign c = Carry

p = Parity o = Overflow z = Zero

条件转移指令的使用
  • 条件转移指令和cmp指令配合使用,构造条件转移指令
    • 不必考虑cmp指令对相关标志位的影响和条件转移指令对相关标志位的检测

示例

; 如果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
应用示例

统计数值为8的字节个数。

code segment
start:
		mov ax,data
		mov ds,ax
		mov bx,0
		mov ax,0
		mov cx,8
	  s:cmp byte ptr [bx],8
	    je ok
	    jmp short next
	 ok:inc ax
   next:inc bx
   		loop s
   	
   	mov ax,4c00h
   	int 21h
code ends

统计数值大于8的字节个数

; 找到大于8的数就将ax的值加1
code segment
start:
		mov ax,data
		mov ds,ax
		mov bx,0
		mov ax,0
		mov cx,8
	  s:cmp byte ptr [bx],8
	    jna next
	    inc ax
   next:inc bx
   		loop s
   		
   	mov ax,4c00h
   	int 21h
code ends
end start

统计数值小于8的字节个数

; 找到小于8的数将ax的值加1
code segment
start:
		mov ax,data
		mov ds,ax
		mov bx,0
		mov ax,0
		mov cx,8
	  s:cmp byte ptr [bx],8
	    jnb next
	    inc ax
   next:inc bx
   		loop s
   		
   		mov ax,4c00h
   		int 21h
code ends
end start

DF标志和串传送指令

DF方向标志位

  • 在串处理指令当中,控制每次操作后si,di的增减。
  • DF = 0,每次操作后si,di递增
  • DF = 1,每次操作后si,di递减

对DF位进行设置的指令

  • cld指令: 将标志寄存器的DF位设置为0(clear)
  • std指令: 将标志寄存器的DF位设置为1(setup)

串传送指令movsb

  • ((es) * 16 + (di)) = ((ds) * 16 + (si))

  • 如果DF = 0,则

    • (si) = (si) + 1
    • (di) = (di) + 1

    如果DF = 1,则

    • (si) = (si) - 1
    • (di) = (di) - 1

串传送指令movsw

  • ((es) * 16 + (di)) = ((ds) * 16 + (si))

  • 如果DF = 0,则

    • (si) = (si) + 2
    • (di) = (di) + 2

    如果DF = 1,则

    • (si) = (si) - 2
    • (di) = (di) - 2

示例

assume cs:codesg,ds:data
data segment
	db 'Welcome to masm!'
	db 16 dup(0)
data ends

codesg segment
	start:
		mov ax,data
		mov si,0
		mov es,ax
		mov di,16
		cld
		
		mov cx,16
	  s:movsb
	  	loop s
	  	
	  	mov ax,4c00h
	  	int 21h
codesg ends
end start

rep指令

该指令常常与串传送指令搭配使用。

  • 根据cx的值,重复执行后面的指令

    rep movsb 相当于

    s: movsb

    loop s

  • rep movsw 相当于

    s: movsw

    loop s

示例

assume cs:code,ds,data
data segment
	db 'Welcome to masm!'
	db 16 dup(0)
data ends

code segment
start:
		mov ax,data
		mov ds,ax
		mov si,0
		mov es,ax
		mov di,16
		cld
		mov cx,8
		rep movsw
		
		mov ax,4c00h
		int 21h
code ends
end start

将F000H段中的最后16个字符复制到data段中(F000FFFFH)。

assume cs:code,ds:data
data segment
	db 16 dup(0)
data ends
code segment
start:
		mov ax,F000H
		mov ds,ax
		mov si,FFFFH
		mov ax,data
		mov es,ax
		mov di,15
		mov cx,16
		std
		rep movsb
		
		mov ax,4c00H
		int 21h
code ends
start end

注意事项

  • 在编写调用子程序的程序时,注意看下子程序中有没有用到会产生冲突的寄存器,如果有,则使用其它寄存器。
  • 在编写子程序时,不要使用会产生冲突的寄存器。

示例

将data段中的字符串转换为大写。(通过将数据段尾加上0来做判断语句结束)

assume cs:code ds:data
data segment
	db 'conversation',0
data ends

code segment
start:	mov ax,data
		mov ds,ax
		mov si,0
		call capital
		mov ax,4c00h
		int 21h
capital:mov cl,[si]
		mov ch,0
		jcxz ok
		and byte ptr [si], 11011111b
		inc si
		jmp short capital
	 ok:ret
code ends
end start

; 以上子程序依次读取每个字符进行检测,如果不是0,进行大写的转换,如果是0,结束处理。不再需要字符串的长度作为参数。

在运行中将s处的一条指令复制到s0处。

assume cs:codesg
codesg segment
s:  mov ax,bx
	mov si,offset s
	mov di,offset s0
	mov ax,cs:[si]
	mov cs:[di],ax
s0: nop
	nop
codesg ends
ends
; nop机器码占一个字节,起占位作用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值