在GDB中如何在特定异常类型被抛出时中断程序?

64
根据文档,我可以使用条件断点来针对特定的异常类型进行中断。但是条件语句的语法对我来说不是很清楚:
condition bnum <expression>

看着这个表达式的语法,我认为这是我需要的模式:

{type} addr

然而,我不知道我应该传递什么参数给addr。我尝试了以下内容:

(gdb) catch throw
(gdb) condition 1 boost::bad_function_call *

但它不起作用(gdb在所有异常类型上中断)。

有人能帮忙吗?


更新

我也尝试了 @Adam 的建议,但结果出现错误信息:

(gdb) catch throw boost::bad_function_call
Junk at end of arguments.

没有 boost:: 命名空间:

(gdb) catch throw bad_function_call
Junk at end of arguments.


解决方法

bad_function_call的构造函数中断言可以起作用。


在Exception对象的构造函数上设置断点怎么样? - peterh
9个回答

29

编辑

文档中建议使用catch throw <exceptname>来在抛出<exceptname>类型的异常时中断程序,但实际上似乎并不起作用。

(gdb) help catch
Set catchpoints to catch events.
Raised signals may be caught:
        catch signal              - all signals
        catch signal <signame>    - a particular signal
Raised exceptions may be caught:
        catch throw               - all exceptions, when thrown
        catch throw <exceptname>  - a particular exception, when thrown
        catch catch               - all exceptions, when caught
        catch catch <exceptname>  - a particular exception, when caught
Thread or process events may be caught:
        catch thread_start        - any threads, just after creation
        catch thread_exit         - any threads, just before expiration
        catch thread_join         - any threads, just after joins
Process events may be caught:
        catch start               - any processes, just after creation
        catch exit                - any processes, just before expiration
        catch fork                - calls to fork()
        catch vfork               - calls to vfork()
        catch exec                - calls to exec()
Dynamically-linked library events may be caught:
        catch load                - loads of any library
        catch load <libname>      - loads of a particular library
        catch unload              - unloads of any library
        catch unload <libname>    - unloads of a particular library
The act of your program's execution stopping may also be caught:
        catch stop

C++ exceptions may be caught:
        catch throw               - all exceptions, when thrown
        catch catch               - all exceptions, when caught
Ada exceptions may be caught:
        catch exception           - all exceptions, when raised
        catch exception <name>    - a particular exception, when raised
        catch exception unhandled - all unhandled exceptions, when raised
        catch assert              - all failed assertions, when raised

Do "help set follow-fork-mode" for info on debugging your program
after a fork or vfork is caught.

Do "help breakpoints" for info on other commands dealing with breakpoints.

