如何调试写入变量的 amok 运行 指针?

How to debug an amok running pointer writing to my variable?

用 C11 编写的嵌入式微控制器软件正在发生疯狂的事情。 (是的,不是 C++,只有 C)总之,我有一个 static 变量,它只写在整个软件的两个地方。由于我不应该在这里 post 数千行代码,我将总结写入变量的代码:

static index foo = 0;
define MAX_FOO 10

void bar_timer() {
    printf("foo:%d\n", foo);
    foo++;
    if (foo >= MAX_FOO)
        foo=0;
}

需要说明的是,该软件看起来确实不像上面的简单截图那么简单。 foo 实际上是静态结构数组中的一个索引 (int),该数组包含整数、浮点数和指向使用 malloc 保留的区域的浮点指针。然而,printf 的输出是这样的:

foo: 0
foo: 2
foo: 4
foo: 6
foo: 8
foo: 0
foo: 2
...

因为索引 foo 是数组的一部分,所以我想出了一个主意并增加了该数组,这会导致疯狂的副作用,例如软件非常慢或在开始时挂起。

我还尝试在 foo++ 之前放置一个 printf(Before %d, foo),在 foo++ 之后放置一个 printf(After %d, foo),结果是:

Before 0
After  1
Before 2
After  3
...

删除 foo++ 最终得到:

Before 0
After  0
Before 0
After  0
...

我可以打赌有某种疯狂的 运行 指针(隐藏在数千行代码中某处的软件错误中的任何地方)只会增加我的变量背后的存储值 foo.

如何调试 amok 运行 指针写入我的变量?

首先,static 和 malloc 没有意义,您的变量不能同时具有静态存储和分配存储 - 它必须具有两者之一。

How to debug amok running pointer writing to my variable?

取决于目标。仅在现代 MCU 上可用的最佳方法是在内存位置使用硬件写断点,然后当断点命中时检查硬件指令跟踪以找到罪魁祸首。您还需要一个具有跟踪支持的像样的调试器。

如果具备以上所有条件,则查找错误大约需要 5 到 10 分钟。与使用常规调试的 hours/days/weeks 相反。所以这绝对是选择MCU和工具链时要考虑的事情。

假设您没有现代零件和设备,那么一些在线调试器可能具有实时更新监视变量的能力。这可能会提供线索,特别是如果您禁用应该写入变量的代码,然后发现它仍然写入。

获得完全随机行为时最有可能的罪魁祸首是堆栈溢出。 static 变量存储在 .bss/.data(或 .heap PC 程序员编码嵌入式系统......)但一个常见的初学者错误是内存映射堆栈,以便它溢出到 RAM 的其他部分,例如 .bss。这是您应该排除的第一件事。

检查堆栈溢出的常用技巧是在调试器中启动程序,将整个堆栈区域设置为已知值,如 0xAA,然后让程序 运行 运行一段时间并冻结它。检查堆栈所在的内存区域,看看它已经消耗了多少 0xAA 单元格。

如果不是堆栈溢出而是某些损坏的指针或数组越界,则首先检查链接器生成的映射文件。您的变量附近是否有大型缓冲区或数组?

万一你怀疑超过运行s,你可以制作自己的“金丝雀”,类似于上面用0xAA填充堆栈。在损坏的变量上方或下方声明一些虚拟 volatile 变量,看看它们是否也发生变化。

另一个可能的罪魁祸首是竞争条件。如果 bar_timer() 从 ISR 执行,但 static 变量在 ISR 和 main() 之间共享。不用说,您也不应该从 ISR 内部调用像 stdio.h 这样的膨胀函数。

当其他一切都失败时,您必须求助于单步执行程序,直到 bug 首次出现为止。

最后,避免此类错误的最好方法是首先不编写它们。这是通过严格使用编码标准来实现的。例如你提到malloc,它不应该在微控制器应用程序中使用,主要是因为使用它没有丝毫意义。但是,如果您仍然使用它,您还会遇到常见的 PC 编程问题,例如内存泄漏、堆损坏和碎片化等。