测试使用未定义指令引发的使用错误异常(软件断点就是使用
未定义指令
来实现的)
在进入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:
EXC_RETURN
合法值 = 0xFFFFFFF9:异常返回线程模式(执行my_main),并且使用主栈MSP注意:如果不加while(1)会一直打印,因为没有清除异常悬起标志位。同时其异常栈帧保存的返回地址,就是未定义指令所在的地址,一但返回也会触发异常。
在未定义指令前使能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:
当然,也可以在进入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]);
}
从exception stack frame读取保存的寄存器值,与进入异常前的值一致,硬件成功保存现场。但是异常处理完后却无法正常返回,一直卡在UsageFault_Handler
中,进行deubg分析:
exception stack frame中保存的PC值(异常返回地址)为0x08000074
,与上面串口打印保存的PC值一致,该值即为当前未定义指令DCD 0xffffffff
的地址,再次触发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
:
END