首页 > 解决方案 > 调试 ARM Cortex-M4 中的硬故障

问题描述

我在尝试调试 EFR32BG12 处理器上的硬故障时束手无策。我一直在按照 Silicon Labs 知识库中的说明进行操作:

https://www.silabs.com/community/mcu/32-bit/knowledge-base.entry.html/2014/05/26/debug_a_hardfault-78gc

我也一直在这里使用 Keil 应用笔记来填写一些细节:

http://www.keil.com/appnotes/files/apnt209.pdf

我已经设法让硬故障在一个地方始终如一地发生。当硬故障发生时,知识库文章中的代码为我提供了以下值(在调用硬故障处理程序之前由处理器压入堆栈):

Name     Type        Value               Location
~~~~     ~~~~        ~~~~~               ~~~~~~~~
cfsr     uint32_t    0x20000 (Hex)       0x2000078c    
hfsr     uint32_t    0x40000000 (Hex)    0x20000788    
mmfar    uint32_t    0xe000ed34 (Hex)    0x20000784    
bfar     uint32_t    0xe000ed38 (Hex)    0x20000780    
r0       uint32_t    0x0 (Hex)           0x2000077c    
r1       uint32_t    0x8 (Hex)           0x20000778    
r2       uint32_t    0x0 (Hex)           0x20000774    
r3       uint32_t    0x0 (Hex)           0x20000770    
r12      uint32_t    0x1 (Hex)           0x2000076c    
lr       uint32_t    0xab61 (Hex)        0x20000768    
pc       uint32_t    0x38dc8 (Hex)       0x20000764    
psr      uint32_t    0x0 (Hex)           0x20000760  

查看 Keil 应用说明,我相信 CFSR 值为 0x20000 表示设置了 INVSTATE 位的使用错误,即:

INVSTATE:无效状态:0 = 无无效状态 1 = 处理器试图执行非法使用执行程序状态寄存器 (EPSR) 的指令。当该位置位时,为异常返回而堆积的 PC 值指向试图非法使用 EPSR 的指令。潜在原因: a) 将 LSB=0 的分支目标地址加载到 PC。b) 堆栈 PSR 在异常或中断处理期间损坏。c) 向量表包含一个 LSB=0 的向量地址。

异常压入堆栈的 PC 值(由知识库文章中的代码提供)似乎是 0x38dc8。如果我在 Simplicity Studio“反汇编”窗口中转到此地址,我会看到以下内容:

