[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:

[014] [ARM-Cortex-M3/4] Usage Fault 使用错误异常实战_第1张图片

  • 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]);
}

打印结果:
[014] [ARM-Cortex-M3/4] Usage Fault 使用错误异常实战_第2张图片

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

[014] [ARM-Cortex-M3/4] Usage Fault 使用错误异常实战_第3张图片

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

你可能感兴趣的:(ARM,arm,stm32)