汇编
寄存器 (14个)
通用寄存器:AX,BX,CX,DX
其中为了保证8位CPU和16位CPU的兼容性,这四个寄存器都可以分成两个独立的8位寄存器使用
AX = AH(高8位) + AL(低8位) 其余同理。
段寄存器:CS(代码段使用的寄存器),DS(数据段使用的寄存器),SS(堆栈段使用的寄存器),ES(附加段使用的寄存器)
指针寄存器:SP(堆栈内的偏移值),BP(寻址,默认在堆栈段寻找),SI(寻址,默认在数据段寻找),DI(寻址,默认在数据段寻找),IP(存储要执行的下一条指令的偏移地址)
标志寄存器:PSW
其中,BX、BP、SI、DI可用来寻址
其中标志寄存器涉及到的通用标志位是:
零标志位(ZF):结果为0,则ZF =1 ;结果为1,则ZF=0
奇偶标志位(PF):1的个数为偶数,则PF =1;个数为奇数,则PF =0
符号标志位(SF):结果为负,则SF =1;结果为正,则SF=0
进位标志位(CF):无符号数运算的进位值或借位值
溢出标志位(OF):有符号数运算,溢出为1,无溢出为0
OF= 最高位进位(符号位)异或 次高位进位(数值最高位)
方向标志位(DF) : 在串处理指令中控制每次操作后si、di的递减。
寻址方式
大多数汇编语言指令都需要处理操作数。操作数地址提供了要处理的数据存储的位置。有些指令不需要操作数,而另一些指令则需要一个,两个或三个操作数。当一条指令需要两个操作数时,第一个操作数通常是目的地,它在寄存器或存储器位置中包含数据,第二个操作数是源。源包含要传递的数据(立即寻址)或数据的地址(在寄存器或存储器中)。通常,操作后源数据保持不变。
- [idata]用一个常量来表示地址,可用于直接定位一个内存单元
- [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元
- [bx + idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元
- [bx + si]用两个变量表示地址
- [bx + si + idata]用两个变量和一个常量表示地址

例如:
1 | mov ax,[bx + 200] |
mov指令可能具有以下形式:
1 | MOV 寄存器, 寄存器 |
物理地址与逻辑地址的转换:
物理地址 = 段地址 × 16 +偏移地址
偏移地址–> 段地址:偏移地址
变量
| 指令 | 目的 | 储存空间 |
|---|---|---|
| db | 定义字节 | 分配1个字节 |
| dw | 定义字 | 分配2个字节 |
| dd | 定义双字 | 分配4个字节 |
在数据段中使用如下:
1 | data segment |
dup 是一个操作符,和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复。
1 | db 3 dup ('abc','ABC') |
基本语法
汇编程序可以分成三个段:数据段、代码段、堆栈段。
注释以分号;开头
其中汇编语句的格式为:
1 | [label] mnemonic [operands] [;comment] |
堆栈段
push ax : 将ax内容送入栈中,sp=sp-2 指向偏移地址(进栈)
pop ax : 将ss:sp指向的内存单元处数据送入ax寄存器中,sp=sp+2指向当前栈顶下面的单元(出栈)
当栈满的时候再使用push指令进栈以及当栈空的时候再使用pop指令出栈都会发生栈顶超界。
pushf:将标志寄存器的值压栈
popf:从栈中弹出数据,送入标志寄存器中
初始化代码:(栈就是高地址往低地址生长的)
1 | assume cs:code,ds:data,ss:stack |
逻辑指令
and指令 : 按位进行与运算。(同时为1才能为1)
1 | mov al,01100011B |
or指令 : 按位进行或运算。(同时为0才能为0)
1 | mov al,01100011B |
not指令 : 按位进行非运算。(全部倒转,反码操作)
1 | mov al,01100011B |
xor指令 : 按位进行异或运算。(异为1,同为0)
特点:
- 与自己异或为全0
- 与自己相反的异或为全1
- 所有值与0异或保持不变
- 所有值与1异或则取反
作用:
- 数据部分取反
- 同时清除寄存器及CF
1 | xor ax,ax |
算术指令
inc 指令 : 将操作数加1.(相当于c语言的num++)
1 | inc bx ;32位寄存器 自增1 |
dec 指令 : 将操作数减1.(相当于c语言的num–)
1 | dec [value] |
add/sub 指令 : 简单的加/减法指令,用于对字节、字和双字大小的二进制数据进行简单的加/减。
1 | add/sub destination,source |
add/sub 指令可以发生在:
- 寄存器 to 寄存器
- 内存 to 寄存器
- 寄存器 to 内存
- 寄存器 to 常量数据
- 内存 to 常量数据
adc 指令 : 带进位的加法指令,利用CF位上记录的进位值。
1 | 格式: |
sbb 指令 : 带错位减法指令,利用CF位上记录的借位值。
1 | 格式: |
mul 指令 : 乘法指令,对应位数(同为8位或者16位)
两个字节相乘(8位):
被乘数在AL寄存器中,乘数在存储器或者另一个寄存器中。
结果放在AX中,高8位存储在AH中,低8位存储在AL中。
两个单字相乘(16位):
被乘数在AX寄存器中,乘数在内存或其他存储器中。
高阶(最左侧)部分存储在DX中,而低阶(最右侧)存储在AX中。
例子:计算100*10
1 | mov al,100 |
例子:计算100*10000
1 | mov ax,100 |
div 指令 : 除法指令。
当除数为1个字节时:
除数在寄存器或内存单元中,被除数在AX中。
结果中的商放在AL中,余数放AH中。
当除数为1个单字时:
除数在寄存器或者内存单元中,被除数在DX和AX中。
结果中的商放在AX中,余数放在DX中。
shl 指令 : 逻辑左移指令,将数据向左移位,最后移出的一位写入CF,最低位用0补充。
sal 指令 : 算术左移指令,本质与shl相同。
shr 指令 :逻辑右移指令,将数据向右移位,最后移出的一位写入CF,最高位用0补充。
sar 指令 :算术右移指令,将数据向右移位,最后移出的一位写入CF,最高位用最高位相同的数字补充。
串传送指令
movsb(以字节为单位传送)
将ds:si指向的内存单元送入es:di中
DF=0则si=si+1 ;DF=1则si =si -1
movsw(以字为单位传送)
将ds:si指向的内存单元送入es:di中
DF=0则si=si+2 ;DF=1则si =si -2
rep是根据cx的值重复执行后面的串传送指令
1 | rep movsb |
cld 指令:将标志寄存器DF置0
std 指令:将标志寄存器DF置1
例子:用串传送指令,将data段的第一个字符串复制到它后面的空间中。
1 | assume ds:data,cs:code |
条件
汇编语言中的条件执行是通过几个循环和分支指令来完成的,这些指令可以更改程序中的控制流。
CMP 指令 : 比较两个操作数,从另一个操作数中减去一个操作数,以比较操作数是否相等。它不会干扰目标或源操作数。它与条件跳转指令一起用于决策。
1 | CMP DX,00 ;将DX与0进行比较 |
通常用于比较计数器值是否达到需要循环的次数:
1 | inc cx |
无条件跳转 : JMP 指令
1 | MOV AX, 00 ; 将AX初始化为0 |
(机器码:EB)jmp short 标号,对IP的修改范围为-128~127
1 | assume cs:codesg |
(机器码:E9)jmp near ptr 标号 ,修改范围为-32769~32767
(机器码:EA)jmp far ptr 标号,(CS)=标号所在段的段地址,(IP)=标号所在段中的偏移地址
条件跳转
| 指令 | 描述 | 检测的相关标志位 |
|---|---|---|
| je / jz (equal) | 等于则转移 | ZF = 1 |
| jne / jnz | 不等于则转移 | ZF = 0 |
| jb (below) | 低于则转移 | CF = 1 |
| jnb | 不低于则转移 | CF = 0 |
| ja (above) | 高于则转移 | CF = 0,ZF = 0 |
| jna | 不高于则转移 | CF = 1 或 ZF = 1 |
子程序

call s : 将当前IP或CS和IP压入栈中(压入栈中保留时IP已经指向下一条指令,所以保留的地址指向CALL的下一条指令,RET返回该地址可以继续执行),然后转移
call far ptr s : CS寄存器先进栈,IP寄存器后进栈
ret 指令 :返回指令,将栈顶一个字节弹出栈送进IP寄存器
retf 指令 : 先弹出的送入IP寄存器,后弹出的送入CS寄存器


相关程序
大小写字母转换
在ASCII编码中,61h表示“a”,41h表示“A”,相差20h。
“A” : 41H = 01000001B
“a” : 61H = 01100001B
就是第五位不同,所以可以考虑使用xor操作将第5位清零或置一以实现转换。
1 | assume cs:code,ds:data |
字符串或内存块内容的批量复制
例子:用si和di实现将字符串‘welcome to masm!’复制到他后面的数据区中。
分析:原来数据位置在data:0开始,有16个字节,所以后面的偏移地址为16.用ds:si指向要复制的源始字符串,用ds:di指向复制的目的空间,使用循环进行复制。
1 | assume cs:code,ds:data |
例子:内存块数据拷贝
1 | assume cs:code |
求一个或多个数的N次方
例子:计算2的12次方(但是这个add只局限于2的次方)
1 | assume cs:code |
例子:计算4的10次方(可以用于单个数的N次方)
1 | assume cs:code |
环境安装
本地环境为Windows,使用的工具有DOSBox、MASM5,8086汇编语言需要在DOS环境下进行,因此需要在电脑上安装一个DOS虚拟机。虚拟机软件选用DOSBox-x 0.83.19,该软件为绿色软件,将压缩包解压后运行文件夹中的DosBox-x程序即可。
DOS环境搭好后需要安装宏汇编开发包MASM5,具体方法为:
\1) 在本机硬盘上创建一个DOS目录,如D:\DOS ;
\2) 将MASM5.RAR复制到该目录并解压 ;
这里注意的是,masm的路径应该直接为D:\DOS:\MSAM5,里面包含masm.exe、link.exe、 debug.exe、 exe2bin.exe,分别用于汇编asm程序、连接、调用。
\3) 编辑DOSBOX-X目录下的 dosbox-x.conf 文件:
在文件末尾[autoexec]段落(功能是开机自动运行)下添加一行:
1 | mount c d:\dos |
该行将你的DOS目录映射为DOS虚拟机的C盘。
然后在上面若干行处将
1 | set path = Z:\;Z:\SYSTEM;Z:\BIN;Z:\DOS;Z:\4DOS;Z:\DEBUG;Z:\TEXTUTIL |
这一行修改为
1 | set path = Z:\;Z:\SYSTEM;Z:\BIN;Z:\DOS;Z:\4DOS;Z:\DEBUG;Z:\TEXTUTIL;C:\MASM5 |
这样即可将宏汇编MASM5的功能加入搜索路径,输入命令即可使用。
\4) 存盘退出,启动虚拟机即可
可以在DOS盘下建立一个exp目录,将实验程序源代码都存在目录里。源代码的编写用任何文本编辑软件编写均可。
至此汇编语言开发环境搭建完毕。
单代码段程序的编写
编写汇编程序
在记事本中写入汇编程序,并修改后缀名为.asm,存放在建立的D:\DOS:\exp中
汇编文件
点开解压之后的dosbox文件夹,双击exe打开,cd到存放文件的地方,输入masm回车,在之后的语句输入上述已编写的asm文件名(不需要后缀)然后连续敲回车,显示0 Warning/Severe Errors表明汇编成功,生成一个后缀为.obj文件(也可以检查一下masm是否可用)
还可以直接输入 masm [文件名]
以下是我输入的例子:
1 | assume cs:codesg |

