笔者来聊聊对CortexM3/M4的异常模式理解。
之前的了解,都是基于具体的芯片而言的,比如ST/GD/NXP公司的,STM32,很常用,基于Keil或者IAR集成开发环境,一直到工作之后,才更清楚的了解了架构这种概念,这种芯片本质上还是基于CortexM3/M4这种架构的,各种厂商都是基于这种架构进行开发的,然后添加很多外设,满足不同的功能需求。
寄存器首先是架构的不可或缺的一部分,也是了解架构编程模型的重要地方。
寄存器 ,r0-r12,r13(sp),r14(lr),r15(pc);
状态寄存器PSR:APSR、EPSR、IPSR;
T:thumb指令,总是为1
异常编号:正常处理的异常编号,与接下来介绍的中断向量表编号相关,
NZCV:标志位,
与经典的arm架构相比(比如arm7 ),M系列做了很多改变。
模式:模式减少,只有特权模式和非特权模式(均是在线程下,异常模式自动是特权等级),
堆栈:只有主栈+线程栈,MSP+PSP,不像经典架构,每个模式都有各自独立的栈。
寄存器:减少,模式下除了SP,其他寄存器都是共享,经典架构下,寄存器较多,各个模式下都有几个独立的寄存器。
异常:异常也有变化,新增hard fault等,将svc模式转为svc的函数处理,等等
中断相关寄存器 :PRIMASK、FAULTMASK和BASEPRI
a. PRIMASK:中断屏蔽位,1位宽,屏蔽除NMI以及hard_fault之外的中断
b. FAULTMASK:中断屏蔽位,1位宽,屏蔽除NMI之外的所有中断
c . BASEPRI:当前优先级设置,位数取决于厂商有多少个外设中断,
d. CMSIS-Core 提供了多个C函数,可以访问中断相关寄存器,也提供了汇编函数
x = __get_BASEPRI(); // Read BASEPRI register
x = __get_PRIMARK(); // Read PRIMASK register
x = __get_FAULTMASK(); // Read FAULTMASK register
__set_BASEPRI(x); // Set new value for BASEPRI
__set_PRIMASK(x); // Set new value for PRIMASK
__set_FAULTMASK(x); // Set new value for FAULTMASK
__disable_irq(); // Set PRIMASK, disable IRQ
enable_irq(); // Clear PRIMASK, enable IRQ
; 汇编代码
MRS r0, BASEPRI ; Read BASEPRI register into R0
MRS r0, PRIMASK ; Read PRIMASK register into R0
MRS r0, FAULTMASK ; Read FAULTMASK register into R0
MSR BASEPRI, r0 ; Write R0 into BASEPRI register
MSR PRIMASK, r0 ; Write R0 into PRIMASK register
MSR FAULTMASK, r0 ; Write R0 into FAULTMASK register
;primask 以及 faultmask 特殊处理
CPSIE i ; Enable interrupt (clear PRIMASK)
CPSID i ; Disable interrupt (set PRIMASK)
CPSIE f ; Enable interrupt (clear FAULTMASK)
CPSID f ; Disable interrupt (set FAULTMASK)
上文说的:特权模式与图中的处理模式一致含义。
异常返回值LR (EXC_RETURN),异常处理LR在M3/M4架构下有新的意义,
异常的寄存器如下:主要是4个中断(hardfault、mem/bus/usr fault )关联的寄存器信息。
__set_PRIMASK(1); //屏蔽除NMI 和 HardFault以外其他的中断 ;__set_PRIMASK(0);
__set_FAULTMASK(1); //屏蔽除NMI 中断;__set_FAULTMASK(0);
//可嵌套的开关中断函数primask
u32 arm_v7_disable_irq()
{
u32 mask;
asm volatile("mrs %0, primask" : "=r"(mask));
asm volatile("cpsid i");
return mask
}
void arm_v7_enable_irq(u32 mask)
{
asm volatile("msr primask,%0" : : "r"(mask));
}
//可嵌套的开关中断函数faultmask
u32 arm_v7_disable_fiq()
{
u32 mask;
asm volatile("mrs %0, faultmask" : "=r"(mask));
asm volatile("cpsid i");
return mask
}
void arm_v7_enable_fiq(u32 mask)
{
asm volatile("msr faultmask,%0" : : "r"(mask));
}
void save_generic_regs();
save_generic_regs:
stmfd sp!,{r9-r12}
stmfd sp!,{r5-r8}
stmfd sp!,{r1-r4}
stmfd sp!,{r0}
bx lr
/* void restore_generic_regs(); */
restore_generic_regs_from_stack:
ldmfd sp!,{r0}
ldmfd sp!,{r1-r4}
ldmfd sp!,{r5-r8}
ldmfd sp!,{r9-r12}
bx lr
u32 regs_data[15];
void save_regs_to_global_var(u32 save_reg_addr)
{
memcpy((u8*)regs_data,(u8*)save_reg_addr,15*4);
}
/*1:hard 2:mem 3:bus 4:usr*/
void enter_exception_mode(u8 exception_reason)
{
/* get the exception reg info*/
/* print backtrace */
/* save the stack and global reg */
}
void HardFault_Handler();
HardFault_Handler:
/* save the lr reg to the stack */
push lr
/* reserve stack space for sp */
sub sp,sp,#4
/*save r0-r12 to stack (13*4=52) */
bl save_generic_regs
/*save sp to stack ,the sp is the reg before it entered the hardfault,4+4+52=60*/
add r0,sp,#60
str r0,[sp,#52]
/*save regs to the global reg */
mov r0,sp
bl save_regs_to_global_var
/* restore the regs from the stack */
bl restore_generic_regs
mov r0,#1
/* enter the assert mode */
blx enter_exception_mode
/* skip the stack space for sp */
add sp,sp,#4
/* restore the lr from stack */
ldmfd sp!,{lr}
bx lr
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
NVIC_SetPriority(MemoryManagement_IRQn,0);
bus fault:总线访问异常
usr fault:
来看一个实际的例子,引发了非对齐访问的血案。
typedef struct wifi_data_rec_struct
{
u16 header;
u16 addr;
u32 arg1;
u32 arg2;
u16 arg3;
u16 checksum;
}wifi_data_rec_t,*wifi_data_rec_ptr_t;
typedef strcut wifi_info_struct
{
u32 wifi_addr;
u8 recv_flag;
u8 test_flag;
u16 wifi_data;
u8 tx_buf[128+2];
u8 rx_buf[16];
}wifi_info_t, wifi_info_ptr_t;
wifi_info_t wifi_info_g;
u8* rx_buf=wifi_info_g.rx_buf;
/*......*/
u16 temp_value = (*(u16*)rx_buf);
PRINTF("temp_value=%x rx_buf_addr=0x%p \r\n",temp_value,rx_buf);
wifi_data_rec_t temp_checksum=(*(wifi_data_rec_ptr_t )rx_buf);
PRINTF("temp_value=%x temp_check_sum=%x\r\n",temp value, temp_checksum.checksum);
u16 cal_check_sum=cal_wifi_data_cmd(temp_checksum);
详细介绍一下错误的原因:
那就是那句强转代码导致的异常,
因为后面要计算校验和,所以编译器就把每个数据加载到寄存器,然后去计算。
SCB->CCR = SCB_CCR_UNALIGN_TRP_MSK;
使能了改bit之后,ldrh 正常执行,因为其满足 half word对齐,但是ldr造成了异常,因为不满足word 对齐。
addr2line 查看异常地址信息
320行来看,其实并不是强转的代码,是因为编译器把打印和加载放到一行代码来对应,所以也不能全部按照源码的行数来看,因为源码和汇编指令可能不对应,在开了优化的情况下。
如果关闭优化,321行,就是强转的那行代码,对于调试来说,更加友好。
0x200098E8:R0-R3
0x20009FE8:R12,LR,PC,xPSR
可以见到PC:0x080086A0,可以正确看到程序出错前的地址,帮助我们分析错误。
ARM Cortex-M3与Cortex-M4权威指南
Cortex-M3权威指南