使用 GDB 自定义命令调试 ARM Cortex-M 硬故障

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

在“ 帮助解决硬故障的处理器专家组件 ”中,我使用了带有一些汇编代码的 C 处理程序,这些代码是使用处理器专家创建的,以帮助我调试 ARM Cortex-M 上的硬故障。受 此处 GNU gdb 脚本的启发,我现在有了另一种方法。由于此方法使用 GDB 命令行方法,因此它既适用于 Eclipse GUI,也适用于仅在命令行模式下使用 GDB :-)。


-- 用于调试 ARM 硬故障的 GDB 脚本

这个想法是:

  1. 在硬故障异常处理程序中设置断点
  2. 当发生硬故障时,CPU 将调用硬故障异常处理程序,调试器将停止目标
  3. 在 GDB 中执行“armex”(ARM 异常)脚本/命令以转储堆栈寄存器以显示发生问题的程序计数器。

.gdbinit 脚本

有几种方法可以使用自己的命令扩展 GDB。一种简单的方法是将额外的函数添加到 GDB 在启动时加载的 .gdbinit 脚本中。

我已将以下内容添加到我的 .gdbinit 文件中以定义我的“armex”命令:


 define armex
  printf "EXEC_RETURN (LR):\n",
  info registers $lr
    if $lr & 0x4 == 0x4
    printf "Uses MSP 0x%x return.\n", $MSP
    set $armex_base = $MSP
    else
    printf "Uses PSP 0x%x return.\n", $PSP
    set $armex_base = $PSP
    end
printf "xPSR            0x%x\n", *($armex_base+28)
printf "ReturnAddress   0x%x\n", *($armex_base+24)
printf "LR (R14)        0x%x\n", *($armex_base+20)
printf "R12             0x%x\n", *($armex_base+16)
printf "R3              0x%x\n", *($armex_base+12)
printf "R2              0x%x\n", *($armex_base+8)
printf "R1              0x%x\n", *($armex_base+4)
printf "R0              0x%x\n", *($armex_base)
printf "Return instruction:\n"
x/i *($armex_base+24)
printf "LR instruction:\n"
x/i *($armex_base+20)

end

document armex ARMv7 Exception entry behavior. xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0 end

您可以将 .gdbinit 文件放在任何地方。我将其放置在我的 gdb 位于 Freescale Kinetis Design Studio 中的位置 (C:\Freescale\KDS_3.0.0\toolchain\bin)。

为了确保 GDB 找到 .gdbinit,我在 Eclipse 工作区首选项中指定了它的路径:

-- Eclipse 工作区首选项中的 GDB 命令文件

调试硬故障

为了调试硬故障,我在硬故障中断处理程序中设置了一个断点,以便在故障发生时停止调试器:


-- 因硬故障停止

为了找出问题发生的位置,我现在在 gdb 控制台中使用“armex”命令:

使用控制台的“三角”菜单切换到 arm-none-eabi-gdb 视图

-- gdb 控制台中的 armex 命令

armex 命令列出堆栈寄存器(与我在“ 调试 ARM Cortex-M 上的硬故障 ”中显示的处理程序相同)。重要信息要么是返回指令,要么是LR指令信息。我可以在反汇编视图中输入该地址以找出问题发生的位置:

硬故障原因拆解图

在上面的示例中,LR(链接寄存器或返回地址)是 0xbd2(设置了 Thumb 位的 0xbd3)。在反汇编视图中,这是处理程序将返回到的地址,因此问题一定就在这之前。检查汇编代码有间接分支寄存器


 define armex
  printf "EXEC_RETURN (LR):\n",
  info registers $lr
    if $lr & 0x4 == 0x4
    printf "Uses MSP 0x%x return.\n", $MSP
    set $armex_base = $MSP
    else
    printf "Uses PSP 0x%x return.\n", $PSP
    set $armex_base = $PSP
    end
printf "xPSR            0x%x\n", *($armex_base+28)
printf "ReturnAddress   0x%x\n", *($armex_base+24)
printf "LR (R14)        0x%x\n", *($armex_base+20)
printf "R12             0x%x\n", *($armex_base+16)
printf "R3              0x%x\n", *($armex_base+12)
printf "R2              0x%x\n", *($armex_base+8)
printf "R1              0x%x\n", *($armex_base+4)
printf "R0              0x%x\n", *($armex_base)
printf "Return instruction:\n"
x/i *($armex_base+24)
printf "LR instruction:\n"
x/i *($armex_base+20)

end

document armex ARMv7 Exception entry behavior. xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0 end

堆栈寄存器显示


 define armex
  printf "EXEC_RETURN (LR):\n",
  info registers $lr
    if $lr & 0x4 == 0x4
    printf "Uses MSP 0x%x return.\n", $MSP
    set $armex_base = $MSP
    else
    printf "Uses PSP 0x%x return.\n", $PSP
    set $armex_base = $PSP
    end
printf "xPSR            0x%x\n", *($armex_base+28)
printf "ReturnAddress   0x%x\n", *($armex_base+24)
printf "LR (R14)        0x%x\n", *($armex_base+20)
printf "R12             0x%x\n", *($armex_base+16)
printf "R3              0x%x\n", *($armex_base+12)
printf "R2              0x%x\n", *($armex_base+8)
printf "R1              0x%x\n", *($armex_base+4)
printf "R0              0x%x\n", *($armex_base)
printf "Return instruction:\n"
x/i *($armex_base+24)
printf "LR instruction:\n"
x/i *($armex_base+20)

end

document armex ARMv7 Exception entry behavior. xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0 end

这导致了硬故障。如果问题不是那么清楚,那么只需在该位置周围设置一个断点并重新启动应用程序以调试在触发硬故障之前发生的情况。有了这个,应该很容易找到并解决问题。

概括

我现在有另一种方法来调试我的硬故障:使用我的自定义 gdb 命令转储堆栈寄存器。与我之前的解决方案相比,这种方法的优点是它不需要目标上的任何额外资源(代码中没有额外的处理程序,也没有变量)。额外的好处是现在我知道如何使用我的自定义命令扩展 GDB :-)。