【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
背景
上篇 blog
【OS】【Nuttx】【栈溢出】up_initial_state(xPSR)
分析了 up_initial_state 任务初始化函数中(ARMV7M),关于 xPSR 寄存器的设置,其中提到了对 Cortex-M 来说,最关键的就是 Bit 24(Thumb 位),Cortex-M 只支持 Thumb 指令集(16/32 位混合),不支持 ARM 32 位指令,如果没设置 Thumb 位,当 CPU 从异常返回(或上下文切换 恢复寄存器)时,就会触发 HardFault,导致系统崩溃,接着又分析了 APSR 里的 NZCV 标志位,下面继续
栈溢出检测
上篇 blog 提到,这些标志由 ALU 算术逻辑单元指令自动更新 ,然后用于条件跳转,在任务初始化开始时,先清干净,保持一个干净的起点,所以 APSR = 0 是合理的默认值
然后是 IPSR(Interrupt PSR),占 bit0 ~ bit8

表示当前正在处理的异常编号

具体而言,当 IPSR 为 0 时,表示 Thread mode,非异常状态,也就是普通任务模式,而非 0 时,则表示 Exception Number 异常中断向量号,在 ARMV7M 中,Exception Number 从 16 开始为芯片定制的中断向量号,前 1~15 则为 ARMV7M 内核保留的异常中断类型

比如假设芯片的 UART 是 IRQ19,那么当 UART 中断触发时,IPSR 会被设置成 35(16 + 19 = 35)

任务运行在初始化时,运行在 Thread mode,不处理任何异常,如果 IPSR ≠ 0,CPU 会认为当前处于异常处理中,会影响
- 中断优先级判断
- 异常返回行为(比如使用 EXC_RETRUN)
所以,上下文必须反映正常线程状态,所以 IPSR 也必须为 0,表示这是普通任务
然后最后再说下这里的 EPSR

EPSR 除了有 Thumb 位之外,还有个 IT(If-Then)状态位,用来支持 Thumb-2 的条件执行语句,举个例子
c
CMP R0, #0
IT EQ ; If R0 == 0, then next 1~4 条指令条件执行
MOVEQ R1, #1 ; 只有在 Z=1 时才执行
这里有点反常识,Z = 1 表示的是上一次比较,或运算结果为 0,也就是相等 ,而在 C 语言等高级语言的逻辑习惯里,if(0) 是 false,而 if(非0) 则是 true,所以大多数高级语言用户可能会潜意识认为,比较结果为 0,应该是 false,但在 CPU 标志寄存器层面,Z 标志不是布尔值,而是零结果指示器,比如上面的
c
CMP R0, #0
CPU 在对 CMP a, b 操作进行运算时,不是返回的 true 或者 false,其实际执行的是 a - b,然后丢弃结果,只根据结果设置标志位
| 情况 | a - b 的值 |
Z 标志 |
|---|---|---|
| a == b | 0 | Z = 1(因为结果是 0) |
| a != b | ≠ 0 | Z = 0(因为结果非 0) |
所以 Z 标志表示的是:上一次算术/逻辑操作的结果是不是零,而不是直接表示相等,但可以用 Z 标志来判断相等

在上面那个例子中,IT 指令会根据 EQ 操作符,设置 EPSR.IT 字段,该字段会决定是否执行后续的指令,而在新任务的初始化函数中,新任务将从函数入口处开始执行,不在任何 IT 块中,如果 EPSR.IT ≠ 0,CPU 会尝试按条件执行接下来的指令,即使它们不是条件指令,导致指令解码错误或逻辑混乱,所以也要清零,确保 CPU 以普通顺序执行启动,而 EPSR.IT = 0 就表示不处于条件执行块中
OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog