LV01-04-ARM汇编-01-ARM汇编基础

本文主要ARM汇编基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
Win32DiskImager Win32DiskImager v1.0
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
https://elixir.bootlin.com/linux/latest/source 在线阅读linux kernel源码
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
ARM Cortex-A7 MPCore Technical Reference Manual Cortex-A7 MPCore技术参考手册
ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition ARM架构参考手册ARMv7-A和ARMv7-R版
Arm Generic Interrupt Controller Architecture Specification- version 3 and version 4 Arm通用中断控制器架构规范-版本3和版本4
ARM Generic Interrupt Controller Architecture Specification - Version 2.0 Arm通用中断控制器架构规范-版本2.0
ARM Cortex-A Series Programmer's Guide for ARMv7-A Cortex-A系列ARMv7-A编程指南

汇编相关可以参考《ARM® Architecture Reference Manual ARMv7-A and ARMv7-R edition 》的Chapter A5 ARM Instruction Set Encoding 部分。

一、CISC和RISC

根据指令复杂度来区分,所有 CPU 可以分为 2 类:

  • CISC 复杂指令集计算机, Complex Instruction Set Computer,比如x86
  • RISC 精简指令集计算机, Reduced Instruction Set Computing,比如ARM, RISC-V

比如,对于加法运算: a = a + b,它涉及 4 个步骤的操作:读出 a,读出b,计算 a+b,把结果写回 a。

(1)使用 CISC(复杂指令集计算机,比如 x86)提供的加法指令:只需要一条指令即可完成这 4 步操作。当然,这一个指令需要多个 CPU 周期才可以完成。
(2)而 RISC 不提供“一站式”的加法指令:需调用四条指令完成两数相加:内存 a 加载到寄存器,内存 b 加载到寄存器,两个寄存器中数相加,寄存器结果存入内存 a。

ARM 芯片属于精简指令集计算机(RISC: Reduced Instruction SetComputing),它所用的指令比较简单,有如下特点:

(1)对内存只有读、写指令。

(2)对于数据的运算是在 CPU 内部实现。

(3)使用 RISC 指令的 CPU 复杂度小一点,易于设计 。

二、为什么要学习汇编?

为什么要学ARM汇编?

我们在学习 STM32的时候几乎没有用到过汇编,但是我们在进行嵌入式 Linux 开发的时候是绝对要掌握基本的 ARM 汇编,对于 Cortex-A 芯片来讲,大部分芯片在上电以后 C 语言环境还没准备好,所以第一行程序肯定是汇编的,至于要写多少汇编程序,那就看我们能在哪一步把 C 语言环境准备好。

所谓的 C 语言环境就是保证 C 语言能够正常运行。 C 语言中的函数调用涉及到出栈入栈,出栈入栈就要对堆栈进行操作,所谓的堆栈其实就是一段内存,这段内存比较特殊,由 SP 指针访问, SP 指针指向栈顶。芯片一上电 SP 指针还没有初始化,所以 C 语言没法运行,对于有些芯片还需要初始化 DDR ,因为芯片本身没有 RAM ,或者内部 RAM 不开放给用户使用,用户代码需要在 DDR 中运行,因此一开始要用汇编来初始化 DDR 控制器。后面学习 Uboot 和 Linux 内核的时候汇编是必须要会的。

官方文档?

我们在 ARM 的官网上可以找到很多的芯片手册以及编程指南,关于这篇笔记中的 ARM 指令,我们可以参考这篇文档:《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》的 A4 章节,我们可以在这一章节系统的了解到 Cotex-A7 的所有汇编指令。我使用的开发板使用的 Cotex-A 系列的 ARM 处理器,所以这份文档就可以了。

三、 GNU 汇编语法

1. 伪指令和伪操作

1.1 伪指令

伪指令( Pseudo Instruction )是用于对汇编过程进行控制的指令,该类指令并不是可执行指令没有机器代码,只用于汇编过程中为汇编程序提供汇编信息。例如,提供如下信息:哪些是指令、哪些是数据及数据的字长、程序的起始地址和结束地址等。伪指令有 2 个特点:

(1)由于是伪“指令”,因而它只存在于汇编语言中。高级语言中不叫指令,叫语句;

(2)由于是“伪”指令,也即“假”指令,因而不是可执行指令,不会产生机器代码,不会占用 ROM 空间,只用于汇编过程中为汇编程序提供汇编信息。

1.2 伪操作

汇编语言程序语句除指令以外还可以由伪操作和宏指令组成。伪操作不像机器指令那样是在程序运行期间由 CPU 来执行的,它是在汇编程序对源程序汇编期间由汇编程序处理的操作,它们可以完成如数据定义、分配存储区、指示程序结束等功能。

2. 汇编语法格式

GNU 汇编语法适用于所有的架构,并不是 ARM 独享的, GNU 汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分:

1
label: instruction @ comment

【语句说明】

  • label :标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label后面的 : ,任何以 : 结尾的标识符都会被识别为 一个标号。

  • instruction :指令,也就是汇编指令或伪指令。

  • @ :表示后面的是注释,就跟 C 语言里面的 “ /* “和 ” */ “ 一样,其实在 GNU 汇编文件中我们也可以使用” /* “和” */ “来注释。

  • comment :就是注释的内容了。

【注意】

(1) ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。

(2)汇编指令每行结束不需要带 ; 或者其他任何符号。

3. 预定义段

我们可以使用以下伪操作来定义一个段:

1
.section .UserSection @定义一个 UserSection段

在汇编系统中预定义了一些段名:

.text 表示代码段。
.data 初始化的数据段。
.bss 未初始化的数据段。
.rodata 只读数据段。

4. 汇编的开始与结束

汇编程序的默认入口标号是 _start ,不过我们也可以在链接脚本中使用 ENTRY 来指明其它的入口点,一般还是使用 _start 作为入口标号,下边就是一个完整的 GNU 汇编程序开始与结束的格式:

1
2
3
4
5
6
7
8
9
10
.text				@ 表示当前段为代码段 
.global _start @ 声明_start为全局符号
_start: @ 汇编程序的入口

@ 中间这里是我们要写的指令

STOP: @ 死循环,防止程序跑飞
B STOP

.end @ 汇编程序的结束

5. GNU 函数语法

5.1 函数语法

在汇编中同时也支持函数的定义使用,一般定义格式如下:

1
2
3
函数名 : 
函数体
返回语句

【注意】 GNU 汇编函数返回语句不是必须的。

5.2 使用实例

点击查看实例
1
2
3
4
/* 未定义中断 */ 
Undefined_Handler: @ 函数名
LDR R0, = Undefined_Handler @ 函数体
BX R0 @ 返回语句,并非必须

四、汇编与机器码

1. 从一个例子开始

前面我们有这么一张图:

image-20230719203508088

数值 a 原来是保存在内存里的,执行了某条指令后,它的值被读入内存,那问题来了:

  • (1)什么指令,可以让 CPU 从内存里把数据读进来?

例如:

1
2
mov r3, #addr_a   @把变量 a 的地址传给 CPU 寄存器 r3
ldr r0, [r3] @从 r3 所指的内存把数值读进 CPU 寄存器 r0
  • (2)读进来后,这个数保存在哪里?

保存在 CPU 内部,存在某个寄存器里,上面的代码用寄存器 r0 来保存该值。

  • (3)如何处理数据?

CPU 执行加法指令,比如:

1
add r0, r0, r1  @在 CPU 内部, r0=r0+r1
  • (4)最终数据怎么写入内存?

CPU 执行指令,比如 :

1
str r0, [r3]  @将 r0 的值写入 r3 所指的内存

2. 指令是什么?

上面例子中, mov、 add、 ldr、 str 等都是汇编指令,或者说它们是“助记符”──也就是帮助我们记忆的。这些指令其实是一个一个数值,我们去记这些数值有难度,所以就用 mov、 add 表示不同指令对应的数值。在下面的表格中, op1 对应bit[27:25],不同的 op1 对应不同的指令。我们就用 mov、 add等等来表示这些值。

需要注意的是, op1 只是指令,它只占据 3 位(bit[27:25]),一条完整的指令还需要更多参数。比如“ mov r1, r0”里面的 r1、 r0 就是参数。这些参数也是保存在同一个 32 位数里。这个 32 位数值就是机器码,即汇编指令机器码的助记符。 ARM 指令机器码是有一定格式(对于ARMCotex-A7系列的指令编码格式可以看这篇文档《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》的A5章节),如下:

image-20230721190130743

举个例子?我们后边编译完程序,可能会得到反汇编文件,里边将会包含机器码与汇编指令的对应关系,如:

image-20230721190407864

3. 汇编的执行

这里举两个例子,一个是MOV,一个是BL。

3.1 MOV指令

我们以MOV指令为例,MOV 指令: Move register or constant,把某个寄存器的值移给另一个寄存器,或是把一个常数移入寄存器。 有如下汇编指令:

1
mov r1, #10 @ 将 10 赋值给寄存器 r1,即 r1=10

执行过程如下:

image-20230721192654760

(1)取指: 假设从内存的 addrA 地址取机器码 e3a0100a(即 mov r1, #10 指令)

(2)译码: 原来是 MOV 指令

(3)执行: CPU 内部寄存器 R1 等于 10

机器码 e3a0100a, MOV 指令各位的解析如下:

image-20230721193017078

[31:28]位是条件码 0xe; [15: 12]位是寄存器 R1,即 0x1; [12:0]位是立即数(前边学习另一个课程的时候起始学过了) 10, 即 0x00a

3.2 BL指令

BL 指令: Branch with Link,跳转并把返回地址记录在 LR 寄存器里。 例如

1
2
3
4
5
6
bl test_tag
mov r1, #10

test_tag:
mov r3, #0
mov pc, lr

第 1 行,跳转到 test_tag 标签处执行“mov r3, #0”指令,并且将下一条指令即“mov r1, #10”指令的地址存储到 LR 寄存器。

第 6 行,返回到“ mov r1, #10”指令地址,并且执行“ mov r1, #10”指令指令执行过程,如下:

image-20230721202213081

(1)CPU 从内存的 addrA 地址取机器码 eb000000(即“bl test_tag”指令),执行后, PC 跳转到 test_tag 标签位置,即内存的 addrA+8 地址,从上图可知,其实 test_tag 标签的地址是“mov r3, #0”指令的地址。同时自动将内存的 addrA+4 地址存储在寄存器 LR 中。

(2)CPU 从内存的 addrA+8 地址取机器码 e3a03000(即“mov r3, #0”指令),执行, CPU 内部寄存器 R3 等于 0。

(3)CPU 从内存的 addrA+12 地址取机器码 e1a0f00e(即“mov pc, lr”指令),执行, PC 跳转到内存的 addrA+4 地址。

(4)CPU 从内存的 addrA+4 地址取机器码 e3a0100a(即“mov r1, #10”指令),执行, CPU 内部寄存器 R1 等于 10其中,机器码 eb000000, BL 指令各位的解析可以查《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》的A5章节。