ARM Cortex-M 异常-HardFault(UsageFault) INVPC置1解决过程
 2020.12.19    |      嵌入式学习    |     AilsonJack    |     暂无评论    |     1296 views
By: Ailson Jack
Date: 2020-12-19
个人博客: http://www.only2fire.com/
<p><span style="color: rgb(0, 112, 192);">CPU:STM32F429IGT6</span><br/><span style="color: rgb(255, 0, 0);">对于其他的stm32芯片或者其他ARM Cortex-M芯片,其实解决方法都相通。建议先完整阅读了本文之后,再对照着你所遇到问题的现象进行调试。</span><br/></p><p class="artical_littlestyle1">1.基础知识</p><p>在ARM Cortex-M系列处理器中,有若干个系统异常专用于 fault 处理。 CM3 中的 Faults 可分为以下几类:<br/>(1).总线 faults;<br/>(2).存储器管理 faults;<br/>(3).用法 faults;<br/>(4).硬 fault;<br/><strong>1.1.总线 faults</strong><br/>当 AHB 接口上正在传送数据时,如果回复了一个错误信号(error response),则会产生总线faults,产生的场合可以是:<br/>(1).取指,通常被称作“预取流产”(prefetch abort);<br/>(2).数据读/写,通常被称作“数据流产”(data abort);<br/>在 CM3 中,执行如下动作时,如果地址有误,亦会触发总线异常:<br/>(1).中断处理起始阶段的堆栈 PUSH 动作。此时若发生总线 fault,则称为“入栈错误”;<br/>(2).中断处理收尾阶段的堆栈 POP 动作。此时若发生总线 fault,则称为“出栈错误”;<br/>(3).在处理器启动中断服务序列(sequence)后读取向量时。这是一种极度罕见的特殊情况,被归类为硬 fault。<br/>总线 fault 状态寄存器(BFSR),地址:0xE000_ED29,BFSR的各个位的定义如下:</p><p style="text-align:center"><img src="/uploads/AilsonJack/2020.12.19/143834619451122.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143834619451122.png&#39;)"/></p><p><strong>1.2.存储器管理 faults</strong><br/>存储器管理 faults 多与 MPU 有关,其诱因常常是某次访问触犯了 MPU 设置的保护规范。另外,某些非法访问,例如,在不可执行的存储器区域试图取指,也会触发一个 MemManage fault,而且在这种场合下,即使没有 MPU 也会触发 MemMange fault。<br/>MemManage faults 的常见诱因如下所示:<br/>(1).访问了所有 MPU regions 覆盖范围之外的地址;<br/>(2).访问了没有存储器与之对应的空地址;<br/>(3).往只读 region 写数据;<br/>(4).用户级下访问了只允许在特权级下访问的地址;<br/>存储器管理 fault 状态寄存器(MFSR),地址:0xE000_ED28,MFSR的各个位的定义如下:<br/></p><p style="text-align:center"><img src="/uploads/AilsonJack/2020.12.19/143834293146984.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143834293146984.png&#39;)"/></p><p><strong>1.3.用法 faults</strong><br/>用法 faults 发生的场合可以是:<br/>(1).执行了协处理器指令。 Cortex-M3 本身并不支持协处理器,但是通过 fault 异常机制,可以建立一套“软件模拟”的机制,来执行一段程序模拟协处理器的功能,从而可以方便地在其它 Cortex 处理器间移植。<br/>(2).执行了未定义的指令。同上一点的道理,亦可以软件模拟未定义指令的功能。<br/>(3).尝试进入 ARM 状态。因为 CM3 不支持 ARM 状态,所以用法 fault 会在切换时产生。软件可以利用此机制来测试某处理器是否支持 ARM 状态。<br/>(4).无效的中断返回(LR 中包含了无效/错误的值);<br/>(5).使用多重加载/存储指令时,地址没有对齐。<br/>另外,如果需要严格要求程序的质量,还可以让 CM3 在遇到除数为零的时候,以及遇到未对齐访问的时候也产生用法 fault。在 NVIC 中有两个控制位分别与它们对应。通过设置这两个控制位,就可以激活它们。<br/>用法 fault 状态寄存器(UFSR),地址:0xE000_ED2A,UFSR的各个位的定义如下:</p><p style="text-align:center"><img src="/uploads/AilsonJack/2020.12.19/143835597400839.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143835597400839.png&#39;)"/></p><p><strong>1.4.硬 fault</strong><br/></p><p style="text-indent: 2em;">硬 fault 是上文讨论的总线 fault、存储器管理 fault 以及用法 fault 上访的结果。如果这些 fault 的服务例程无法执行,它们就会成为“硬伤” ——上访( escalation)成硬 fault。另外,在取向量(异常处理时对异常向量表的读取)时产生的总线 fault 也按硬 fault 处理。在 NVIC中有一个硬 fault 状态寄存器(HFSR),它指出产生硬 fault 的原因。如果不是由于取向量造成的,则硬 fault 服务例程必须检查其它的 fault 状态寄存器,以最终决定是谁上访的。<br/>硬 fault 状态寄存器(HFSR),地址:0xE000_ED2C,HFSR的各个位的定义如下:</p><p style="text-align:center"><img src="/uploads/AilsonJack/2020.12.19/143835811132738.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143835811132738.png&#39;)"/></p><p class="artical_littlestyle2">2.UsageFault INVPC置1解决过程</p><p style="text-indent: 2em;">最近在使用RTOS增加DMA驱动时,在对内存到设备和设备到内存的DMA传输测试时,出现了UsageFault,并且UFSR中的INVPC置1了。最开始,单独测试DMA发送是没有问题的,但是DMA发送和接收一起测试时,就会出现UsageFault(INVPC置1)。这个异常不太好定位出现问题的具体位置,因此就检查DMA驱动,并且逐步调试吧。最终,DMA驱动检查和修改好了,仍然出现UsageFault,实在没法了,还是从为什么会出现UsageFault(INVPC置1)开始分析吧。<br/><strong>2.1.出现UsageFault(INVPC置1)的原因</strong><br/></p><p style="text-indent: 2em;">如果LR中的EXC_RETURN不是合法的值(合法值见下图,包括企图返回ARM状态),则引起用法fault。如果用法fault被除能,也上访成硬fault。此时,用法Fault状态寄存器(UFSR,地址: 0xE000_ED2A)中的INVPC位(位偏移: 2),或者是INVSTATE位(位偏移: 1)置位。</p><p style="text-align:center"><img src="/uploads/AilsonJack/2020.12.19/143835402160399.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143835402160399.png&#39;)"/></p><p style="text-indent: 2em;">上面就是出现该异常的文字分析了。<br/><strong>2.2.UsageFault(INVPC置1)的解决过程</strong><br/></p><p style="text-indent: 2em;">因为该异常是异常响应期间才可能出现的异常(&lt;&lt;Cortex-M3权威指南&gt;&gt; 9.8节介绍了下,在9.8.4节进行文字说明),因此,只要在异常或中断的返回处打断点,执行下一步就有可能进入UsageFault异常。(当然了这个方法,是比较笨的,不过在缩小了异常出现的范围之后,可以在每次异常或中断的返回处打断点,然后执行下一步,就有可能进入UsageFault异常)<br/></p><p style="text-indent: 2em;">我这里是每次开始DMA测试之后,就进入UsageFault异常,并且我的系统中目前就打开了SysTick,PendSV,DMA1_STREAM5,DMA1_STREAM6,NMI,HardFault,MemFault,BusFault,UsageFault这些异常和中断。因此我在开始DMA测试的时候打一个断点,在程序运行到DMA测试开始的断点处时,再在DMA1_STREAM5,DMA1_STREAM6,NMI,HardFault,MemFault,BusFault,UsageFault这些函数的入口打断点,在PendSV的返回处打断点,SysTick就暂时先不管。然后全力运行,发现每次都会在PendSV的异常返回断点处停留(因为任务切换嘛)总共在PendSV的断点处停留了大概7到8次,就进入到了UsageFault。<br/></p><p style="text-indent: 2em;">有了上面步骤的铺垫,先去除中断和异常中的断点,还是先在DMA测试开始处打断点,等运行到DMA测试开始处,再在上述的中断和异常相关位置打断点。接下来我就慢慢的调试,在开始DMA测试之后,全速运行,在退出PendSV异常时,执行单步运行到下一步,重复7到8次,从PendSV就进入了UsageFault,在这7到8次中,我看在退出PendSV时,LR寄存器中的值都是0xFFFFFFFD,是合法的啊,当时仔细一想,有可能是退出异常时硬件再将堆栈中的PC赋值给PC时出问题,导致进入了UsageFault。果不其然,在PenSV退出之前,我查看每个PSP(0xFFFFFFFD:返回线程模式,并使用线程堆栈)对应内存数值,能够正常退出PendSV的寄存器和PSP堆栈内容如下图所示:</p><p style="text-align:center"><img src="/uploads/AilsonJack/2020.12.19/143835370510151.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143835370510151.png&#39;)"/></p><p style="text-align:center"><img src="/uploads/AilsonJack/2020.12.19/143835837419681.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143835837419681.png&#39;)"/></p><p style="text-indent: 2em;"><span style="color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 4; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">进入UsageFault异常之前的寄存器和PSP堆栈内容如下图所示:</span></p><p style="text-align:center"><span style="color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: inline !important;"><img src="/uploads/AilsonJack/2020.12.19/143835773412438.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143835773412438.png&#39;)"/><br/></span></p><p style="text-align:center"><span style="color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: inline !important;"><img src="/uploads/AilsonJack/2020.12.19/143835781951234.png" onclick="preview_image(&#39;/uploads/AilsonJack/2020.12.19/143835781951234.png&#39;)"/></span></p><p style="text-indent: 2em;"><span class="md-plain md-expand" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 4; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">此处堆栈中的内容,明显的0x20003D94堆栈中的PC值是有问题的,我的程序是烧写到flash中的PC地址应该是08xxxxxx,然而现在堆栈中的PC地址是0x20003DA8,这个地址是SRAM中的地址,SRAM存储的数据而不是代码,出现这个问题的原因,猜想一下,应该就是</span><span class="md-pair-s " style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 4; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"><strong style="box-sizing: border-box;"><span class="md-plain" style="box-sizing: border-box;">任务堆栈溢出导致</span></strong></span><span class="md-plain md-expand" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 4; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">,当我增加任务堆栈的大小之后,哈哈,程序正常运行,世界是如此美好。</span></p><p style="text-indent: 2em;"><span class="md-plain md-expand" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 4; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"></span></p><p><span class="md-plain md-expand" style="box-sizing: border-box; color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 4; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"></span></p><p class="artical_littlestyle3">3.调试小结</p><p style="text-indent: 0em;"><strong>3.1.解决过程小结</strong><br/></p><p style="text-indent: 2em;">其实上面的步骤2,是我自己的一个调试解决问题的过程,这里给大家提供一个比较直接的解决方式。在开始运行程序之前,直接在UsageFault异常入口函数中打一个断点,然后全速运行程序,等程序停止在UsageFault异常函数的断点处时,需要注意以下几点:<br/>(1).如果LR是合法值,那么根据LR判断退出异常时使用的堆栈,然后在Memory查看窗口中,查看堆栈中R0,R1,R2,R3,R12,LR,PC,xPSR这些寄存器的值,根据这些寄存器的值,判断是否是堆栈溢出导致该异常发生;如果不是堆栈溢出导致该异常发生,那么就要根据PC值,在汇编窗口中跳转到PC值对应的代码处,分析导致异常发生的原因;<br/>(2).如果LR不是合法值,就要分析下你的代码中,有哪些地方修改过LR的值,确保修改的值要是合法的。<strong><br/>3.2.关于UsageFault 如何才能让INVPC置1</strong><br/>(1).在退出异常或中断时,执行BX LR时,LR的值是非法的,此时就会触发UsageFault异常,并且INVPC置1。<br/>(2).在退出异常或中断时,执行BX LR时,LR的值是合法的,但是退出异常之后要使用的堆栈中,堆栈里面的PC值是有问题的,此时就有可能触发UsageFault异常,并且INVPC置1。<br/></p><p style="text-indent: 2em;">对于上面的两点的模拟其实也比较好做,在PendSV或者其他异常的退出的地方打一个断点,然后手动修改LR或者堆栈中PC的值,就能触发UsageFault异常,并且INVPC置1。这里注意一下,修改堆栈中PC的值,我这里测试时候,设置PC值为其他值可能引起其他的异常,貌似修改PC的值为RAM中数据区的地址才会出现该异常,不太清楚为什么会这样,可能是数据区是没有执行代码的权限,因此出异常吧,不太确定,有知道的朋友,欢迎留言讲解。<br/></p><p style="text-indent: 2em;">当然了,博主的知识水平有限,对于异常的一些说明可能会有误,如果有误,欢迎指正,谢谢!</p>
欢迎关注博主的公众号呀,精彩内容随时掌握:
热情邀请仔细浏览下博客中的广告,万一有对自己有用或感兴趣的呢。◕ᴗ◕。。
如果这篇文章对你有帮助,记得点赞和关注博主就行了^_^,当然了能够赞赏博主,那就非常感谢啦!
注: 转载请注明出处,谢谢!^_^
暂无评论,要不要来个沙发
发表评论

 
Copyright © 2015~2023  说好一起走   保留所有权利   |  百度统计  蜀ICP备15004292号