1. 基础知识

Fault 类异常
有若干个系统异常专用于 fault 处理。 CM3 中的 Faults 可分为以下几类:

  • 总线 faults
  • 存储器管理 faults
  • 用法 faults
  • 硬 fault

表 7.8 总线 fault 状态寄存器(BFSR),            地址:0xE000_ED29
表 7.9 存储器管理 fault 状态寄存器(MFSR),地址:0xE000_ED28
表 7.10 用法 fault 状态寄存器(UFSR),          地址:0xE000_ED2A
表 7.11 硬 fault 状态寄存器                              地址:0xE000_ED2C

1.1 总线 Faults

当 AHB 接口上正在传送数据时,如果回复了一个错误信号(error response),则会产生总线 faults,产生的场合可以是:

  • z 取指,通常被称作“预取流产”(prefetch abort)
  • z 数据读/写,通常被称作“数据流产”(data abort)

在 CM3 中执行如下动作可以触发总线异常:

  • 中断处理起始阶段的堆栈 PUSH 动作。 称为“入栈错误”
  • 中断处理收尾阶段的堆栈 POP 动作。 称为“出栈错误”
  • 在处理器启动中断处理序列(sequence)后的向量读取时。这是一种罕见的特殊情况,被归类为硬 fault。

1.3存储器管理 faults

存储器管理faults多与MPU有关,其诱因常常是某次访问触犯了MPU设置的保护策略。另外,某些非法访问,例如,在不可执行的存储器区域试图取指,也会触发一个 MemManagefault,而且即使没有 MPU 也会触发。MemManage faults 的常见诱因如下所示:

  • 访问了 MPU 设置区域覆盖范围之外的地址
  • 往只读 region 写数据
  • 用户级下访问了只允许在特权级下访问的地址

1.4 用法 faults

用法 faults 发生的场合可以是:

  • 执行了未定义的指令
  • 执行了协处理器指令(Cortex‐M3 不支持协处理器,但是可以通过 fault 异常机制来使用软件模拟协处理器的功能,从而可以方便地在其它 Cortex 处理器间移植)
  • 尝试进入 ARM 状态(因为 CM3 不支持 ARM 状态,所以用法 fault 会在切换时产生。软件可以利用此机制来测试某处理器是否支持 ARM 状态)
  • 无效的中断返回(LR 中包含了无效/错误的值)
  • 使用多重加载/存储指令时,地址没有对齐。另外,通过设置 NVIC 的对应控制位,可以在下列场合下也产生用法 fault:
  • 除数为零
  • 任何未对齐的访问

1.5硬 fault

硬 fault 是上文讨论的总线 fault、存储器管理 fault 以及用法 fault 上访的结果。如果这些 fault 的服务例程无法执行,它们就会成为“硬伤”——上访(escalation)成硬 fault。另外,在取向量(异常处理是对异常向量表的读取)时产生的总线 fault 也按硬 fault 处理。

在NVIC 中有一个硬 fault 状态寄存器(HFSR),它指出产生硬 fault 的原因。如果不是由于取向量造成的,则硬 fault 服务例程必须检查其它的 fault 状态寄存器,以最终决定是谁上访的。

2. 经典案例

STM32F103项目中使用了uCOS-II,出现一个致命问题:当只跑uCOS-II时,程序运行正常,一旦开启USB功能(或任何其它带高优先级中断的程序),程序运行一段时间后就会死掉,时间是随机的。

通过keil启动程序,死机时停下来,看到死在HardFault_Handler中:

HardFault_Handler\PROCEXPORT  HardFault_Handler          [WEAK]B       .ENDP

提示出现了硬件错误。
看下这时的寄存器:

注意其中的LR,它是一个奇怪的值0xFFFFFFF5,后面再介绍它。既然出现了硬件错误,可以看下异常寄存器(菜单Peripherals->Core Peripherals->Fault Reports):(这样查看比较快,后文有通过寄存器查看的方法)

可以看到硬件错误(Hard Faults)是上访造成的(FORCED位),而真实的错误原因是由用法错误(Usage Faults)引起的,具体引起用法错误的原因是INVPC错误。如果用的不是Keil,也可以通过直接查看异常寄存器值来得到错误原因。查看《ARM Cortex-M3 Processor Technical Reference Manual》和《Cortex-M3 Devices Generic User Guide》这两个手册(可以从ARM官网直接下载),SCB寄存器(System control block (SCB))的地址是0xE000E000开始的,HardFault Status Register的地址是0xE000ED2C(HFSR,百度上也有),查看相应地址的值:

可以看到HFSR(0xE000ED2C)的值是0x40000000,而UFSR(0xE000ED2A)的值是0x0004,查看《Cortex-M3 Devices Generic User Guide》手册,相应比特位的意思是:

HFSR的FORCED位为1,表示硬件错误的原因是上访造成的,此位置1表示产生了其他类型的异常,但由于优先级问题或者使能问题导致无法处理异常,于是这些异常就升级成硬件错误异常。

UFSR的INVPC位为1,表示在异常中断返回时尝试向PC载入非法的EXC_RETURN值,从而引起用法错误。

这里用法错误升级为硬件错误的原因是没有使能用法错误位,SHCRS(0xE000ED24, System Handler Control and State Register)的USGFAULTENA位为0。

使用keil重新加载程序,在运行程序之前先将0xE000ED24的值改为0x00070000(临时使能USGFAULTENA位),然后再次运行,程序死机时可以看到死机的位置变成UsageFault_Handler了(这一步没必要,只是为了验证对异常机制的理解)。

UsageFault_Handler\PROCEXPORT  UsageFault_Handler         [WEAK]B       .ENDP

这个时候异常寄存器的值也变成了:

可以看到HFSR的值没有了,只剩UFSR的值指示发生了INVPC错误。

现在要检查为什么会发生INVPC错误。

对于INVPC错误,《Cortex-M3 Devices Generic User Guide》的描述是:

这上面说如果由于错误的上下文,或者错误的EXC_RETURN值,导致向PC中非法载入EXC_RETURN值,就会引起此错误。

当INVPC位是1的时候,在相应的堆栈中保存了引起这个错误的异常中断返回点的PC值。

说一下EXC_RETURN是什么意思:

EXC_RETURN是用于程序从异常中断中返回的。

根据Cortex-M3的异常处理流程,当发生异常时,CPU先将核心寄存器压入当前堆栈(如果当前是线程模式,则压入PSP堆栈,如果当前是Handler模式,则压入MSP堆栈),然后CPU会将LR设置为一个特殊的值,比如0xFFFFFFFD,然后切换到Handler模式,切换成MSP堆栈,最后进入异常处理例程(异常处理例程总是使用MSP堆栈)。在异常处理例程完成后需要从中断返回时,就将LR的值载入到PC中(通常是BX LR指令,也可以是MOV PC,LR指令,或者POP {..., PC}等指令,只要能将LR赋给PC即可),由于LR的值是0xFFFFFFFD,CPU检测到向PC中载入的是这个特殊值时,就知道是中断返回,于是做中断返回的动作(与压入动作相反:从堆栈中弹出核心寄存器的值,恢复到线程模式或Handler模式等)。

这里这个特殊的值(0xFFFFFFFD)就是EXC_RETURN,它的特点是高28位全部是1,只有低4位可变化,不同的低4位表示不同的中断返回动作。

这个值是CPU在进入异常处理前自动设置的,只有3个值是合法的:

0xFFFFFFF1    表示中断返回时从MSP堆栈恢复寄存器值,中断返回后进入Handler模式,使用MSP堆栈,(相当于从中断返回到另一个中断)。

0xFFFFFFF9    表示中断返回时从MSP堆栈恢复寄存器值,中断返回后进入线程模式,使用MSP堆栈(这种用于不使用PSP只使用MSP堆栈的情况)。

0xFFFFFFFD    表示中断返回时从PSP堆栈恢复寄存器值,中断返回后进入线程模式,使用PSP堆栈(这是常见的,OS处理完中断后返回用户程序)。

可以看到,中断返回依赖于LR中的值,在此项目中,LR的值变成了0xFFFFFFF5,显然也是一个EXC_RETURN值,但这个值与上面3个都不同,是非法的,所以引起了INVPC错误。

进入中断时LR的值是CPU自动设置的,不会有错,为什么退出中断时LR值变成非法的了呢?只有一个原因:中断例程修改了LR的值,改错了。

为了找到修改LR的中断例程,需要找到引起UsageFault的中断返回指令。

下面根据UsageFault错误信息查找引起错误的指令。

如INVPC描述中所述,堆栈中保存了引起UsageFault错误的位置。

首先查看LR的值(0xFFFFFFF5),第4比特位是1,所以使用的是PSP堆栈(不要去管R13(SP)的值,R13是MSP堆栈的值)。

PSP的值是0x20000760(见前面寄存器截图),查看相应内存的值:

