ARM指令集与汇编基础
- ARM指令集概述
- ARM指令
-
- (一)数据处理指令
-
- [1. 数据搬移指令](#1. 数据搬移指令)
- [2. 数据运算指令](#2. 数据运算指令)
- (二)跳转指令
-
- [1. 数据跳转的三种方式](#1. 数据跳转的三种方式)
- [2. 条件码](#2. 条件码)
- [(三)Load / Srore指令](#(三)Load / Srore指令)
-
- [1. 读写内存](#1. 读写内存)
- [2. 寻址方式](#2. 寻址方式)
- [3. 多寄存器内存访问](#3. 多寄存器内存访问)
- [4. 栈的使用](#4. 栈的使用)
- (四)状态寄存器传送指令
-
- [1. 读CPSR](#1. 读CPSR)
- [2. 写CPSR](#2. 写CPSR)
- (五)软中断指令
- (六)协处理器指令
- ARM伪指令
- ARM伪操作
- ARM混合编程
- ATPCS协议
ARM指令集概述
指令:
- 能够指示处理器执行某种运算的命令称为指令(如加、减、乘 ...)
- 指令在内存中以机器码(二进制)的方式存在
- 每一条指令都对应一条汇编
- 程序是指令的有序集合
指令集:
- 处理器能识别的指令的集合称为指令集
- 不同架构的处理器指令集不同
- 指令集是处理器对开发者提供的接口
汇编的本质:
- 每条汇编都会唯一对应一条机器码,且CPU能直接识别和执行,即汇编中所有的指令都是CPU能够识别和执行的
- 汇编中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等都需要自己维护
汇编中的符号: 主要包括指令、伪指令、伪操作
- 指令: 能够编译生成一条32bit的机器码, 并且能被CPU识别和执行
- 伪指令: 本身不是指令,编译器可以将其替换成若干条等效指令
- 伪操作: 不会生成代码,只是在编译之前告诉编译器怎么编译
环境安装
为keil安装交叉编译工具链,配在keil内配置编译工具链:
设置指令初始运行地址为 0x00000000
ARM指令
ARM指令集包括:
- 数据处理指令:数学运算、逻辑运算
- 跳转指令:实现程序的跳转,本质就是修改了PC寄存器的值
- Load / Srore指令:访问(读写)内存
- 状态寄存器传送指令:访问(读写)CPSR寄存器
- 软中断指令:触发软中断异常
- 协处理器指令:操作协处理器的指令
(一)数据处理指令
1. 数据搬移指令
MOV:
rust
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
MOV R1,#1 @ R1 = 1
MOV R2,#2 @ R2 = 2
MOV R3,#3 @ R3 = 3
MOV R4, R3 @ R4 = R3 = 3
stop: @死循环,防止程序跑飞
B stop
.end @汇编程序的结束
注: 后续的命令都写在 _start
到 stop
之间
仿真结果如下,同时通过仿真结果验证一些理论,通过观察可验证以下结论:
- PC的值自动递增4,并且指向下一条指令的地址
- ARM指令的地址均是4的倍数
- 一条指令能够编译生成一条32bit的机器码
设置CP的值为0,构成循环:
rust
MOV R1, #1 @ R1 = 1
MOV R2, #2 @ R2 = 2
MOV R3, #3 @ R3 = 3
MOV PC, #0 @ PC = 0 设置PC的地址为0 构成循环
由于指令地址都是4的倍数,在人为赋值的时候,令PC = 7,会是什么情况:
解释: 因为PC的最低两位未定义,为00,因此令PC = 7 (111),实际变成4(100),因此指向地址为4的指令
MVN:
cpp
MOV R1, #1 @ R1 = 1
MOV R2, R3 @ R2 = R3
MVN R0, #0xFF @ R0 = ~0xFF 按位取反
立即数:
-
本质:立即数的本质就是包含在指令中的数,属于指令的一部分
-
优点: 取指的时候就可以将其读取到CPU,不用单独去内存读取,速度快
-
缺点: 不能是任意的32位的数字,有局限性
rust@ 立即数 @ MOV R0, #0xffffffff MOV R0, #0x12 @ 一条指令总共占32位, 而一个数总不能把32位都占了
当然在编译时候,若遇到非立即数,编译器会将MOV改为MVN达到相同的效果(可实现的条件下)
工程代码
rust
MOV R1, #1 @ R1 = 1
MOV R2, R3 @ R2 = R3
MVN R0, #0xFF @ R0 = ~0xFF 按位取反
@ 立即数 立即数
@ 本质:立即数的本质就是包含在指令中的数,属于指令的一部分
@ 优点:取指的时候就可以将其读取到CPU,不用单独去内存读取,速度快
@ 缺点:不能是任意的32位的数字,有局限性
@ 伪指令
MOV R0, #0x00FFFFFF
@ MOV R0, #0x
@ 一条指令总共占32位, 而一个数总不能把32位都占了
2. 数据运算指令
数据运算标准格式:
《操作码》 《目标寄存器》,《第一个寄存器》,《第二操作数》
- 操作码: 指示执行哪种运算
- 目标寄存器:存储运算结果
- 第一操作寄存器:第一个参与运算的数据(只能是寄存器)
- 第二操作数: 第二个参与运算的数据(可以是寄存器或立即数)
1. 加法指令: ADD
rust
@ 加法指令 ADD
MOV R2, #5 @ R2 = 5
MOV R3, #3 @ R3 = 3
ADD R1, R2, R3 @ R1 = R2 + R3
ADD R1, R2, #5 @ R1 = R2 + 5
2. 减法指令: SUB
RSB
rust
@ 减法指令 SUB
MOV R2, #5 @ R2 = 5
MOV R3, #3 @ R3 = 3
SUB R1, R2, R3 @ R1 = R2 - R3
SUB R1, R2, #3 @ R1 = R2 - 3
@ 逆向减法指令 实现 数 - 寄存器
RSB R1, R2, #3 @ R1 = 3 - R2
3. 乘法指令: MUL
乘法指令只能是两个寄存器相乘,不能有立即数
rust
MOV R2, #5 @ R2 = 5
MOV R3, #3 @ R3 = 3
MUL R1, R2, R3 @ R1 = R2 * R3
@ 乘法指令只能是两个寄存器相乘,不能有立即数
4. 位运算指令:
(一)基础位运算:
rust
MOV R2, #5 @ R2 = 5
MOV R3, #3 @ R3 = 3
@ 按位与 AND
AND R1, R2, R3 @ R1 = R2 & R3;
@ 按位或 ORR
ORR R1, R2, R3 @ R1 = R2 | R3;
@ 按位疑异或 EOR
EOR R1, R2, R3 @ R1 = R2 ^ R3;
(二)位偏移:
rust
MOV R2, #0xf0 @ R2 = 000011110000
MOV R3, #2 @ R3 = 2
@ 左移 LSL
LSL R1, R2, R3 @ R1 = (R2 << R3) R1 = 3C0
@ 右移 LSR
LSR R1, R2, R3 @ R1 = (R2 >> R3) R1 = 3C
(三)汇编特有的运算
-
位清零: 第二操作数中的哪一位为1,就将第一操作寄存器的中哪一位清零,然后将结果写入目标寄存器
-
格式扩展: 根据数据运算标准格式即可,操作码,目标寄存器,第一操作寄存器,第二操作数
rust@ 位清零 BIC MOV R2, #0xFF BIC R1, R2, #0x0F @ R1 = 0xF0 @ 格式扩展 MOV R1, R2, LSL, #1 @ 根据数据运算标准格式,R1 = (R2 << 1)
5. 进位、借位与CPSR寄存器
重点在于理解32位处理器怎么运算64位数
rust
@ 数据运算指令对条件位(N、Z、C、V)的影响
@ 默认情况下数据运算不会对条件位产生影响?
@ 需要在指令后加后缀"S"后才可以影响
MOV R1, #3
SUBS R0, R1, #5 @ CPRS中 N 位置1
SUBS R0, R1, #3 @ CPRS中 Z 位置1, C 位置1
MOV R2, #0xFFFFFFFF
ADDS R0, R2, #3 @ CPRS中 C 位置1
MOV R3, #0x7FFFFFFF
ADDS R0, R3, #3 @ CPRS中 V 位置1, N 位置1
@---------------------------------------------
@ 带进位的加法指令
@ 两个64位的数据做加法运算
@ 第一个数的低32位放在R1
@ 第一个数的高32位放在R2
@ 第二个数的低32位放在R3
@ 第二个数的高32位放在R4
@ 运算结果的低32位放在R5
@ 运算结果的高32位放在R6
@ 第一个数
@ 0x00000001 FFFFFFFF
@ 第二个数
@ 0x00000002 00000005
MOV R1, #0xFFFFFFFF
MOV R2, #0x00000001
MOV R3, #0x00000005
MOV R4, #0x00000002
ADDS R5, R1, R3
ADC R6, R2, R4
@ 本质:R6 = R2 + R4 + C位
@---------------------------------------------
@ 带借位的减法指令
@ 第一个数
@ 0x00000002 00000001
@ 第二个数
@ 0x00000001 00000005
MOV R1, #0x00000001
MOV R2, #0x00000002
MOV R3, #0x00000005
MOV R4, #0x00000001
SUBS R5, R1, R3
SBC R6, R2, R4
@ 本质:R6 = R2 - R4 - !C位
这里以加法进位运算为例:
(二)跳转指令
跳转指令:实现程序的跳转,本质就是修改了PC寄存器的值
1. 数据跳转的三种方式
方式一: 直接修改PC寄存器的值(不建议使用,需要自己计算目标指令的绝对地址)
rust
MAIN:
MOV R1, #1 @ ADDR:0x00
MOV R2, #2 @ ADDR:0x04
MOV R3, #3 @ ADDR:0x08
MOV PC, #0x18 @ ADDR:0x0C 修改PC的值 进行跳转
MOV R4, #4 @ ADDR:0x10
MOV R5, #5 @ ADDR:0x14
FUNC:
MOV R6, #6 @ ADDR:0x18
MOV R7, #7 @ ADDR:0x1c
MOV R8, #8 @ ADDR:0x20
方式二: 不带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址
rust
MAIN:
MOV R1, #1
MOV R2, #2
MOV R3, #3
B FUNC @ 使用B指令进行跳转
MOV R4, #4
MOV R5, #5
FUNC:
MOV R6, #6
MOV R7, #7
MOV R8, #8
方式三: 带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址,同时将跳转指令下一条指令的地址存储到LR寄存器,但不会自动返回
rust
MAIN:
MOV R1, #1 @ ADDR:0x00
MOV R2, #2 @ ADDR:0x04
MOV R3, #3 @ ADDR:0x08
BL FUNC @ ADDR:0x0C
MOV R4, #4 @ ADDR:0x10
MOV R5, #5 @ ADDR:0x14
FUNC:
MOV R6, #6 @ ADDR:0x18
MOV R7, #7 @ ADDR:0x1c
MOV R8, #8 @ ADDR:0x20
MOV PC, LR @ ADDR:0x24
2. 条件码
比较两个值的大小 :CMP
rust
CMP R1, R2
CPU判断两个数大小示例:
rust
@ 比较指令
MOV R1, #3
MOV R2, #5
CMP R1, R2
@ CMP的本质是一条减法指令,只是没有将运算的结果存入寄存器
@ 结果是bool类型 结果存在(N Z C V中)只占1位
@ R1 == R2, z = 1
@ R1 != R2, Z = 0
@ R1 < R2, 借位 C = 0
@ R1 <= R2, 借位 C = 0 或 Z = 1
@ R1 > R2, C = 1 且 Z = 0
@ R1 >= R2, C = 1
CMP的本质是一条减法指令,只是没有将运算的结果存入寄存器,结果存放在(N Z C V)位中
ARM条件码
ARM条件码是添加在指令的后面,用于判断,如果成立,则执行该语句,示例:
cpp
@ 比较指令
MOV R1, #1
MOV R2, #2
CMP R1, R2
EQ FUNC @ if(QE) B FUNC 本质: if(Z == 1) B FUNC
@ BNE FUNC @ if(!NEQ) B FUNC 本质: if(Z == 0) B FUNC
MOV R3, #3
MOV R4, #4
FUNC:
MOV R5, #5
MOV R6, #6
补充:ARM指令中,大多数指令都可以带条件码后缀
用汇编实现以下逻辑:
c
int R1 = 9;
int R2 = 15;
START:
if(R1 == R2)
{
STOP();
}
else if(R1 > R2)
{
R1 = R1 - R2;
goto START;
}
else
{
R2 = R2 - R1;
goto START;
}
STOP:
汇编实现:
rust
MOV R1, #9
MOV R2, #15
START:
CMP R1, R2
BEQ STOP
SUBGT R1, R1, R2
SUBLT R2, R2, R1
B START
STOP:
(三)Load / Srore指令
1. 读写内存
写内存 STR
rust
STR R1, [R2]
将 R1 寄存器中的数据存储到 R2 指向的内存空间
读内存 LDR
rust
LDR R3, [R2]
将内存中 R2 指向的内存空间的数据读取到 R3
读 / 写指定的数据类型
rust
@ 写内存 STR
MOV R1, #0xFFFFFFFF
MOV R2, #0x40000000
@ 将R1寄存器中的数据存储到R2指向的内存空间
@ STR R1, [R2]
@ STRB R1, [R2] @ 写R1的一个字节到R2地址空间
STRH R1, [R2] @ 写R1的两个字节到R2地址空间
@ LDR指令同样支持以上后缀
LDR
指令同样支持以上后缀
2. 寻址方式
寻址方式: 就是CPU去寻找一个操作数的方式
(1)立即寻址
rust
MOV R1, #1
ADD R1, R2, #1
(2)寄存器寻址
rust
ADD R1, R2, R3
(3) 寄存器移位寻址
rust
MOV R1, R2, LSL #1
(4)寄存器间接寻址
rust
STR R1,[R2]
(5)基址加变址寻址
rust
@ 基址加变址寻址
MOV R1, #0xFFFFFFFF
MOV R2, #0x40000000
MOV R3, #4
STR R1, [R2,R3]
@ 将R1寄存器中的数据写入到R2+R3指向的内存空间
STR R1, [R2,R3,LSL #1]
@ 将R1寄存器中的数据写入到R2+(R3<<1)指向的内存空间
(6) 基址加变址寻址的索引方式
-
前索引
rustMOV R1, #0xFFFFFFFF MOV R2, #0x40000000 STR R1, [R2, #8] @ 将R1寄存器中的数据写入到R2+8指向的内存空间
-
后索引
rustMOV R1, #0xFFFFFFFF MOV R2, #0x40000000 STR R1, [R2], #8 @ 将R1寄存器中的数据写入到R2指向的内存空间, 然后 R2 自增 8
-
自动索引
rustMOV R1, #0xFFFFFFFF MOV R2, #0x40000000 STR R1, [R2, #8]! @ 将R1寄存器中的数据写入到 R2+8 指向的内存空间, 然后 R2 再自增 8
以上寻址方式和索引方式同样适用于LDR
3. 多寄存器内存访问
多寄存器内存访问指令:
rust
@ 多寄存器访问指令
MOV R1, #1
MOV R2, #2
MOV R3, #3
MOV R4, #4
MOV R11, #0x40000020
@ STM R11, {R1-R4}
@ 将R1-R4寄存器中的数据写入到以R11为起始地址的内存空间中
@ LDM R11, {R6-R9}
@ 将内存中以R11为起始地址的数据读取到R6-R9寄存器
@ 当寄存器编号不连续时,使用逗号分隔
@ STM R11, {R1,R2,R4}
@ 不管寄存器列表中的顺序如何,存取时永远是低地址对应小编号的寄存器
@ STM R11,{R3,R1,R4,R2}
@ 自动索引照样适用于多寄存器内存访问指令
STM R11!,{R1-R4}
自动索引在指定的内存空间上存储数据后,内存空间地址会自动发生偏移到下个存储空间,为了下次存数据预先计算好内存地址,提高CPU存储速度。
多寄存器内存访问指令的寻址方式:
rust
@ 多寄存器访问指令
MOV R1, #1
MOV R2, #2
MOV R3, #3
MOV R4, #4
MOV R11, #0x40000020
@ STM 默认就是IA
@ STMIA R11!, {R1-R4} @ 先存储数据,后增长地址
@ STMIB R11!, {R1-R4} @ 先增长地址,后存储数据
STMDA R11!, {R1-R4} @ 先存储数据,后递减地址
STMDB R11!, {R1-R4} @ 先递减地址,后存储数据
@ 加!是为了自动索引
数据存储前后地址对比图:
后缀 | 实际意义 |
---|---|
IA (increase after) |
先存储数据,后增长地址 |
IB (increase before) |
先增长地址,后存储数据 |
DA (decrease after) |
先存储数据,后递减地址 |
DB (decrease before) |
先递减地址,后存储数据 |
4. 栈的使用
栈的本质:栈就是一段内存,程序运行时用于保存一些临时数据,如局部变量、函数的参数、返回值、以及程序跳转时需要保护的寄存器等
栈的分类: 针对压栈时指针的偏移的方向分为增栈与减栈
栈的分类: 根据当前栈指针SP指向的内存空间内是否有数据分为满栈与空栈
栈的分类:
- 增栈:压栈时栈指针越来越大,出栈时栈指针越来越小
- 减栈:压栈时栈指针越来越大,出栈时栈指针越来越小
- 满栈:栈指针指向最后一次压入到栈中的数据,压栈时,需要先移动栈指针到相邻位置然后再压栈
- 空栈:栈指针指向最后一次压入到栈中的数据的相邻位置,压栈时可直接压栈,之后需要将栈指针移动到相邻位置
栈分为空增(EA)、空减(ED)、满增(FA)、满减(FD)四种,ARM处理器一般使用满减栈
思考: 满减栈压栈对应哪种多寄存器内存访问指令的寻址方式(STMDB),其他的栈又分别对应哪种呢。
思考: 满减栈出栈对应哪种多寄存器内存访问指令的寻址方式(LDMIA),其他的栈又分别对应哪种呢。
为了防止程序设计时栈类型与后缀不符合(错乱),ARM设计时候提供了四种专门的后缀,空增(EA)、空减(ED)、满增(FA)、满减(FD)
rust
MOV R1, #1
MOV R2, #2
MOV R3, #3
MOV R4, #4
MOV R11, #0x40000020
STMFD R11!, {R1-R4} @ 满减栈压栈
LDMFD R11!, {R6-R9} @ 满减栈出栈
栈的举例应用:
- 叶子函数: 不再调用其他函数的函数称为叶子函数
- 非叶子函数: 函数中继续调用其他函数的函数称为非叶子函数
叶子函数的调用过程举例:
rust
@ 栈的应用举例
@ 初始化栈指针
MOV SP, #0x40000020
MAIN:
MOV R1, #3
MOV R2, #5
BL FUNC1
ADD R3,R1,R2
B stop
FUNC1:
@ 数据压栈 保存跳转前的数据
@ 压栈保护现场
STMFD SP!,{R1,R2}
MOV R1, #10
MOV R2, #20
SUB R3,R2,R1
@ 出栈恢复现场
LDMFD SP!, {R1,R2}
MOV PC,LR
非叶子函数的调用过程举例:
rust
@ 栈的应用举例
@ 初始化栈指针
MOV SP, #0x40000020
MAIN:
MOV R1, #3
MOV R2, #5
BL FUNC1
ADD R3,R1,R2
B stop
FUNC1:
@ 压栈保护现场
STMFD SP!,{R1,R2,LR}
MOV R1, #10
MOV R2, #20
BL FUNC2
SUB R3,R2,R1
@ 出栈恢复现场
LDMFD SP!, {R1,R2,LR}
MOV PC,LR
FUNC2:
STMFD SP!,{R1,R2}
MOV R1, #7
MOV R2, #8
MUL R3,R2,R1
LDMFD SP!, {R1,R2}
MOV PC, LR
对于叶子函数而言,只需要对数据变量进行压栈保护,不用对 LR 寄存器进行保护,因为LR不会改变。
而对于非叶子函数而言,不仅要对数据、还要对LR寄存器进行地址压栈保护 ,因为在函数中再调用其他函数时候,LR寄存器会发生变化。
(四)状态寄存器传送指令
1. 读CPSR
rust
MRS R1, CPSR @ R1 = CPSR
电或者复位时,CPSR是0xD3,svc特权模式
2. 写CPSR
rust
MSR CPSR, #0x10 @ CPSR = 0x10 USER模式
MSR CPSR, #0xD3 @ 修改无效
在USER模式下不能随意修改CPSR,因为USER模式属于非特权模式
(五)软中断指令
异常处理过程,产生动作(自动)
- 拷贝CPSR(状态寄存器)中的内容到 对应异常模式下的
SPSR_<mode>
(备份异常之前的模式及状态) - 修改CPSR的值(切换模式,进入异常模式)
- 修改中断禁止位禁止相应的中断(优先级变化,禁止同级)
- 修改模式位进入相应的异常模式(切换模式)
- 修改状态位进入ARM状态(异常只能在ARM状态)
- 保存返回地址到对应异常模式下的
LR_<mode>
(为了异常结束后返回) - 设置PC 为相应的异常向量(异常向量表对应的地址)(跳转到固定内存地址(异常向量表))
通过SWI语句触发软中断:
rust
@ 软中断指令
MAIN:
MSR CPSR,#0x10 @ 切换CPU模式为user模式,打开IRQ和FIQ
MOV R1,#1
MOV R2,#2
SWI #1
ADD R3,R1,R2
B stop
寄存器变化结果:
因为MAIN地址从0开始,因此第三条指令的地址就是0x08,占用了异常向量表的地址,所以异常后跳转到了第三条指令,实际应该将0-31的地址留给异常向量表使用。
异常向量表如图:
软中断的应用与处理过程:
rust
@ 软中断指令
@ 异常向量表
B MAIN @ 初始化异常 进入MAIN
B .
B SWI_HANDLER @ 触发软中断 跳转到异常处理程序
B .
B .
B .
B .
B .
MAIN:
@ 必须修改的是svc下的SP,因此必须先赋值,再切换模式
MOV SP,#0x40000020
MSR CPSR,#0x10 @ 切换CPU模式为user模式,打开IRQ和FIQ
MOV R1,#1
MOV R2,#2
SWI #1
ADD R3,R1,R2
B stop
@ 异常处理程序
SWI_HANDLER:
@ 压栈保护现场
@ 异常处理程序中 将LR压入栈 出栈时候给PC
STMFD SP!, {R1,R2,LR}
MOV R1,#10
MOV R2,#20
SUB R3,R1,R2
@ 出栈恢复现场
LDMFD SP!, {R1,R2,PC}^
@ ^: 表示出栈的时候将SPSR传给CPSR 实现模式恢复
(六)协处理器指令
协处理器指令: 操作控制协处理器的指令
协处理器数据运算指令: CDP
协处理器存储器访问指令:
STC
:将协处理器中的数据写入到存储器LDC
:将存储器中的数据读取到协处理器
协处理器寄存器传送指令:
MRC
:将协处理器中寄存器中的数据传送到ARM处理器中的寄存器
MCR
:将ARM处理器中寄存器中的数据传送到协处理器中的寄存器
ARM伪指令
本身不是指令,编译器可以将其替换成若干条等效指令
-
空指令
rustNOP @ 等价于 MOV R0,R0
-
LDR伪指令
rust@ 以下形式为指令 @ 将R2指向的内存空间中的数据读取到R1寄存器 LDR R1,[R2] @ 以下形式为伪指令 LDR R1, =0x12345678 @ R1 = 0x12345678 @ 可以将任意一个32的数据放到寄存器中 LDR R1, =stop @ 将STOP表示的地址写入R1寄存器 @ LDR R1, STOP @ 将STOP地址中的内容写入R1寄存器
ARM伪操作
不会生成代码,只是在编译之前告诉编译器怎么编译,不会生成代码,GNU的伪操作一般都以 .
开头
rust
.global symbol @ 将symbol声明成全局符号
.local symbol @ 将symbol声明成局部符号
rust
.equ DATA, 0xFF @宏定义
MOV R1, #DATA
rust
.macro FUNC @ 类似于函数封装
MOV R1, #1
MOV R2, #2
.endm
FUNC @相当于调用函数
rust
.if 1 @ 条件编译
MOV R1, #1
MOV R2, #2
.endif
rust
.rept 3 @ 重复执行 相当于循环
MOV R1, #1
MOV R2, #2
.endr
rust
@ .weak symbol
@ 弱化一个符号,即告诉编译器即便没有这个符号也不要报错
.weak func
B func
@没有func,编译成NOP
rust
@ .word VALUE
@ 在当前地址申请一个字的空间并将其初始化为VALUE
MOV R1, #1
.word 0xFFFFFFFF
MOV R2, #2
rust
@ .byte VALUE 在当前地址申请一个字节的空间并将其初始化为VALUE
@ .align N 告诉编译器后续的代码2的N次方对其
MOV R1, #1
.byte 0xFF @ 在当前地址申请一个字节的空间并将其初始化为0xFF
.align 2 @ 告诉编译器后续的代码2的2次方对其
MOV R2, #2
rust
@ 杂项
.arm @ 告诉编译器后续的代码是ARM指令
.thumb @ 告诉编译器后续的代码是Thumb指令
.text @ 定义一个代码段
.data @ 定义一个数据段
.end @ 汇编的结束
rust
@ .space N, VALUE
@ 在当前地址申请N个字节的空间并将其初始化为VALUE
MOV R1, #1
.space 12, 0x12
MOV R2, #2
ARM混合编程
C和汇编的混合编程原则:在哪种语言环境下符合哪种语言的语法规则
- 在汇编中将C中的函数当做标号处理
- 在C中将汇编中的标号当做函数处理
- 在C中内联的汇编当做C的语句来处理
方式一:汇编语言调用(跳转)C语言
-
汇编:
cppMOV R1, #1 MOV R2, #2 BL func_c @ 跳转到C语言函数 MOV R3, #3
-
C语言
cppvoid func_c() { int a; a++; }
方式二:C语言调用(跳转)汇编
-
汇编:
cppMOV R1, #1 MOV R2, #2 BL func_c @ 跳转到C语言 保证先进入C语言 .global FUNC_ASM FUNC_ASM: MOV R4, #4 MOV R5, #5
-
C语言
cppvoid func_c() { int a; a++; FUNC_ASM(); // 跳转到汇编函数 a--; }
方式三:C内联汇编
-
汇编
cppMOV R1, #1 MOV R2, #2 BL func_c @ 跳转到C语言 保证先进入C语言
-
C语言
cppvoid func_c() { int a; a++; // 汇编语句 asm ( "MOV R6,#6\n" "MOV R7,#7\n" "MOV R8,#8\n" ); }
ATPCS协议
全称: ARM-THUMB Procedure Call Standard
ATPCS协议的主要内容:
- 使用满减栈
- R15(PC):程序计数器,只能用于存储程序指针,不能做其他用途
- R14(LR):链接寄存器,只能用于存储返回地址,不能作其他用途
- R13(SP):栈指针,只能用于存储栈指针,不能作其他用途
- R0-R3:当函数的参数不多于4个时使用R0-R3传递,当函数的参数多于4个时,多出的部分用栈传递
- 函数的返回值使用R0传递
- 其它寄存器主要用于存储局部变量