【汇编语言】流程转移和子程序
`
文章目录
- 【汇编语言】流程转移和子程序
- 前言
- 一、“转移”综述
- 二、操作符offset
- 三、jmp指令
- jmp指令——无条件转移
- jmp指令:依据位移进行转移
- 两种段内转移
- 远转移:jmp far ptr 标号
- 转移地址在寄存器中的jmp指令
- 转移地址在内存中的jmp指令
- jmp 指令小结
- 四、其他转移指令
- jcxz指令
- loop指令
- 根据位移进行“相对”转移的意义
- 五、call 指令和 ret 指令
- 模块化程序设计
- call 指令
- 指令“call far ptr 标号”实现的是段间转移
- 转移地址在寄存器中的call指令
- 转移地址在内存中的call指令
- 返回指令:ret 和 retf
- 执行过程
- 六、call 和 ret 配合使用
- 具有子程序的源程序的框架
- call 和 ret 配合使用
- 为call和ret指令设置栈
- 七、乘法:mul 指令
- 回顾:除法div指令
- 用mul指令做乘法
- 应用案例
- 八、汇编语言模块化程序设计
- 模块化程序设计
- 参数和结果传递的问题
- 用寄存器来储存参数和结果是最常使用的方法
- 用内存单元批量传递数据
- 用栈传递参数
- 程序的执行过程中栈的变化
- 小结:参数和结果传递的问题
- 九、寄存器冲突问题
- 引子
- 编程将data段中的字符串转化为大写
- 寄存器冲突问题的解决
- 寄存器冲突问题的解决示例
- 十、标志寄存器
- 认识标志寄存器的特殊之处
- ZF-零标志(Zero Flag)
- PF-奇偶标志(Parity Flag)
- SF-符号标志(Sign Flag)
- CF-进位标志(Carry Flag)
- OF-溢出标志(Overflow Flag)
- 综合:一条指令会带来多个标志寄存器的变化
- 十一、带进(借)位的加减法
- adc带进位加法指令
- adc指令应用:大数相加
- 128 位数据相加
- sbb 指令
- 十二、cmp与条件转移指令
- cmp指令
- 无符号数比较与标志位取值
- 有符号数比较与标志位取值
- 条件转移指令
- 条件转移指令的使用
- 十三、条件转移指令的应用
- 应用案例
- 十四、DF标志和串传送指令
- 问题分析
- DF标志和串传送指令
- rep指令
- 应用实例
- 总结
前言
在本篇文章当中我们将详细讲到,转移的综述,操作符offset,jmp指令,其他转移指令,call指令和ret指令,call指令和ret指令的配合使用,mul指令,汇编语言的模块化程序设计,寄存器冲突的问题和解决方法,标志寄存器,带进(借)位的加减法,cmp和条件转移指令,条件转移指令的应用,DF标志和串传送指令。
一、“转移”综述
背景:一般情况下指令是顺序地逐条执行的,而在实际中,常需要改变程序的执行流程。
转移指令:
可以控制CPU执行内存中某处代码的指令
可以修改IP,或同时修改CS和IP的指令
例:
转移指令分类:
二、操作符offset
用操作符offset取得标号的偏移地址****
格式:
offset 标号
例:
练习:
问题:有如下程序段,添写2条指令,使该程序在运行中将s处的一条指令复制到s0处。
这边我已经给出了。
nop的机器码占一个字节,起“占位”作用
我们来分析一下:
1)s和s0处的指令所在的内存单元的地址是多少?
cs:offset s 和cs:offset s0。
2)将s处的指令复制到s0处,就是________
就是将cs:offset s 处的数据复制到cs:offset s0处。
3)地址如何表示?
段地址已知在cs中,偏移地址已经送入si和di中。
4)要复制的数据有多长?
mov ax,bx指令的长度为两字节,即一个字
三、jmp指令
jmp指令——无条件转移
无条件转移,可以只修改IP,也可以同时修改CS和IP
jmp指令要给出两种信息:
转移的目的地址
转移的距离
段间转移(远转移): jmp 2000:1000
段内短转移: jmp short 标号 ; IP的修改范围为 -128~127,8位的位移
段内近转移: jmp near ptr 标号 ; IP的修改范围为 -32768~32767,16位的位移
jmp指令:依据位移进行转移
引子:常见指令中的立即数均在机器指令中有体现
可以看出立即数0123在机器码中有所体现。
问题:jmp short 指令中,转移到了哪里?
—>jmp short 的机器指令中,包含的是跳转到指令的相对位置,而不是转移的目标地址。
上边程序jmp short s指令的读取和执行:
1)(IP)=0003,CS:IP指向EB 05(jmp的机器码)
2) 读取指令码EB 05进入指令缓冲器;
3)(IP)=(IP)+所读取指令的长度=(IP)+2=0005,CS:IP指向add ax,0001;
4)CPU执行指令缓冲器中的指令EB05;
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位位移的范围为 -32769~32767,用补码表示;
4)16位位移由编译程序在编译时算出。
远转移:jmp far ptr 标号
转移地址在寄存器中的jmp指令
指令格式:jmp 16位寄存器
功能:IP =(16位寄存器)
举例:
jmp ax
jmp bx
跳到哪儿由变量定
转移地址在内存中的jmp指令
jmp 指令小结
注意:
四、其他转移指令
jcxz指令
指令格式:jcxz 标号
功能:如果(cx)=0,则转移到标号处执行 ; 当(cx)≠0时,什么也不做(程序向下执行)
当(cx)=0时,(IP)=(IP)+8位位移)
8位位移=“标号”处的地址-jcxz指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出;
jcxz是有条件转移指令
所有的有条件转移指令都是短转移
对IP的修改范围都为-128~127
在对应的机器码中包含转移的位移,而不是目的地址
例:
loop指令
指令格式:loop 标号
指令操作:
1)(cx)=(cx)-1;
2)当(cx)≠0时,则转移到标号处执行 ; 当(cx)=0时,程序向下执行
8位位移=“标号”处的地址-loop指令后的第一个字节的地址
8位位移的范围为-128~127,用补码表示
8位位移由编译程序在编译时算出
根据位移进行“相对”转移的意义
对 IP的修改是根据转移目的地址和转移起始地址之间的位移来进行
jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号
五、call 指令和 ret 指令
模块化程序设计
C语言中我们可以实现模块化设计
汇编中也可以实现模块化设计
call 指令
字面意思:调用子程序
实质:流程转移
call指令实现转移的方法和 jmp 指令的原理相似
格式:call 标号
CPU执行call指令,进行两步操作:
1)将当前的 IP 或 CS和IP 压入栈中;
2) 转移到标号处执行指令。
call 标号
16位位移=“标号”处的地址-call指令后的第一个字节的地址;
16位位移的范围为 -32768~32767,用补码表示;
16位位移由编译程序在编译时算出。
指令“call far ptr 标号”实现的是段间转移
CPU执行“call far ptr 标号”时的操作
1)(sp) = (sp) – 2
((ss) ×16+(sp)) = (CS)
(sp) = (sp) – 2
2) (CS) = 标号所在的段地址
(IP) = 标号所在的偏移地址
“call far ptr 标号” 相当于
push CS
push IP
jmp 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 内存单元地址
call dword ptr 内存单元地址
相当于:
push CS
push IP
jmp dword ptr 内存单元地址
返回指令:ret 和 retf
执行过程
六、call 和 ret 配合使用
具有子程序的源程序的框架
call 和 ret 配合使用
例:
计算2的N次方,计算前,N的值由CX提供。
我们在这里没有设置栈段,这是非常危险的!
为call和ret指令设置栈
七、乘法:mul 指令
回顾:除法div指令
div是除法指令,格式为:
div 寄存器
div 内存单元
使用div作除法的时候
被除数:(默认)放在AX 或 DX和AX中
除数:8位或16位,在寄存器或内存单元中
结果:…
用mul指令做乘法
格式:
mul 寄存器
mul 内存单元
应用案例
八、汇编语言模块化程序设计
模块化程序设计
调用子程序:call指令
返回:ret指令
子程序:根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。
参数和结果传递的问题
问题:根据提供的N,计算N的三次方。
考虑:
1)我们将参数N存储在什么地方?
2)计算得到的数值,存储在什么地方?
方案:
用寄存器传递参数
用内存单元进行参数传递
用栈传递参数
用寄存器来储存参数和结果是最常使用的方法
问题:根据提供的N,计算N的三次方。
考虑:
1)我们将参数N存储在什么地方?
2)计算得到的数值,存储在什么地方?
用寄存器传递参数
参数放到 bx 中,即(bx)=N
子程序中用多个 mul 指令计算 N^3
将结果放到 dx 和 ax中:(dx:ax)=N^3
编程任务:计算data段中第一组数据的 3 次方,结果保存在后面一组dword单元中。
问题:
如果需要传递的数据有3个、4个或更多,寄存器不够了,怎么办?
用内存单元批量传递数据
方案:
将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。
对于具有批量数据的返回结果,也可用同样的方法。
编程任务:
将data段中的字符串转化为大写。
用栈传递参数
原理:由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数
任务:计算( a – b ) ^ 3 ,a、b 为 word 型数据。
进入子程序前,参数a、b入栈
调用子程序,将使栈顶存放IP
结果:( dx : ax ) = ( a – b ) ^ 3
例:设 a = 3 、b = 1 ,计算:( a – b ) ^ 3
将bp入栈出栈后不改变先前bp的值。
将sp回复。
程序的执行过程中栈的变化
小结:参数和结果传递的问题
问题:根据提供的N,计算N的3次方。
考虑:
1)我们将参数N存储在什么地方?
2)计算得到的数值,存储在什么地方?
方案:
用寄存器传递参数
用内存单元进行参数传递
用栈传递参数
九、寄存器冲突问题
引子
这个12哪来的?
这样写是数出来的,有没有别的办法呢?
C语言中用\0
汇编也可以。在后边加个0,db ‘conversation’,0
编程将data段中的字符串转化为大写
在例:将以下字符串转为大写
写出:
但是这个代码有一个大问题:
cx既用于循环又用于读取数据-------冲突!!!
寄存器冲突问题的解决
两个可能方案
1)在编写调用子程序的程序时 ,注意看看子程序中有没有用到会产生冲突的寄存器。
如果有,调用者使用别的寄存器;
2)在编写子程序的时候,不要使用会产生冲突的寄存器。
我们希望
1)编写调用了程序的程序的时候不必关心子程序到底使用了哪些寄存器;
2)编写子程序的时候不必关心调用者使用了哪些寄存器;
3)不会发生寄存器冲突。
寄存器冲突问题的解决示例
十、标志寄存器
8086CPU有14个寄存器:
通用寄存器:AX、BX、CX、DX
变址寄存器:SI、DI
指针寄存器:SP、BP
指令指针寄存器: IP
段寄存器:CS、SS、DS、ES
标志(flag)寄存器:PSW/FLAGS别称:程序状态字
认识标志寄存器的特殊之处
标志寄存器的结构:
flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
8086CPU中没有使用flag的1、3、5、12、13、14、15位,这些位不具有任何含义。
标志寄存器的作用:
用来存储相关指令的某些执行结果
用来为CPU执行相关指令提供行为依据
用来控制CPU的相关工作方式
观察寄存器的值:
直接访问标志寄存器的方法
pushf:将标志寄存器的值压栈;
popf:从栈中弹出数据,送入标志寄存器中。
ZF-零标志(Zero Flag)
ZF标记相关指令的计算结果是否为0;
ZF=1,表示“结果是0 ”,1表示“逻辑真”
ZF=0,表示“结果不是0”,0表示“逻辑假”
例:
PF-奇偶标志(Parity Flag)
PF记录指令执行后,结果的所有二进制位中1 的个数:
1的个数为偶数,PF=1;
1的个数为奇数,PF=0;
例:
SF-符号标志(Sign Flag)
SF记录指令执行后,将结果视为有符号数
结果为负,SF=1;
结果为非负,SF=0;
例:
CF-进位标志(Carry Flag)
在进行无符号数运算的时候,CF记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
CF记录指令执行后:
有进位或借位,CF = 1
无进位或借位,CF = 0
例:
OF-溢出标志(Overflow Flag)
在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。
OF记录有符号数操作指令执行后,
有溢出,OF = 1
无溢出,OF = 0
例:
综合:一条指令会带来多个标志寄存器的变化
可通过Debug检验结果:
十一、带进(借)位的加减法
adc带进位加法指令
adc是带进位加法指令 ,它利用了CF位上记录的进位值。
adc是带进位加法指令 ,它利用了CF位上记录的进位值。
功能:操作对象1=操作对象1+操作对象2+CF
例:adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF
实例:
adc指令应用:大数相加
问题:8086指令提供add指令,完成8位或16位加法,有更大的数相加时,如何做?
32位、64位、24位?
128 位数据相加
问题:编写一个子程序,对两个128位数据进行相加。
名称:add128
功能:两个逆序存放的128位数据进行相加
数据为128位,需要8个字单元,由低地址到高地址单元,依次存放由低到高的各个字。
分析:
ds:si指向存储第一个数的内存空间
ds:di指向存储第二个数的内存空间
运算结果存储在第一个数的存储空间中。
答案是不行的:
sub ax,ax将CF清零
inc di 不会影响进位,add di,2 会影响进位。
sbb 指令
sbb:带借位减法指令
格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1–操作对象2–CF
与sub区别:利用CF位上记录的借位值
比如:sbb ax,bx
实现功能: (ax) = (ax) – (bx) – CF
十二、cmp与条件转移指令
cmp指令
cmp指令
格式:cmp 操作对象1,操作对象2
功能:计算操作对象1–操作对象2
应用: 其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
例如:
无符号数比较与标志位取值
思路:通过cmp 指令执行后相关标志位的值,可以看出比较的结果
指令:cmp ax,bx
有符号数比较与标志位取值
问题:用cmp来进行有符号数比较时,CPU用哪些标志位对比较结果进行记录?
示例指令:cmp ah,bh
条件转移指令
套路:
cmp oper1,oper2 ;或者其他影响标志寄存器的指令
jxxx 标号
条件转移指令的使用
jxxx系列指令和cmp指令配合,构造条件转移指令
不必再考虑cmp指令对相关标志位的影响和jxxx指令对相关标志位的检测
可以直接考虑cmp和jxxx指令配合使用时表现出来的逻辑含义
jxxx系列指令和cmp指令配合实现高级语言中if语句的功能
十三、条件转移指令的应用
条件转移指令:jxxx——je/jna/jae…
可以根据某种“条件”,决定是否“转移”程序执行流程
“转移”= 修改IP
如何检测条件?
通过检测标志位,由标志位体现条件
条件转移指令通常都和cmp相配合使用,cmp指令改变标志位
例:双分支结构的实现
应用案例
- 给出下面一组数据:
请编程实现如下统计,用ax保存统计结果
1)统计数值为8的字节的个数
2)统计数值大于8的字节的个数
3)统计数值小于8的字节的个数
1)编程思路:初始设置(ax)=0,然后用循环依次比较每个字节的值,找到一个和8相等的数就将ax的值加1。
2)初始设置(ax)=0,然后用循环依次比较每个字节的值,找到一个大于8的数就将ax的值加1。
3)初始设置(ax)=0,然后用循环依次比较每个字节的值,找到一个小于8的数就将ax的值加1。
十四、DF标志和串传送指令
问题分析
编程:将data段中的第一个字符串复制到它后面的空间中。
这是我们之前写过的代码,在这情况下能在简洁吗?
DF标志和串传送指令
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
rep指令
rep指令常和串传送指令搭配使用
功能:根据cx的值,重复执行后面的指令
用法:
应用实例
任务:用串传送指令,将F000H段中的最后16个字符复制到data段中。(F000H段的最后一个字符的位置:F000:FFFF)
data segment
db 16 dup (0)
data ends
总结
到这里这篇文章的内容就结束了,谢谢大家的观看,如果有好的建议可以留言喔,谢谢大家啦!