主堆栈指针(MSP), 或写作SP_main, 这是缺省堆栈指针,用于OS内核,异常中断处理程序以及所有需要特权访问的应用程序代码。
进程堆栈指针(PSP), 或写作SP_process, 用于常规代码/task执行。
入栈:把8个寄存器值压入栈
压入哪个堆栈 MSP/PSP的选择:
如果当响应异常时,当前代码正在使用PSP, 则压入PSP, 即使用线程堆栈;
否则压入MSP, 使用主堆栈。
取向量: 从向量表里找到正确的异常向量, 然后在服务程序入口取指;
更新寄存器: 在执行异常/终端处理程序之前,会更新一系列寄存器:
异常/中断返回时,CM3处理流程
根据EXC_RETURN的值,从不同的SP里恢复那8个寄存器。
EXC_RETURN=0xFFFF_FFF1时,从MSP里恢复,
EXC_RETURN=0xFFFF_FFFD时,从PSP里恢复。
MemMagae Fault
MemManage Fault也称作“存储器管理fault”,是CM3编号为4的异常。
触发的因素
相关寄存器
为了查找问题,NVIC有一个”存储器管理fault状态寄存器(MFSR)”, 它表示导致MemManage fault的原因;
如果是数据访问违例(DACCVIOL)或是取指访问违例(IACCVIOL),则违例指令的地址已经被压入栈中;
如果MMARVALID被置位,则还能进一步查出引发fault时访问的地址,读取NVIC”存储 器管理地址寄存器(MMAR)”.
调试AP(Cortex-A53/A73) idle的时候,由于AP的power on/off在SCP(Cortex-M3)里完成,
发现A73 idle,会导致SCP出现MemManage Fault异常。
[17:01:22]=== HANDLER EXCEPTION: 04 ====== xPSR: 61000077 ===
[17:01:22]r0 :10009308 r1 :10008408 r2 :004da495 r3 :00000000
[17:01:22]r4 :00000000 r5 :00000000 r6 :00000000 r7 :00000000
[17:01:22]r8 :00000000 r9 :00000000 r10:00000000 r11:00000000
[17:01:22]r12:0000000a sp :100083e0 lr :fffffffd pc :4685480a
[17:01:22]Instruction access violation
[17:01:22]mmfs = 1, shcsr = 70001, hfsr = 0, dfsr = 0
[17:01:22]
[17:01:22]=========== Process Stack Contents ===========
[17:01:22]100085d8: 00000000 00000000 03b00100 ff80023c
[17:01:22]100085e8: 00000000 10000c1f 10000c20 01000000
[17:01:22]100085f8: 00000000 100009fd deadd00d deadd00d
[17:01:22]10008608: deadd00d deadd00d deadd00d deadd00d
分析的方向是找到触发异常的地址在哪里。
从异常打印上来看, MMFS寄存器值1,表示是取指令违例,但无法用MMAR寄存器来查看出问题的地址;
从异常log来看, 进程堆栈内容是正常的,PC值是错误的,但这个PC值不在芯片的正常区域,无法从PC值判断出问题的触发点,参考价值不是太大;
修改代码,在MemManage Fault异常处理的入口停止,然后连DS5进去查看:
通过DS5查看LR/SP寄存器:
LR值是0xFFFF_FFF1, 根据EXC_RETURN的定义,可以得知触发异常的地方并不在进程里,而在异常/中断处理/特权代码执行区域,根据执行场景来看,当时主要是在做AP core的power on/off, 初步判断是在执行AP power on/off的中断处理程序中触发了异常。
到底是power off还是power on出的问题,需要进一步区分, 根据LR的值,说明出现异常的context被压入MSP堆栈里,查看MSP堆栈内容,可以发现其中xPSR的值是0x61000077, 0x77是中断号,查找对应的中断,发现这是触发CM3执行AP power on的中断号,问题进一步锁定在power on流程里;
重新回到LR的值,仍是EXC_RETURN, 说明问题出在刚刚进入中断处理程序,还没有任何函数调用来更改LR的值,所以开始从异常向量表中断入口查起,最终发现在异常向量表入口处,中断处理程序入口没有定义好, 下图if条件是false了。
修改CONFIG_IRQ_COUNT, 正确定义0x77号中断的处理程序入口,问题解决。
后记
在分析问题过程中,发现异常的panic report log打印, lr: 0xffff_fffd, 而实际用DS5连进去,可以看到lr: 0xFFFF_FFF1, 这里出现了不一致,如果没有连DS5, log打印会让分析问题的方向出现偏差。
跟踪代码发现,在打印lr的时候,使用的是pdata->cm.frame里的lr值,而不是pdata->cm.regs里的值, 代码并没有把pdata->cm.regs的lr数据copy到pdata->cm.frame, 修改打印代码, 使用pdata->cm.regs来打印lr, 即可获得正确的lr值。
// 打印lr: panic_data_print(...)
if (pdata->flags & PANIC_DATA_FLAG_FRAME_VALID)
sregs = pdata->cm.frame;
......
print_reg(14, sregs, 5);
// 而pdata->cm.frame copy的寄存器只有8个 report_panic(...)
for (i = 0; i < 8; i++)
pdata->cm.frame[i] = sregs[i];
pdata->flags |= PANIC_DATA_FLAG_FRAME_VALID;
// lr打印修改成:
print_reg(14, lregs, 5);