基础知识
早期机器语言:01打孔。
汇编语言:机器语言便于记忆的格式。通过编译器替换为机器码。
寄存器:cpu 中的存储器。比如汇编中 AX BX 就是寄存器的代号。
存储器中的存储单元是 8 bit,从0开始顺序编号。
汇编语言的组成
- 汇编指令(机器码的助记符)
- 伪指令(编译器执行)
- 其他符号(编译器识别)
核心部分是汇编指令。
CPU 读写存储器
需要知道:存储器地址,器件的控制命令,读写的数据。
CPU 连接其他芯片的线叫做总线,分为地址、数据、控制总线。
地址总线的宽度决定了寻址能力,数据总线的宽度决定了数据传送速率,控制总线的宽度决定了有多少种控制指令。
内存

(汇编编程要从 CPU 角度出发)
对于 CPU 来说,看到的就是各个 RAM ROM 对应的地址空间。所有存储单元对 CPU 来说都处于一个统一逻辑存储空间中,也就是内存地址空间。
寄存器
CPU 内部总线连接内部的运算器、控制器、寄存器,外部总线实现 CPU 和主板上其他部件的联系。
以 8086CPU 为例,所有寄存器都是16位两字节的。其中存放一般数据的是通用寄存器,如 AX BX CX DX .
8086CPU 上一代 CPU 是8位寄存器。不过 8086 可以通过拆分通用寄存器为(AH AL)形式来向下兼容。
能存储的最大数据值:2^16 -1.
字
指明一个存储位置的地址。8086 CPU 是16位,连着两个字节,先低位后高位。可以存储在16位寄存器中。
汇编指令示例:不区分大小写。
这里注意给 HL 赋值溢出的问题。进制位无法在寄存器中保存。但是也并没有被丢弃,这一点后面再展开讲。
物理地址
内存中的真实地址,不只是逻辑上的了。
8086 内部是16位结构,只能传送16位地址,寻址能力64K。外部则是20位结构,实现方法是两个16位地址合成。16位的段地址和16位的偏移地址经过地址加法器合成。(段地址*16+偏移地址,即段地址左移4位)意思就是20位我们的寄存器存储不下,所以我们用16位段和额外的4位偏移地址表示。
表示方式有多种,比如 2000H 1F60H 或 2100H 0F60H。
算法:段*16+偏移。
指令执行
段地址存储在 CPU 上的四位寄存器中,CS,DS,SS,ES. 即:代码,数据,堆栈,附加。
CS 是代码段寄存器,用于存放指令的段地址,IP 是指令指针寄存器。
IP 相当于偏移码。两者结合取出一条指令中的数据(如 B82301),而后放到指令缓冲器中,再执行。然后 IP+3 跳到下一条指令。
开机后 CS=FFFFH,IP=0000H,cpu 执行 FFFF0H 的指令作为开机后第一个指令。
CS IP 的值的修改不能用一般的方法,如之前学到的 mov 指令。要用专门的转移指令。
转移指令:如 jmp 3:0B46,即 CS=0003,IP=0B46,物理地址=00030+00B46=00B76。
仅修改 IP:jmp 某一合法寄存器,如 jmp AX 就是把 AX 的值放入 IP。
段
内存自己并没有分段,分段的只是 CPU 逻辑上给内存分的段。
段长度为16倍数,连续。包含若干指令。
CPU 执行段的时候也不关注段怎么划分,它只注重眼前手里的 CS IP 去找对应的指令。
debug 程序
debug 是一个程序,可用于8086模式 debug 或写程序。
win10 不能直接兼容 debug.exe,需要配合 DosBox 使用。
DosBox 每次启动后需要先挂载 asm 文件和 debug.exe 所在的位置,语法:mount c path
.可以直接在 option.bat 里配置,使得打开文件时自动启动挂载。
挂载后输入 c: 进入c盘。再输入 debug 运行 debug.exe。
解决win10学习汇编工具的烦恼——汇编Debug的下载和使用(包含可用下载链接)_汇编debug下载_NULL not error的博客-优快云博客
r:查看或修改某寄存器值。修改:r 寄存器名
,下一行跟修改数值。
d:查看内存中的内容。
e:以机器指令格式改写内存中内容。但是 ROM 中的数据无法修改,比如 fff0:00 ~ ff 里的生产日期信息,修改后再查看值没变。
b810 里的地址是显存地址,用 e 修改后会直接改变屏幕上的显示。
a:以汇编指令格式写入指令。
a 段地址:偏移地址
输入a指令后逐行输入代码,按两次回车退出。d 段地址:偏移地址
可以查看内存中的指令内容,可以类似 d fff0:0 ff
查看一整段。
d换成u可以翻译为汇编语言查看。
代码执行:先改变CS IP 位置,用r查看当前指针位置,看下一条语句是否是 mov ae,4e20.
然后按下t就执行了当前语句。
例:一个不断*2的程序:
在2000:0 位置写入:
mov ax,1 //地址:20000
add ax,ax //地址:20003
jmp 2000:3 //地址:20006
执行顺序:ax=1,ax * 2,ax * 2,ax * 2……
内存访数据
依靠数据段地址 ds 和偏移地址。
mov bx,1000H
mov ds,bx //不能直接送入段寄存器,所以可以用通用寄存器赋值
mov al,[0] //把ds:0的内存数据读到al里。[]代表偏移地址,这时不用写ds,cpu会自动把数据段地址和偏移地址合起来。
寄存器中数据写入内存就反过来写。
一个内存中的字和段寄存器都是16位的,8086一次也能传送16位。
mov al,[0] 就只把 10000 位置的一个字节放到 al 中。如果是
mov ax,[0] 就是把 10001 10000 两个字节拼成一个字放到 ax 中。
执行程序顺序:先 e 段:偏移 值
为内存赋值,然后 a 段:偏移 程序
写入汇编程序,然后 r 调整 cs ip 指针,t 逐行执行代码。
栈
LIFO。
CPU 提供相关指令来以栈的方式访问内存空间。
push ax:ax 数据入栈。
pop ax:出栈数据给 ax。
都是以字为单位执行的。
CPU 通过 SS 寄存器的指向,可知这一片内存空间被用作栈。SP 寄存器存储栈顶指针的偏移地址。
如图,数据先存低位再存高位。栈从高位堆到低位,入栈指针-2,出栈+2.
初始栈为空时,sp 在 1000F+1=10010 的位置。存入第一个元素时SP+2,再存入数据。
pop 和 push 相反,先取数据再移动指针。1000C 1000D 里的数据并没有消失,只是改变了索引,类似硬盘格式化。
栈顶和栈底越界是危险的。8086 CPU 并没有保存栈范围的寄存器,无法校验栈的安全范围。CPU 只关心当前栈顶在何处,以及当前要执行的指令是哪一条。
因此我们编程时要自行注意。
因为 push pop 只会改变 sp,因此栈的变化最大范围是0~FFFFH。
例:栈范围10000~1FFFFH,SS=1000H,求空栈时SP的值。
答:栈第一个元素位置:1FFFEH,即SS:1000H,SP:FFFEH.
再出栈:SP+=2,SP=0000H。