流程转移与子程序

2024-03-03  本文已影响0人  freemanIT

综述

一般情况下, 指令顺序执行, 实际中, 常需要改变程序的执行流程

这就用到转移指令

分类

按行为分类

根据指令对ip 修改范围不同分类

按转移指令分类

操作符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 指令是依据位移进行转移的

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根据位移转移

执行顺序

  1. (ip)=0003, cs:ip 指向EB 05, jmp 的机器码
  2. 读取指令码EB 05 进入指令缓冲器
  3. (ip)=(ip)+多读取指令长度=(ip)+2=0005, cs:ip 指向add ax,0001
  4. cpu 指向指令缓冲器里的指令EB 05
  5. 指令EB 05 执行后, (ip)=(ip)+05=000AH, cs:ip 指向inc ax

两种段内转移

短转移

jmp short 标号

功能: (ip)=(ip)+8 位位移

原理:

  1. 8 位位移="标号" 处的地址-jmp指令后的第一个字节的地址
  2. short 指明此处的位移为8 位位移
  3. 8 位位移的范围-128~127, 用补码表示
  4. 8 位位移由编译程序在编译时算出

近转移

jmp near ptr 标号

功能: (ip)=(ip)+16位位移

原理:

  1. 16 位位移="标号" 处的地址-jmp指令后的第一个字节的地址
  2. near ptr 指明此处的位移为16 位位移, 进行的是段内近转移
  3. 16 位位移的范围-32768~32767, 用补码表示
  4. 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 标号

操作:

  1. (cx)=(cx)-1

  2. 当(cx)≠0, 转移到标号处执行

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

如果(cx)≠0, (ip)=(ip)+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 指令的原理类似

过程:

  1. 将当前的ip 或cs 和ip 压入栈中
  2. 转移到标号处执行指令

call 标号

call far ptr 标号, 实现段间转移

  1. (sp)=(sp)-2

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

    (sp)=(sp)-2

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

  2. (cs)=标号所在段地址

    (ip)=标号所在偏移地址

相当于:

push cs
push ip
jmp far ptr 标号

"call 标号" 类似于"jmp near ptr 标号", 对应的机器指令中为相对于当前ip 的转移位移, 而不是转移的目的地址, 实现段内转移. "call far ptr 标号" 实现的是段间转移

转移地址在寄存器中的call 指令

格式: call 16位寄存器

功能:

相当于

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

call word ptr 内存单元地址

相当于:

mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
执行后, (ip)=0123h, (sp)=0Eh

call 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位

模块化程序设计

参数和结果传递的问题

例如, 计算N 的3 次方

参数N 存放位置, 计算结果放在哪里?

方案

  1. 寄存器传递参数
  2. 内存单元传递参数
  3. 用栈传递参数

寄存器来存储参数和结果是最常用的方法

遇到的问题, 如果传递更多参数, 寄存器不够了怎么办?

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)^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 别称, 程序状态字

结构:

作用:

直接访问标志寄存器的方法

标志寄存器说明

ZF 零标志(zero flag)

标记相关指令计算结果是否为0

指令 结果
mov ax,1
and ax,0
zf=1, 表示结果是0
mov ax,1
or ax,0
zf=0, 结果非0

PF 奇偶标志(parity flag)

指令执行后, 结果的所有二进制中1 的个数

指令 结果
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)

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

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

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

CF 进位标志(carry flag)

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

执行指令后

指令 结果
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)

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

指令执行后

指令 结果
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 区别

带进(借) 位加减法

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 指令配合, 构造条件转移指令

配合使用实现高级语言中的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

如何检测

示例

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)

功能

对df 位进行设置的指令

转送指令1: movsb

以字节为单位传送

  1. ((es)×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. ((es)×16 + (di)) = ((ds) ×16 + (si))

  2. 如果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段
上一篇下一篇

猜你喜欢

热点阅读