一、实战_未定义指令异常
要想深入理解异常处理,需要写程序来验证。 本节课程故意执行一条SVC指令,让它触发异常。
参考资料:ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
1.1 A7的异常向量表
从向量表可以看出,A7支持哪些异常:未定义指令、软中断(SVC)、预取指令中止、数据中止、IRQ、FIQ。
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt /*cortex的A7中称为SVC*/
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
1.2 什么是未定义指令
未定义指令,即使"还没有定义的指令",也就是CPU不认识的指令。很多时候,我们故意在代码里插入一些伪造的指令,故意让CPU执行到它时触发错误。这在调试时很有用,比如想打断点:怎么实现呢?
有很多种方法:硬件监视点(watch point,数量有限)、软件断点(数量无限)。
软件断点就是使用未定义指令来实现的,比如想让程序执行到某个地址A时停下来,可以这样做:
本节教程并不打算制作调试器,这里只是讲述一下未定义指令的作用,使用它来深入理解异常处理流程。
1.4 编程过程
1.4.1 在汇编代码里插入未定义指令
在代码中插入:
.word 0xffffffff
1.4.2 设置异常向量表基地址
MRC p15, 0, <Rt>, c12, c0, 0 ; Read VBAR into Rt
MCR p15, 0, <Rt>, c12, c0, 0 ; Write Rt to VBAR
1.4.3 设置未定义的栈
本次实验代码建立在前几节代码的重定位基础上,这里设置异常部分程序使用的栈空间
1.4.4 保存现场
这里可以重温一下上一节笔记中的2.3小节
stmdb sp!, {R0-R3,R12,LR}
1.4.5 处理异常
这里我们新建一个C文件,里面写一些调试信息,然后使用该命令跳转去执行
bl do_undefined_c
1.4.6 恢复现场
ldmia sp!, {R0-R3,R12,PC}^
1.4.7 总结
//#define STACK_BASE (0xc0000000 + 0x100000) // stm32mp157
#define STACK_BASE (0x80000000 + 0x100000) // imx6ull
#define STACK_SIZE (2048)
.text
.global _start
_start:
b reset
ldr pc, =do_undefined
.word 0 // ldr pc, _software_interrupt
.word 0 // ldr pc, _prefetch_abort
.word 0 // ldr pc, _data_abort
.word 0 // ldr pc, _not_used
.word 0 // ldr pc, _irq
.word 0 // ldr pc, _fiq
reset:
/* 设置sp */
/* 对于STM32MP157设置链接地址为0xC0200000, 对于IMX6ULL设为0x80200000 */
ldr sp, =STACK_BASE
adr r0, _start
bl SystemInit
bl uart_init
/* 设置异常向量表基地址 : VBAR */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0
.word 0xffffffff
/* 调用main函数 */
//bl main
ldr pc, =main
do_undefined:
/* 设置SP_und */
ldr sp, =STACK_BASE - STACK_SIZE
/* 保存现场 */
stmdb sp!, {R0-R3,R12,LR}
/* 调用处理函数 */
bl do_undefined_c
/* 恢复现场 */
ldmia sp!, {R0-R3,R12,PC}^
二、实战_SVC异常
2.1 什么是SVC异常
简单地说就是执行SVC这条汇编指令时就会触发这个异常,CPU就会跳转过执行SVC异常向量的代码。
supervisor call(SVC)通常在用户模式下使用,这使得用户模式的代码能够访问OS功能。例如,如果用户代码想要访问系统的特权部分(例如执行文件I / O),则通常将使用SVC指令执行此操作。在Linux中对文件的open/read/write等APP层的系统函数,它的本质都是执行SVC指令,从而进入Linux内核中预设的SVC异常处理函数,在内核里操作文件。
可以使用寄存器或者操作码中某个字段将参数传递给SVC处理程序。
发生异常时,异常处理程序可能必须确定内核是处于ARM还是Thumb状态。
特别是SVC处理程序,可能必须读取指令集状态。这是通过检查SPSR T位完成的。该位设置为Thumb状态,清除为ARM状态。
ARM和Thumb指令集都具有SVC指令。从Thumb状态调用SVC时,必须考虑以下因素:
•指令地址位于LR-2,而不是LR-4;
•指令本身是16位的,因此需要半字加载;
• SVC编号为8位而不是ARM状态下的24位。
注意:ARM9等比较老的芯片里,这个异常是SWI异常,对应的指令是SWI。
本节课程不讲解SVC在内核中的使用,我们只是看看如何处理SVC触发的异常。