ARM指令集---汇编指令集的学习与应用:C程序与汇编的相互调用,处理器工作模式的改变等

目录

[一. 学习汇编的目的](#一. 学习汇编的目的)

[二. ARM 指令集](#二. ARM 指令集)

[2.1 ARM 指令集体系结构](#2.1 ARM 指令集体系结构)

[三. 常用汇编指令](#三. 常用汇编指令)

[3.1 MOV](#3.1 MOV)

[3.2 LSR ASR ROR](#3.2 LSR ASR ROR)

[3.3 ADD](#3.3 ADD)

[3.4 SUB](#3.4 SUB)

[3.5 有关立即数的判断](#3.5 有关立即数的判断)

[3.5.1 判断示例](#3.5.1 判断示例)

[3.5.2 原因](#3.5.2 原因)

[3.6 LDR](#3.6 LDR)

[3.7 STR](#3.7 STR)

[3.8 清零与置位指令](#3.8 清零与置位指令)

[3.9 跳转指令](#3.9 跳转指令)

[3.10 两个对'栈'进行操作的指令](#3.10 两个对‘栈’进行操作的指令)

[3.11 对特殊功能寄存器进行操作的指令](#3.11 对特殊功能寄存器进行操作的指令)

[四 . C程序与汇编程序的相互调用](#四 . C程序与汇编程序的相互调用)

[4.1 栈](#4.1 栈)

[4.2 在汇编中调用C函数](#4.2 在汇编中调用C函数)

[4.2.1 步骤](#4.2.1 步骤)

[4.3 在C程序中调用汇编函数](#4.3 在C程序中调用汇编函数)

[4.3.1 步骤](#4.3.1 步骤)

[五. 处理器工作模式的改变](#五. 处理器工作模式的改变)


一. 学习汇编的目的

学习ARM汇编的主要目的是为了编写ARM启动代码,启动代码启动以后,引导程序到c语言环境下运行。启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:

1、初始化异常向量表

2、初始化各工作模式的栈指针寄存器

3、开启ARM内核中断允许

4、将工作模式设置为User模式 ---系统初始为SVC模式

5、完成上述工作后,引导程序进入c语言主函数执行

二. ARM 指令集

2.1 ARM 指令集体系结构

ARM公司定的主要指令集体系结构版本:

ARMv1:

该版本的原型机是ARM1,没有用于商业产品。

ARMv2:

V1版进行了扩展,包含了对32位结果的乘法指令和协处理器指令的支持。

ARMv3:

ARM公司第一个微处理器ARM6核心是版本3的,它作为IP核、独立的处理器、具有片

上高速缓存、MMU和写缓冲的集成CPU。

ARMv4:

当前应用最广泛的ARM指令集版本。

ARM7TDMI、ARM720T、ARM9TDMI、ARM940T、ARM920T、Intel的StrongARM等

是基于ARMv4T版本。

ARMv5:

ARM9E-S、ARM966E-S、ARM1020E、ARM 1022E以及XScale是ARMv5TE的。

ARM9EJ-S、ARM926EJ-S、ARM7EJ-S、ARM1026EJ-S是基于ARMv5EJ的。

ARM10也采用。

其中后缀意义如下:

**E:**增强型DSP指令集。包括全部算法和16位乘法操作。

**J:**支持新的Java。

ARMv6:

采用ARMv6核的处理器是ARM11系列。

ARM1136J(F)-S基于ARMv6主要特性有SIMD、Thumb、Jazelle、DBX、(VFP)、MMU。

ARM1156T2(F)-S基于ARMv6T2 主要特性有SIMD、Thumb-2、(VFP)、MPU。

ARM1176JZ(F)-S基于ARMv6KZ 在 ARM1136EJ(F)-S 基础上增加MMU、TrustZone。

ARM11 MPCore基于ARMv6K 在ARM1136EJ(F)-S基础上可以包括1-4 核SMP、MMU。

ARMv7-A:

ARM处理器核:

ARM公司开发了很多ARM处理器核,最新版位ARM11。 Cortex A7等。

三. 常用汇编指令

本节介绍几种 ARMv4/v7 指令集中常用的汇编指令

首先先看看指令的格式,以 SUB指令为例:

{S}: 指令后面加s,指令执行结果会影响CPSR寄存器中的N Z C V位

汇编指令的s后缀,几乎所有的汇编指令都可以在指令后面加上s后缀,s后缀的含义是在指令执行过程中会更新CPSR寄存器的N,V,C,Z位

|-------|-------------------------------------------------------------------------------------------------------|
| N | 在结果是有符号的二进制补码情况下,如果结果为负数,则N=1; 如果结果为非负数,则N=0 |
| Z | 如果结果为0,则Z=1;如果结果为非零,否则Z=0 |
| C | 是针对无符号数最高有效位向更高位进位时C=1 减法中运算结果的最高有效位从更高位借位时C=0 |
| V | 该位是针对有符号数的操作,会在下面两种情形变为1: 两个最高有效位均为0的数相加,得到的结果最高有效位为1; 两个最高有效位均为1的数相加,得到的结果最高有效位为0; 除了这两种情况以外V位为0 |

示例:

cpp 复制代码
mov r0, #0xFFFFFFFF
adds r1, r0, #1

上面的操作会导致Z,C置位,这是因为结果为0,并且从无符号数角度来看,已经从最高位向更高位进位了

cpp 复制代码
mov r0. #0x7FFFFFFF
adds r1, r0, #1

会造成N位和C位置位,这是因为计算结果0x80000000最为位为1,代表负数,并且 从有符号角度来看,把一个整数加成了负数。

**<c>:**相当于该条指令执行的条件

几乎所有的arm指令都可以在指令之后可选地增加执行条件

示例:

找三个数中的最大值

CMP比较指令用于比较两个寄存器的值或者比较一个寄存器和立即数的值,其原理是对待比较的两个数求差,看结果是否为0,这个指令会无条件修改N,V,C,Z位

cpp 复制代码
MOV r0, #1
MOV r1, #2
MOV r2, #3
CMP r0, r1
MOVLT r3, r1    /如果比较结果是小于,则将r1中的值存入r3
MOVGT r3, r0    /如果比较结果是大于,则将r0中的值存入r3
CMP r2, r3
MOVGT r3, r2    /如果比较结果是大于,则将r2中的值存入r3

3.1 MOV

**作用:**加载12位立即数到寄存器或转移一个寄存器的值到另外一个寄存器

用法:

cpp 复制代码
	area reset, code, readonly
	code32
	entry

	mov r0, #1		  ;move 1 to r0
	mov r1, #0xFF     ;move 0xFF to r1
	mov r2, #2

	end

3.2 LSR ASR ROR

**LSR:**逻辑右移,针对无符号数,最高位补0即可

**ASR:**算术右移,最高位补符号位

**ROR:**循环右移,循环将每一次移出的最低为补到最高位

对于左移来说,只有逻辑左移和循环左移

常见的使用方法:

cpp 复制代码
mov r3, r0, lsl #2    /将r0中的数逻辑左移2位后存到r3中
mov r4, r0, lsl r3    /将r0中的数逻辑左移r3中的位后存到r4中
ADD r5, r0, r2, lsl r0    /先将r2中的数逻辑左移r0中的数后再和r0相加,存到r5中

3.3 ADD

**作用:**加法操作

**一般用法:**ADD 目标寄存器 第一操作数寄存器 立即数

3.4 SUB

**作用:**减法操作

一般用法:SUB 目标寄存器 第一操作数寄存器 立即数

3.5 有关立即数的判断

很多指令都有立即数作为第二操作数的情况,那么是什么立即数呢?

立即数判断条件:

1、如果某个数的数值范围是 0~0xFF 之间,那么这个数一定是立即数

2、把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能

超过8位

3、这个数的二进制序列凑够8位之后 的的右边必须为偶数个连续的 0

3.5.1 判断示例

1、0x3f4 = 0000 0000 0000 0000 0000 0011 1111 0100

最高位1至最低位1之间的二进制数序列:1111 1101 从第一个1开始到最后一个1之间没有超过8位末尾1的右边有2个0,所以0x3f4是立即数

2、0x132 = 0000 0000 0000 0000 0000 0001 0011 0010

最高位1至最低位1之间的二进制数序列:1001 1001 从第一个1开始到最后一个1之间没有超过8位末尾1的右边有1个0,不满足第二条,所以0x132不是立即数

3、0x7f8 = 0000 0000 0000 0000 0000 0111 1111 1000

最高位1至最低位1之间的二进制数序列:1111 1111从第一个1开始到最后一个1之间没有超过8位 末尾1的右边有3个0,不满足第二条,所以0x7f8不是立即数

4、0xfab4 = 0000 0000 0000 0000 1111 1010 1011 0100

最高位1至最低位1之间的二进制数序列:0011 1110 1010 1101 从第一个1开始到最后一个1之间超过8位,不满足条件1,所以这个数不是立即数

3.5.2 原因

为什么要这样进行立即数的存储:是为了增大立即数的表示范围

在ARMv汇编指令集中,指令的格式:

可以看到指令格式中,用12位去存一个立即数:

ARM中将这 12bits 分为 8bit 常数(0~255)4bit 循环右移位值(0~15)

8bit 常数范围(0~255),位移的步进值是以2为单位(即实际位移 2 * rotate 位),可以表示循环有以(0~30)偶数位: 0、2、4、6、8、10、12、14、16、18、20、22、24、26、28、30。在实际存储这个数值的时候,要想办法把这个数压缩到这12位中去。压缩的方法就是找一个数,这个数必须是一个8bit数,之后循环右移2 * rotate位。如果能找打这个数,那么待保存的数就是立即数,否则就不是。

3.6 LDR

**作用:**向寄存器中加载一个非立即数;将RAM中的数据加载到通用寄存器

内存读取数据到 CPU 寄存器(内存 → 寄存器,读)

用法:

cpp 复制代码
LDR r0, =0x40000000
LDR r1, [r0]    /r0中的数据为地址,将此地址中的值装载到r1中
                /一次性读四字节

LDR r0, =0x40000000
LDR r1, [r0]
LDR r0, =0x40000004
LDR r2, [r0]

3.7 STR

**作用:**将通用寄存器中的值写入到RAM中

把 CPU 寄存器数据写入内存(寄存器 → 内存,写)

用法:

cpp 复制代码
STR r1, [r0]
STR  r1, [R0, #4]
				/将r1中的值存到(r0+4)的地址处 将r0中的地址先向后偏移4个字节,r0不变
STR r1, [r0], #4	
				/将r1中的值存到(r0+4)的地址处,但是r0要改变,写完后r0再偏移,r0要改变
STR r1,[r0, #4]!
				/先偏移4字节,但是r0要改变!!!

3.8 清零与置位指令

清零:BIC

用法:BIC+目的寄存器+第一操作数寄存器+立即数

将操作数中为bit1的位清0,结果写回目的寄存器

BIC r0, r0, #(1 << 7) //将D7位清0

置位:ORR

将操作数中bit1的位置一

eg:orr r0, r0, #3 //将低两位置1

示例代码:

cpp 复制代码
LDR r0, =0xFFFFFFFF
BIC r0, r0, #3    /将低两位一起清零
	
LDR r0, =0x0
ORR r0 , r0, #0xF    /将低四位置1
ORR r0, r0, #(1 << 3) | (1 << 2)  /将D2位和D3位置1

3.9 跳转指令

b指令类似c语言的goto语句,能够实现无条件跳转。跳转时需要一个lable,表示要跳转到什么地方去

常见的三种跳转指令:

|--------|-----------------------------------------------------------|
| B | 无条件跳转,不装返回地址 |
| BL | 在函数跳转前可以将返回地址装入到 LR 寄存器 |
| BX | 后面加寄存器,可以将寄存器中的地址恢复给pc 经常在某一函数返回时使用 eg:BX lr = MOV pc, lr |

示例代码:

找三个数的最大值:

cpp 复制代码
	MOV r0, #1
	MOV r1, #3
	MOV r2, #5
	CMP r0, r1
	BGT great    /如果大于则跳转到great标签处
	BLT less     /如果小于则跳转到less标签处

great
	MOV r3, r0    /保存最大值到r3寄存器中
	B lable       /跳转到lable标签处
less
	MOV r3, r1    /保存最大值到r3寄存器中

lable
	CMP r3, r2    /比较
	MOVLT r3, r2  /如果r3 < r2则将r2中的值装载到r3中

finish
	B finish    /这两句是为了防止程序跑飞

求1 - 100的和,使用两种方法,while循环和do-while循环:

cpp 复制代码
/while循环求1-100和
	MOV r0, #0
	MOV r1, #0
lable
	CMP r0, #100
	BGT finish    /若大于100,则跳转结束
	ADD r1, r1, r0
	ADD r0, r0, #1
	B lable
----------------------------
/do while循环求1-100和
	MOV r0, #0
	MOV r1, #0
lable	
	ADD r1, r1, r0
	ADD r0, r0, #1
	CMP r0, #100
	BLE lable    /若小于等于100则继续

finish
	B finish

3.10 两个对'栈'进行操作的指令

STMFD 入栈

STMFD<c> <Rn>{!}, <registers>

LDMFD 出栈

LDMFD<c> <Rn>{!}, <registers>

示例:

cpp 复制代码
STMFD sp!, {r0, r1, lr}    /将r0,r1,lr寄存器的值入栈

	/这里进行函数跳转/

LDMFD sp!, {r0, r1, lr}    //将r0,r1,lr寄存器的值出栈

/!表示入栈之后sp自动自减

------------------------
/也可以这样

STMFD sp!, {r0-r12, lr}  

	/这里进行函数跳转/

LDMFD sp!, {r0-r12, lr}  

3.11 对特殊功能寄存器进行操作的指令

MRS 指令

读取某个特殊寄存器到通用寄存器

eg:MRS r0, cpsr

MSR 指令

将通用寄存器的值写到某个特殊寄存器

eg:MSR cpsr, r0

四 . C程序与汇编程序的相互调用

此小节将说明在c程序中如何调用汇编代码的函数,以及在汇编程序中如何调用c程序写好的函数,众所周知,完成函数调用就要保护现场与恢复现场,这就要使用到 '栈' ,所以先对 '栈' 进行介绍

4.1 栈

栈的分类:

空减栈,空增栈,满减栈,满赠栈

**空增:**先写入数据,再让栈指针自增;

**空减:**先写入数据,再让栈指针自减;

**满增:**先让栈指针自增,再写入数据;

**满减:**先让栈指针自减,再写入数据。

ARM 体系 采用的方案是满减栈,但是在进行操作之前,必须设置栈底的位置,这里把栈底设置为0x40001000 ,从地址0x40000000开始的0x1000这段内存空间对应的是 2440芯片 内部的一段RAM ,总共4k 。实际能够使用的内存空间为**0x40000000\~0x40000FFF**

设置栈底指针寄存器指令: LDR sp =0x40001000

4.2 在汇编中调用C函数

4.2.1 步骤

1、首先在汇编程序中使用import 函数名 导入外部函数,eg:import c_add

2、汇编程序向C程序函数传参:

通过通用寄存器传参

注意: 参数个数<=4,可以通过通用寄存器R0 - R3 传参

超过4个时,必须通过 传参

3、C函数将返回值保存到通用寄存器 R0

其他注意事项:

在keil中,要加上 PRESERVE8才不会报错----->强制堆栈按照8字节对齐

示例代码:

C函数:

cpp 复制代码
/C函数
int c_add(int a, int b, int c, int d, int e)
{
	int sum;

	sum = a + b + c + d + e;

	return sum;
}

汇编程序:

cpp 复制代码
LDR pc, =asm_main	
asm_main
	LDR sp, =0x40001000    /初始化栈底指针
	import c_add    /在汇编中使用c函数,需要导入函数名

	MOV r0, #5
	MOV r1, #6
	MOV r2, #1
	MOV r3, #1
	MOV r4, #1
	STMFD sp!, {r0-r12, lr}    /保护现场
	STMFD sp!, {r4}            /传参超过四个时使用栈传递

	BL c_add

	LDMFD sp!, {r4}
	LDMFD sp!, {r0-r12, lr}    /恢复现场

finish
	B finish

4.3 在C程序中调用汇编函数

4.3.1 步骤

1、首先要声明函数

eg:extern int asm_min(int a, int b);

2、C调用汇编传参

将c传递的参数保存到通用寄存器

汇编函数执行的结果会装入到R0 寄存器,通过R0 返回

3、在汇编中需要导出函数:export 函数名 eg:export asm_min

示例代码:

c代码:

cpp 复制代码
extern int asm_min(int a, int b);

int main()
{
	int ret = 0;
	ret = asm_min(30, 20);

	while(1)
	{
	}
	return 0;
}

汇编代码:

cpp 复制代码
	PRESERVE8
	area reset, code, readonly
	code32
	entry

	LDR pc, =asm_main	

asm_min
	CMP r0, r1
	MOVGT r2, r1
	MOVLT r2, r0
	MOV r0, r2
	BX lr

asm_main
	LDR sp, =0x40001000
	import c_add;在汇编中使用c函数,需要导入函数名
	import main
	export asm_min;在c代码中使用汇编函数,需要导出函数

	STMFD sp!, {r0-r12, lr}

	BL main

	LDMFD sp!, {r0-r12, lr}

finish
	B finish

	end

五. 处理器工作模式的改变

切换工作方式的思路很简单,由于内核的工作模式是由CPSR寄存器的低5位 来设置的,那么就可以先把CPSR 读出来,更改低5位之后再设置进去。这里读取CPSR 使用MRS指令,写CPSR 寄存器用MSR指令,需要注意的是在keil环境下写CPSR 需要写成: MSR cpsr_c r0 ;将r0的值写入到cpsr寄存器

改变处理器工作模式,实际就是改变CPSR寄存器中的低五位

示例代码:

**基本流程:**读取CPSR---修改低五位---写回CPSR---分配栈区空间

cpp 复制代码
	PRESERVE8
	area reset, code, readonly
	code32
	entry

;工作模式的切换
	MRS r0, cpsr
	BIC r0, r0, #0x1F
	ORR r0, r0, #0x10
	MSR cpsr_c, r0
	LDR sp, =0x40001000	  ;不同工作模式使用不同的SP栈指针,故需要单独设置
	SUB sp, sp, #1024     ;为User工作模式分配1K大小的栈区

finish
	B finish

	end

设置栈区图示:

对同一块RAM进行分配,每种工作模式都为其分配栈区

对于其他工作模式,可以使用相同的方法来进行设置

相关推荐
治愈系贝壳4 小时前
ARM ---day1
linux·arm开发·学习
zlinear数据采集卡1 天前
从万用表的6步调零到硅片级微秒自校准:硬核拆解LHAMP188的宽压轨到轨与零漂移实战
arm开发·stm32·单片机·嵌入式硬件·fpga开发
CHY_1281 天前
Arm Ethos-U65 介绍(1) 架构、数据格式与算子
arm开发·人工智能·机器学习·npu
Yunhoa1 天前
ARMday1
arm
Luminous.1 天前
51单片机--ARM 体系架构
arm开发·架构·51单片机
LDyun_2 天前
2026暑期云手机选型指南:从稳定性、成本到风控六大核心评判维度
arm开发·ios·智能手机·安卓·玩游戏
zlinear数据采集卡2 天前
2mV纹波的代价:硬核拆解ZLinear模拟前端放大器与正负可调电源设计
arm开发·单片机·嵌入式硬件·fpga开发·架构·开源
学逆向的2 天前
汇编——函数
开发语言·汇编·网络安全
2601_954706492 天前
云虚拟化终端架构解析:ARM 云端集群部署与自动化运维实践
运维·arm开发·架构