文章目录
前言
点赞再看,养成习惯!
回顾
1.转移指令原理
还记得什么叫做转移指令吗?我们之前说过:能够修改IP寄存器或者同时修改CS及IP寄存器的指令叫做转移指令。 而通过定义我们又可以推断转移指令之所以可以跳转,主要是由于它可以修改负责存储指令指针信息的CS:IP 地址寄存器的值。
让我们写个小程序加深一下理解:
assume cs : codea
codea segment
start:
mov ax,0
inc ax
jmp short s
mov ax,0
s: inc ax
codea ends
end start
程序分析:
- 首先将
ax
寄存器的值初始化为0 ax
寄存器自增加一,此时AX寄存器的值为1- 执行
jmp short s
进行短转移 - 若转移成功应该执行标号
s
处指令inc ax
进行自增,此时寄存器值应为2 - 若转移失败则继续按照顺序执行
mov ax,0
,将ax重置为0 - 观察运行结果
运行结果:
2. 已接触过的操作符
jmp
: 无条件转移指令,可以同事修改CS:IP两个寄存器的值来改变指令指针指向。jcxz
: 有条件的转移指令,它的判断条件为此时CX寄存器是否为零,若为零则进行转移,若不为零则什么都不做。loop
: 有条件的短转移指令。判断CX寄存器的值进行循环,每次循环CX寄存器的值减一,直到为零。
3. 寄存器回顾
最后让我们回顾一下我们接触过的寄存器,首先让我们看下寄存器列表:
通用数据处理寄存器
- AX寄存器(Accumulator): 累加地址寄存器,简称累加器。AX寄存器可以拆分为AH,AL两个八位的寄存器进行分别使用。(外设输入的值只能存放在AL寄存器或者AX寄存器中,后续使用到会讲。)
- BX寄存器(Base) : 基地址寄存器,常被用作数据寄存器,访问存储器时可以存放被读写单元的地址,也可被拆分为BL和BH两个8位寄存器分别使用。
- CX寄存器(Count): 操作数寄存器,常用存放条件指令,如
loop
,jcxz
等指令的判断条件。 - DX(DATA): 数据寄存器,在乘除法中作为数据累加器,再输入输出操作中存放端口的地址。
指针寄存器
- SP寄存器(Stack Point): 堆栈指针寄存器,用来存放栈顶的偏移地址,供栈操作使用。
- BP寄存器(Base Point): 基指针寄存器,存放堆栈中数据的基地址。
变址寄存器
- SI(Source Index): 源变址寄存器,通常用来搭配DS段地址寄存器存放偏移地址。基本用法与BX寄存器 相似,只不过不能拆分位高低八位寄存器。
- DI(Destination Index): 目的变址寄存器,与SI寄存器类似,搭配ES段地址寄存器使用。
段地址寄存器
- CS(Code Segment): 代码段地址寄存器,通常搭配IP寄存器一起使用,CS:IP指向CPU当前需要执行的指令地址。
- DS(Data Segment): 数据段地址寄存器,常用来指向我们自己定义的数据段地址。
- SS(Stack Segment): 栈段地址寄存器,用来存放栈顶段地址。
- ES(Extra Segment): 附加段地址寄存器,常用于存放当前执行程序的辅助数据段地址,常搭配DI目的地址寄存器一起使用。
其他寄存器
- IP(Instructoin Pointer): 指令指针寄存器,常搭配CS代码段地址寄存器一起使用,标识指针指针地址。
- FLAG: 标志寄存器,也称为程序状态字PSW。 FLAG寄存器的用法与其他寄存器都不相同,其16位存储空间每一位都可以存放信息(程序状态字,PSW,暂不涉及)
一、ret及retf
1.1 ret指令
ret
指令常用于实现近转移,它通过使用栈中的数据修改IP寄存器的内容来达到转移目的。
CPU在执行ret
指令时会进行以下两步操作:
- IP = (SS × 16) + SP
- SP = SP + 2
首先解释下:IP = (SS × 16) + SP ,是不是看起来很眼熟,没错这就是我们的寻址公式,对应的含义其实就是讲此时SS:SP表达的栈空间的栈顶数据,而第二步的SP = SP + 2则是取出栈顶数据,这两步加起来其实等同于:
pop ip
我们写个小程序来试验下:
assume cs:codeA
stack segment
db 16 dup(0)
stack ends
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,8
push ax
ret
codeA ends
end start
流程解析:
mov ax,stack
将stack数据段标号指向ax寄存器mov ss,ax
将栈段地址更新到ss段地址寄存器mov sp,16
修改sp寄存器的值,更改栈顶空间mov ax,8
修改ax寄存器的值push ax
将ax寄存器中的值压入栈空间ret
弹栈,将栈顶值赋予ip寄存器
验证:
1.2 retf指令
retf
与ret
用法相似,细微的不同点是retf
指令是远转移的一种,其行为是通过栈中数据同时修改CS
和IP
的内容来达到转移的目的。当CPU调用retf
指令时会进行以下的操作:
- IP = SS × 16 + SP
- SP = SP + 2
- CS = SS×16 + SP
- SP = SP + 2
用通俗的话就是当我们调用retf指令时栈空间会进行两次弹栈,第一次弹栈的值会赋予IP寄存器,第二次弹栈的值会赋予CS寄存器。当CS:IP同时修改时可达到实现远转移的目的。
代码验证:
assume cs:codea
stack segment
db 16 dup(0)
stack ends
code segment
mov ax,4c00H
int 21h
start : mov ax,stack
mov ss ,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
code ends
end start
流程解析:
mov ax,stack
mov ss ,ax
将栈段初始值赋予SS寄存器mov sp ,16
mov ax,0
初始化栈空间大小push cs
push ax
记录指令指针位置mov bx,0
重置BX寄存器retf
远转移
结果验证:
1.3 小练习
题目: 写一个程序,实现从内存1000:000处开始执行指令。
代码实现:
assume cs:codea
stack segment
db 16 dup(0)
stack ends
codea segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,1000
push ax
mov ax,0000
push ax
retf
codea ends
end start
二、Call指令
call
指令通常都是搭配ret
指令一起使用的,当我们调用call
指令时,CPU会进行以下操作:
- 将当前的IP或CS和IP压入栈中
- 转移
call
指令不能够实现远转移,除此之外,call
指令实现转移的方式和jmp相同。接下来我们通过call
指令搭配不同方式进行转移来详细讲一下call
指令的使用。
2.1 依据位移进行转移
格式: call + 标号
CPU遇到这种格式的指令时会进行:
- SP = SP -2
- SS × 16 + SP = IP
- IP = IP + 16位位移
其中16位位移 = 标号处地址 - call指令后的第一个字节的地址
以此推断,当我们执行call + 标号
时就等于执行:
push IP
jmp near ptr 标号
2.2 包含目的地址进行转移
格式: call far ptr 标号
实现段间转移
当CPU遇到call far ptr 标号
格式的指令时会进行以下操作:
- SP = SP - 2
- SS*16 + SP = SP
- SP = SP-1
- SS*16 + SP = IP
- CS = 标号所在段的段地址
- IP = 标号所在段中的偏移地址
以此推断,当我们执行call far ptr 标号
时就等于执行:
push cs
push ip
jmp far ptr 标号
2.3 搭配寄存器进行转移
格式: call 16位reg
当CPU遇到call 16位reg
格式的指令时会进行以下操作:
- sp = sp -2
- ss × 16 + sp = ip
- ip = (16位REG)
以此推断,当我们执行call 16位reg
时就等于执行:
push ip
jmp 16位reg
2.4 转移地址在内存单元
call word ptr 内存单元地址
相当于:
push ip
jmp word ptr 内存单元地址
或者
call dword ptr 内存单元地址
相当于:
push cs
push ip
jmp dword ptr 内存单元地址
三、 ret搭配call使用
3.1 分析两段程序
问题: bx寄存器的值在执行后是多少
assume cs:codea
codea segment
start : mov ax,1
mov cx,3
call s
mov bx,ax
mov ax,4c00h
int 21h
s: add ax,ax
loop s
ret
codea ends
end start
思路图解:
3.2 反思
通过上面这段程序 兄弟们有没有发现call
和ret
搭配使用可以组成一个简单的框架:
assume cs:code
code segment
main :
call sub1 ; 调用子程序sub1 ,也可以说是方法1
...
mov ax,4c00h
int 21h
sub1:
...
ret ;返回主程序
code ends
end main
看到这里是不是很熟悉,是不是很像其它语言的method调用?恭喜坚持学习到这里的小伙伴,我们已经离学习写一个完整的汇编程序不远了。
四、 mul指令(乘法指令)
4.1 回顾
简单讲一下四则运算,我们下章会用到
我们首先要回顾下已经学习过的四则运算:
4.1.1 add
加法指令,格式如下:
add reg,reg
使用时需要注意两点:
- 两个相加的数可以是八位或者十六位,但是类型需要匹配,不能一个是八位一个是十六位
- 至少需要一个寄存器参与运算并放到最前面
4.1.2 sub
减法指令,语法及注意点与add
相同
4.2 mul指令
语法: mul reg
注意点:
- 乘法的第一个乘数默认存放在AX寄存器中
- 结果是8位乘8位,则存放在AX寄存器中
- 若是16位乘16位,则高位默认存放在DX寄存器中,低位在AX寄存器中
比如,我们计算100×10
:
assume cs:code
code segment
main :
mov al,100
mov bl,10
mul bl
code ends
end main
是不是很简单
结语
今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。
Spring通用架构及工具已上传到gitee仓库,需要的小伙伴们可以自取:
https://gitee.com/xiaolong-oba/common-base
码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。