【ARM】(三)ARM指令集与汇编基础

ARM指令集与汇编基础

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				@汇编程序的结束

注: 后续的命令都写在 _startstop 之间

仿真结果如下,同时通过仿真结果验证一些理论,通过观察可验证以下结论:

  • 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. 减法指令: SUBRSB

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) 基址加变址寻址的索引方式

  • 前索引

    rust 复制代码
    MOV R1, #0xFFFFFFFF
    MOV R2, #0x40000000
    STR R1, [R2, #8]
    @ 将R1寄存器中的数据写入到R2+8指向的内存空间	
  • 后索引

    rust 复制代码
    MOV R1, #0xFFFFFFFF
    MOV R2, #0x40000000
    STR R1, [R2], #8
    @ 将R1寄存器中的数据写入到R2指向的内存空间, 然后 R2 自增 8
  • 自动索引

    rust 复制代码
    MOV 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伪指令

本身不是指令,编译器可以将其替换成若干条等效指令

  • 空指令

    rust 复制代码
    NOP		@ 等价于 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语言

  • 汇编:

    cpp 复制代码
    MOV R1, #1
    MOV R2, #2
    BL func_c		@ 跳转到C语言函数
    MOV R3, #3
  • C语言

    cpp 复制代码
    void func_c()
    {
    	int a;
    	a++;
    }

方式二:C语言调用(跳转)汇编

  • 汇编:

    cpp 复制代码
    	MOV R1, #1
    	MOV R2, #2
    	BL func_c		@ 跳转到C语言 保证先进入C语言
    	
    .global FUNC_ASM
    FUNC_ASM:
    	MOV R4, #4
    	MOV R5, #5
  • C语言

    cpp 复制代码
    void func_c()
    {
    	int a;
    	a++;
    	FUNC_ASM();		// 跳转到汇编函数
    	a--;
    }

方式三:C内联汇编

  • 汇编

    cpp 复制代码
    MOV R1, #1
    MOV R2, #2
    BL func_c		@ 跳转到C语言 保证先进入C语言
  • C语言

    cpp 复制代码
    void 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传递
  • 其它寄存器主要用于存储局部变量
相关推荐
韦德斯21 小时前
嵌入式Linux的RTC读写操作应用
linux·运维·c语言·arm开发·实时音视频
byte轻骑兵1 天前
嵌入式 ARM Linux 系统构成全解:从硬件到应用层层剖析
linux·arm开发·arm·嵌入式开发
思尔芯S2C1 天前
面向未来的智能视觉参考设计与汽车架构,思尔芯提供基于Arm技术的创新方案
arm开发·架构·汽车·iot·fpga原型验证·prototyping·智慧视觉
Crossoads1 天前
【汇编语言】call 和 ret 指令(一) —— 探讨汇编中的ret和retf指令以及call指令及其多种转移方式
android·开发语言·javascript·汇编·人工智能·数据挖掘·c#
Eternal-Student2 天前
【docker了解】如何将x86镜像转换为适用于Jetson的ARM镜像
arm开发·docker·容器
不怕犯错,就怕不做2 天前
修复kernel编译栈帧大小异常问题error: the frame size of 1928 bytes is larger than 1024 bytes
linux·arm开发·驱动开发
Crossoads2 天前
【汇编语言】转移指令的原理(三) —— 汇编跳转指南:jcxz、loop与位移的深度解读
android·汇编·人工智能·redis·单片机·深度学习·机器学习
zhuqiyua3 天前
深入解析Kernel32.dll与Msvcrt.dll
汇编·microsoft·windbg·二进制·dll
憧憬一下3 天前
UART硬件介绍
arm开发·嵌入式硬件·串口·嵌入式·linux驱动开发
Petal9909124 天前
UEFI学习笔记(十八):ARM电源管理之PSCI和SCMI概述
arm开发·笔记·学习·uefi