连接文件
在汇编完成的语句后输入link,之后再输入文件名,连续敲回车,显示LINK:warning L4021:no stack segment表明连接成功,可以看到后缀为.exe的文件
同样也可以直接输入 link [文件名]

调试文件
输入debug [文件名].exe就可以进入调试,在短横线后输入命令即可。
以下为常用命令:
r:查看、改变寄存器的内容
查看就输入r,修改就输入r [需要修改的寄存器名字] ,然后输入修改的数值即可完成。

d:查看内存中的内容
直接输入d,可以查看预设地址内存处的128个字节的内容

也可以查看指定段的,输入d [后]:[前]

e:修改内存单元中的内容
可以写入数据、指令,内存中,数据和指令都是机器码,没什么区别
写入数据(需要指明地址和要写入的地址)
1 | e [地址] [数据](数据如果有多个,中间要加空格) |

逐个询问修改数据
1 | e [地址] |
输入这个地址就会告诉你这个地址的内容,修改就在后面写上需要修改的数据,如果想对下一个字节进行修改,敲空格即可跳到下一个地址

u:将内存中的机器指令翻译成汇编指令

a:以汇编的指令格式在内存中写入机器指令
1 | a [写入的地址] |
t:单步运行

注意观察每次输入t指令后,ax,bx的值变化。可以观察出t命令是使用一次执行一次汇编指令。
q:退出debug