【问题标题】:Debugging a clobbered static variable in C (gdb broken?)在 C 中调试一个被破坏的静态变量(gdb 损坏?)
【发布时间】:2009-06-17 04:00:21
【问题描述】:

我做过很多编程,但 C 语言不多,我需要有关调试的建议。我有一个静态变量(文件范围),在执行多线程程序(在 OS X 10.4 上使用 pthreads)大约 10-100 秒后被破坏。我的代码如下所示:

static float some_values[SIZE];
static int * addr;

addr 指向有效的内存地址一段时间,然后被某个值(有时为 0,有时为非零)破坏,从而在取消引用时导致段错误。与gdb 打交道我已经验证addrsome_values 之后立即被布置在内存中,正如人们所期望的那样,所以我的第一个猜测是我使用了一个越界索引来写入@ 987654326@。但是,这是一个很小的文件,因此很容易检查这不是问题。

显而易见的调试技术是在变量addr 上设置观察点。但这样做似乎会在gdb 中产生不稳定和莫名其妙的行为。观察点在第一次分配给addr 时被触发;然后在我继续执行之后,我立即在另一个线程中得到一个无意义的段错误......据说是在访问程序不同部分中的静态变量的地址时出现段错误!但是gdb 让我可以交互地读取和写入该内存地址。

程序接收到信号 EXC_BAD_ACCESS,无法访问内存。 原因:KERN_PROTECTION_FAILURE 地址:0x001d5bd0 0x0000678d 在 mainloop.c:39 处接收 (arg=0x0) 39 sample_buf_cleared ++; (gdb) p &sample_buf_cleared $17 = (int *) 0x1d5bd0 (gdb) p sample_buf_cleared 18 美元 = 1 (gdb) 设置 sample_buf_cleared = 2 (gdb)

gdb 显然很困惑。有谁知道为什么?或者有人对不使用观察点调试此错误有任何建议吗?

【问题讨论】:

  • 您没有指定 - 这些值中的任何一个是否被超过 1 个线程访问?
  • D'oh 才发现这太老了。

标签: c debugging gdb segmentation-fault


【解决方案1】:
  1. 您可以在 some_values 和 addr 之间放置一个 uint 数组,并确定您是否超出了 some_values 或者损坏是否影响了您最初想到的更多地址。我会将填充初始化为 DEADBEEF 或其他一些易于区分且不太可能在程序中出现的明显模式。如果填充中的值发生变化,则将其转换为浮点数,看看该数字作为浮点数是否有意义。

静态浮动 some_values[SIZE]; 静态无符号整数填充[1024]; 静态 int * 地址;

  1. 多次运行程序。在每次运行中禁用不同的线程并查看问题何时消失。

  2. 将程序进程关联设置为单个内核,然后尝试观察点。如果您没有两个线程同时修改该值,您可能会有更好的运气。注意:此解决方案并不排除这种情况的发生。它可能更容易在调试器中捕获。

【讨论】:

    【解决方案2】:

    static变量和多线程一般不要混用。

    没有看到你的代码(你应该包括你的线程代码),我猜你有两个线程同时写入addr变量。它不起作用。

    您需要:

    • 为每个线程创建单独的addr 实例;或
    • 围绕addr 提供某种同步,以阻止两个线程同时更改值。

    【讨论】:

      【解决方案3】:

      尝试使用 valgrind;我没有在 OS X 上尝试过 valgrind,我不明白你的问题,但是当你说“clobbered”时,我首先想到的是“try valgrind”。

      【讨论】:

      • valgrind 最近获得了 Mac OS X 支持。我还没有在这个平台上广泛使用它,但它至少适用于简单的例子。
      • AFAIK,valgrind 仅适用于 OS X 10.5,不适用于 10.4。
      • 另外,VG 不会检测从一个有效区域溢出到另一个有效区域。 VG 对这个程序保持沉默: int aaa[5];诠释 bbb; int main() { int i;对于 (i = 0; i
      • 添加 libefence (或类似的)并稍作更改以在免费存储中分配 some_values 会有所帮助。默认情况下,libefence 将围绕每个空闲存储分配分配内存,这样它就会紧跟一个无效的内存页面,因此一旦发生溢出就会出现段错误。有一些注意事项 - 例如对齐 - 和其他问题 - 例如大量增加的内存分配,这对于通过 VG 造成的性能损失“非常有效” - 但这通常是我开始的地方。
      • @boycy 如果您将其作为新答案而不是评论发布,则 OP 更有可能看到您的评论。
      【解决方案4】:

      您可以尝试的一件事是创建一个单独的线程,其唯一目的是观察addr 的值,并在它发生变化时中断。例如:

      static int * volatile addr;  // volatile here is important, and must be after the *
      void *addr_thread_proc(void *arg)
      {
          while(1)
          {
              int *old_value = addr;
              while(addr == old_value) /* spin */;
              __asm__("int3");  // break the debugger, or raise SIGTRAP if no debugger
          }
      }
      ...
      pthread_t spin_thread;
      pthread_create(&spin_thread, NULL, &addr_thread_proc, NULL);
      

      然后,每当addr 的值发生变化时,int3 指令就会运行,这会破坏调试器,停止所有线程。

      【讨论】:

        【解决方案5】:

        gdb 在多线程程序中经常表现得很奇怪。另一个解决方案(如果你负担得起的话)是把printf()s 放在各处,试图抓住你的价值被破坏的那一刻。不是很优雅,但有时​​很有效。

        【讨论】:

          【解决方案6】:

          我没有在 OSX 上进行任何调试,但我在 Linux 上的 GDB 中看到了相同的行为:程序崩溃,但 GDB 可以读取和写入程序刚刚尝试读取/写入失败的内存。

          这并不一定意味着 GDB 很困惑;而是内核允许 GDB 通过 ptrace() 读取/写入内存,不允许下级进程读取或写入。 IOW,这是一个(最近修复的)内核错误。

          不过,无论出于何种原因,GDB 观察点似乎都不适合您。

          您可以使用的一种技术是mmapsome_values 分配空间,而不是为它们静态分配空间,将数组安排到页面边界结束,然后安排下一页不可访问(通过mprotect)。

          如果任何代码试图访问超过 some_values 的末尾,它会得到一个异常(实际上你正在设置一个不可写的“观察点”刚刚超过 some_values)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-04-22
            • 2018-02-26
            • 2012-07-24
            • 2017-05-20
            • 2013-12-21
            • 2012-04-06
            • 2011-01-02
            相关资源
            最近更新 更多