Basic Part
Memory Segmentation in 8086 Microprocessor
REF ….
General-Purpose Registers (GPR) - 16-bit naming conventions
The 8 GPRs are as follows:
- Accumulator register (AX). Used in arithmetic operations
- Counter register (CX). Used in shift/rotate instructions and loops.
- Data register (DX). Used in arithmetic operations and I/O operations.
- Base register (BX). Used as a pointer to data (located in segment register DS, when in segmented mode).
- Stack Pointer register (SP). Pointer to the top of the stack.
- Stack Base Pointer register (BP). Used to point to the base of the stack.
- Source Index register (SI). Used as a pointer to a source in stream operations.
- Destination Index register (DI). Used as a pointer to a destination in stream operations.
存储器 、存储单元
存储器
也就是我们平时所说的内存
。 负责为CPU提供指令和数据 , 在一台PC机中内存的作用仅次于CPU,存储器被划分为若干个存储单元
, 每个存储单元从0开始顺序编号. 一般微型机的存储器的存储单元可以存储一个Byte
,即一个字节(8个二进制位) - 微机存储器容量的最小单位
。
一般应具有 存储数据 和 读写数据 的功能, 每个单元有一个地址,是一个整数 编码 ,可以表示为 二进制 整数。
磁盘不同于内存,磁盘上的数据或程序不读取到内存中就无法被CPU使用
CPU对存储器的读写
存储单元在存储器中顺序编号,这些编号可以看作存储单元在存储器中的地址。
计算机中 连接 CPU 与其他芯片的导线被称作是 "总线"
,
根据传输信息的不同, 其从逻辑上可分为三类 :
地址总线 (CPU通过地址总线来指定存储器单元)
地址总线的宽度
(位数)决定了CPU寻址的最大空间大小,假设一个CPU有10根地址总线,其寻址最大数为2^10 = 1024个
最小数为0,最大数为1023地址总线可寻到的内存单元构成了这个CPU的
内存地址空间
。控制总线
控制总线的宽度决定了CPU对外部器件的控制能力。
CPU通过地址总线来指定存储单元 ,控制总线上能传送信息的多少(控制总线的宽度),决定了CPU可以对多少个存储单元进行寻址。
数据总线
数据总线的宽度决定了
CPU与外界的数据传输速度
, 8根数据总线可以一次传送8位二进制数据 (即1Byte),后注: 8086CPU数据总线宽度为16 ,寄存器为16位, 即一次最大可传送
一个字
的数据大小计算机的
字长
即取决于数据总线的宽度.
内存地址空间
CPU操作存储器(RAM ROM 带有BIOS的ROM)时将他们看作内存来对待,将他们总的看作一个由若干个存储单元组成的逻辑存储器
. 这个逻辑存储器就是所说的内存空间地址.
每一个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间
,对于CPU来说,在这段地址空间中进行数据读写 , 实际也就是对对应的物理存储器进行读写.
因为内存空间地址与存储单元相关,其大小受到CPU地址总线宽度的限制,例如8086CPU的地址总线为20
,则其可以传送2^20
个不同的地址信息(0 ~ 2^20-1)
,也就是可以定位2^20
个内存单元,则其内存地址空间大小为 2^20 / 1024^2
在基于一个计算机硬件进行系统编程时,必须知道这个系统中内存地址的分配情况, 在对某类存储器进行数据读写时,必须知道它的第一个单元的地址和最后一个单元的地址,才能保证读写操作是在预期的存储器中进行的.
寄存器
CPU由运算器 \ 寄存器 \ 控制器 等器件组成,这些器件靠内部的总线相连..
内部总线实现CPU内部各个器件之间的联系外部总线实现CPU和主板上其他器件的联系
🚩 字在寄存器中的存储
字(记为word
) , 一个字由2个字节
组成 这两个字节分别称为这个字的高位字节
和低位字节
.
字单元
,即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成, 高地址内存单元
中存放字型数据的高位字节
, 低地址内存单元
中存放字型数据的低位字节
.
由此可见8086CPU采取了小端模式
所谓的小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致. 记忆: 地址的增长顺序与值的增长顺序相同
一个十六进制数 = 四位二进制数
几条汇编指令与8086CPU给出物理地址的方式
在进行数据传送或运算时,要注意两个操作对象的位数应当是一致的. 高低位对应
1 | add ax,bx |
8086CPU
有20位地址总线,可以传送20位地址,寻址能力 2^20 (bytes) / 1024^2 (bytes) = 1MB
内部采用2个16位地址合成的方法来形成一个20位的物理地址(一个称为段地址
, 一个称为偏移地址
)
段地址和偏移地址通过内部总线被送入一个称作地址加法器
的部件中,由它将两个16位的地址组合为20位的物理地址
地址加法器通过物理地址 = 段地址 × 16(d) + 偏移地址
的方式合成物理地址(5位十六进制数 , 即20位二进制数)
段地址 × 16
常用说法 左移4位
(指二进制位) , 相当于 乘 2^4
关于8086CPU的物理地址的五位十六进制数表示方式:
因为其寻址能力为1MB , 一位十六进制数相当于4位二进制数 , 所以五位十六进制数 = 4*5 = 20位 = 2^20 = 1MB 正好符合了8086CPU的寻址能力范围.
🔓 测验题解 - 2.2
有意思的题目…
有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为 ? 最大为 ?
最大值: 比较好求, 设SA为x, 则 EA为 0000H
, x = 20000H / 16 = 2000H
最小值: EA 最大 为FFFF
, 则段地址为 2 0000H - FFFF 后右移4位(逆向) 结果 1000H,但是经过验算结果非20000H 而是 1FFFF
,初见这我也觉得很奇怪… (我的计算器绝对没有问题.jpg) 但其实1FFFF即段地址1000H的最大寻址范围
, 与所需求内存单元就差 1 , 所以1000H + 1 = 1001H
这才是正确的值.也即是最小满足SA条件的值
好怪但雀氏
1 | (20000H-ffffH)/16 |
段寄存器
8086CPU有四个段寄存器 : CS \ DS \ SS \ ES
CS:IP 是8086CPU中最关键的俩个寄存器 , 它们指示了 CPU 当前要读取指令的地址
CS为代码段寄存器 (Code Section)
IP为指令指针寄存器 (Instruction Pointer)
CS * 16 + IP = 物理地址
关于 IP / CS 的修改
能改变IP / CS 内容的指令被统称为转移指令
, 目前可以用一个简单的指令来修改它们 :jmp 指令
指令形如 jmp 段地址:偏移地址
, 将CS修改为段地址, IP修改为偏移地址
或者使用 jmp 合法寄存器
来修改IP
的内容
e.g. jmp ax => mov IP,ax
jmp指令的功能类似于这样子
寄存器与内存访问
DS寄存器(数据段寄存器 , data segment register)
通常用来存储要访问数据的段地址
🚩 CPU提供的栈机制
栈 – 拥有特殊访问方式的存储空间 – LIFO 后进先出
, CPU提供的最基本两个指令是 PUSH(入栈) 和 POP(出栈)
,它们的操作都是以字
为单位进行的
SS \ SP 寄存器 – ( Stack Segement ) / ( Stack Pointer )
,通过两者定义栈段,任意时刻 SS:SP都指向栈顶元素
,CPU将从此处取得栈顶
的地址 , 注意 CPU 只记录栈顶,栈空间的大小需要自己管理
pop / push 的执行过程
执行push时.CPU的两步操作是: 先改变SP, 后向SS:SP处传送, 执行 pop时, CPU的两步操作是:先读取SS:SP处的数据,后改变SP
⭐ 栈综述
将一段内存当作栈段, 这是我们自己在编程中的安排, 想要pop / push 等栈操作指令访问我们定义的栈段,就得将SS:SP
指向我们定义的栈段
一个栈段的最大容量为64KB
栈满时继续压栈将导致栈顶环绕
❤️ 务必注意, 当栈空时, SS:SP将指向栈底的后一个内存单元
.
When the stack is empty ,SP Point to the address of the next memory unit at the bottom of the stack , namely SP = At the bottom of the stack +1.
⭐ 关于32位架构中为什么不能以内存到内存的形式传递数据
#### ⭐ 关于汇编中 十六进制数据前加 '0' 规则The answer involves a fuller understanding of RAM. Simply stated, RAM can only be in two states, read mode or write mode. If you wish to copy one byte in ram to another location, you must have a temporary storage area outside of RAM as you switch from read to write.
It is certainly possible for the architecture to have such a RAM to RAM instruction, but it would be a high level instruction that in microcode would translate to copying of data from RAM to a register then back to RAM. Alternatively, it could be possible to extend the RAM controller to have such a temporary register just for this copying of data, but it wouldnt provide much of a benefit for the added complexity of CPU/Hardware interaction.
如何做?
0~9开头的不用加, A~F开头的要加
主要是汇编语言编译器的设计者需要用户按这个规则编程, 否则编译器不知道你写的是什么。
因为标号
不能以0~9的数字开头,但可以以字母开头,
导致无法区分数值与标记
⭐ 8086CPU的工作过程
- 从
CS:IP
指向的内存单元读取指令 , 读取的指令进入指令缓冲器 IP
指向下一条指令- 执行指令 ( 转到步骤 1 , 重复这个过程 )
程序执行过程与跟踪
程序由Shell
(操作系统的外壳程序,用户使用Shell来操作计算机系统进行工作 (如DOS中的command.com
就是一个Shell) 加载入内存
之后Shell将设置CS:IP
来指向程序的入口点
, 然后暂停运行, 将程序交给CPU执行.
待任务执行完毕后, 控制返回到Shell , 屏幕显示由当前盘符和当前路径组成的提示符,等待用户输入.(返回到加载者)
📌 实验三 - 编程|编译|连接|跟踪 - - 分析
debug 跟踪以下程序执行过程 , 记录每步执行后相关寄存器中内存和栈顶内容
1 | assume cs:codesg |
✨
Base Register[bx] And Loop instruction
[bx]
(base, 基址寄存器 , 常用于地址索引 )实际为一个偏移地址EA , 段地址SA默认存放在DS
中
LOOP 指令格式 : loop 标号
, 通过loop指令实现循环功能 , 通过 CX ( count )寄存器
控制循环次数
执行顺序 :
- (CX) = (CX) -1
- 判断 若 CX > 0 , 则向前转至
标号
处执行 , 若 CX = 0 , 则继续向下执行
值得注意的是, 这里跳转到 标号 处,相当于高级语言中常见的 for \ while 等 循环中常见的 条件循环 , 汇编实现为 jle,jmp,jne等. 或许之后会详细提到
总体程序框架:
1 | mov cx,循环次数 |
Pseudo code
1 | CX = CX - 1 |
Debug和汇编编译器MASM对指令的不同处理
若处理以[]
包含的指令时, 型如[idata]
,它们的处理结果如下
Debug 将[idata]
视作一个内存单元, 将内部idata
作为内存单元的偏移地址
编译器将[idata]
解释作idata
如果要让编译器将其解释为一个 内存单元, 则必须在[]前显式的给出段地址DS
(Concept)段前缀
用于显式指明内存单元的段地址的"DS:","CS:","SS:","ES:"
, 在汇编语言中被称为 段前缀
📌 实验四 - [bx]与Loop的使用 (3) - - 分析
补全程序 , 将 mov ax,4c00H 之前的指令复制到内存 0:200中
1 | assume cs:code |
通过debug获取 程序的总字节量, 计算于 mov ax,4c00H之前的字节量.( 且注意debug中以十六进制表示的数据)
可以发现需要获取的数据在076C:0017之前
,也就是从偏移地址0~16H
总共23(17H)
个字节
包含多个段的程序 \ dw \ end
在代码段中使用数据: 可以使用dw(define word)
定义字型数据.
利用end的另一个作用:通知编译器程序的入口点在何处
,可以使用Start
标号指明程序入口点
🍊 程序分析 6.3
1 | assume cs:code |
编写多段程序
利用assume
伪指令将定义的段和相关的寄存器联系起来 , 对于不同的段, 要有不同的段名
段名就相当于一个标号,它代表了一个段的段地址
, 编译器会把它处理为一个表示段地址的数值
注意在8086CPU不允许直接将数值传入段寄存器中
, 所以要将定义的段程序赋给特定的寄存器时需要一个寄存器作中转.
CPU如何处理所定义的段的内容, 当作指令执行或是数据访问, 亦或是栈空间… 完全取决于程序中具体的汇编指令 , 及汇编指令中对CS:IP
SS:SP
DS
等寄存器的设置决定
📌 实验五 - 编写与调试多段程序 - - 分析
编译链接并跟踪调试
1 | ; Experiment 5 |
Experiment 5 Quiz 1
程序返回前, data段中数据为 23 01 56 04… 共16个字节 , 剩下部分用00补全
程序返回前, CS = 076E SS = 076D DS = 076C
程序加载后, 设Code段的段地址为X , 则data 段的段地址为
X-2
, stack段的段地址为X-1
对于如下定义的段:
1 | name segment |
如果段中的数据占N个字节,则程序加载后 , 该段实际占有的空间为 (N / 16 + 1 )N小于16,则向下取整 * 16 个字节 , 若N大于16 , 则向上取整 …
- 若将伪指令 end start 改为 end (不指明程序的入口点) . 程序均可正常执行 , 只是不再从指定的入口点开始执行,而是从程序开始的程序段处开始执行, 若此程序段为数据段或栈段, 编译器会将其处理为汇编指令并执行.
Experiment 5 Quiz 5
将a段和b段数据依次相加 , 结果存在cg段
1 | assume cs:code |
Experiment 5 Quiz 6
用push指令将 A 段中的前8个字型数据逆向存储到B段中
1 | ; E5q6 |
更灵活的定位内存地址的方法
如果一个问题的解决方案,使我们陷入一种矛盾之中。那么,很可能是我们考虑问题的出发点有了问题,或是说,我们起初运用的规律并不合适。
AND 与 OR 指令
与基本逻辑与 &
或 |
相同 , 只是汇编中皆按位进行运算
AND
: 逻辑与指令, 按位进行与运算 , 通过该指令将操作对象相应位设为 0 ,其他位不变
OR
: 逻辑或指令, 按位进行或运算 , 通过该指令将操作对象相应位设为 1 ,其他位不变
以字符形式给出的数据
在汇编程序中 , 可以使用形如 '....'
的方式指明数据是以字符的形式给出的 , 编译器会将其转换为相应的 ASCII 码
E.G.
1 | db 'unIX' ; 相当于 db 75H,6EH,49H,58H 与字符的 ascii码相对应 |
[ BX + IDATA ]
可以以此标题方式来灵活的表明一个内存单元 , [bx + idata]
表示一个内存单元,偏移地址为 (bx) + idata
E.G.
1 | mov ax,[bx+200] ; 将一个内存单元存入AX , 这个内存单元占2个字节, 存放一个字,偏移地址为 bx的值 + 200 , 段地址在ds中 |
SI (Source Index Register) & DI (Destination Index Register)
SI
和DI
都是变址寄存器(Index Register)
它们主要用于存放存储单元在段内的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。它们可作一般的存储器指针使用。在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特殊的功能。
不同寻址方式的灵活应用
不同的寻址方式:
[idata]
用一个常量来表示地址, 可用于直接定位一个内存单元.直接寻址
[bx]
用一个变量来表示内存地址,可用于间接定位一个内存单元. 寄存器间接寻址
[bx+idata]
用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元.寄存器相对寻址
[bx+si]
用两个变量表示地址. 基址变址寻址
[bx+si+idata]
用两个变量和一个常量表示地址。相对基址变址寻址
自顶向下逐渐可以用更灵活的寻址方式来定位一个内存单元的地址 。这使得数据在使用者的角度可以以结构化
的角度看待。
关于数据的暂存
有时会遇到需要数据暂存的情况 , 如嵌套循环的CX , 作为计数器使用 . 由于寄存器的数量有限, 且每个程序使用的寄存器不一样 , 遂需要更为通用的方案
在不考虑使用寄存器的情况下
- 可以将要暂存的数据存放到内存空间中,需要的时候再从内存单元中恢复. 但是当数据量多的时候,这样的存储方式需要记住哪个数据放到 了哪个单元。容易引起程序混乱。
- 将需要暂存的数据存入栈中 , 利用压栈出栈与 栈顶指向 控制栈空间的数据
📌 实验六 - 灵活定位内存地址并修改值 - - 分析调试
将datasg段中的每个单词的前四个字母改为大写字母
1 | ; Experiment 6 |
数据处理的两个基本问题
- 要处理的数据在何处
- 要处理的的数据有多长
于汇编中数据位置的表达
- 立即数
idata
,在汇编中直接给出的数据 - 寄存器 , 汇编指令中需给出相应的寄存器名
- 段地址(SA) 和 偏移地址(EA) , 汇编中可用
[x]
形式给出EA , SA存在于某个段寄存器中
指明要处理的数据长度 - x ptr
8086CPU可处理两种长度的数据 Byte 和 Word ,在指令中需要指明需要处理的数据的尺寸.
- 可以通过寄存器名来指明数据的尺寸.
- 在没有寄存器名的情况下,可以使用形如
X ptr
来指明内存单元的长度 ,X
在汇编中可以为word
或是byte
.
如mov word ptr ds:[0],1
inc word ptr [bx]
这俩指令使用word ptr
指明了指令访问的内存单元是一个字单元
.
注意有的指令就不要指明内存单元的长度了 , 像是push \ pop
,其只能进行字操作.
div Instruction 除法指令
语法
1 | div divisor |
除法指令 , 需要注意几个问题
除数 的两种情况 :
8bit
&16bit
, 在一个寄存器或是内存单元
中被除数 默认放在
AX
中 或是DX 和 AX
中 , 情况如下- 若除数为 8位 , 被除数则为
16位
, 此时被除数默认存放在AX
中 - 若除数为16位 , 被除数则为
32位
, 此时被除数被存放在DX和AX
中 ,DX中存放高16位 , AX中存放低16位
- 若除数为 8位 , 被除数则为
对于计算结果的存储
- 若除数为 8位 , 则
AL
将存储 除法操作的商
,AH
中存储的则是余数
- 若除数为 16位 , 则
AX
中将存储 除法操作的商
,DX
中存储的是余数
- 若除数为 8位 , 则
一般寄存器的存储主要是关于被除数 和 结果.
实例:
1 | ; 1001 / 100 div calculation |
#### 伪指令 - dd (define dword)
dd
用来定义双字
型数据 , 占用32
位 , 需要2个16位寄存器
来分别存储其高低16为位
dup Operator
dup是一个操作符 , 和 db \ dw \ dd
一样, 其也是由编译器识别处理的符号 , 与他们(数据定义指令)配合使用 , 用以进行数据的重复
.
示例如下:
1 | db 3 dup (0) ; 定义了3个字节 , 他们的值都是 0 , 相当于db 0,0,0 |
可见,dup的使用格式如下:
db 重复的次数 dup(重复的字节型数据)
dw 重复的次数 dup(重复的字型数据)
dd 重复的次数 dup(重复的双字型数据)
📌 实验七 - 寻址方式在结构化数据访问中的应用 - - 分析调试
; 比较综合的一个实验, 融合之前的知识
将 data 段中的数据按照以下指定格式写入 table段
1 | assume cs:codesg |
✨ 转移指令及其原理
可以修改IP , 或同时修改 CS和IP的指令统称为 转移指令
针对8086CPU来说, 其转移行为
有以下几类:
- 只修改IP时 , 为
段内转移
- 同时修改CS和 IP 时 , 为
段间转移
针对转移指令对IP修改范围的不同, 段内转移还分为: 短转移 (IP修改范围: -128 ~ 127)
和 近转移 (IP修改范围: -32768 ~ 32767)
(前后八位的区别吗)
操作符 Offset
操作符offset
在汇编语言中是由编译器处理的符号 , 它的功能是取得标号的偏移地址
Jmp 指令
Jmp
指令为无条件转移指令 , 可以只修改IP , 或同时修改 CS和IP
此指令需要给出两种信息:
- 转移的距离 (
段间
转移 \段内 短
转移 \段内 近
转移 ) - 转移的目的地址
✨ 依据位移进行转移的 JMP 指令 +. 补码
Jmp short 标号 ( 转到标号处执行指令 )
此格式的 JMP指令 实现的是段内短转移 , 对
IP
的修改范围为 -128 ~ 127 , 即向前转移最多越过 128个字节 , 向后转移最多越过 127个字节 , “Short” 说明了其进行得是短转移 ,间接说明Short类型为8位 , “标号”存在于代码段中 , 是指令转移的目的地
注意 在jmp short 标号
指令所对应的机器码中,并不包含转移的 目的地址
,而包含的是转移的位移
, 此位移是编译器根据汇编指令中的 标号 计算出来的。
jmp short 标号
的功能实际为 : (IP) = (IP) + 8位位移
- 8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
short
指明此处的位移为8位位移- 8位位移的范围为-128~127,用
补码
表示 , 8位的最大最小值 ( 即IP最多向前移动128字节 , 向后移动 127字节, 若不在范围内会引发转移位移超界
的问题 ) - 8位位移由编译程序在编译时算出。
可得在 jmp short
指令的机器指令中 , 包含的是跳转到目标指令的相对位置 偏移量(即位移 , 以补码
形式存储) , 而不是转移的目标地址
补码 Two’s Complement Review:
以8位数据表示 有符号数 , 其最高位表符号 , 1 为负 , 0 为正 . 用其他位表示数值
正数的原码反码补码均相同
补码的基本思想:先确定用 0000 0000b ~ 0111 1111b表示0 ~ 127,然后再用它们按位取反加1后的数据表示负数。
补码方案特性:
最高位为1表示负数
正数的二进制值(原码)取反加1后,为其对应的负数(相反数)的 二进制数 :负数的补码取反加1后,为其绝对值。
由于一个负数的补码不太容易看出它所表示的数据 , 但是利用补码的特性将其 按位取反再加1 后可得其 绝对值 的二进制数, 则负数的值取相反数即可.
补码可以让正数与负数之间进行加法运算 , 即所以减法都可转化为加一个负数的形式 :
A - B = A + (-B)
顺便一提 ,
计算机都是以补码的形式存储数据的
段内近转移: short near ptr 标号
- 16位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
near
指明此处的位移为16位位移- 16位位移的范围为-32768~32767,用
补码
表示 , 8位的最大最小值 - 16位位移由编译程序在编译时算出。
jmp far ptr 标号
实现的是段间
转移 , 又称为远
转移
far ptr
指明了指令用标号的段地址和偏移地址修改CS和IP
, 在机器码中 , 高地址部分存放段地址 , 低地址存放偏移地址
, 顺序遵循小端存储模式
Recommend to refer : 汇编编译器(MASM.exe) 对 jmp 指令的相关处理
转移地址在寄存器中的JMP指令
格式 :
1 | jmp 16bit reg ; 格式 |
🛠️转移地址在内存中的JMP指令
两种格式 :
1 | 1. |
jcxz指令 (Jump if CX equals Zero)
所有的有条件跳转指令
都属于[短转移](#依据位移进行转移的 JMP 指令 +. 补码)(段内短转移),其在机器码中包含的是转移的位移
(位移这个概念还是很重要的Refer Chapter 9.9 ),而非目的地址
格式:
1 | jcxz flag |
loop 指令
循环指令, 注: 所有的循环指令都是 短转移
, 其机器码中 同样包含的是 相对目的地址的位移
而非偏移地址
格式:
1 | loop 标号 |
📌 实验八 - 分析奇怪的程序 (jmp 与 位移) - - 分析调试
分析以下程序是否会正常返回
1 | assume cs:codesg |
📌 实验九 - 根据材料编程 (彩色字符显示) 分析调试 (°ー°〃)
1 | assume cs:codesg,ds:data,ss:stacksg |
CALL 和 RET 指令
CALL
和RET
都是转移指令
,常用来实现子程序的设计.
1. ret 和 retf
ret
使用栈中的数据修改IP
, 实现近转移
retf
使用栈中的数据修改IP
和cs
, 实现远转移
以汇编语法解释这两条指令相当于:
1 | ret: |
LIFO
, 同时注意压栈的顺序 , 先压栈CS
,再是IP
2. call 指令
call
的执行进行两步操作
- 将当前
IP
或 将当前CS和IP
压入栈中 - 转移
注意call无法实现
短转移 , 其实现转移的方法和jmp
指令原理相同 , 即位移
根据位移进行转移 , 语法:
1 | call 标号 |
转移的目的地址在指令
中的CALL指令:
1 | call far ptr 标号 ; 实现的是段间转移 (同时改变了CS IP) |
转移目的地址在寄存器
中 :
1 | call 16bitReg ; 以十六位寄存器内容作爲轉移目的IP |
相当于:
1 | push IP |
转移地址在内存
中的CALL , 存在两种格式 ,
与 🛠️转移地址在内存中的jmp指令 类似 , 可参考
1:
1 | call word ptr 内存單元地址 ; 以内存單元処的字單元作爲目的地址IP |
2:
1 | call dword ptr 内存单元地址 ; 段间转移 ,低地址存放偏移地址 高地址存放段地址 |
mul Instruction 乘法指令
格式
1 | mul reg |
使用mul
指令 , 需要注意两点:
1. 两乘数
两个相乘的数要么都是8位 , 要么都是16位
- 8位的情况下 , 一个乘数默认放在
AL
中 , 另一个放在一个8位的reg中 或 内存字节单元中
- 16的情况下,默认的乘数将放在
AX
中, 另一个放在一个16位的reg中 或 内存字单元中
(注意是reg与内存单元中
而非立即数 , 不允许直接提供立即数(idata)进行运算)
2 结果
- 8位的情况下 , 结果默认存放在
AX
中 - 16位的情况下, 结果的高位存放在
DX
中 , 低位存放在AX
中
对现实问题进行分析时,把它转化成为相互联系 \ 不同层次的子问题 , 是必须的解决方法
汇编模块化程序设计 - 以栈进行的参数传递
除了以寄存器
和内存单元
来进行对参数的存储外 , 也可使用栈存储
因为这种技术和高级语言编译器的工作原理密切相关, ( 很难不想到编译原理… ) 所以简单记录一下
其原理:
调用者将需要传递给子程序的参数压入栈中, 子程序从栈中取得参数.
指令ret n
的含义 , 以汇编指令描述为:
1 | pop ip |
子程序中 [ 寄存器冲突 ] 问题 解决方案
未避免子程序中寄存器冲突. 即在编写子程序时不需要考虑 调用者 使用了哪些寄存器
将使用以下子程序编写标准框架:
1 | 子程序开始:子程序中使用的寄存器入栈 |
📌 实验十 - 编写子程序
1 | ; 显示字符串 |
2略
3.
🔦 综合设计 1
:TODO
标志寄存器(Flag)
为16位寄存器 , 按位起作用, 每一位都有专门的含义, 记录特定的信息. 其中1\3\5\12-15 位是没有使用的标志位 , 不具有任何意义
ZF 标志(Zero Flag)
处于标志寄存器的第六位
, 零标志位
,记录相关指令执行后, 其结果是否为0 , 若结果为0 , 则ZF = 1, 反之 ZF = 0
使用某条指令的时候 , 要注意这条指令的全部功能 , 包括其执行结果对于标志寄存器的标志位造成的影响
PF 标志(Parity Flag)
处于标志寄存器的第二位
, 奇偶标志位
,记录相关指令执行后, 其结果所有bit位中1的个数是否为偶数 , 若1的个数为偶数 , 则PF = 1, 反之 PF = 0
SF 标志(Sign Flag)
Flag的第七位
, 符号标志位 . 记录相关指令执行后 , 其结果是否为负 . 若为负 , 则 SF = 1, 反之 SF = 0
对于同一个二进制数据, 计算机可以将它当作无符号数据来运算, 也可以当作有符号数据来运算.
SF标志仅是对有符号数运算结果的记录 , 所以若是将数据作为有符号数 , 则 SF 有意义 , 若作为无符号数 , 则没有意义.(取决于程序功能实现, 见机行事)
CF 标志 (Carry Flag - Usually indicated as the C flag)
处于第零位
, 进位标志位 , 在进行无符号数运算
的时候, 它记录了运算结果的最高有效位向更高位的进位值(即加法) , 或是从更高位的借位值(减法情况)
一图胜千言.jpg
OF 标志 (Overflow Flag)
溢出标志位 , 处于第11位
, 在进行有符号运算
时, 如果结果超出了机器所能表示的范围即溢出 ,则OF为1 , 若无溢出 , 则为0
ADC and SBB instructions
这两个指令可用来处理任意大的数之间的加减运算 ,利用了进位与借位值 ,与CF
的关系密不可分
指令格式分别与add与sub相同
cmp Instruction
cmp
指令的执行将对标志寄存器产生影响 , 其他相关指令可以通过识别标志寄存器的值
来得知比较的结果
指令格式 :
cmp 操作对象1,操作对象2
根据 操作对象1 - 操作对象2
计算结果并对标志寄存器进行设置 , 不保存结果
检测比较结果的条件转移指令
“转移”指指令可修改IP
, 条件即决定是否修改ip
所有转移指令的位移都是[-128,127]
大多条件转移指令都通过检测cmp指令所影响的标志位
的检测结果来决定是否修改IP
DF标志 和 串传送指令
DF标志是 Flag中的第10位 ,"方向标志位"
在串处理指令中 , 控制每次操作后si与di的增减
df=0 每次操作后 si \ di 递增
df=1 每次操作后 si \ di 递减
步长以字节
为单位
MOVS/MOVSB/MOVSW:
This instruction copies a byte(movsb) or a word(movsw) from a location in the data segment [DS:SI] to a location in the extra segment [ES:DI]. The offset in the data segment for the source is to be stored in the
SI(Source Index)
register and the offset for the destination in the extra segment is to be stored in theDI(Destination Index)
register.
以上指令通常和Rep
指令(Repeat while equal)共同使用 , 类似的还有repnz
(repeat while nonzero) or repz
(repeat while zero) , 此类指令为Repeat String Operation
PUSHF & POPF
前者将标志寄存器的值压入栈中 后者将栈中弹出数据并送入标志寄存器中