1
在这里也可以查看:设置Catchpoints(http://sourceware.org/gdb/onlinedocs/gdb/Set-Catchpoints.html) - yasouser
3
使用 catch throw <exceptname> 会导致错误信息显示为“参数末尾有垃圾字符”。 - StackedCrooked
@StackedCrooked:糟糕,你是对的。这就是我只看文档而没有试过的结果。 - Adam Rosenfield
文档实际上指出,C++异常不能被指定;在C++的标题下,没有“catch exception <exceptionname>”这一项。另一个例子是,根据您发布的文档,当未处理的异常被引发时,只有Ada支持捕获它们。 - TamaMcGlinn

20

当gdb命令'catch throw'失败时,请尝试此解决方法:
(已在Linux g++ 4.4.5 / gdb 6.6上进行测试)
1/ 在程序中的任意位置添加以下代码以进行调试:

#include <stdexcept>
#include <exception>
#include <typeinfo>

struct __cxa_exception {
    std::type_info *inf;
};
struct __cxa_eh_globals {
    __cxa_exception *exc;
};
extern "C" __cxa_eh_globals* __cxa_get_globals();
const char* what_exc() {
    __cxa_eh_globals* eh = __cxa_get_globals();
    if (eh && eh->exc && eh->exc->inf)
        return eh->exc->inf->name();
    return NULL;
}

2/ 在gdb中,您可以使用以下命令过滤异常:

(gdb) break __cxa_begin_catch  
(gdb) cond N (what_exc()?strstr(what_exc(),"exception_name"):0!=0)  

其中N是断点编号,exception_name是我们希望中断的异常名称。


15

从这里的问题中我所理解的是,你想要在你的应用程序中当特定的异常boost::bad_function_call被抛出时终止程序。

$> gdb /path/to/binary
(gdb) break boost::bad_function_call::bad_function_call()
(gdb) run --some-cli-options

因此,在准备抛出throw时,临时对象boost::bad_function_call被构造;这时gdb将会中断执行!

我已经测试过,它确实可行。如果您精确地知道异常对象的构建方式,则可以在特定构造函数上设置断点。否则,如下例所示,您可以省略参数原型列表,gdb将在所有不同版本的构造函数上设置断点。

$ gdb /path/to/binary

(gdb) break boost::bad_function_call::bad_function_call
Breakpoint 1 at 0x850f7bf: boost::bad_function_call::bad_function_call. (4 locations)

(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   <MULTIPLE>
1.1                         y     0x0850f7bf in boost::bad_function_call::bad_function_call() at /usr/include/boost/function/function_base.hpp:742
1.2                         y     0x0850fdd5 in boost::bad_function_call::bad_function_call(boost::bad_function_call const&) at /usr/include/boost/function/function_base.hpp:739
1.3                         y     0x0863b7d2 <boost::bad_function_call::bad_function_call()+4>
1.4                         y     0x086490ee <boost::bad_function_call::bad_function_call(boost::bad_function_call const&)+6>

1
这个方法非常有效。感谢您提供的简单易行的解决方案。 - MKroehnert
3
要在异常的所有构造函数上设置断点,您可以使用正则表达式断点命令,例如:rb my_exception::my_exception。顺便问一下,break boost::bad_function_call() 命令对您真的有效吗?我必须使用 gdb 7.8.2 中的 break boost::bad_function_call::bad_function_call()。否则会显示:“未定义函数”boost::bad_function_call()“”。 - maxschlepzig
@maxschlepzig 感谢您指出这一点,我已经更新了答案。 - ϹοδεMεδιϲ
我想知道这个方法是否也适用于 std::exception,当你想忽略所有不派生自 std::exception 的其他抛出异常时。 - Emile Cormier

4
另一种方法是依赖于 catch 点触发时可用的 tinfo 参数,它是指向由 typeid(type) 返回的对象的指针。
因此,如果我想捕获抛出的 std::bad_alloc 异常,只需执行以下操作:
> p &typeid(std::bad_alloc)
> $1 = (__cxxabiv1::__si_class_type_info *) 0x8c6db60 <typeinfo for std::bad_alloc>
> catch throw if tinfo == 0x8c6db60

4
在gdb中如何使用&typeid(std::bad_alloc)?我的gdb只显示“当前上下文中没有符号"typeid"。” - sehe

3
假设您有以下带有抛出异常线程的 code.cpp 代码:
#include <iostream>
#include <thread>

void thr()
{
    while (true) {
      new int[1000000000000ul];
    }
}

int main(int argc, char* argv[]) {
  std::thread t(thr);
  t.join();
  std::cout << "Hello, World!" << std::endl;
  return 0;
}

使用下面的CMakeLists.txt编译它。
cmake_minimum_required(VERSION 3.5)
project(tutorial)

set(CMAKE_CXX_STANDARD 11)

add_executable(test_exceptions main.cpp)

target_link_libraries(test stdc++ pthread)

现在你可以运行它,但会因为bad_alloc而导致中止。 在继续之前,最好安装libstd debug symbols,例如sudo apt-get install libstdc++6-5-dbg 或其他版本。

调试编译

如果您正在进行调试编译,您可以按照此答案(https://dev59.com/Mmw15IYBdhLWcg3wFH3f#12434170)的步骤进行操作,因为通常定义了构造函数。

发布编译

如果您正在进行发布编译,则可能无法找到适当的构造函数来设置断点,因为编译器已经进行了优化,这时您有其他选择。让我们继续。

源代码更改解决方案

如果您可以轻松更改源代码,则可以使用此方法(https://dev59.com/Mmw15IYBdhLWcg3wFH3f#9363680)

Gdb catch throw 简单解决方案

如果您不想更改代码,则可以尝试看看catch throw bad_alloc或一般情况下的catch throw exception_name是否起作用。

Gdb catch throw 解决方案

我将在此答案(https://dev59.com/Mmw15IYBdhLWcg3wFH3f#6849989)的基础上进行构建,在gdb中为函数__cxxabiv1::__cxa_throw设置断点。此函数需要一个称为tinfo的参数,其中包含我们需要有条件检查的异常信息。
我们想要类似于catch throw if exception==bad_alloc,那么如何找到适当的比较? 事实证明,tinfo是指向具有名为__name变量的结构的指针。 此变量具有带有异常类型名称的编码名称字符串。
因此,我们可以做类似这样的事情:catch throw if tinfo->__name == mangled_exception_name 我们已经接近成功了!
我们需要一种进行字符串比较的方法,而gdb具有内置函数$ _streq(str1,str2),该函数恰好可满足我们的需求。 异常的编码名称有点难以找到,但您可以尝试猜测或在本答案的附录中查找。现在假设它是“St9bad_alloc”。
最终指令是: catch throw if $_streq(tinfo->__name , "St9bad_alloc") 或等效

break __cxxabiv1::__cxa_throw if $_streq(tinfo->__name , "St9bad_alloc")

如何查找异常名称

您有两个选项

在库中查找符号

假设您已安装了libstd调试符号,您可以像这样找到库名称:

apt search libstd | grep dbg | grep installed

名称类似于libstdc++6-5-dbg

现在检查已安装的文件:

dpkg -L libstdc++6-5-dbg

查找路径中具有debug和.so扩展名的内容。在我的PC上,我有/usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.21 最后,在其中查找您想要的异常。

nm /usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.21 | grep -i bad_alloc

或者 nm /usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.21 | grep -i runtime_error 等等。

在我的情况下,我发现类似于00000000003a4b20 V _ZTISt9bad_alloc,这表明我应该使用"St9bad_alloc"作为名称。

在gdb中抛出它并检查其中的名称

这很容易,只需启动gdb,catch throw everything并运行我之前编写的小型可执行文件。当您在gdb内部时,可以发出p *tinfo并从gdb中查找__name描述。

gdb -ex 'file test_exceptions' -ex 'catch throw' -ex 'run'

(gdb) p *tinfo $1 = {_vptr.type_info = 0x406260 <vtable for __cxxabiv1::__si_class_type_info+16>, __name = 0x7ffff7b8ae78 <typeinfo name for std::bad_alloc> "St9bad_alloc"}


2

我不确定这是否是最近修复的,但使用 GDB GNU gdb (Debian 9.1-2) 9.1,我已成功使用 catch throw std::logical_error。我不想过早地概括,但现在 GDB (2020年4月)可能正确地工作。


2

正如其他人已经提到的,实际上这个功能无法正常工作。但是作为解决方法,您可以在catch throw上放置条件。当异常被抛出时,我们会进入__cxa_throw函数。它有几个指向异常类的参数,因此我们可以在其中一个参数上设置条件。在下面的示例gdb会话中,我在__cxa_throwdest参数上放置了条件。唯一的问题是,dest的值(在本例中为0x80486ec)事先是未知的。例如,可以通过在断点上首先运行gdb而知道它。

[root@localhost ~]#
[root@localhost ~]# gdb ./a.out
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/a.out...done.
(gdb) catch throw
Catchpoint 1 (throw)
(gdb) condition 1 dest==0x80486ec
No symbol "dest" in current context.
(gdb) r
warning: failed to reevaluate condition for breakpoint 1: No symbol "dest" in current context.
warning: failed to reevaluate condition for breakpoint 1: No symbol "dest" in current context.
warning: failed to reevaluate condition for breakpoint 1: No symbol "dest" in current context.
Catchpoint 1 (exception thrown), __cxxabiv1::__cxa_throw (obj=0x804a080, tinfo=0x8049ca0, dest=0x80486ec <_ZNSt13runtime_errorD1Ev@plt>) at ../../../../gcc-4.4.3/libstdc++-v3/libsupc++/eh_throw.cc:68
68      ../../../../gcc-4.4.3/libstdc++-v3/libsupc++/eh_throw.cc: No such file or directory.
        in ../../../../gcc-4.4.3/libstdc++-v3/libsupc++/eh_throw.cc
(gdb) bt
#0  __cxxabiv1::__cxa_throw (obj=0x804a080, tinfo=0x8049ca0, dest=0x80486ec <_ZNSt13runtime_errorD1Ev@plt>) at ../../../../gcc-4.4.3/libstdc++-v3/libsupc++/eh_throw.cc:68
#1  0x08048940 in main () at test.cpp:14
(gdb) i b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x008d9ddb exception throw
        stop only if dest==0x80486ec
        breakpoint already hit 1 time
(gdb)

更新

为使此解决方法生效,您还需加载libstdc++的调试信息。


在我的系统上(Linux, g++ 4.5.2),__cxa_throw似乎没有任何参数。至少,当我在那个函数上断点时,gdb将其显示为__cxa_throw()。你是如何编译你的程序的? - Łukasz Milewski
我的系统是 Fedora 12,gcc 版本为 4.4.4 20100630。我使用以下命令进行编译:g++ -g test.cpp - ks1322
我还安装并加载了libstdc++的调试信息。在Fedora上,可以使用命令“debuginfo-install libstdc++”来完成此操作。我认为这就是您看不到__cxa_throw参数的原因。请检查您系统中是否有libstdc++的调试信息。 - ks1322

1

我认为我可以回答有关设置条件断点的部分。我不会回答关于异常的问题,因为在g++ 4.5.2中似乎不存在__raise_exception (?)

假设您有以下代码(我使用void来获得类似于gdb文档中的__raise_exception)

void foo(void* x) {

}

int main() {
    foo((void*)1);
    foo((void*)2);
}

要在foo(2)处中断,您可以使用以下命令

(gdb) break foo
Breakpoint 1 at 0x804851c: file q.cpp, line 20.
(gdb) condition 1 x == 2

如果你执行

(gdb) r

您将会看到程序停留在第二个“foo”的位置,但不会停留在第一个“foo”的位置。

我认为,文档中所指的是要设置断点在函数 __raise_exception 上(这取决于具体的实现)。

 /* addr is where the exception identifier is stored
    id is the exception identifier.  */
    void __raise_exception (void **addr, void *id);

然后按照上述所述的方式在id上设置条件断点(您必须以某种方式确定id是您的异常类型)。

不幸的是

 (gdb) break __raise_exception

使用 (g++ 4.5.2) 的结果

 Function "__raise_exception" not defined.

在g++ 4.5.2的标准库中,函数名似乎是__cxa_raise。在那里设置断点似乎相当于只说catch throw - Adam Rosenfield
@Adam 我无法在__cxa_raise处中断(函数“__cxa_raise”未定义)。虽然有__cxa_allocate_exception和__cxa_throw函数,但问题在于这两个函数都没有参数,因此无法根据异常类型进行条件性中断。我在互联网上进行了一些研究,似乎gdb文档已经存在漏洞很长时间了。我用“nm”命令验证了可执行文件不包含__raise_exception符号。不过对于这个漏洞我也不确定。 - Łukasz Milewski
1
@mmilewski:糟糕,是的,我指的是__cxa_throw,而不是__cxa_raise(我犯了另一个错误)。您仍然可以创建一个有条件的断点,查看堆栈/寄存器以确定异常的类型,但这相当笨拙且依赖于平台。 - Adam Rosenfield

0

如果问题是没有有效的堆栈跟踪(不在raise中断),则重新编译而不重启gdb时似乎会出现问题。(即在gdb控制台内调用“make”)。

重新启动gdb后,它会在raise.c中正确中断。 (我的版本:GNU gdb 8.1.0.20180409-git,gcc 7.4.0,GNU make 4.1)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接