文章目录
一.ARM核(ARMv7)寄存器资源
有单独标识的是这个寄存器独有的寄存器资源
(1)寄存器用途分析
- R0 - R10 用来存放用户的数据
- R11(fp:frame-pointer) 用来记录一个栈空间的开始地址
- R12(ip: The Intra-Procedure-call scratch register) 用来临时存储sp
- R13(sp:stack pointer) 栈指针寄存器
- R14(lr:link register) 在发生跳转的时候,用来保存PC寄存器的值
- R15(pc:program counter) 用来存放CPU需要执行的指令所在内存的地址
(2)CPSR(当前程序状态寄存器)
ARM核工作模式
(3)SPSR
异常产生的时候,用来保存CPSR的值
二.ARM指令集
1.ARM编译器介绍
- 交叉编译工具链的命名规则为:arch [-vendor] [-os] [- (gnu)eabi]
- arch - 体系架构,如ARM、MIPS等
- vendor - 工具链提供商
- os - 目标操作系统
- eabi - 嵌入式应用二进制接口(Embedded Application Binary Interface)
交叉编译
:在PC机上完成代码的编写与编译,在开发板上完成代码的运行- Embedded Application Binary Interface:嵌入式应用二进制接口指定了文件格式、数据类型、使用、堆积组织优化和在一个嵌入式软件中的参数的标准约定
- 根据对操作系统的支持与否,ARM GCC可分为支持和不支持操作系统,如arm-none-eabi:这个是没有操作系统的,自然不可能支持那些跟操作系统关系密切的函数,比如fork(2),它使用的是newlib这个专用于嵌入式系统的C库。arm-none-linux-eabi:用于Linux的,使用Glibc
(1)arm-none-eabi-gcc
Arm 官方用于编译 ARM 架构的裸机系统(包括 ARM Linux 的 boot、kernel,不适用编译 Linux 应用),一般适合 ARM7、Cortex-M 和 Cortex-R 内核的芯片使用,所以不支持那些跟操作系统关系密切的函数,比如 fork (2),它使用的是 newlib 这个专用于嵌入式系统的 C 库。
下载地址:https://developer.arm.com/downloads/-/gnu-rm
(2). arm-none-linux-gnueabi-gcc
- 主要用于基于ARM架构的Linux系统,可用于编译ARM架构的u-boot、Linux内核、Linux应用等。
- arm-none-linux-gnueabi基于GCC,使用Glibc库,经过Codesourcery公司优化过推出的编译器。
- arm-none-linux-gnueabi-xxx交叉编译工具的浮点运算非常优秀。一般ARM9、ARM11、Cortex-A内核,带有Linux操作系统的会用到。
(3) arm-eabi-gcc
- Android ARM编译器。
(4). armcc
- ARM公司推出的编译工具,功能和arm-none-eabi类似,可以编译裸机程序(u-boot、kernel),但是不能编译Linux应用程序。
- armcc一般和ARM一起,Keil MDK、ADS、RVDS和DS-5中的编译器都是armcc,所以armcc编译器都是收费的。
(5). aarch64-linux-gnu-gcc
- aarch64-linux-gnu-gcc是由Linaro公司基于GCC推出的的ARM交叉编译工具。
- 可用于交叉编译ARMv8 64位目标中的裸机程序、u-boot、Linux kernel、filesystem和App应用程序。
- aarch64-linux-gnu-gcc交叉编译器必须安装在64位主机上,才能编译目标代码。
2.指令格式
(1)立即数
- 快速判定是否合法立即数:
- 首先将这个数转换为32bit的16进制形式,例如218=0xDA=0x000000DA
- 除零外,仅有一位数为合法立即数0x0010 000
- 除零外,仅有二位数,并且相邻(包括首尾,如0x1000000A)的为合法立即数。
- 除零外,仅有三位数,并且相邻(包括中间有0相间,例如0x10800000,包括首尾相邻,如:0x14000003),这三位数中,最高位取值仅能为1、2、3,最低位取值仅能为4、8、C,中间位0x0~0xF。这种组合的为合法立即数。
- 下面哪些立即数是合法的立即数:
- (a) 0x00AB0000
- (b) 0x0000FFFF
- © 0xF000000F
- (d) 0x08000012
- (e) 0x00001f80
- (f) 0xFFFFFFFF
(2)寄存器移位
- 将寄存器读取之后,进行移位运算后,作为操作数2参与运算。支持的移位方式如下:
- LSL(Logical Shift Left):逻辑左移
- LSR(Logical Shift Right):逻辑右移
- ASR(Arithmetic Shift Right):算术右移
- 示例:
r0, lsr #4
表示r0 >> 4
r0, lsr r1
表示r0 >> r1
#3, lsl #4
错误,只能是寄存器移位,不能是立即数移位
3.常用ARM核指令
(1)MOV指令
- 格式:
mov 目标寄存器,操作数2
- 功能:将操作数2的值赋值给目标寄存器
- 举例:
mov r0, #12
//r0 = 12
mov r1, r0
//r1 = r0
mov r1, r0, lsl #2
//r1 = r0 << r2
(2)MVN指令
- 格式:
mvn 目标寄存器,操作数2
- 功能:将操作数2取反的值给目标寄存器
- 举例:
MVN r0, #0
//r0 = ~0 => r0 = 0xffff, ffff
(3)LDR指令
- 格式:
LDR 目标寄存器, =数据
- 功能:完成任意的数据传送到目标寄存器
- 注意:数据前面不能加
#
,因为此时数据不按立即数来处理 - 举例:
LDR r0, =0x12345678
//r0 = 0x12345678
(4)ADD指令
- 格式:
add 目标寄存器,操作数1,操作数2
- 功能:将操作数1加上操作数2的结果给目标寄存器
- 举例:
add r0, r1, #3
//r0 = r1 + 3
add r0, r1, r2
//r0 = r1 + r2
add r0, r1, r2, lsl #2
//r0 = r1 + (r2 << 2)
(5)SUB指令
- 格式:
sub 目标寄存器,操作数1,操作数2
- 功能:将操作数1减去操作数2的结果给目标寄存器
- 举例:
sub r0, r1, #3
//r0 = r1 - 3
sub r0, r1, r2
//r0 = r1 - r2
sub r0, r1, r2, lsl #2
//r0 = r1 - (r2 << 2)
(6)MUL指令
- 格式:
mul 目标寄存器,操作数1,操作数2
- 功能:将操作数1乘以操作数2的结果存放在目标寄存器
- 注意:
- 操作数1和操作数2必须都是寄存器,并且操作数1的寄存器编号不能和目标寄存器一样
- 举例:
mul r0, r1, r2
mul r0, r1, #3
// 错误mul r0, r0, r1
// 错误mul r0, r1, r0
(7)AND指令
- 格式:
and 目标寄存器,操作数1,操作数2
- 功能:将操作数1按位与操作数2的结果存放在目标寄存器
- 举例:
and r0, r1, r2
//r0 = r1 & r2
and r0, r1, #10
//r0 = r1 & 10
and r0, r1, r2, lsl #2
//r0 = r1 & (r2 << 2)
(8)ORR指令
- 格式:
orr 目标寄存器,操作数1,操作数2
- 功能:将操作数1按位或操作数2的结果存放在目标寄存器
- 举例:
orr r0, r1, r2
//r0 = r1 | r2
orr r0, r1, #10
//r0 = r1 | 10
orr r0, r1, r2, lsl #2
//r0 = r1 | (r2 << 2)
(9)EOR指令
- 格式:
eor 目标寄存器,操作数1,操作数2
- 功能:将操作数1按位异或操作数2的结果存放在目标寄存器
- 举例:
eor r0, r1, r2
//r0 = r1 ^ r2
eor r0, r1, #10
//r0 = r1 ^ 10
eor r0, r1, r2, lsl #2
//r0 = r1 ^ (r2 << 2)
(10)BIC指令
- 格式:
bic 目标寄存器,操作数1,操作数2
- 功能:将操作数1按位与操作数2取反的结果存放在目标寄存器
- 目标寄存器 = 操作数1 & ~操作数2
- 举例:
mov r0, #0xff
//r0 = 1111, 1111
bic r1, r0, #0xa
//-0xa = 0000, 1010
// & 1111 0101
(11)cmp比较指令
- 格式:
cmp 寄存器,操作数2
- 功能:将寄存器的值与操作数2比较,比较的结果会自动影响CPSR的NZCV
- 练习:
- 将下列代码用ARM汇编指令实现
a = 5 r0 b = 6 r1 c = 10 r2 if(a > b){ b ++; } if(a <= c){ c ++; }
- 将下列代码用ARM汇编指令实现
.global _start
_start:
mov r0,#5
movr r1,#6
mov r2,#10
cmp r0, r1
addgt r0,r0,#1
cmp r0,r2
addle r2,r2,#1
stop:
b stop
(12)B/BL(跳转指令)
- 格式:
B/BL 标签
- 功能:跳到一个指定的标签,BL跳转之前,将跳转前的PC的值保存在LR,跳转范围 +/- 32M
(13)给PC赋值
练习
.global _start
_start:
mov r0,#0
mov r1,#1
cmp r1,#100
ble loop
stop:
b stop
4.内存访问指令
(1)单个数据访问
以下是提取的内容:
- LDR:将内存中的值加载到寄存器(读内存)
- STR:将寄存器的内容写入内存(写内存)
- 寄存器间接寻址:寄存器的值是一个地址
LDR r0, [r1]
//r0 = *r1
STR r0, [r1]
//*r1 = r0
- 基址变址寻址:将基地址寄存器加上指令中给出的偏移量,得到数据存放的地址
- A. 前索引
STR r0, [r1, #4]
//*(r1 + 4) = r0
LDR r0, [r1, #4]
//r0 = *(r1 + 4)
- B. 后索引
STR r0, [r1], #4
//*r1 = r0 && r1 = r1 + 4
LDR r0, [r1], #4
//r0 = *r1 && r1 = r1 + 4
- C. 自动索引
STR r0, [r1, #4]!
//*(r1 + 4) = r0 && r1 = r1 + 4
LDR r0, [r1, #4]!
//r0 = *(r1 + 4) && r1 = r1 + 4
- A. 前索引
//2
.global _start
_start:
mov r0,#0x40000000
ldr r1,=0x1234
str r1,[r0],#4
ldr r1,=0xabcd
str r1,[r0],#-4
ldr r1,[r0]
ldr r2,[r0,#4]
add r0,r1,r2
stop b stop
//1
.global _start
_start:
mov r0,#0x40000000
mov r1,#1
mov r2,#10
bl write_data
mov r0,#0x4000000
ldr r1,=0x40000100
mov r2,#10
bl copy_data
stop:
b stop
write_data:
str r1,[r0],#4
add r1,r1,#1
cmp r1,r2
ble write_data
mov pc,lr
copy_data:
ldr r3,[r0],#4
str r3,[r1],#4
sub r2,r2,#1
cmp r2,#0
bgt copy_data
mov pc,lr
(2)多个数据访问
-
LDM:将一块内存的数据,加载到多个寄存器中
-
STM:将多个寄存器的值,存储到一块内存
-
格式:
LDM{条件}{s}<MODE> 基址寄存器{!}, {Reglist}^
STM{条件}{s}<MODE> 基址寄存器{!}, {Reglist}^
_start:
mov r0 #0x40000000
mov r1,#0x11
mov r2,#0x22
mov r3,#0x33
stmia r0,{r1-r3}
ldmdb r0!,{r4-r6}
(3)栈操作指令
- A. 进栈
stmfd sp!, {寄存器列表}
- B. 出栈
ldmfd sp!, {寄存器列表}
- 注意:
- 在对栈操作之前,必须先设置
sp
的值,进栈和出栈的方式一样,ATPCS标准规定满减栈
- 在对栈操作之前,必须先设置
5.CPSR/SPSR操作指令
- A. 读操作
MRS Rn, CPSR/SPSR
- 将状态寄存器的值,读到通用寄存器中
- B. 写操作
MSR CPSR/SPSR, Rn
- 将通用寄存器的值,写到状态寄存器
- 练习:
- A. 写一段代码,将CPSR的第I(7)位清0,其他位不变(使能IRQ异常)
- B. 写一段代码,将CPSR的第I(7)位置1,其他位不变(禁用IRQ异常)
.global _start
_start:
mrs r0,cpsr
mov r1,#1
bic r0,r0,r1,lsl #7
msr cpsr,r0
mrs r0,cpsr
mov r1,#1
orr r0,r0,r1,lsl #7
msr cpsr,r0
_stop:
b stop
6.指令流水线
- 在ARM核中,为增加处理器指令流的速度,ARM7系列使用3级流水线,允许多个操作同时处理,而非顺序执行。
不同的ARM核,流水线的级数是不一样的,ARM核版本越高,流水线级数越多
。- 对于软件工程师编程而言,统一按照三级流水线来分析就可以了。
7.ARM伪指令
8.ATPCS标准
(1)ATPCS标准介绍
- 前四个寄存器r0-r3用于将参数值传递给例程,并将结果值传递给例程,并在例程内保存中间值(但通常仅在子程序调用之间)。在ARM状态下,寄存器r12也称为IP可用于保存子程序调用之间的中间值。
- 通常,从r4到r11的寄存器用于保存例程局部变量的值。它们也被标记为v1-v8。只有v1-v4可以被整个Thumb指令集统一使用(如图所示)
- 在过程调用标准的所有变体中,寄存器r12-r15都有特殊角色。在这些角色中,它们被标记为IP、SP、LR和PC(或ip、sp、lr和pc,但本规范使用大写名称来表示特殊角色)。
- 只有寄存器r0-r7、SP、LR和PC在Thumb状态下普遍可用。它们的同义词和特殊名称显示得更加大胆。很少有Thumb指令可以访问寄存器、v5-v8、SB、SL和IP。
(2)参数传递
- 函数间传递参数的时候,当参数不超过4个时,可以使用寄存器R0~R3来进行参数传递,当参数超过4个时,还可以使用数据栈来传递参数。
- 在参数传递时,将所有参数看做是存放在连续的内存单元中的字数据。然后,依次将各名字数据传送到寄存器R0,R1,R2,R3;如果参数多于4个,将剩余的字数据传送到数据栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。
- 所以在写C程序进行函数传参的时候,最好不要超过4个参数,这样可以提高效率。
(3)函数返回值
- 返回值为一个32位的整数时,可以通过寄存器R0返回。
- 返回值为一个64位整数时,可以通过R0和R1返回,依此类推。