【问题标题】:Setting conditional breakpoint to catch an invalid pointer value设置条件断点以捕获无效的指针值
【发布时间】:2019-12-12 04:56:24
【问题描述】:

我正在调试一个大型多线程应用程序,并试图在将无效指针值分配给变量时捕获错误。我不能简单地从故障点回溯,因为消费者和生产者是不同的线程,所以我试图在该变量的每个赋值语句上设置一个条件断点,以测试赋值是否无效。

让我们考虑这个例子:

#include <stdio.h>

int main() {
    char* arr[] = {"foo", "bar", 0x1234, "baz"};

    for(int i=0; i<4; ++i) {
        char* str = arr[i];

        puts(str);
    }
}

我发现我可以这样处理:

break test.c:7 if !arr[i][0]

它有效,但是,我不确定这种方法是否可靠,因为它实际上试图访问(可能)无效的内存地址。另外,当字符串为空时有明显的误报。

另外,我遇​​到了this answer,但看起来我必须以某种方式告诉链接器将这些函数添加到我的可执行文件中以供 gdb 使用,而且在单列中使用它看起来太复杂了条件。

所以,我的问题是:是否有任何可靠的方法来检查 gdb 中的指针是否无效,它足够短,可以在单行条件中使用并且不依赖于指向的值。

【问题讨论】:

  • “我不确定这种方法是否可靠”。即使除了您指出的问题,我也看不出这是多么可靠。该条件检查 0 值,但根本不能保证无效指针指向 0。事实上,无效指针可能会在访问时出现段错误,甚至更糟的是,它会指向随机内存,这将不是段错误,并且在访问时不会返回可预测的值。
  • 所以退后一步,在这种情况下,您对“无效指针”的定义是什么?也就是说,您如何得出问题在于指针具有无效值的结论?
  • @kaylum 这里的“无效指针”是指“取消引用时会导致段错误的东西”。我知道这个定义太狭窄了,但对我来说已经足够了
  • 看来你知道任务发生在哪里。如果这是真的,那么只需将分配更改为函数调用。函数调用将首先尝试取消引用指针,然后执行实际分配。然后调试器可以将此作为正常的段错误捕获。小心确保编译器不会优化代码的取消引用部分。
  • 感谢您的建议,但这是一些遗留代码,除非必要,否则我真的不想修改它。如果没有更好的方法,我会这样做

标签: c linux gdb


【解决方案1】:

由于您使用的是 Linux,因此最好的方法是最有可能使用 reverse 调试器,例如 rr

您使用rr record ./a.out arg0 ... 运行程序,然后,一旦它崩溃,您可以在“坏”变量上设置一个观察点,并将reverse-continue 设置为变量最后更改的位置。它就像魔术一样工作!

以下是您的示例的会话:

$ rr record ./a.out
rr: Saving execution to trace directory `$HOME/.local/share/rr/a.out-5'.
foo
bar
Segmentation fault

$ rr replay
...
Program stopped.
0x00007f69546b4f30 in _start () from /lib64/ld-linux-x86-64.so.2
(rr) c
Continuing.
foo
bar

Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:96
96  ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.
(rr) bt
#0  __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:96
#1  0x00007f6954366040 in __GI__IO_puts (str=0x1234 <error: Cannot access memory at address 0x1234>) at ioputs.c:35
#2  0x000056227d5ec189 in main () at foo.c:9

(rr) up
#1  0x00007f6954366040 in __GI__IO_puts (str=0x1234 <error: Cannot access memory at address 0x1234>) at ioputs.c:35
35  ioputs.c: No such file or directory.
(rr) up
#2  0x000056227d5ec189 in main () at foo.c:9
9           puts(str);
(rr) p str
$1 = 0x1234 <error: Cannot access memory at address 0x1234>
(rr) watch -l str
Hardware watchpoint 1: -location str
(rr) reverse-cont
Continuing.

Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:96
96  ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.
(rr) reverse-cont
Continuing.

Hardware watchpoint 1: -location str

Old value = 0x1234 <error: Cannot access memory at address 0x1234>
New value = 0x56227d5ed008 "bar"
0x000056227d5ec179 in main () at foo.c:7
7           char* str = arr[i];
(rr) p i
$2 = 2
(rr) p arr[i]
$3 = 0x1234 <error: Cannot access memory at address 0x1234>

【讨论】:

  • 我在想这个,但不确定它是否能很好地与多个线程一起工作。特别是如果分配在一个线程中并且指针 deref 在另一个线程中,就像这里的情况一样。你怎么看?
  • 太棒了!那么这绝对是要走的路。
  • 看起来不错。不知道那个工具,一定要去看看
猜你喜欢
  • 1970-01-01
  • 2013-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多