Skip to content

LV010-异常处理示例

一、SVC 异常

1. 软中断指令

前边其实有学习过 SWI 指令,我们可以通过这个指令,触发软中断,从而进入 SVC 模式,一般语法如下:

assembly
SWI immed_24

immed_24 是一个 24 位立即数,比如 0x123 些。注意:我们使用“ swi”指令,实际上我们可以在 .dis 中看到它被翻译为 SVC 指令。在老的芯片比如 S3C2440 中我们使用 SWI 指令,在 IMX6ULL 中可以使用 SVC 代替它。

2. SVC 异常处理

当使用 SWI 软中断的时候,就会产生 SVC 异常,进入这个模式,对应的 CPSR 的 M[4:0]的值为 10011,也就是 0x13。关于 SVC 异常的汇编如下:

assembly
/* SVC中断 */
SVC_Handler:
	/** 执行到这里之前:
	  * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
	  * 2. SPSR_svc保存有被中断模式的CPSR
	  * 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
	  * 4. 跳到0x08的地方执行程序 
	  */

	/* 保存现场 */
	/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  

	mov r4, lr
	
	/* 处理swi异常 */
	mrs r0, cpsr
	ldr r1, =swi_string
	bl printException

	sub r0, r4, #4
	bl printSWIVal
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
swi_string:
	.string "swi exception"
	.align 4

在 SVC_Handler 里将 r0-r12 和 lr 保存在 SVC 模式的栈上,然后将 lr 的值移动到 R4,调用 printException 函数打印出当前的 CPSR 值,和产生异常的原因。将 R4 减去 4,赋值给 R0,这是 swi 指令所在的地址,然后调用 printSWIVal 函数打印出 swi 指令的参数。最后将 r0-r12 从栈上恢复, lr 从栈上弹出到 PC,并同时将 SPSR 恢复到 CPSR,从而返回去执行 swi 指令的下一条指令。

3. 设置堆栈

注意一定要设置堆栈,否则进入对应的异常后,堆栈可能会异常。

assembly
	/* 进入SVC模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0X80200000	/* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */

4. 完整实例

完整的实例及修改可以看这里:08_EXCEPTION/SWI · sumumm/imx6ull-bare-demo

5. 测试结果

我们可以在终端看到以下打印信息:

image-20240118215617370

二、未定义异常

1. 怎么产生未定义异常?

前边已经学习过了,就是 CPU 或协处理器不认识这条指令,执行这样的指令时就会产生“未定义指令异常”。 我们可以这样产生一个未定义异常:

assembly
.word 0xeeadc0de  /* undefine instruction */
.word 0xFFFFFFFF

上边两条都可以产生未定义异常。

2. 未定义异常处理

assembly
Undefined_Handler:
	/** 执行到这里之前:
	  * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	  * 2. SPSR_und保存有被中断模式的CPSR
	  * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	  * 4. 跳到0x4的地方执行程序 
	  */

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"
	.align 4

在 Undefined_Handler 里将 r0-r12 和 lr 保存在 und 模式的栈上,然后调用 printException 打印当前的 CPSR 值,并打印一个字符串。最后将 r0-r12 从栈上恢复, lr 从栈上弹出到 PC,并同时将 SPSR 恢复到 CPSR,从而返回去执行出现未定义异常指令的下一条指令。

3. 设置堆栈

assembly
	/* 进入undef模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x1B 	/* r0或上0x1B,表示使用undef模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0x80800000	/* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */

4. 完整实例

完整的实例和修改的地方可以看这里:08_EXCEPTION/UNDEF · sumumm/imx6ull-bare-demo

5. 测试结果

最终我们可以在终端看到以下打印信息:

image-20240118221514998