写在前面
小结一下最近看的汇编语言王爽(第三版)的寄存器, 内存访问与基本汇编指令部分的内容. 其实学习操作系统, 熟悉一些基本的汇编语法即可, 但是要想读懂后面的程序链接等内容还是要好好研究一下汇编的实现细节. 毕竟这是探寻计算机底层的最好的工具.
下面的讨论均基于Intel的8086芯片. 寄存器部分参考1.
寄存器(register)
分类(共14个)
- 通用寄存器:
AX
,BX
,CX
,DX
, 用于存放一般的数据, 每一个寄存器都是16位的(下同), 可以分成高位和低位两种情况(例如AX=AH+AL
, 这里面的AH
,AL
等均为8位寄存器) - 段(Segment)寄存器:
- 代码段寄存器(Code segment register):
CS
, 控制待执行的机器指令. - 数据段寄存器(Data segment register):
DS
, 存放要访问的数据的段地址. - 栈段寄存器(Stack segment register):
SS
, 存放栈顶地址. - 额外段寄存器(Extra segment register):
ES
, 用于控制显存等信息.
- 代码段寄存器(Code segment register):
- 指针(Pointer)寄存器:
- 指令指针寄存器(instructor pointer register):
IP
, 指令偏移地址, 与CS
合用,CS:IP
指向待执行的下一条指令. - 栈指针寄存器(Stack pointer register):
SP
, 栈的偏移地址, 与SS
合用,SS:SP
指向栈顶元素. - 栈基指针寄存器(Stack Base pointer register):
BP
,
- 指令指针寄存器(instructor pointer register):
- 索引(Index)寄存器:
- 源索引寄存器(Source index register):
SI
, - 目的索引寄存器(Destination index register):
DI
,
- 源索引寄存器(Source index register):
- 程序状态字(Program status word):
PSW
, 执行状态寄存器和程序计数器功能的寄存器2.
寄存器中数据的存储
- 字节(byte), 一个字节=8个bit(位)
- 字(word), 一个字=2字节
寻址(物理地址=基址+偏移)
8086CPU(硬件结构仅为16位)有20位地址总线, 所以单次可以传输2^20=1,048,576=1MB
的数据, 达到1MB寻址能力. 下面的式子展示了为什么16位芯片可以达到20位寻址能力:
\(物理地址=段地址\times16+偏移地址=段地址<<4+偏移地址\)
指令寻址(CS:IP)
使用8086CPU的机器, 任意时刻, CPU将从内存的CS<<4+IP
单元开始取指, 执行. 基本步骤:
- 从
CS:IP
指向的内存单元读取指令, 读取到的指令进入指令缓冲器. IP+=所读取指令的长度
, 从而指向下一条指令.- 执行指令,转步骤(1), 重复.
不能通过mov
修改CS:IP
指向的指令, 可以通过jmp
指令来完成.
栈顶寻址(SS:SP)
任意时刻, SS:SP指向栈顶元素---低地址
23 <--低8位 <--栈顶(SS:SP)
01 <--高8位
---高地址
基本汇编指令
这里就进入汇编的基本语法部分了, 和高级语言相同, 汇编也是由很多语句构成的, 其中最常用的就是mov
, 可以类比一下C语言中的赋值运算符=
.
16位汇编中的语句不区分大小写, 这里的语句都以小写形式给出, 但是写汇编源文件的时候
mov(赋值)
格式为:mov destination, source
, 即mov 目的操作数, 源操作数
, 下面是汇编语句通过高级语言的描述:
数字均为16进制(HEX), 在DEBUG程序中写入指令的时候不需要加数后面的H, 但是在汇编源程序文件中需要加上后缀的H以区分十进制和十六进制.
汇编指令 | 含义 | 高级语言描述 |
---|---|---|
mov ax, 18 |
将18送入AX 寄存器 |
AX=18 |
mov ah, 18 |
将18送入AX 寄存器的高8位寄存器AH |
AH=18 |
mov ax, bx |
将BX 寄存器中的数据送入AX 寄存器 |
AX=BX |
针对内存访问
通过数据段寄存器DS
和[]
操作符完成, 语法:mov ax, [0]
, 表示将DS:0
位置的数据送入AX
寄存器.
不能对DS
/CS
寄存器直接进行赋值, 需要借助通用寄存器中转:
mov ax, 1000
mov ds, ax
但是可以直接从DS
或CS
寄存器中读取值并写入AX
寄存器或写入内存单元.
mov ax, cs
mov [0], cs
add,sub(加/减)
可以类比C语言的+=,-=
操作符, 其格式为: add/sub destination, source
.
汇编指令 | 含义 | 高级语言描述 |
---|---|---|
add ax, 18 |
将AX 寄存器中的数据加上18后送入AX 寄存器 |
AX+=18 |
add ah, 18 |
将AH 寄存器中的数据加上18后送入AH 寄存器 |
AH+=18 |
add ax, bx |
将AX 寄存器中的数据加上BX 中数据后送入AX 寄存器 |
AX+=BX |
需要注意对通用寄存器或者通用寄存器的高位/低位寄存器操作的时候也会存在数据的舍入的情况, 例如:
mov ax, ffff add ax, 1 ; 这里AX 寄存器实际的值为0000, 因为FF+FF=10000, 高位截断 mov al, ff add al, ff ; 这里AL 寄存器实际的值为FE, 因为FF+FF=1FE, 高位截断
针对内存访问
可以执行内存单元\通用寄存器\立即数(字面值)之间的增减, 但是不能执行到段寄存器的增减和两内存单元之间的增减.
add ax, 8
add ax, [0]
add [0], ax
; add [0], [0] ; error
; add ds, ax ; error
jmp(指令跳转)
语法:
jmp 段地址:偏移地址
,jmp 寄存器名
(表示用寄存器中的数据作为待跳转的IP
值), 可以理解为mov IP, ax
.
push,pop(压栈,出栈)
语法: push AX
, 表示将AX
寄存器的数据压入栈中, pop AX
, 将栈顶元素弹出, 写入AX
寄存器.
对内存单元也有类似的操作.
push
的执行步骤:
SP-=2
,SS:SP
指向当前栈顶前面的内存单元, 以当前栈顶前面的内存单元作为新的栈顶.- 将
AX
中数据送入SS:SP
指向的内存单元处,SS:SP
此时指向新的栈顶.
pop
正好相反:
- 将
SS:SP
指向的内存单元处的数据存入AX
寄存器. SP+=2
,SS:SP
指向当前栈顶下面的内存单元, 以当前栈顶下面的单元作为新的栈顶.
实验部分
用到了dosbox
, 跨平台的DOS环境模拟器, 我使用的dosbox-x
, 配置过程见前文.
基本DOS命令
cls
: 清屏exit
: 退出ls
或dir
: 显示当前目录下所有文件(dir显示更详细)cd
: 切换路径(同一盘符)C:
: 切换到C盘下md
: 创建文件夹date
: 日期del
: 删除文件ver
: 版本time
: 时间echo
: 回显消息vol
: 显示卷标...
Debug调试工具
dosbox中直接自带Debug.exe
, 直接输入Debug
回车即可, 大小写不敏感.
一些常用命令
Q
命令: 退出Debug程序R
命令: 查看与改变CPU寄存器内容D
命令: 查看内存内容E
命令: 改写内存中内容U
命令:翻译内存中的机器指令为汇编指令T
命令: 执行一条机器指令A
命令: 一汇编指令形式在内存中写入一条机器指令P
命令: 执行循环程序时使用G
命令: 直接跳转到指定的指令位置, 可以用g 0016
, 表示 执行到CS:0016
指令处
R
查看寄存器, 改写寄存器
D
查看内存内容
E
改写内存内容
U
查看机器码翻译后的汇编代码
A/T
A:写入汇编 T:执行汇编