在 gdb 中中断失败断言的正确方法是啥?

Posted

技术标签:

【中文标题】在 gdb 中中断失败断言的正确方法是啥?【英文标题】:What is the proper way to break on failed asserts in gdb?在 gdb 中中断失败断言的正确方法是什么? 【发布时间】:2015-11-11 08:40:19 【问题描述】:

我试图在我的程序中捕获失败的断言。我正在使用一个直接调用 assert() 的库,而不是自定义函数或宏,我目前正试图在这个库中跟踪几个与移植相关的错误。所涉及的所有内容都已使用 g++ 中的调试符号编译。

我发现的最佳解决方案是在断言的文件:行处中断,并使用断言表达式的条件。这允许在断言失败之前停止断言,但这是一个可怕的解决方案。它需要对每个可能失败的断言进行特殊设置,在我的 IDE 中无法使用,而且总体上需要付出太多努力。

如何使用 gdb 和 gcc 打破 任何 失败的断言,以允许检查断言调用范围内的调用堆栈和变量?

如果解决方案允许我放弃断言的失败并继续运行,那就更好了。

【问题讨论】:

在 Visual Studio 中的 Windows 上,中断是失败 assert 的默认行为。我很惊讶在 *nix 世界中并非如此 - 失败的 assert 通常在那里做什么? 在gdb中,在程序运行之前,可以使用break abort(或者只是b abort)在函数abort()处添加断点。这至少可以让您在断言失败时进行回溯(假设它在失败时调用abort();某些实现可能会调用exit())。但不确定是否继续执行。 通常默认情况下这是有效的,因为 assert() 调用 abort,abort 会引发 SIGABRT 信号,gdb 默认情况下会中断该信号,允许您检查堆栈,向上/向下移动堆栈例如到包含 assert() 和检查变量等的函数。 @notmyfriend 在我的实现中,似乎不是默认中断失败的断言/中止;可能是因为这是一个 MSYS2-MinGW 设置,尽管我可以发誓这对我来说在 Linux 上发生过一次。在任何情况下,break abort 似乎都有效,并且在我的 IDE 中也有效。如果您想将其格式化为答案,我会接受。 @WilliamKappler 似乎您应该自己写下答案并自己接受 - 我相信您可以获得某种徽章。 ;-)。我对 MinGW 有同样的问题。 【参考方案1】:

abort() 上设置断点似乎是最好的答案。

break abort 在 gdb 的 CLI 中。

【讨论】:

原发帖者和@notmyfriend都在他们的cmets中发布了答案,但是在建议某人写一个官方答案之后,每当我忘记时都会回到这个页面,然后不得不重新阅读所有的 cmets 都想知道它是什么,我只是把它写出来! 这真是天才!【参考方案2】:

在 Linux 上不需要break,只需在提示符下输入bt

abort() 导致在 Linux 中引发SIGABRT 信号,默认情况下 GDB 已经中断信号。例如:

交流

#include <assert.h>

void g(int i) 
    assert(0);


void f(int i) 
    g(i);


int main(void) 
    f(1);

然后:

gcc -std=c99 -O0 -ggdb3 -o a a.c
gdb -ex run ./a

然后只需在 shell 中输入bt

(gdb) bt
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:58
#1  0x00007ffff7a483ea in __GI_abort () at abort.c:89
#2  0x00007ffff7a3ebb7 in __assert_fail_base (fmt=<optimized out>, assertion=assertion@entry=0x555555554788 "0", file=file@entry=0x555555554784 "a.c", line=line@entry=4, 
    function=function@entry=0x55555555478a <__PRETTY_FUNCTION__.1772> "g") at assert.c:92
#3  0x00007ffff7a3ec62 in __GI___assert_fail (assertion=0x555555554788 "0", file=0x555555554784 "a.c", line=4, function=0x55555555478a <__PRETTY_FUNCTION__.1772> "g")
    at assert.c:101
#4  0x00005555555546ca in g (i=1) at a.c:4
#5  0x00005555555546df in f (i=1) at a.c:8
#6  0x00005555555546f0 in main () at a.c:12

其中已经显示了函数值 (f (i=1))。

你也可以照常做:

(gdb) f 4
#4  0x00005555555546ca in g (i=1) at a.c:4
4           assert(0);
(gdb) p i
$1 = 1

控制 GDB 是否默认中断信号的设置是:handle all nostop 显示在:How to handle all signals in GDB

在 Ubuntu 16.10、gdb 7.11 中测试。

【讨论】:

注意:在某些平台上(例如某些 Windows shell 下的 MinGW)gdb 看不到 SIGABRT 信号,因此程序只是在断言上死掉而不停止 gdb 但在中止时设置断点仍然有效。 一个值得注意的例外是在 GTest 中运行测试,因此在调试测试时您可能仍希望在 abort 上设置断点。【参考方案3】:

如果上述建议的答案对您不起作用,您可以尝试打破 __assert_fail 函数。

break __assert_fail

名称很可能是依赖于实现的,但如果您查看平台上的断言宏定义,很容易找到。这将允许您在 SIGABRT 之前中断。

【讨论】:

【参考方案4】:

代码中的另一个选项:

#include <windows.h>
#include <signal.h>

static void abortHandler(int signalNumber)

    DebugBreak();


int main()

    signal(SIGABRT, &abortHandler);

【讨论】:

以上是关于在 gdb 中中断失败断言的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

中断事件循环后清理的正确方法是啥?

RTOS 中中断延迟的正确定义是啥?

断言失败:第 6075 行 pos 12:'child == _child':不正确

断言失败:在 Flutter 中使用 Navigator.popUntil() 时 !_debugLocked 不正确

断言失败:'纬度!= null':不正确。我已经尝试了几件事

断言失败:第 551 行 pos 12:'child.hasSize':不正确