文章目录
前言
点赞再看,养成习惯!
关注晓龙oba公众号,获取更多电子书及学习资源。
该系列博文基于王爽老师 <汇编语言 第四版> 一书,需要的同学链接自取:
链接:https://pan.baidu.com/s/1NAgD1Z15LtK1BuH92xmICA
提取码:xlzb
另外书中提到的DosBox软件不想去官网下载的小伙伴也可以自取:
链接:https://pan.baidu.com/s/1O6PnLb_hN-WUS2avicNpcw
提取码:xlzb
最后如果还没有计算机基础的同学,建议先补充下计算机相关的基础知识:
一、转移指令原理
1.1 计算机如何识别数据和指令
让我们先回顾下之前讲过的知识。计算机在组成上分为硬件和软件两部分,这里我们先抛开硬件不谈,计算机的软件本质上就是由指令和数据组成。然而无论是指令还是数据在存储到内存器的时候都是二进制串,此时并没有明显的标识告诉计算机这是数据还是指令。比如:
这里我为073F:0100区域插入了三条数据,分别是B8,01,00,而此时CS:IP寄存器的指向刚好是073F:0100,这个时候虽然我们是作为数据录入到的内存单元中,但是计算机却可以将其翻译为mov ax,0001 这个指令。这就涉及到计算机是如何区分数据和指令的了。其实这个问题在之前的博客里我已经提到过很多次,先贴一个之前的文章,想要了解的小伙伴可以看下:
这里简单的概括就是只要是CS:IP寄存器指向的内存单元就会被计算机当作是指令进行翻译并处理。
1.2 转移指令原理
当我们了解计算机如何区分指令的时候,我们就可以进一步的推断:
如果一个指令可以修改IP或者同事修改CS和IP地址,那么这个指令就可以使程序转移接下来要执行的指令,因此我们将可以修改CS或IP寄存器值的指令称为转移指令。
1.3 转移分类
1.3.1 转移行为分类
我们可以简单的概括下86CPU中存在的转移行为
1. 只修改IP的时候我们通常将其称为段内转移,比如:jmp ax
2. 同时修改CS和IP的指令被称为段间转移,比如:jmp 1000:0
这里需要说下,由于修改ip寄存器的范围不同,我们又可以将其细分为:短转移和近转移
短转移IP的修改范围为: -128 ~ 127
近转移ip的修改范围为: -32768 ~ 32767
1.3.2 转移指令分类
- 无条件转移指令(如:jmp)
- 条件转移指令
- 循环指令(如:loop)
- 过程
- 中断
二、操作符详解
2.1 offset
操作符offset在汇编语言中是由编译器处理的操作操作符,它的功能是取得标号的偏移地址。
#按照惯例让我们写个程序演示下
assume cs:codesg
codesg segment
start:mov ax, offset start ; #相当于mov ax,0
s:mov ax, offset s ; #相当于mov ax,3
codesg ends
end start
运行结果
在这段程序中我们可以看到offset操作符取得了我们设置的标号start和s的偏移地址0和3,因此指令mov ax,offset start 就相当于mov ax,0 。 offset start 就相当于获取start的偏移地址。
接下来让我们做个小题目练习一下:
# 现在有一个程序段,需要我们添加两行指令,将s处的一条指令复制到s0处
assume cs:codesg
codesg segment
s: mov ax,bx
mov si, offset s
mov di, offset s0
?
?
s0: nop ;这里声明一下nop是一个占位符,占用一个字节
nop
codesg ends
end s
让我们先来列出一些小问题
- offset s 及 offset s0 分别指向了什么地址?
- 我们的需求是将s处的指令赋值给s0处,也就是将cs:offset s 的数据赋值给 cs:offset s0
- 我们需要复制的地址有多长?也就是mov ax,bx的长度是多少?
让我们按照顺序进行分析。
- 当前的offset s 其实就是等价于 cs:offset s,同理 offset s0 就等价于 cs: offset s0
- 那也就是我们将 offset s上面的命令赋值给 offset s0即可
- 我们可以借助si,di寄存器实现值的交换
- mov ax,bx指令长度为两个字节,即一个字,刚好是ax寄存器的存储上限
#让我们看一下按照思路实现后的代码
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
end s
运行结果:
2.2 jmp指令
jmp指令属于无条件转移指令,他可以修改IP寄存器的值或者同事修改CS:IP寄存器的值来实现寄存器指向指令的变更。那么相对的jmp指令需要给出两种明确的信息。
- 转移的目的地址
- 转移的距离(段间转移,段内短转移,段内近转移)
不同的给出目的地址的方法和不同的位移位置都对应着有不同的jmp指令格式。
2.2.1 依据位移进行转移的jmp指令
指令格式: jmp short 标号(转到标号处执行指令)
这种格式的jmp指令实现的是段内短转移,他对ip寄存器的修改范围为-128~127,也就是说它可以向前跨跃128个字节或者向后跨越127个字节,jmp中的short符号也表明了其属于短转移。jmp指令中的标号是代码段中的标号,表明了指令想要转移的地址,转移指令结束后,cs:ip应该指向标号处。
# 比如
assume cs:codesg
codesg segment
start: mov ax,0
jmp short s
add ax,1
s: inc ax
codesg ends
end start
实验结果
通过实验结果观察我们可以得知,ax寄存器的最终值为1是由于执行了jmp short s操作后,越过了原本的add ax,1 从而直接执行了 inc ax。
2.2.2 跳转原理解析
我们学习汇编语言的目的其实并不是单纯地通过汇编语言去写一个程序,更多的是想要通过汇编语言去了解计算机软件和硬件之间的关联。就拿刚刚的例子来说,因为执行了jmp short s命令,程序直接越过了add ax,1 ,IP寄存器指向了标号S处的inc ax命令。为了搞懂这其中的奥妙,让我们先看下这几条指令转换为机器指令是什么样子的:
对照机器指令,我们可以看出jmp short s 中的s表示为了inc ax指令的偏移地址 8 ,并将jmp short s 直接转换为了jmp 0008,这一切看似很简单,但是我们可以往下深想:
无论是jmp 0008 亦或是jmp short s两个命令所对应的机器码都是EB03,注意,这个机器指令竟然不包含我们转移的目标地址08,这意味着CPU在执行EB03的机器指令的时候是并不知道我们此时的转移目标地址的。那么CPU是根据什么进行转移的呢?它如何知道目标地址在哪里呢?
我们来改写下程序:
assume cs:codesg
codesg segment
start: mov ax,0
mov bx,0
jmp short s
add ax,1
s: inc ax
codesg ends
end start
我们翻译成机器码:
这里我们可以发现,我们的指令inc ax的地址已经更改为了40,但是对应的jmp short s的机器码依旧是EB03,这在侧面的证明了CPU在执行jmp指令的时候并不需要知道转移的目的地址。当然我们知道CPU并不是人工智能,他不具备思考的能力,它只能够处理我们提供给它的信息。所以CPU是如何获取转移的目标地址呢?
这里让我们简单的回一下CPU执行指令的过程
1. 从CS:IP指向的内存单元读取数据作为指令,读取的指令进入指令缓冲器
2. (IP)=(IP)+所读取指令的长度,从而指向下一条指令。
3. 执行指令,回到第一步
按照这个逻辑我们来分析下上面这段程序的运行过程
1. CS = 076A , IP=0000
2. 加载B80000进入指令缓冲器,IP = IP+3
3. 执行B80000即mov ax,0000
4. CS=076A ,IP=0003
5. 加载BB0000进入指令缓冲器 IP=IP+3
6. 执行BB0000即mov bx,0000
7. CS=076A ,IP=0006
8. 加载EB03进入指令缓冲器,IP=IP+2
9. 执行EB03即jmp 000B
10. CS=076A ,IP = 0009
接下来按照我们的预期,CPU会继续执行CS:IP即076A:0009处命令,此处命令为AX,0001,但是CPU却直接跳过,执行了INC AX。那么计算机是根据什么做到的转移呢?奥秘其实就在指令中的03处。让我们再改下程序:
assume cs:codesg
codesg segment
start: mov ax,0
mov bx,0
jmp short s
add ax,2
add ax,3 # 新增一行代码
s: inc ax
codesg ends
end start
我们比较下上下两个版本代码可以发现两者之间的区别在于在跳转指令后多了一行代码。而我们查看第二版代码的jmp short s指令所对应的机器码会发现其变成了EB06,聪明的小伙伴已经看出了问题的所在。
虽然CPU无法直接获取目标地址,但是它能够通过诸如EB03或EB06这种指令码中的转移地址长度来判断它的目标地址。其中03,06等表示的就是IP寄存器的位移距离。
我们可以用生活中的案例来映射CPU获取目标地址的方式。比如我要去一家附近的网红店,那常见的方式就说获取网红店的精准地址,比如:成都市青羊区光华街道115号,那我就可以通过这个精准地址前往饭店。这就是我们说的通过目标偏移地址进行转移。
还有另外一种方式就是我虽然不知道它的精确地址,但是可以通过询问路人获得前行500米左转再直行300米这种转移信息来进行位移,同样也可以获得目的地。这就是jmp short s中EB03的运行原理。
2.2.3 依据目的地址进行转移
刚刚的例子中,我们是依靠的标号进行的段内近转移,而接下来我们要进行一次段间远转移。
assume cs:codesg
codesg segment
start : mov ax,0
mov bx,0
jmp far ptr s
db 256 dup (0) # db表示double byte ,dup表示重复元素
s: add ax,1
inc ax
codesg ends
end start
按照惯例,让我们来看下机器码:
如图所示,源程序中有两个地方需要我们注意下
- db 256 dup (0) 被debug解释为相应的若干条汇编指令。
- jmp far ptr s 指令所对应的机器码是: EA 0B 01 BD 0B,和短位移不同,这里面的0B 01 BD 0B不再是位移长度,而是明确的位移地址,BD 0B代表的是段地址,0B 01是偏移地址。
2.2.4 jmp配合寄存器的使用
当然我们可以将目标地址存放到寄存器中,这种指令其实我们之前就已经用过了:
assume cs:codesg
codesg segment
start:
mov ax,1
jmp ax
codesg ends
end start
这里的机器码是标记了AX的内存单元,然后将AX内存单元中的值获取出来作为IP的偏移地址。由于之前用过很多次了,就不再赘述了。
2.2.5 jmp配合内存单元的使用
jmp配合内存单元的使用分为两种:
1. 段内转移
指令格式: jmp word ptr 内存单元地址
原理很简单,就是将内存单元地址中的值赋予IP寄存器
2. 段间转移
指令格式: jmp dword ptr 内存单元地址
这个原理也很简单高地址处的字赋予CS,低地址处的字赋予IP
简单的举例:
assume cs:codesg
codesg segment
start: mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2] ,0
jmp dword ptr ds:[0]
codesg ends
end start
机器码:
2.3 jcxz指令
jcxz指令为有条件的转移指令,这里重点说下,所有有条件的转移指令都是短转移 ,即只修改IP寄存器的值。
指令格式:
jcxz 标号
这里也提到了JCXZ是有条件的转移指令。它的条件就是判断CX寄存器此时的值是否为零,若为零则进行转移,若不为零则什么都不做。
简单举例:
assume cs:codesg
codesg segment
start:
mov cx,0
mov ax,0
jcxz s
mov ax,2
s: inc ax
codesg ends
end start
机器码:
由于是短转移,其原理就是通过该百年IP = ip+ 位移距离实现的。
有其他语言基础的同学可以理解JCXZ命令等于:
if(cx == 0){
jmp 标号;
}
2.4 loop指令
我们的老朋友了,loop指令是一个循环指令,所有的循环指令都是短转移。其判断条件也是通过CX寄存器的值进行,每次进行转移则CX寄存器的值减一,直到为零。
由于我们已经用过很多次了,就不详细举例了。不了解的小伙伴建议看下之前的文章。
结语
今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。
项目通用架构及工具已上传到gitee仓库,需要的小伙伴们可以自取:
https://gitee.com/xiaolong-oba/common-base
屏幕前努力学习的你如果想要持续了解博主最新的学习笔记或收集到的资源,可以关注博主的个人公众号。这里有很多最新的技术领域PDF电子书及好用的软件分享
码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。