美文网首页
流程转移与子程序

流程转移与子程序

作者: freemanIT | 来源:发表于2024-03-03 15:48 被阅读0次

综述

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

这就用到转移指令

  • 可以控制cpu 执行内存中某处代码的指令
  • 可以修改ip, 或同时修改cs 和ip 的指令

分类

按行为分类

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

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

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

按转移指令分类

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

操作符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 2000:1000
    • 段内短转移: jmp short 标号, ip 修改范围-128~127, 8 位的位移
    • 段内近转移: jmp near ptr, ip 修改范围-32768~32767, 16 位的位移

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

  • 8 位位移="标号" 处的地址 - loop 指令后的第一个字节地址
  • 8 位范围为-128~127, 补码表示
  • 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 标号

  • 16 位位移="标号" 处地址 - call 指令后的第一个字节地址
  • 16 位范围-32768~32767, 补码表示
  • 位移由编译程序编译时计算

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位寄存器

功能:

  • (sp)=(sp)-2
  • ((ss)*16+(sp))=(ip)
  • (ip)=(16位寄存器)

相当于

  • push ip
  • jmp 16位寄存器

转移地址在内存中的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)=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

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位

模块化程序设计

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

参数和结果传递的问题

例如, 计算N 的3 次方

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

方案

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

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

  • 参数存放到bx 中, (bx)=N
  • 子程序中用多个mul 指令计算N 的3 次方
  • 将结果放到dx 和ax 中, (dx:ax)={N^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 入栈
  • 调用子程序, 将使栈顶存放ip
  • 结果(dx:ax)=(a-b)^3
用栈传递参数

计算(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 别称, 程序状态字

结构:

  • flag 寄存器是按位起作用的, 就是说, 它的每一位都有专门的含义, 记录特定信息
  • 8086 中没有使用flag 的1, 3, 5, 12~15 位, 这些位不具有任何含义

作用:

  • 用来存储相关指令的某些执行结果
  • 为cpu 执行相关指令提供行为依据
  • 控制cpu 相关工作方式

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

  • pushf: 将标志寄存器的值压栈
  • popf: 栈中弹出数据, 送入到标志寄存器
标志寄存器说明

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
  • 在8086 指令集中, 有的指令执行是影响标志寄存器的, 比如: and, sub, mul, div, inc, or, add, 大都是运算指令, 进行逻辑或算术运算
  • 有的指令执行对标志寄存器没有影响, 比如, mov, push, pop, 大都是传送指令
  • 使用一条指令的时候, 要注意指令的全部功能, 其中敖阔执行结果对标志寄存器的哪些标志位造成影响

PF 奇偶标志(parity flag)

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

  • 1 的个数为偶数, pf=1
  • 1 的个数为奇数, pf=0
指令 结果
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)

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

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

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

CF 进位标志(carry flag)

进行无符号数运算的时候, 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=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 是对有符号数运算有意义的溢出标志位

带进(借) 位加减法

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

  • 不必再考虑cmp 指令对相关标志位的影响和jxxx 指令对相关标志位的检测
  • 可以直接考虑cmp 和jxxx 配合使用表现出来的逻辑含义

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

如何检测

  • 通过检测标志位, 由标志位体现条件
  • 条件转移指令通常都和cmp 相配合使用, cmp 指令改变标志位

示例

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)

功能

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

对df 位进行设置的指令

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

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

转送指令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段

相关文章

  • 2020-08-29(Call和Ret)

    Call(操作数) 指令是将转移到指定的子程序处,它的操作数就是给定的地址在OD中可以按F7跟进子程序内部,如果只...

  • hxb2017 pwns canary bruteforce

    利用fork的子程序保持canary不变的特性来逐字节爆破canary 流程概述 本题主要流程是: 询问是否输入数...

  • Learning Perl 学习笔记 子程序

    定义子程序使用关键字sub和花括号{},调用(也叫calling)子程序使用“与&”号:定义子程序:sub mar...

  • java 读写txt文件

    (A) java写txt文件例子程序如图 大致流程可以简化为: 1.先定义路径“logpath”(String类型...

  • 汇编语言(第三版)-- 实验10

    子程序代码: 子程序代码: 子程序代码:

  • 2017年社保跨省转移新规与转移流程

    2017年社保跨省转移新规_2017年社保跨省转移流程在日常工作和生活中,难免发生因工作、生活等原因需要转移居住地...

  • ARM System Developer's Guide 学习笔

    分支指令(Branch Instructions) 分支指令改变程序执行的流程或用来调用子程序,这种类型的指令允许...

  • Perl 定义和调用子程序

    1. 定义和调用子程序 定义子程序格式:sub 程序名 {}。 调用子程序:& 程序名。 子程序返回值:子程序都有...

  • C++学习大纲

    交流728483370,一起学习加油! C++ 基本数据类型和表达 C++ 无条件转移控制 C++ 子程序间的数据...

  • Halcon技术数据

    一、用haclon来开发程序的流程: 1、分析,建立 First 程序可分为不同的子程序,每个procedu...

网友评论

      本文标题:流程转移与子程序

      本文链接:https://www.haomeiwen.com/subject/cjnkzdtx.html