汇编语言学习笔记
前言
本来是想学基于 ARM 架构的汇编语言,但苦于基础薄弱,既不会 ARM ,又没有系统的学过计算机组成原理。正巧,手上有本王爽的《汇编语言》,略作翻阅,竟发现内容如此全面。故今天开一文来撰写本书的学习笔记。前几年是想着笔耕不辍,坚持写博文的;可这几年因种种原因渐渐放弃了技术文章的写作,那就让这篇学习笔记成为下一个阶段的开始吧。
本文的组织形式是由一小篇一小篇的博文拼凑好的,最后会统一写好上传到博客上。如果博文太长,我将会把整个文章拆成好几段的形式。
才疏学浅,再加上基础薄弱,学习新知识难免会有理解不到位的地方。如有错误,欢迎斧正。
——2022.3.6 kenlig 于武汉
第一章
这一章大概是非常基础的,总体讲了下计算机是什么,内存是什么和我们应该干什么。注意,这本书是针对 8086 的汇编语言教学,我们通过学习 8086 CPU 的汇编语言来理解概念。
寻址能力和总线的宽度有关,具体算法就是 2 的总线宽度次方。
注意内存地址空间的概念。没学过之前还是有点错觉的,实际上是把所有的存储器看成了一个逻辑处理器,我们只需要操作内存地址空间就够了。这个东西实际上是被 CPU 拼凑出来的。
约定:十六进制数后面加H。
第二章
本章内容主要是寄存器和CPU如何执行指令的内容。
通用寄存器
有四个: AX BX CX DZ
每个又能分为 H L
这两段。比如, AX=AH+AL
。这四个寄存器都是 16 位的,分开的两个则是 8 位的。
所以,我们可以和前面学过的联系起来:字节 = 8 位,字 = 两个字节。一个字可以存在一个 16 位的寄存器中,当然也可以拆开来看。
mov add指令
mov
和 add
指令:第一个是把右边的移到左边,第二个是把右边的加在左边上。
注意,进位得到的要被截取,如果运算出来了 1044C
这种东西,应该把第一位的 1
去掉。同时,操作位数要一致。如果不一致的话,肯定不会通过(
物理地址
和第一章呼应,内存地址空间是被生成的。
8086机是16位的 CPU 。 这代表着:寄存器、处理器处理的数据规模、数据通路都是16位的。然而他有20位的地址总线,如果我们只传16位,那显然只有 2^16 = 64 KB 的寻址能力。 8086 将两个 16 位地址合成为一个 20 位的物理地址,那么我们的寻址能力就是2^20=1024KB=1MB。算法见下文。
物理地址=段地址*16+偏移地址
你可以把它看成“物理地址=基础地址+偏移地址”,基础地址就是段地址<< 4(*16)。为什么要这么做?因为我们要有很大的寻址空间(64KB 在现在显然不是很够用),但总线的数量是有限的,所以我们采用16+16=20的方式,来让我们的寻址空间更大一些。
为什么要*16?
对于16进制的数来说,数 x 乘以16就等于后边加一个0。这样的话,和偏移地址相加,刚好能组成 20 位。
你懂吧,什么都是被设计出来的(
段
数据都是存在内存里的,但是内存是一大块东西。我们为了协调“诶这段内存到底是干啥的“之类的问题,为什么不把内存划分成一块块的呢?
好,那我们就把内存划成一块一块的。但是,这不代表物理上被划分成一块块的,我们只需要在逻辑上划分就可以了。这就是内存段,你可以理解为这是一块一块的内存,而16位地址的寻址能力是 64KB ,所以一个段的大小也就是 64KB 。
段寄存器
刚刚我们提到了段,而且通过物理地址的计算方法我们也能轻松寻址。而计算物理地址的两个值:段地址和偏移地址,也被存储在8086CPU中的寄存器中。我们先来看下指令寄存器: CS
和 IP
。CS
是代码段寄存器, IP
是指令指针寄存器。
执行指令的过程
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器。
- IP+=指令的长度。(为了读取下条指令)
- 执行
修改CS:IP
8086CPU当然会提供工具来修改CS:IP的值,否则的话怎么控制运行哪段代码呢?
jmp 指令
用法:jmp CS:IP
或者是 jmp IP
上述的寄存器处填上你需要把这个寄存器变为什么值即可。
代码段
我们已经知道了段
的概念,那我们的代码也应该存在代码段里。(同理,数据也是这样的,但代码就是数据,CPU得知道那个地方是代码)
CPU只认为CS:IP
指向的地方为代码段。
第三章
老实说,以前基础打得不牢,看第三章有点难受
本章从CPU访问内存的角度分析了几个寄存器。也就是说,这一章着重于讲解“数据在哪里”
大小端
在《深入理解计算机系统》里已经详细的学习过了。
详细一点:一个字符占用一个字节,但是一个字(是16位)要占用两个字节。如果我们把数字的高位存储在内存的高位中,就是小端;反之则是大端。
举个例子:4E20H
如果用小端序就是20H,4EH
DS和[]
这是啥?参考第二章的CS:IP,我们要是访问数据也应该可以用一个什么寄存器:什么寄存器的方式来访问。同样,依然按照物理地址的那条规律来算出实际上的存储位置。
8086CPU中用DS寄存器来存储某个数据段的地址,用[]来访问偏移地址。
举个例子:DS=10000H,[0]就是10000H,[2]就是10002H。
但是,DS寄存器不能直接放入。比如,mov ds,1000H
这句话是是错的!我们只能用
mov ax,1000H
mov ds,ax
的方式来修改DS寄存器的内容。
修改偏移地址的方法为mov [0],ax
。这个偏移地址是用DS寄存器送过来的。
mov add sub指令的详细理解
前面我们已经写了mov和add指令,但是这个指令到底能修改什么呢?
我的理解是:左参数可以为寄存器、内存单元;右参数不仅可以是寄存器和内存单元,也可以是数据。
sub指令:左边的减去右边的。比如:sub ax,[2]
的意思是把ax寄存器的内容减去DS:2寄存器的内容,数据存储在ax寄存器中。
数据段
第二章的“段-代码段”这一节已经讲过了代码段,而数据段其实就是一段一段的内存,里头装着数据。我们用DS:[]的访问方式来访问数据段。
栈(Stack)
我们不仅可以在C语言中实现栈数据结构,也可以学习8086CPU中有的系统栈。爆栈了就是因为这个。
8086提供一段内存充当栈的功能。
SS:SP寄存器
这两个寄存器依然可以用CS:IP的方式来理解。不同的是,SS:SP是来访问栈地址的。任意时刻,SS:SP指向栈顶元素。如果栈为空,那么指向栈顶的更后一个元素。这样的话,我们在任意时刻访问到栈顶元素的下个地址,来push进元素。
SP在每次push后会把自己的地址+2,这样才能访问到下一个地址。
超界怎么办
无解。写代码的时候注意一下。
push和pop指令
这玩意和C语言描述的Stack功能一样。我们直接说一下语法就可以了。
push 寄存器
把一个寄存器的元素入栈
pop 寄存器
把栈顶元素拿到这个寄存器中
怎么执行
执行push的时候,CPU先改变SP,然后往SS:SP的地方扔数据。
执行pop的时候,CPU先把SS:SP的数据拿出来,然后改变SP。
栈段
和数据段代码段一样,不同的是用SS:SP访问。一个栈的容量就是偏移地址的寻址范围:64KB。
为了确定这个段到底是干什么的,CPU只需要确定CS IP DS SS SP这几个寄存器指向的位置。实际上,他们都是数据(二进制)存储在内存中的。