汇编语言是介于机器不可识别的高级语言与人不可读的机器代码之间的中间语言, 对我们理解计算机程序的底层运行机理非常有帮助。
一、基础知识
1.1 现代CPU的体系结构
现代CPU的体系结构,融合了冯诺依曼结构的和哈佛结构的概念,属于混合型架构,包括控制器(Control Unit, CU)、运算器(Arithmetic Logic Unit, ALU)、寄存器、多级缓存、地址映射器(Memory Management Unit, MMU)等。存储器中的指令和输入数据通过总线传输至CPU,控制器负责读取解释指令和下达对应的操作,运算器进行算术运算和逻辑运算处理。寄存器或缓存中的数据结果可能被再次使用或在合适时机刷新回存储器。
1.2 寄存器
寄存器是CPU内部的一些小型存储区域,用来暂时存放参与运算的数据和运算结果以及一些 CPU 运行中必须的信息,是现代CPU的重要组成部分。
每个CPU核心都有自己的一套寄存器,对于超线程CPU核心,每个超线程都拥有一套(如4核8线程CPU就会有8套寄存器)。
以8086体系为例,可以将寄存器按用途分为以下4大类:
通用寄存器、段寄存器、指令指针寄存器、标志寄存器。
(1)通用寄存器包括AX、BX等,主要用于存储数据变量、指针变址等,如果只取低8位,使用AL、BL等表示,取高8位使用AH、BH表示。
(2)段寄存器包括代码段寄存器CS、数据段寄存器DS等,方便指示每类程序段的基地址。
(3)指令指针寄存器指IP(Instruction Pointer),用于提供偏移地址,和CS配合以指示CPU要读取的一条条指令地址。
(4)标志寄存器包括OF(OverFlow Flag)、SF(Sign Flag)等,用于指示加减运算是否有溢出、运算结果的正负等。
1.3 寻址方式
CPU访问数据的过程称为寻址,下面将介绍各类寻址方式并给出8086汇编示例:
1.3.1 非内存寻址
1、立即数寻址
1 | mov ax, 1234H ;将立即数0x1234赋值给ax寄存器 |
2、寄存器直接寻址
1 | mov ax, bx ;将bx寄存器的值赋值给ax寄存器 |
1.3.2 内存寻址
1、寄存器间接寻址
1 | mov ax, [bx] ;将段地址为ds寄存器值、偏移地址为bx寄存器值的内存地址中的值赋值给ax寄存器 |
2、内存直接寻址
1 | mov ax, [1200H] ;将段地址为ds寄存器值、偏移地址为0x1200的内存地址中的值赋值给ax寄存器(仅Debug模式可使用) |
3、基址寻址
1 | add [bx], 0x1234 ;将段地址为ds寄存器值、偏移地址为bx寄存器值的内存地址中的值加上0x1234后写回 |
4、变址寻址
1 | mov [di], ax ;将寄存器ax的值写入段地址为ds寄存器值、偏移地址为di寄存器值的内存地址中,其中的变址是di |
5、基址变址寻址
1 | mov [bx+di], ax ;将ax中的值存入以ds为段基址,bx+di为偏移地址的内存中,其中的基址是bx、变址是di |
8086汇编(16位)
1.1 汇编指令分类
汇编语言的核心是汇编指令,按机器是否能理解分为:
1.汇编指令(机器码的助记符);2.伪指令(由编译器执行);3.其他符号(由编译器识别)
按编码风格可分为 MASM 汇编和 NASM 汇编。
MASM举例:
1 | mov ax, 18 将18写入寄存器AX |
8086CPU给出物理地址的方法
地址加法器合成物理地址的方法:物理地址 = 段地址x16 + 偏移地址
如:段地址为 1230H,偏移地址为00C8H,则计算出的物理地址为
1230H×16+00C8H=123C8H
相当于将段地址末尾加0,再加上偏移地址。
注意:CPU可以根据不同的段地址和偏移地址组合,计算出同一个物理地址。
如果CPU当前要获取下条指令的物理地址,那么将由CS提供代码段基地址,IP提供偏移地址,通过地址加法器计算得到物理地址。
在8086CPU加电启动或复位后,CS和IP分别被设置为CS=FFFFH,IP=0000H。因此,FFFF0H单元中的指令是8086CPU启动后执行的第一条指令。
1.3.1 JMP指令
由于CS、IP寄存器的特殊地位,8086CPU不允许用户使用MOV指令设置它们的值。用户若想修改CS、IP的值,需使用转移指令(JMP),有两种指令格式,第一种用法是直接指定段地址和偏移地址,格式为:
jmp 段地址:偏移地址
如:
1 | jmp 2AE3:3 |
将段地址修改为2AE3
,偏移地址修改为3
。
第二种用法是通过寄存器的值仅修改偏移地址,格式为:
jmp 某一合法寄存器
如:
1 | jmp ax |
思考题:16位的CS和IP寄存器,能存储的代码最大容量是多少?
答案
1.4 8086读取内存单元的方法
1.4.1 DS寄存器
通过DS寄存器和[address]
的组合,我们可以读取内存单元的数据,如,读取10000H单元的内容可以通过在debug.exe程序(使用方法可见实验部分)中运行如下程序段获得:
1 | mov bx, 1000H |
上述指令便将10000H(1000:0)的内容读取到寄存器AL中。
二、实验
2.1 DOSBox环境和MASM程序包
使用Windows系统,从这里下载安装DOSBox和MASM程序包(包括masm、link、debug等命令):https://github.com/xDarkLemon/DOSBox_MASM/tree/master
安装后可以尝试启动DOSBox,由于此时尚未使用,使用很不方便,所以回到Windows系统,在解压目录(我的是G:\Experiments\DOSBox_MASM-master)下新建一个存放代码的文件夹dos,然后找到配置文件(我的是在C:\Users\10769\AppData\Local\DOSBox\dosbox-0.74.conf),在[autoexec]
一栏下添加:
1 | mount C G:\Experiments\DOSBox_MASM-master ; 挂载驱动器C盘 |
保存关闭,再打开DOSBox,发现启动时会自动执行这些配置,输入masm,如下显示则说明配置成功:
配置成功截图
使用macOS系统的,先从官网下载DosBox,然后从2.1的github链接中下载masm程序包。配置命令类似:
1 | mount C ~/Downloads/masm |
如果遇到不能及时刷新文件夹数据的情况,在DOSBox中执行Ctrl+F4可以刷新。
DEBUG 中命令参数:
R:查看、改变CPU寄存器的内容;
D:查看内存中的内容;
E:改写内存中的内容;
U:将内存中的机器指令翻译成汇编指令;
T:执行一条机器指令;
P:结束单步执行 / 执行一个循环指令直到结束
A:以汇编指令的格式在内存中写入指令。
G:执行指令直到指定的偏移地址处
Q:退出DEBUG
非常有意思的一条指令:
1 | e b810:0 49 03 03 75 75 |
效果:
DEBUG运行指令与.asm源程序编译的语法不同点
(1)在debug程序中,输入a进入指令编写,编写完成后输入t单步执行指令;而在编写asm源程序时,需要用到文本编辑器,编写保存后使用masm编译+link连接,最后执行exe。
(2)debug中可以直接开始执行指令;asm源程序中必须指定好程序段,有开始和结束标志:
1 | assume cs:程序段别名 |
(3)debug中的数值默认是16进制,不需要加后缀H;asm源程序中数值默认是十进制,对16进制表示必须加H后缀,并且不能以字母开头表示数据,需要在前面补0(如0FFFFH)。
(4)debug中的内存寻址可直接传入数值,如mov al, [1]
;在asm源程序中如果直接传入数值,则必须在前面带上ds寄存器名加冒号,否则应通过寄存器传入数值,如
1 | mov bx, 1 |
三、指令讲解
MOV、ADD、SUB指令
mov指令的6种形式:
1 | mov 寄存器,立即数 |
add指令的4种形式:
1 | add 寄存器,立即数 |
sub指令同add指令
PUSH、POP指令
1 | push 寄存器 |
栈顶段地址寄存器:SS,栈顶偏移地址寄存器:SP
任意时刻,SS:SP指向栈顶元素。
INC 指令
累加指令
LOOP 指令
1 | mov cx, 11 |
每次loop,相当于cx=cx-1,然后判断cx是否为0,若为0,则跳出循环,否则继续执行s部分。
DOS一段安全的空间
0:200 - 0:2FF
评论
shortname
for Disqus. Please set it in_config.yml
.