00038db8:   str     r5,[r5,#0x14]
00038dba:   str     r0,[r7,r1]
00038dbc:   str     r4,[r5,#0x14]
00038dbe:   ldr     r4,[pc,#0x1e4] ; 0x38fa0
00038dc0:   strb    r1,[r4,#0x11]
00038dc2:   ldr     r5,[r4,#0x64]
00038dc4:   ldrb    r3,[r4,#0x5]
00038dc6:   movs    r3,r6
00038dc8:   strb    r1,[r4,#0x15]
00038dca:   ldr     r4,[r4,#0x14]
00038dcc:   cmp     r7,#0x6f
00038dce:   cmp     r6,#0x30
00038dd0:   str     r7,[r6,#0x14]
00038dd2:   lsls    r6,r6,#1
00038dd4:   movs    r5,r0
00038dd6:   movs    r0,r0

该地址似乎远远超出了我的代码末尾。如果我在“内存”窗口中查看相同的地址,我会看到:

0x00038DC8  69647561 2E302F6F 00766177 00000005  audio/0.wav.....
0x00038DD8  00000000 000F4240 00000105 00000000  ....@B..........
0x00038DE8  00000000 00000000 00000005 00000000  ................
0x00038DF8  0001C200 00000500 00001000 00000000  .Â..............
0x00038E08  00000000 F00000F0 02F00001 0003F000  ....ð..ð..ð..ð..
0x00038E18  F00004F0 06010005 01020101 01011201  ð..ð............
0x00038E28  35010121 01010D01 6C363025 2E6E6775  !..5....%06lugn.
0x00038E38  00746164 00000001 000008D0 00038400  dat.....Ð.......

奇怪的是,“audio/0.wav”是一个静态字符串,它是固件的一部分。如果我理解正确,我在这里学到的是 PC 以某种方式设置到内存中的这一点,这当然不是有效指令并导致硬故障。

要调试这个问题,我需要知道 PC 是如何被设置为这个不正确的值的。我相信 LR 寄存器应该给我一个想法。异常压入堆栈的LR寄存器似乎是0xab61。如果我查看此位置,我会在“反汇编”窗口中看到以下内容:

1270            dp->sect = clst2sect(fs, clst);
0000ab58:   ldr     r0,[r7,#0x10]
0000ab5a:   ldr     r1,[r7,#0x14]
0000ab5c:   bl      0x00009904
0000ab60:   mov     r2,r0
0000ab62:   ldr     r3,[r7,#0x4]
0000ab64:   str     r2,[r3,#0x18]

在我看来,这个问题特别是在这次通话中发生的:

0000ab5c:   bl      0x00009904

这让我认为问题是由于堆栈损坏导致的,这导致 clst2sect 返回到内存的无效部分而不是 0xab60。clst2sect 的代码非常无害:

/*-----------------------------------------------------------------------*/
/* Get physical sector number from cluster number                        */
/*-----------------------------------------------------------------------*/

DWORD clst2sect (   /* !=0:Sector number, 0:Failed (invalid cluster#) */
    FATFS* fs,      /* Filesystem object */
    DWORD clst      /* Cluster# to be converted */
)
{
    clst -= 2;      /* Cluster number is origin from 2 */
    if (clst >= fs->n_fatent - 2) return 0;     /* Is it invalid cluster number? */
    return fs->database + fs->csize * clst;     /* Start sector number of the cluster */
}

这个分析听起来对吗?

我想我遇到的问题是我不知道什么可能导致这种行为......我已经尝试在我所有的中断处理程序中放置断点,看看其中一个是否可能会破坏堆栈,但似乎没有任何模式——有时,没有调用中断处理程序,但问题仍然存在。

但是,在那种情况下,我很难看到程序如何尝试在代码实际结束之后的位置执行代码......我觉得函数指针可能是一个可能的候选者,但在那种情况下我希望看到问题出现,例如,使用函数指针的地方。但是,我没有看到在发生错误的地方附近使用了任何函数指针。

也许我可以从上面给出的调试信息中提取更多信息?这个问题是非常可重现的,所以如果有什么我没有尝试过,但你认为可能会提供一些见解,我很想听听。

谢谢你尽你所能的帮助!

标签: debuggingarm

解决方案


经过大约一个月的追逐,我设法确定了问题的原因。我希望我可以在这里提供足够的信息,这对其他人有用。

最后,问题是由将指向非静态局部变量的指针传递给状态机引起的,该状态机稍后更改了该内存位置的值。因为局部变量不再在范围内,所以该内存位置是堆栈中的一个随机点,并且更改那里的值会破坏堆栈。

这个问题很难追踪,原因有两个:

  1. 根据代码的编译方式,更改的内存位置可能是非关键的,例如另一个局部变量,这会导致更微妙的错误。只有当我幸运时,更改才会影响 PC 寄存器并导致硬故障。

  2. 即使我找到了始终生成硬故障的代码版本,实际的硬故障通常发生在调用堆栈的某处,当一个函数返回并将堆栈值弹出到 PC 时。这使得很难确定问题的原因——我所知道的只是在该函数返回之前某些东西正在破坏堆栈。

一些工具对于确定问题的原因非常有帮助:

  1. 早些时候,我使用 GPIO 引脚确定了通常发生硬故障的代码块。我会在进入块之前将引脚切换为高电平,在退出块时切换为低电平。然后我进行了许多测试,检查发生硬故障时引脚是高电平还是低电平,并使用一种二进制搜索来确定始终包含所有硬故障的最小代码块。

  2. 硬故障将一些重要的寄存器压入堆栈。这些帮助我确认了 PC 寄存器在哪里损坏,也帮助我了解它是由于堆栈损坏而损坏的。

  3. 从该代码块之前的某个地方开始,并在关注局部变量的同时向前迈进,我能够识别出破坏堆栈的函数调用。我可以使用 Simplicity Studio 的内存视图来确认这一点。

  4. 最后,详细了解有问题的函数,我意识到问题是在我取消引用存储的指针并写入该内存位置时发生的。回顾该指针值的设置位置,我意识到它已被设置为指向一个现在超出范围的非静态局部变量。

感谢@SeanHoulihane 和@cooperised,他们帮助我消除了一些可能的原因,让我对调试工具更有信心。


推荐阅读