根据Cortex-M3的异常处理流程,进入中断时,CPU按如下位置保存寄存器的值:

xPSR  (高地址)
PC
LR
R12
R3
R2
R1
R0 (低地址)

根据上面的顺序,在PSP堆栈的截图中标记了对应的寄存器位置,它们就是进入异常中断(这里是UsageFault异常)前CPU所处的状态。

从PSP堆栈的值可以看到,进入UsageFault异常中断前,PC值是0x08002200,LR值是0x08000F59。

在keil的反汇编窗口,右键菜单选择“Show Disassembley at Address...”,输入0x08002200,对应的源码区也一起变化,可以看到引起UsageFault异常的代码是:

OS_CPU_SR_RestoreMSR     PRIMASK, R0BX      LR

这是uCOS-II里的代码,就是这条BX LR引起的UsageFault异常,应该是运行到这里的时候,LR值已经被错误修改了。

到这里还看不出什么,继续往上走一层,查看LR对应的代码,在反汇编窗口中查看0x08000F58的代码(注意LR中的值是奇数,表示返回地址是THUMB指令,实际代码地址是其对应的偶数地址0x08000F58)。

可以看到对应的汇编代码是:

  1621:                 OS_TASK_SW();       /* Perform a context switch  */ 1622:             } 1623:         } 1624:     }
0x08000F4E F001F968  BL.W     OSCtxSw (0x08002222)1625:     OS_EXIT_CRITICAL();
0x08000F52 4620      MOV      r0,r4
0x08000F54 F001F952  BL.W     OS_CPU_SR_Restore (0x080021FC)1626: }
0x08000F58 BD10      POP      {r4,pc}

对应的C源代码是:

void  OS_Sched (void)
{...if (OSIntNesting == 0) {          /* Schedule only if all ISRs done and ...    */if (OSLockNesting == 0) {     /* ... scheduler is not locked       */...OSCtxSwCtr++;         /* Increment context switch counter   */OS_TASK_SW();         /* Perform a context switch       */}}}OS_EXIT_CRITICAL();

所以理清思路,调用过程就是:
... --> OSCtxSw --> OS_CPU_SR_Restore --> OS_CPU_SR_Restore里引起UsageFault异常。

OS_CPU_SR_Restore的代码很简单(见前文),它并没有修改LR的值。所以继续看前面一个函数OSCtxSw。

这个函数在os_cpu_a.asm文件中:

OSCtxSwLDR     R0, =NVIC_INT_CTRL    ; Trigger the PendSV exception (causes context switch)LDR     R1, =NVIC_PENDSVSETSTR     R1, [R0]BX      LR

OSCtxSw函数自己用了LR,好像也不会乱改LR(否则自己就无法工作),线索好像中断了。
但仔细看OSCtxSw的代码,它实际上是激活了PendSV标志后返回。阅读OS_Sched代码可知:在OSCtxSw里设置PendSV标志时并不会立即触发中断,因为此时CPU的全局中断是关断的,只有当全局中断被打开时,这个PendSV中断才会真正触发。什么时候会打开全局中断呢?看OS_CPU_SR_Restore的第一句:

OS_CPU_SR_RestoreMSR     PRIMASK, R0BX      LR

MSR PRIMASK, R0,就是在这句打开的。
所以在这句代码运行后就会触发PendSV中断,PendSV中断返回后就会运行BX LR指令。而PendSV中断是uCOS-II真正做任务上下文切换的地方,它会大量修改CPU寄存器,是很有可能修改LR的。

所以继续查看PensSV中断处理代码,也在os_cpu_a.asm文件中,

OS_CPU_PendSVHandler的最后代码是:

OS_CPU_PendSVHandler...; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave...LDR     R0, [R2]       ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;LDM     R0, {R4-R11}   ; Restore r4-11 from new process stackADDS    R0, R0, #0x20MSR     PSP, R0        ; Load PSP with new process SPORR     LR, LR, #0x04  ; Ensure exception return uses process stackCPSIE   IBX      LR      

在OS_CPU_PendSVHandler返回前强制将LR异或0x04,这里强制修改LR,如果这里改错了,就会引起问题(疑问:这里改错了后,应该直接在最后那句BX LR时就会引起异常,为何PSP堆栈中记录的是OS_CPU_SR_Restore中出错的呢?实际上,真正定位问题的那次,就是抓到在OS_CPU_PendSVHandler里的BX LR出错的,但在写此文时又抓到是在OS_CPU_SR_Restore里出错的)。
出错时LR值是0xFFFFFFF5,那么在运行这句ORR语句之前,LR的值应该是0xFFFFFFF1。

