ARM 汇编基础知识教程系列将逐步涵盖以下主题:
第1部分:ARM汇编简介
第2部分:数据类型 寄存器
第3部分:ARM指令集
第4部分:内存指令。
第5部分:加载和存储数据
第6部分:条件执行和分支
第7部分:堆栈和函数
要跟上示例,您将需要一个基于ARM的实验环境。如果你没有ARM设备(如Raspberry Pi),你可以按照本教程在虚拟机中使用QEMU和Raspberry Pi发行版来建立自己的实验环境。如果你不熟悉用GDB进行的基本调试,你可以在本教程中获得基本知识。在本教程中,重点是ARM 32位,例子是在ARMv6上编译的。
为什么是ARM?
本教程一般是为那些想学习ARM汇编基础知识的人准备的。您可能已经注意到,ARM处理器在您周围随处可见。当我环顾四周时,我可以数出家里采用ARM处理器的设备远远多于Intel处理器。这包括手机、路由器,更别忘了最近似乎销量爆棚的物联网设备。也就是说,ARM处理器已经成为世界上最广泛的CPU内核之一。
尽管ARM汇编语言可能是广泛使用的最简单的汇编语言。那么,为什么没有更多的人关注ARM呢?也许是因为涉及英特尔开发的学习资源比涉及 ARM 的多。在这里的系列教程中,我们将重点介绍ARM汇编基础知识。
ARM处理器与 英特尔处理器
英特尔和ARM之间有许多不同之处,但主要区别在于指令集。英特尔是一个CISC(复杂指令集计算)处理器,它的指令集更大、功能更丰富,并允许许多复杂指令访问内存。因此,它有更多的操作、寻址模式,但比ARM更少的寄存器。CISC处理器主要用于普通PC、工作站和服务器。
ARM是一种RISC(精简指令集计算)处理器,因此它有一个简化的指令集(100条指令或更少),并且比CISC有更多的通用寄存器。与英特尔不同,ARM使用仅对寄存器进行操作的指令,并使用加载/存储内存模型进行内存访问,这意味着只有加载/存储指令可以访问内存。这意味着在ARM上增加一个特定内存地址的32位数值需要三种指令(加载、增加和存储),首先将特定地址的数值加载到寄存器中,在寄存器中增加数值,然后从寄存器中存储到内存中。
减少指令集有其优点和缺点。其中一个优点是指令可以更快执行,可能会有更高的速度(RISC系统通过减少每个指令的时钟周期来缩短执行时间)。缺点是,较少的指令意味着要更加强调用有限的指令有效地编写软件。同样需要注意的是,ARM有两种模式,ARM模式和拇指模式。拇指指令可以是2字节或4字节(在第3部分:ARM指令集中有更多介绍)。
ARM和x86之间的更多区别是:
在ARM中,大多数指令可用于条件执行。
Intel x86 和 x86-64 系列处理器使用 little-endian 格式
ARM 体系结构在版本 3 之前是 little-endian。从那时起,ARM处理器变成了BI-endian,并具有允许切换endianness的设置。
不仅Intel和ARM之间存在差异,而且不同的ARM版本本身也存在差异。本系列教程旨在尽可能地保持通用性,以便您对ARM的工作原理有一个大致的了解。一旦您了解了基本原理,就很容易了解您所选择的目标ARM版本的细微差别。本教程中的例子是在32位ARMv6(Raspberry Pi 1)上创建的,因此,解释与这个确切的版本有关。
不同的ARM版本的命名也可能令人困惑:
编写汇编
我们首先需要了解汇编语言编程的基础知识,这需要在开始之前有一些背景知识。
您不需要知道汇编语言的每一个小细节,但其中一些细节对于理解大局是必需的。
本系列教程将涵盖基础知识。如果你想了解更多,你可以访问本章末尾列出的链接。
那么,究竟什么是汇编语言?汇编语言只是机器码之上的一个薄薄的语法层,机器码由指令组成,以二进制表示法(机器码)进行编码,也就是我们的计算机所能理解的。那么,为什么我们不直接写机器码呢?嗯,这将是一个痛苦的过程。出于这个原因,我们要写汇编,即ARM汇编,这对人类来说更容易理解。我们的计算机本身不能运行汇编代码,因为它需要机器代码。我们将使用的将汇编代码组装成机器代码的工具是GNU Binutils项目中的GNU Assembler,其名称为as,可用于具有*.s扩展名的源文件。
一旦你写好了扩展名为*.s的汇编文件,你就需要用as来组装它,并用ld来链接它。
揭开汇编的面纱
让我们从最底层开始,一路往上走到汇编语言。在最底层,我们的电路上有我们的电信号。信号的形成是通过将电压切换到两个级别中的一个,例如0伏("关闭")或5伏("开启")。因为仅仅通过观察,我们不容易知道电路的电压是多少,所以我们选择用直观的数字0和1来书写电压的开/关模式,这不仅是为了表示信号的缺失或存在,而且也是因为0和1是二进制系统的数字。然后我们将0和1的序列分组,形成机器码指令,这是计算机处理器最小的工作单元。下面是一个机器语言指令的例子。
1110 0001 1010 0000 0010 0000 0000 0001
到目前为止还不错,但我们无法记住这些模式(0和1)中的每一个意味着什么。 出于这个原因,我们使用所谓的助记符、缩写来帮助我们记住这些二进制模式,每个机器码指令都有一个名字。这些记忆法通常由三个字母组成,但这并不是必须的。我们可以用这些助记符作为指令写一个程序。这个程序被称为汇编语言程序,而用于表示计算机机器码的一组助记符被称为该计算机的汇编语言。因此,汇编语言是人类用来为计算机编程的最低级别。一条指令的操作数排在助记符之后。下面是一个例子。
MOV R2, R1
现在我们知道,汇编程序是由称为助记符的文本信息组成的,我们需要将其转换为机器码。如上所述,就ARM汇编而言,GNU Binutils项目为我们提供了一个名为as的工具。使用像as这样的汇编器将(ARM)汇编语言转换为(ARM)机器代码的过程称为汇编。
综上所述,我们了解到计算机能够理解(响应)电压(信号)的存在或不存在,并且我们可以用0和1(比特)的序列来表示多个信号。我们可以使用机器代码(信号序列)使计算机以某种定义明确的方式做出反应。因为我们无法记住所有这些序列的含义,所以我们给它们以缩写--助记符,并使用它们来表示指令。这套助记符是计算机的汇编语言,我们使用一个称为汇编程序的程序将代码从助记符表示转换为计算机可读的机器代码,就像编译器对高级语言的作用一样。