在上篇文章中我们具体介绍了汇编语言的一些背景, 以及 8086汇编 工作原理, 在这篇文章中具体讲解8086汇编指令, 看完这个有助于学习ARM汇编. 文章后面手把手教你怎么利用 MASM 编译执行程序.
转移指令
8086 提供了一个 mov
指令, 传送指令, 可以用来修改大部分寄存器的值,
CPU 从何处执行指令是由 CS, IP 中的内容决定的, 我们可以通过 Jmp
这类转移指令, 来修改 CS, IP 的值来控制 CPU 执行目标指令.
-
- jmp 2AE3: 3, 执行后: CS=2AE3H, IP= 0003H, CPU将从 2AE33H 处读取指令.
jmp 段地址: 偏移地址 : 用指令中的段地址, 来修改 CS 的值, 用偏移地址来修改 IP 的值.
- jmp 2AE3: 3, 执行后: CS=2AE3H, IP= 0003H, CPU将从 2AE33H 处读取指令.
-
- jmp ax, 指令执行前: ax= 1000H, CS=2000H. IP=0003H
指令执行后: ax= 1000H, CS=2000H. IP=0003H
- jmp 某一合法寄存器 : 用寄存器中的值来修改IP.
- jmp ax, 在含义上类似, mov IP, ax.
- jmp ax, 指令执行前: ax= 1000H, CS=2000H. IP=0003H
-
- jmp 0100H, 直接改变 IP 的值.
DS和[address]
CPU 要读写一个内存单元时, 必须要先指出内存单元的地址, 在 8086 中, 内存地址(物理地址) 由 段地址 和 偏移地址 组成.
- 8086 中有一个 DS段寄存器, 通常用来存放要访问的数据的段地址
对 al寄存器 写数据
mov bx, 1000H
mov ds, bx
mov al, [0]
- 上面3条指令的作用将10000H(1000:0)中的内存数据赋值到 al寄存器 中.
- mov al,[address] 的意思将 DS:address 中的内存数据赋值到 al寄存器 中.
- 由于 al 是8位寄存器,所以是将一个字节的数据赋值给 al寄存器.
- 8086不支持将数据直接送入到 段寄存器, 不能直接 mov ds, 1000H
对 al寄存器 读数据
mov bx, 1000H
mov ds, bx
mov [0], al
大端模式和小端模式
- 大端模式(Big Endian): 指数据的
高字节
保存在内存的低地址
中,而数据的低字节
保存在内存的高地址
中 ( 高低\低高 ) - 小端模式(Little Endian): 指数据的
高字节
保存在内存的高地址
中,而数据的低字节
保存在内存的低地址
中 ( 高高\低低 )
大端模式: PowerPC, IBM, Sun
小端模式: x86, DEC.
ARM 既可以工作在大端模式, 也可以工作在小端模式.
mov 指令, add 指令, sub 指令
add , sub 和 mov 一样, 都有两个操作对象.
mov 寄存器, 数据 比如: mov ax, 8
mov 寄存器, 寄存器 比如: mov ax, bx
mov 寄存器, 内存单元 比如: mov ax, [0]
mov 内存单元, 寄存器 比如: mov [0], ax
栈
栈 是一种具有特殊访问方式的空间 (后进先出, LIFO, Last In First Out)
- 8086 会将 CS 作为代码段的段地址,将 CS:IP 指向的指令作为下一条需要取出执行的指令
- 8086 会将 DS 作为数据段的段地址,mov ax,[address] 就是取出 DS:address 的内存数据放到 ax寄存器 中.
- 8086 会将 SS 作为栈段的段地址,任意时刻,SS:SP 指向栈顶元素.
- 8086 提供了 PUSH (入栈) 和 POP (出栈) 指令来操作 栈段 的数据.
- 比如 push ax 是将 ax的数据入栈,pop ax 是将栈顶的数据送入ax
push ax
- SP = SP - 2, SS: SP 指向当前栈顶前面的单元, 以 当前栈顶 前面的单元为新的栈顶.
-
将 ax 中的内容送入到 SS: SP 指向的内存单元处, SS:SP 此时指向新栈顶.
pop ax
- 将 SS: SP 指向的内存单元处的数据送入 ax 中.
- SP = SP + 2, SS: SP 指向当前栈顶下面的单元, 以 当前栈顶 下面的单元为新的栈顶.
值得注意的是, 随着 push 和 pop 的操作, 原来内存单元上的数据并没有被清楚掉, 而是随着 SP 的变化, 称为垃圾数据, 当有新的数据 push 进来时, 原数据会被覆盖.
对于上面这种 10000H ~ 1000FH 这段栈空间, 初始状态为空, 此时 SS = 1000H, SP应该为 0010H, 因为栈空时, SS: SP 指向栈空间最高地址单元的下一个单元.
push 与pop
push 寄存器 ; 将一个寄存器中的数据入栈
pop 寄存器 ; 出栈, 用一个寄存器接收出栈的数据
push 内存单元 ; 将一个内存单元处的字入栈
pop 内存单元 ; 出栈, 用一个内存字单元接收出栈的数据
mov ax, 1000H
mov ds, ax ; 内存单元的段地址放在 ds 中
push [0] ; 将 1000: 0 处的字压入栈.
pop [2] ; 出栈, 出栈的数据送入 1000: 2处
将 10000H ~ 1000FH 这段空间当做栈, 此时为空栈, 设置 AX = 001AH, BX = 001BH, 利用栈, 交换 AX 和 BX 的数据.
mov ax, 1000H
mov ss, ax
mov sp, 0010H ; 栈空时, SP 指向栈顶(栈空间的最高地址)单元的下一单元
mov ax, 001AH
mov bx, 001BH
push ax
push bx
pop ax
pop bx
小结:
- 对于 数据段 , 将它的段地址放在 DS 中, 用 mov, add, sub 等访问内存单元的指令时, CPU 就将我们定义的数据段中的内容当做数据来访问.
- 对于 代码段 , 将它的段地址放在 CS 中, 将段中的第一条指令的偏移地址放在 IP 中, 这样 CPU 就将执行我们定义的代码段中的指令.
- 对于 栈段 , 将它的段地址放在 SS 中, 将栈顶单元的偏移地址放在 SP 中, 这样 CPU 在需要进行 pop / push 等操作时, 就将我们定义的栈段当做栈空间来用.
终于可以开始我们的第一个完整的汇编程序.
MASM (Microsoft Macro Assembler) 是微软公司微处理器家族开发的汇编开发环境, 因为MASM工具 是一个exe的执行文件, 所以为了在 Mac OS 上能使用它, 我们需要借助DOSBox.
DOSBox 是一款模拟器软件, 为本地的 DOS 程序提供执行环境, (DOS指 磁盘操作系统), 最早期是为了运行计算机程序设计, 目前已经支持 Windows, Mac OS 多个平台, 简单来说, 它就是一个计算机的模拟, 我们可以在官网下载最新的 DOSBox.
使用 DOSBox
-
双击打开程序, 默认在 Z 盘, 可以使用 dir, 查看系统的情况
-
为了方便处理, 在 用户目录下 新建一个 LCDOSBox 文件夹, 用来存储我们编写的代码文件, 以及 MASM 工具. 这些工具可以在这里下载.
在 DOSBox 界面挂载我们的文件目录, 这里
c
指的是 虚拟c盘,~/LCDOSBox
指虚拟的文件夹位置.
Z:\> mount c ~/LCDOSBox
- 进入 c 盘, 直接执行当前目录中的 debug.exe 程序.
Z:\> c:
C:\> debug
这里的 -r 指令, 用来显示寄存器的内容.
- 编写我们的asm文件.
; 数据段
data segment
string db 'Hello World! $'
data ends
; 代码段
code segment
assume cs: code, ds: data ; 声明代码段和数据段
start:
mov ax, data
mov ds, ax ; 设置 ds 为数据段
mov ah, 9h ; 功能号 9h 代表在屏幕显示字符串
;mov dx, offset string ; ds: dx 代表字符串的地址
lea dx, string ; 同上 lea指令的功能是将存储单元的有效地址(偏移地址)传送到目的操作数
int 21h ; 执行 DOS 系统功能调用
mov ah, 4ch ; 功能号 4ch 代表程序退出
int 21h
code ends
end start ; 遇到 end 停止编译 开始执行 start 里的内容
对于这个程序, 有几点我需要作说明
- 汇编程序由 2 类指令组成
- 汇编指令: 如 mov, add, sub 等, 它是有对应的机器指令, 可以被编译器编译成机器指令, 被 CPU 执行.
- 伪指令: 如 assume, segment, ends, end 等, 他们没ban有对应的机器指令, 由编译器解析, 最终不被 CPU 执行.
- 注释符号用
;
- assume: 声明 code段 是 cs 段, data段是 ds 段.
- segment 和 ends 的作用是定义一个段, segment 代表一个段的开始, ends代表一个段的结束.
-
int 21h
, 用于执行 DOS 系统功能调用, 这是一个中断.
-
编译并链接生成 exe 文件.
如果你在利用 DOSBox 执行程序的时候碰到 Unable to open input file:xxx.asm 的错误, 但是代码文件确实存在. 可能是由于文件的名字系统的差别造成, 你可以这样做
- 命令行操作是支持 tab + Hellox, 这样会快速补全文件名, 即使补全的名字和你实际的文件名不一致, 也没关系, 实际上系统是认可的.
- 而且, 文件名最好是英文, 而且是小写, 这样 通过 tap 键 补全的文件名通常不会错.
如果你在执行的过程中碰到你需要输入的, 直接 enter 键 跳过.
- 调试程序
C:\> debug
- -u 将内存中的机器指令翻译成汇编指令.
- -u 段地址: 偏移地址 可以将内存中内容翻译成对应的汇编指令.
- -r 查看修改, CPU寄存器中的内容.
- -r 寄存器的名称, 可以修改寄存器的值.
- -g [value] 执行到目标行,如:-g f 为执行到第16行, 注意查看此时 CS: IP 的值的变化
- -t [value] 执行机器指令 如:-t 2 为执行2条指令后停下来. 默认执行一条指令. 注意查看此时CS: IP 的值的变化.
- -d 查看内存中的内容
- d 段地址: 偏移地址 , 查看特定位置的内存数据
- d 段地址: 起始偏移地址 结尾偏移地址 , 查看特定范围内的地址
- -e 段地址: 偏移地址 数据串 修改特定位置的内存数据.
- -a, -a 段地址: 偏移地址 可以从某位置开始写入汇编指令.
- -q 退出debug
中断
中断是由于软件的或硬件的信号,使得CPU暂停当前的任务,转而去执行另一段子程序.
- 硬中断(外中断), 由外部设备(比如网卡、硬盘)随机引发的,比如当网卡收到数据包的时候,就会发出一个中断.
- 软中断(内中断), 由执行中断指令产生的,可以通过程序控制触发.
- 从本质上来讲, 中断 是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,跳到中断处理程序的入口点,进行中断处理.
- 在汇编程序中, 可以通过
int n
产生中断.- n 是中断码,内存中有一张中断向量表,用来存放中断码对应中断处理程序的入口地址
- CPU在接收到中断信号后,暂停当前正在执行的程序,跳转到中断码对应的中断向量表地址处,去执行中断处理程序.
- 常见中断包含以下:
int 10h
用于执行BIOS中断,int 3
是“断点中断”,用于调试程序,int 21h
用于执行DOS系统功能调用,AH
寄存器存储功能号.
指令要处理的数据长度
8086指令能处理2种 尺寸的数据, byte , word, 还有其他指令集中的 dword , 通过这来指明需要操作内存的数据长度.
- mov byte ptr [0], 20H , 将 20H 放入 0 位置内存的
字节
单元, 占 1 个字节. - mov word ptr [0] 20H, 将 20H 放入 0 位置内存的
字
单元, 占 2 个字节. - mov dword ptr [0] 20H, 将 20H 放入 0 位置内存的内存单元, 占 4 个字节.
比如 pop [0], push [0] 操作的数据长度默认只能是 2 个字节.
参考
维基百科-DOSBox
如何用MASM5.0