0xFFFFFFF1是合法的,它表示异常中断返回后回到另一个异常中断。uCOS-II是很成熟的代码,这里怎么会改错LR呢?看uCOS-II的说明,OS_CPU_PendSVHandler在返回前将LR异或0x04是为了保证返回后使用的是PSP堆栈,也就是它需要保证回到用户代码。那么这说明OS_CPU_PendSVHandler一定是在用户模式下才进入此中断的,必须不能是在已有其他中断的情况下进入OS_CPU_PendSVHandler(嵌套中断)。

怎么保证OS_CPU_PendSVHandler不会在已有其他中断的情况下进入呢?看下uCOS-II的说明,发现PendSV中断的优先级必须是最低,这样就保证OS_CPU_PendSVHandler不会在有其他中断时进入。

这里LR的本来值是0xFFFFFFF1(后被修改成0xFFFFFFF5),说明OS_CPU_PendSVHandler是在其他中断中进入的。那么一定是PendSV的优先级出了问题。

查看PendSV的中断优先级(菜单Peripherals-->Core Peripherals-->Nested Vectored Interrupt Controller):

果然PendSV的优先级不是最低的,它是0,与其它中断处于相同优先级。

显然,只要Pend System Service的优先级不是最低,就会引起上述问题。

检查设置PendSV优先级的代码,也在os_cpu_a.asm文件里:

NVIC_INT_CTRL   EQU     0xE000ED04     ; Interrupt control state register.
NVIC_SYSPRI2    EQU     0xE000ED20     ; System priority register (priority 2).
NVIC_PENDSV_PRI EQU     0xFFFF         ; PendSV priority value (lowest).
NVIC_PENDSVSET  EQU     0x10000000     ; Value to trigger PendSV exception.OSStartHighRdyLDR     R0, =NVIC_SYSPRI2     ; Set the PendSV exception priorityLDR     R1, =NVIC_PENDSV_PRISTRB    R1, [R0]

OSStartHighRdy将0xE000ED20置为0xFFFF来设置PendSV的优先级,查看《Cortex-M3 Devices Generic User Guide》,发现0xE000ED20的[23:16]才是PendSV的优先级位,这里中断优先级的值错误了。
所以NVIC_PENDSV_PRI的值错误才是罪魁祸首!

考虑到优先级的写入指令是STRB指令,那么NVIC_SYSPRI2的值也需要修改。

将这两个值改为如下数值后,问题就解决了:

NVIC_INT_CTRL   EQU     0xE000ED04    ; Interrupt control state register.
NVIC_SYSPRI2    EQU     0xE000ED22    ; System priority register (priority 2).
NVIC_PENDSV_PRI EQU     0x000000FF    ; PendSV priority value (lowest).
NVIC_PENDSVSET  EQU     0x10000000    ; Value to trigger PendSV exception.

正确运行情况下PendSV的优先级应该是15,如下图所示:

至于这两个值为什么会错呢?可能是uCOS-II代码是从其他工程拷过来的,那个不是Cotex-M3架构(M1架构?),所以那边的值到这边不能使用,具体来源已不可考。

基本上这就是个乱拷uCOS代码引起的悲剧

