[014] [ARM-Cortex-M3/4] Usage Fault 使用错误异常实战

ARM
Contents
先不使能Usage Fault.
使能usage fault
从usage fault返回

测试使用未定义指令引发的使用错误异常(软件断点就是使用未定义指令来实现的)

1 先不使能Usage Fault

在进入my_main前设置一条未定义指令DCD 0xffffffff,并且在未定义指令前初始化串口以打印调试信息。

                PRESERVE8
                THUMB

                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
				IMPORT  HardFault_Handler 
				IMPORT  UsageFault_Handler	
__Vectors       DCD     0              				; Top of Stack
				DCD     Reset_Handler               ; Reset Handler
                DCD     0                			; NMI Handler
                DCD     HardFault_Handler           ; Hard Fault Handler
                DCD     0          					; MPU Fault Handler
                DCD     0           				; Bus Fault Handler
                DCD     UsageFault_Handler_ASM      ; Usage Fault Handler

                AREA    |.text|, CODE, READONLY
Reset_Handler   PROC
				EXPORT  Reset_Handler             [WEAK]
                IMPORT  my_main 
				IMPORT  uart_init 
				LDR     SP, =(0x20000000 + 0xC000)
				; relocate data section
				BL	    relocate
				; clear bss section
				BL	    clear_bss
				ldr r0, =0x40013800
				ldr r1, =115200  		
				BL uart_init
				DCD 0xffffffff
				LDR R0, =my_main	; ldr	pc, =my_main
				BX R0
                ENDP

程序执行到DCD 0xffffffff会触发Usage Fault异常,但是此时还未使能该异常,因此直接上访到hard Fault,进入HardFault_Handler:

image-20220319223137503

  • LR = EXC_RETURN合法值 = 0xFFFFFFF9:异常返回线程模式(执行my_main),并且使用主栈MSP
  • xPSR的ISR位表示异常编号,Hard fault编号即为3
  • Mode:异常服务例程中为处理模式

注意:如果不加while(1)会一直打印,因为没有清除异常悬起标志位。同时其异常栈帧保存的返回地址,就是未定义指令所在的地址,一但返回也会触发异常

2 使能usage fault

在未定义指令前使能usage fault异常:

void usage_fault_enabled(void){
    SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;
}

进入异常前硬件会将r0~r3、r12、lr、pc、xpsr压栈(exception stack frame),因此在异常进入前给这些寄存器赋值进行测试(不能给pc和xpsr赋值,会导致程序跑飞):

BL usage_fault_enabled
ldr r0, =0  
ldr r1, =0x11111111  
ldr r2, =0x22222222
ldr r3, =0x33333333
ldr r12, =0x12121212
ldr lr, =0x14141414
DCD 0xffffffff

因为C函数中无法直接使用SP,因此用汇编传参,定义使用错误异常UsageFault_Handler_ASM复位函数:

UsageFault_Handler_ASM  PROC	        ; 由于硬件调用UsageFault_Handler时不会对其传参, 因此要修改为汇编函数
                mov r0, sp              ; 传入异常栈帧sp指针
                b UsageFault_Handler   ; 不能用bl跳转, 因为lr = EXC_RETURN     
                ENDP

必须使用不带返回地址的跳转指令跳转到最后的异常处理函数UsageFault_Handler,否则会更新LR的值,因为此时LR要用于异常返回被赋予了EXC_RETURN,在进入UsageFault_Handler时会将此LR压入栈中,UsageFault_Handler退出时将LR弹给PC:

image-20220322223015986

当然,也可以在进入UsageFault_Handler前将LR psuh到栈中,跳转回来时在从栈中弹出LR到PC返回,如:

UsageFault_Handler_ASM  PROC	        
                        mov r0, sp    
                        push {lr}
                        b UsageFault_Handler
                        pop  {pc}
                        ENDP

UsageFault_Handler函数代码:

void UsageFault_Handler(uint32_t *exception_stack_frame){
    send_str(usart1, "UsageFault_Handler\r\n");
    put_s_hex(usart1, "\r\nR0\t= ", exception_stack_frame[0]);
    put_s_hex(usart1, "\r\nR1\t= ", exception_stack_frame[1]);
    put_s_hex(usart1, "\r\nR2\t= ", exception_stack_frame[2]);
    put_s_hex(usart1, "\r\nR3\t= ", exception_stack_frame[3]);
    put_s_hex(usart1, "\r\nR12\t= ", exception_stack_frame[4]);
    put_s_hex(usart1, "\r\nLR\t= ", exception_stack_frame[5]);
    put_s_hex(usart1, "\r\nPC\t= ", exception_stack_frame[6]);
    put_s_hex(usart1, "\r\nxPSR\t= ", exception_stack_frame[7]);
}

打印结果:
image-20220319225730274

从exception stack frame读取保存的寄存器值,与进入异常前的值一致,硬件成功保存现场。但是异常处理完后却无法正常返回,一直卡在UsageFault_Handler中,进行deubg分析:

image-20220319231626221

exception stack frame中保存的PC值(异常返回地址)为0x08000074,与上面串口打印保存的PC值一致,该值即为当前未定义指令DCD 0xffffffff的地址,再次触发usage fault,陷入死循环。

3 从usage fault返回

参考:How to debug a HardFault on an ARM Cortex-M MCU。文章中还要使用 SCB->CFSR = SCB->CFSR清除usage fault,但实测不用。

其实很简单,将exception stack frame保存的PC值,修改为未定义指令的下一条指令地址即可:

// 修改异常栈帧的PC值下一条指令地址 pc+4
void UsageFault_Handler(uint32_t *exception_stack_frame){
	[...]
    /*modify exception stack frameset content, set return address to pc+4 
      the exception stack frame 7th is return address(pc) */
    exception_stack_frame[6] += 4;  //   pc += 4: point to next instruction 
}

exception_stack_frame[6]即为异常栈帧中的PC异常返回地址(满减栈),直接pc += 4即修改成功,最后程序成功从UsageFault_Handler返回,执行下一条指令LDR R0, =my_main

image-20220319233336067

END

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Cortex-M0是一种ARM处理器的核心架构,用于嵌入式系统中。"hardfault"是指在程序运行时发生的硬错误异常。当Cortex-M0处理器检测到硬错误时,会引发一个硬错误中断,即硬错误中断(HardFault)。 Cortex-M0的硬错误中断(hardfault)通常是由以下情况触发: 1. 无效的指令:当程序执行到一个非法或无效的指令时,会导致硬错误中断。 2. 内存访问错误:当程序试图访问不存在的内存地址、只读内存进行写操作或对只执行内存进行写操作时,会导致硬错误中断。 3. 栈溢出:当程序使用的堆栈空间超过其分配的大小,会导致硬错误中断。 4. 数学运算错误:当程序进行除零或无效的浮点数操作等数学运算时,会引发硬错误中断。 发生硬错误中断后,Cortex-M0处理器会中止当前正在执行的进程,并将程序控制权传递给硬错误中断处理程序(HardFault Handler)。硬错误中断处理程序可以用于处理硬错误的日志记录、调试等操作,并根据具体情况采取相应的纠正措施。 为了解决Cortex-M0硬错误中断问题,应该进行以下步骤: 1. 使用调试工具来定位并修复导致硬错误中断的问题,例如检查程序是否存在无效的指令或内存访问错误。 2. 检查堆栈空间的分配是否合理,确保不会发生栈溢出。 3. 避免发生数学运算错误,例如在进行除法运算前进行零值检查。 通过以上措施,可以有效解决Cortex-M0硬错误中断的问题,确保程序的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯西的彷徨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值