寄存器资源
R0-R10:通用寄存器
R11(fp:frame-pointer):用来记录一个栈空间的开始地址
Rl2(ip:The Intra-Procedure-call scratch register):用来临时存储sp
R13(sp:stack pointer):栈指针寄存器
R14(lr:link register):在发生跳转的时候,用来保存PC寄存器的值
R15(pc:program counter):用来存放CPU需要执行的指令所在内存的地址
CPSR:Mode[4:0] :记录当前ARM核所在的模式
T[5] : ARM状态(执行32bit指令),Thumb状态(执行16bit指令)
F[6] : FIO禁止(1)/使能(0)
I[7] : IRQ禁止(1)/使能(0)
N[31]: 计算结果为负数,置1
Z[30]: 计算结果为零,置1
C[29]: 产生进位则置1,产生借位则置0
V[28]: 超出一个类型范围溢出
SPSR: 异常产生的时候,用来保存CPSR的值
指令格式
<opcode>{<cond>}{s} <Rd>, <Rn>, {,<operand2>}
opcode:助记符
cond: 条件 {NE 不相等、EQ 相等、GT 大于、LT 小于、GE 大于等于、LE 小于等于}
s:会影响CPSR的NZCVRd: 目标寄存器 R0 ~ R15
Rn: 操作数一 必须是寄存器
operand2: 操作数二 可以是立即数、寄存器、寄存器移位
一、数据传送指令
1. mov 目标寄存器, 操作数二(立即数、寄存器、寄存器移位)
@将操作数二的值赋值给目标寄存器
mov r0, #1 //立即数
mov r1, r0 //寄存器
mov r2, r1, lsl #2 //寄存器移位(lsl逻辑左移、lsr逻辑右移、asr算数右移)2. mvn 目标寄存器, 操作数二
@将操作数二取反的值赋值给目标寄存器
mov r0, #0
3. LDR 目标寄存器, = 数据
@将任意数据放到目标寄存器中
ldr r0, =0x12345678
二、数据计算指令
1. add 目标寄存器, 操作数一(寄存器), 操作数二(立即数、寄存器、寄存器移位)
@将操作数一加上操作数二的结果给目标寄存器
add r0, r1, #0xff
add r1, r0, r1
add r0, r1, r1, lsl #8
2. sub目标寄存器, 操作数一, 操作数二
@将操作数一减去操作数二的结果给目标寄存器
@指令后加s,会影响CPSR寄存器的NZCV, eg: subs
3. mul目标寄存器, 操作数一, 操作数二
@操作数一和操作数二必须是寄存器,且操作数一和目标寄存器不可以一样
三、位运算指令
1. and 目标寄存器, 操作数一, 操作数二
@将操作数一按位与操作数二的结果给目标寄存器
2. orr 目标寄存器, 操作数一, 操作数二
@将操作数一按位或操作数二的结果给目标寄存器
3. eor 目标寄存器, 操作数一, 操作数二
@将操作数一按位异或操作数二的结果给目标寄存器
4. bic 目标寄存器, 操作数一, 操作数二
@将操作数一按位与操作数二取反的结果给目标寄存器
@把操作数一中对应的操作数二中为1的位清除为0
四、比较指令
1. cmp 寄存器, 操作数二
@将寄存器的值与操作二比较,比较的结果会自动影响CPSR的NZCV
五、跳转指令
1. B/BL 标签
@跳到一个指定的标签,BL跳转之前,将跳转前的PC的值保存在LR,跳转范围+/-32M
2. 给pc赋值 ldr pc, =标签名
六、内存访问指令
1. 单个数据访问
LDR 将内存中的值加载到寄存器
STR 将寄存器中的值写入内存
@ 寄存器间接寻址:寄存器的值是一个地址
LDR r0, [r1] @ r0 = *r1
STR r0, [r1] @*r1 = r0
@ 基址变址寻址:将基地址寄存器加上指令中给出的偏移量,得到数据存放的地址
前索引
STR r0, [r1, #4] // *(r1 + 4) = r0
LDR r0, [r1, #4] // r0 = *(r1 + 4)
后索引
STR r0, [r1], #4 // *r1 = r0 && r1 = r1 + 4
LDR r0, [r1], #4 // r0 = *r1 && r1 = r1 + 4
自动索引
STR r0, [r1, #4] // *(r1 + 4) = r0 && r1 = r1 + 4
LDR r0, [r1, #4] // r0 = *(r1 + 4) && r1 = r1 + 4
2.多个数据访问
@ LDM 将一块内存中的值加载到多个寄存器中 LDM{条件}{s}<MODE> 基址寄存器{!},{Reglist}^
@ STM 将多个寄存器中的值,储存到一块内存中 STM{条件}{s}<MODE> 基址寄存器{!},{Reglist}^
MODE: IA 后增加地址;IB 先增加地址;DA 后减小地址;DB 先减小地址
基址寄存器:存放内存的起始地址
!:最后更新基址寄存器的值
Reglist: 多个寄存器,从小到大,中间用","隔开{r0, r2, r4}、{r0, r3-r7},寄存器号大的对应内存的高地址,寄存器号小的对应内存的低地址
^: 它存在,如果Reglist没有pc的时候,这个时候操作的寄存器是用户模式下的寄存器,在LDM指令中,有PC的时候,在数据传送的时候,会将SPSR的值拷贝到CPSR,用于异常的返回
3.栈操作指令
进栈: stmfd sp!,{寄存器列表}
出栈: ldmfd sp!,{寄存器列表}
@ 在对栈操作之前,必须先设置sp的值,进栈和出栈的方式一样,ATPCS标准规定满减栈(满堆栈并且向下增长)
@ 堆栈指针指向最后压入的堆栈的有效数据顶,称为满堆栈
@ 堆栈指针指向下一个待压入数据的空位置,称为空堆栈
4. CPSR/SPSR操作指令
MRS Rn, CPSR/SPSR 将状态寄存器的值读到通用寄存器中
MSR CPSR/SPSR, Rn 将通用寄存器的值写到状态寄存器中
七、ARM常用伪指令分析
1. LDR R0, =0x12345678
ldr r1, =0x12345678
翻译如下:
0x00000004 E51F1000 LDR R1,[PC] @ 执行阶段
0x00000008 EAFFFFFE B 0x00000008 @ 译码阶段
0x0000000C 12345678 EORNES R5,R4,#0x07800000 @ 预取阶段,pc的值
2. LDR R0, =Lable
@指令表示将Label的值写入r0,Label的值由指定的代码段运行地址(-Ttext=地址值)来决定
@ 首先根据指定的代码段开始的地址,算出Labe标签对应的地址值
@ 然后将这个表示的地址值存放在一个位置
@ 生成内存访问指令,根据pc+固定偏移量,找到标签对应值存放的位置
@ ps: 当代码编译结束的时候,标签表示的地址值(根据指定的代码段地址)已经编译死存放在程序文件中了。
3. LDR R0, Lable
@ R0写入的事Lable地址所对应的数据
例如:0x0000000c Lable: .word 0x12345678
R0读到的不是0x0000000c而是0x12345678
4. ADR R0, Lable
@ 也是将Label的值写入r0```
_start:
mov r0, #1 @0x0000
adr r1, Lable @0x0004
stop:
b stop @0x0008
Lable:
.word 0x12345678 @0x0004```
翻译如下:
0x00000000 E3A00001 MOV R0,#0x00000001
0x00000004 E28F1000 ADD R1,PC,#0x00000000
0x00000008 EAFFFFFE B 0x00000008
0x0000000C 12345678 EORNES R5,R4,#0x07800000
ps: 指令表示根据当前的PC的值+/-偏移量,动态获取当前Label所表示的内存地址, 不会在编译阶段就写死
所以通过 ADR R0, _start 可以获取在内存中的实际地址
八、ATPCS标准 -- ARM-Thumb程序调用标准
寄存器 | 同义词 | 特殊名 | 规范作用 |
r15 | PC | 程序计数器 | |
r14 | LR | 链接寄存器,发生跳转时用来保存pc | |
r13 | SP | 栈指针寄存器 | |
r12 | IP | 保存SP | |
r11 | v8 | FP | ARM状态变量寄存器8 & 栈帧寄存器 |
r10 | v7 | SL | ARM状态变量寄存器7 & 堆栈限制指针寄存器 |
r9 | v6 | SB | ARM状态变量寄存器6 |
r8 | v5 | ARM状态变量寄存器5 | |
r7 | v4 | WR | 变量寄存器4 & Thumb状态工作寄存器 |
r6 | v3 | 变量寄存器3 | |
r5 | v2 | 变量寄存器2 | |
r4 | v1 | 变量寄存器1 | |
r3 | a4 | 参数/结果/临时寄存器4 | |
r2 | a3 | 参数/结果/临时寄存器3 | |
r1 | a2 | 参数/结果/临时寄存器2 | |
r0 | a1 | 参数/结果/临时寄存器1 |
@ v1-v8变量寄存器只有v1-v4可以在Thumb下使用
@ Thumb状态下,只有r0-r7,SP,LR,PC普遍可用
@ ARM压栈的顺序很是规矩,依次为当前函数指针PC、返回指针LR、栈指针SP、栈基址FP、传入参数个数及指针、本地变量和临时变量。
九、汇编与c混合编程
1. 汇编调用c
@ 需要在汇编代码中指定sp
mov r0, #1
mov r1, #2
ldr sp, =0x4000fff0
bl add
2. c内嵌汇编
asm(
"指令1\n"
"指令2\n"
... ...
:输出列表
:输入列表
:修改列表(通用的寄存器)
);
指 令:ARM汇编指令
输出列表:将内嵌汇编中的寄存器值输出到c变量
输入列表:将c变量输入到内联汇编中使用的寄存器
修改列表:内联汇编中修改的寄存器```
int add(int a, int b){
int c;
asm(
"add r0, %1, %2\n"
"mov %0, r0\n"
:"=r"(c)
:"r"(a), "r"(b)
:"r0"
);
return c;
}```
ps : C变量的引用,从输出列表到输入列表开始编号:第一个C变量%0,第二个C变量%1,... ...
修饰符 说明
无 被修饰的操作符是只读的
= 被修饰的操作符只写
+ 被修饰的操作符具有可读写的属性
& 被修饰的操作符只能作为输出
十、ARM核异常处理
(一)什么是异常
异常是处理器核在执行程序指令的过程中突然遇到了异常的事情,这些事件包括硬件中断、指令执行错误、用户程序请求服务、内存访问异常、取指令异常等,几乎每种处理器都支持特定的异常处理,中断也是异常的一种。
(二)ARM异常源
异常偏移量 | 异常源 | 描述 | 优先级 | 模式 |
---|---|---|---|---|
0x00 | Reset | 复位异常:在内核复位时执行 | 1 | 管理模式(SVC) |
0x04 | Undefined instructions | 未定义指令异常:流水线执行非法指令产生,该异常发生在流水线译码阶段,如果当前指令不能被识别为有效指令,将会出现此类异常。 | 6 | 未定义模式(UDF) |
0x08 | SVC(Supervisor Call)、SWl(Software Interrupt) | 软中断异常:用于程序触发软件中断执行该异常是由应用程序自己调用时产生,该异常在管理模式(SVC)下运行。 | 6 | 管理模式(SVC) |
0x0c | Prefetch abort | 预取指令中止异常:当一条指令从内存中取出时由于某些原因失败,且如果它能到达执行状态将会触发此异常。 | 5 | 终止模式(ABT) |
0x10 | Data abort | 数据访问中止异常:如果一个预取指令试图存取一个不存在或非法的内存单元时,将会触发此异常。 | 5 | 终止模式(ABT) |
0x14 | Reserved | 保留 | 保留 | 保留 |
0x18 | IRQ | 一般中断异常 | 4 | 一般中断模式(IRQ) |
0x1c | FIQ | 快速中断模式 | 3 | 快速中断模式(FIQ) |
FIQ较IRQ快的原因:
1.FIQ在异常向量表位于最末
a.所以可以直接把异常处理程序写在异常向量表之后,省去了跳转的过程。
b.而IQ需要执行向量表对应的跳转指令之后,跳转到指定的中断处理程序。
2.FIQ模式有5个私有寄存器(R8-R12)
a.执行中断处理程序前无需压栈保存寄存器,可直接处理中断。
b.而IRQ的R8-R12寄存器是与和其它模式共用的,在中断前需要保护现场,即把R8-R12的数据保存在栈中。
3.FIQ的优先级高于IRQ
a.两个中断同时发生时先响应FIQ
b.FIO可以打断IRO,IRO不能打断FIO。
(三)ARM异常处理过程
异常向量表:一块内存存放的是跳转到异常处理函数入口地址的指令,当异常产生的时候
ARM核会到异常向量表的相应位置取指令执行,跳转到对应的异常处理函数。
1. 异常产生,ARM核自动做的事情:
(1)拷贝CPSR的值到SPSR异常
(2)修改CPSR
[1]进入ARM状态
[2]切换到异常模式
[3]禁止中断
(3)将当前PC的值保存到LR_异常
(4)将印c设置到异常向量表相应位置
2. 从异常向量表跳转到异常处理函数 ==》
exception_handler:
通用寄存器压栈保护
异常处理
异常返回(恢复寄存器、CPSR、PC)
异常向量表的位置:
1.0xFFFF0000 or 0x00000000 on ARM720T and on ARM9/10 family devices,Determined by cp15.c1寄存器
2.Cortex-A Determined by cp15.c12存器ps: cp0~cp15共16个协处理器,它们也有自己的寄存器,一般以c开头
(四) SVC/SWI异常处理
指令:SVC/SWI{cond} #imm
1.软中断在操作系统中的应用
2. 实例演示
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word _undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word _irq
_fiq: .word _fiq
resest:
@adr r0, _start
@mcr p15, 0, r0, c12, c0, 0
swi #8 @发生软中断
mov r0, #1 @但是因为破坏了流水线,ARM核会把lr调整到这里
mov r1, #2 @此时pc指向这里
stop:
b stop
software_interrupt:
ldr sp!, =0x40000fff0 @设置栈指针位置
stmfd sp!, {r0-r12, lr} @保护通用寄存器
ldr r0, [lr, #-4]
mov r1, #0xff
bic r0, r0, r1, lsl #24 @以上三步可以获取软中断号
ldmfd sp!, {r0-r12, pc}^ @^: 有PC的时候,在数据传送的时候,会将SPSR的值拷贝到CPSR