【STM32】Fault 类异常_记一次STM32中HardFault问题的调试解决相关推荐

  1. unchecked异常_为什么要在Java中使用Unchecked异常而不是Checked异常

    unchecked异常 关于检查与未检查的异常的争论可以追溯到过去. 有人说这是Java包含的最佳功能之一. 其他人则说这是他们最大的错误之一[ 1 ]. 辩论似乎结束了. 在这篇文章中,我将尝试包含 ...

  2. stm32 内部sram大小_在SRAM、FLASH中调试代码的配置方法(附详细步骤)

    聊天界面发送嵌入式大杂烩获取1TB大杂烩资料包 STM32的FLASH擦写次数有限(大概为1万次),所以为了延长FLASH的使用时间,我们平时调试时可以选择在SRAM中进行硬件调试.除此之外,SRAM ...

  3. openopc.opcerror: dispatch: 无效的类字符串_实战PyQt5: 064-MV框架中的Model类

    模型(Model)简介 在Model-View框架中,模型(Model)为视图(View)和委托(Delegate)使用数据提供了标准接口.大多数情况下模型中并不真正存储数据(如果只有少量的数据,可以 ...

  4. 网页java挂挖矿_记一次服务器被植入挖矿脚本的解决过程

    记一次服务器被植入挖矿脚本的解决过程 删除挖矿脚本和对应的进程 找出并删除对应挖矿脚本文件 找出进程pid,并且kill掉 无法kill掉的是原进程的守护进程,原进程不在它也会自动关闭,所以不用管它 ...

  5. 使用遇到的问题_聚氯化铝在使用过程中遇到的问题及解决方法

    聚氯化铝在使用过程中遇到的问题及解决方法 随着现在工业的发展改良,现在的工业废水水质的变化幅度大,导致处理方面也有新的问题出现,常见的有聚氯化铝投入水中产生泡沫,药剂堵塞泵等问题. 聚氯化铝投入水中产 ...

  6. java 类大写_记java实体类属性名为全部为大写踩的坑(基础)

    1.今天后台使用实体类接收参数,然后有个参数发现明明前后都是对应的,但是那个属性偏偏的不到数据 后面百度才知道(写前端太久java基础都给忘了,该补补了) spring默认的命名方式为,首字母转小写, ...

  7. swing查询输入框无值时出现null异常_如何优雅处理代码中 Null 值引起的 Bug?告别 Null 恐惧症!...

    导语 在笔者几年的开发经验中,经常看到项目中存在到处空值判断的情况,这些判断,会让人觉得摸不这头绪,它的出现很有可能和当前的业务逻辑并没有关系.但它会让你很头疼. 有时候,更可怕的是系统因为这些空值的 ...

  8. java中什么时候应用异常_生产Java应用程序中的十大异常类型-基于1B事件

    java中什么时候应用异常 Pareto记录原理:97%的记录错误语句是由3%的唯一错误引起的 在最新的数据整理帖子之后,我们收到了很多反馈和问题,我们发现97%的记录错误是由10个唯一错误引起的 . ...

  9. stm32采集正弦波峰峰值_科研项目 | 基于STM32的永磁同步电机SVPWM控制设计

    点击上方蓝字,记得关注我们! 一.师资背景 指导老师毕业于中国985高校,毕业后留校工作至今,现为该校电气工程及自动化专业的教授.硕士研究生导师,多家企业研发技术顾问. 主要研究方向包括电力电子拓扑及 ...

  10. php 实时监测网站是否异常_网站监控劫持问题,怎么通过网站监控解决劫持问题...

    网站劫持是目前黑产最喜欢的一种网页引流方式,此手法往往通过政府.教育机构网站(权重高),修改网站源代码.放寄生虫程序.设置二级目录反向代理等实现.网页劫持可以分为服务端劫持.客户端劫持.快照劫持.搜索 ...

最新文章

  1. java swing 架构_Java Swing1 基本框架
  2. 分治最小割 学习总结
  3. Docker修改空间大小
  4. Spring Security 3.1 自定义 authentication provider
  5. 计算机图形学试题a卷,计算机图形学复习题及答案
  6. 为什么DDD是设计微服务的最佳实践
  7. 腾讯视频如何设置主设备
  8. 数据结构排序2-希尔,快速,归并排序
  9. 如何二值图转化为灰度图_AAAI 2020 | 时序转化为图用于可解释可推理异常检测
  10. Bailian3143 验证“歌德巴赫猜想”【筛选法】
  11. 6个基础位运算符和4个逻辑运算符
  12. 案例:数据提取/数据获取/爬虫—工具篇—影刀
  13. 关于给青轴润轴消除弹簧音[误]
  14. html外链自动加nofollow,WordPress文章/页面外链自动添加nofollow属性的方法
  15. 循迹小车智能搬运:调车篇
  16. uni-app优秀的Ui模板和项目案列
  17. sap客户信贷_SAP信贷控制功能与配置详解
  18. 豆瓣评分高于8.8分的计算机书籍
  19. 美好的人生,从良好的人际关系开始。
  20. Cesium空间分析、Cesium通视分析

热门文章

  1. Pocket PC 基础知识
  2. Python—Pycharm社区版下载、安装、配置、使用
  3. Zabbix实现短信报警
  4. python 灰度图像素灰度值求和_图像灰度值 灰度值与像素值的关系
  5. 数学建模常识及论文写作方法
  6. 获取list中出现频数最多的元素
  7. 【微信小程序】图库——(小程序篇)
  8. Flutter Container设置 width 无效
  9. ViewportWidth,Width,MeasuredWidth
  10. 对缓存投毒的学习总结