死机是所有软件从业者无法回避的坑,而死机问题导致的原因千奇百怪,对于可以稳定复现现场的问题,还是比较好处理的,最可怕的情形是你怎么也复现不了,但是在客户那偶发。在此对笔者解决过的死机问题做个分享,若有谬误,请指正。
总的思路为根据堆栈和寄存器,定位到出现异常的语句,然后从逻辑上分析导致其异常可能的原因。
- 基础简介
在查找此类问题时,通用寄存器,堆栈,简单的汇编指令是需要必备的基础知识,以我们最常用的M3/M4内核做个简单介绍。具体知识可以自行学习cortex-M3权威手册等资料。
寄存器简介
(cortex-M3权威手册第三章)
CM3 拥有通用寄存器 R0‐R15 以及一些特殊功能寄存器。
- 通用目的寄存器 R0-R7
R0‐R7 也被称为低组寄存器。所有指令都能访问它们。它们的字长全是 32 位,复位后
的初始值是不可预料的。 - 通用目的寄存器 R8-R12
R8‐R12 也被称为高组寄存器。这是因为只有很少的 16 位 Thumb 指令能访问它们, 32
位的指令则不受限制。它们也是 32 位字长,且复位后的初始值是不可预料的。
- 堆栈指针 R13
R13 是堆栈指针。在 CM3 处理器内核中共有两个堆栈指针:
- 主堆栈指针(MSP),或写作 SP_main。这是缺省的堆栈指针,它由内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
- 进程堆栈指针(PSP),或写作 SP_process。用于常规的应用程序代码(不处于异常服
用例程中时)。
堆栈指针用于访问堆栈,并且 PUSH 指令和 POP 指令默认使用 SP。寄存器的 PUSH 和 POP 操作永远都是 4 字节对齐的
- 连接寄存器 R14
常写做LR,用于在调用子程序时存储返回地址。
- 程序计数器 R15
常写做PC,因为 CM3 内部使用了指令流水线,读 PC 时返回的值是当前指令的地址+4。
- 程序状态寄存器(PSRs 或曰 PSR)
程序状态寄存器在其内部又被分为三个子状态寄存器:
- 应用程序 PSR(APSR)
- 中断号 PSR(IPSR)
- 执行 PSR(EPSR)
通过 MRS/MSR 指令,这 3 个 PSRs 即可以单独访问,也可以组合访问(2 个组合, 3 个组合都可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”。
- 屏蔽寄存器
- PRIMASK 这是个只有 1 个位的寄存器。当它置 1 时, 就关掉所有可屏蔽的异常,只剩下 NMI和硬 fault 可以响应。它的缺省值是 0,表示没有关中断。
- FAULTMASK 这是个只有 1 个位的寄存器。当它置 1 时,只有 NMI 才能响应,所有其它的异常,包括中断和 fault,通通闭嘴。它的缺省值也是 0,表示没有关异常。
- BASEPRI 这个寄存器最多有 9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值。
- 控制寄存器(CONTROL)
控制寄存器用于定义特权级别,还用于选择当前使用哪个堆栈指针。
- CONTROL[1] 堆栈指针选择
0=选择主堆栈指针 MSP(复位后缺省值)
1=选择进程堆栈指针 PSP
在线程或基础级(没有在响应异常——译注),可以使用 PSP。在 handler 模式下,只允许使用 MSP,所以此时不得往该位写 1。
- CONTROL[0] 0=特权级的线程模式
1=用户级的线程模式
Handler 模式永远都是特权级的。
栈内存操作
在 Cortex‐M3 中,除了可以使用 PUSH 和 POP 指令来处理堆栈外,内核还会在异常处理
的始末自动地执行 PUSH 与 POP 操作。
笼统地讲,堆栈操作就是对内存的读写操作,但是其地址由 SP 给出。寄存器的数据通
过 PUSH 操作存入堆栈,以后用 POP 操作从堆栈中取回。
Cortex‐M3 使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32
位数值。在下一次压栈时, SP 先自减 4,再存入新的数值。
POP 操作刚好相反:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4。
虽然 POP 后被压入的数值还保存在栈中,但它已经无效了,因为为下次的 PUSH 将覆盖它的值。
异常与中断
(cortex-M3权威手册第七章)
所有能打断正常执行流的事件都称为异常。
Cortex‐M3 在内核水平上搭载了一个异常响应系统, 支持为数众多的系统异常和外部中断。其中,编号为 1-15 的对应系统异常,大于等于 16 的则全是外部中断。除了个别异常的优先级被定死外, 其它异常的优先级都是可编程的。
下表列出了 Cortex‐M3 可以支持的所有异常。有一定数量的系统异常是用于 fault 处理的,它们可以由多种错误条件引发。 NVIC 还提供了一些 fault 状态寄存器,以便于 fault 服务例程找出导致异常的具体原因。
- 异常类型:
- 优先级
优先级对于异常来说很关键的,它会影响一个异常是否能被响应,以及何时可以响应。优先级的数值越小,则优先级越高。 CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先