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

时间:2011-07-26 19:52:14

标签: c++ gdb

根据文档,我可以通过使用条件断点来破解特定的异常类型。但是这个条件的语法对我来说不是很清楚:

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的构造函数。

9 个答案:

答案 0 :(得分:22)

修改

文档表明,只要抛出类型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 :(得分:15)

当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是我们希望中断的异常的名称。

答案 2 :(得分:13)

根据我从这里的问题中理解,您希望在应用程序中抛出特定异常boost::bad_function_call时中断。

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

所以当构建临时对象boost::bad_function_call以准备throw时; 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>

答案 3 :(得分:3)

另一种方法是依赖触发捕获点时可用的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 :(得分:2)

正如其他人已经提到过的,这种功能在实践中并不起作用。但是作为解决方法,您可以将条件置于catch throw。当抛出异常时,我们来__cxa_throw函数。它有几个指向异常类的参数,因此我们可以在其中一个上设置条件。在下面的示例gdb会话中,我将条件放在dest __cxa_throw参数上。唯一的问题是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 ++的调试信息才能使此解决方法正常工作。

答案 5 :(得分:2)

让我们假设您具有以下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调试符号,sudo apt-get install libstdc++6-5-dbg或任何现有版本。

调试编译

如果您使用Debug进行编译,则可以遵循此答案https://stackoverflow.com/a/12434170/5639395,因为通常会定义构造函数。

发布编译

如果您使用DebWithRelInfo进行编译,则由于编译器的优化,可能无法找到合适的构造函数来放置断点。在这种情况下,您还有其他选择。让我们继续。

源代码更改解决方案

如果您可以轻松更改源代码,则可以https://stackoverflow.com/a/9363680/5639395

Gdb捕获引发简单解决方案

如果您不想更改代码,可以尝试查看catch throw bad_alloc或总体上catch throw exception_name是否有效。

Gdb捕获抛出变通办法

我将基于此答案https://stackoverflow.com/a/6849989/5639395 我们将在函数__cxxabiv1::__cxa_throw中的gdb中添加一个断点。此函数采用名为tinfo的参数,该参数包含我们有条件地检查我们关心的异常所需的信息。

如果异常== bad_alloc ,我们想要类似 catch throw的东西,那么如何找到正确的比较? 事实证明,tinfo是指向内部具有变量__name的结构的指针。该变量具有一个字符串,其中包含异常类型的错误名称。

因此我们可以执行以下操作:如果tinfo-> __ name == mangled_exception_name

我们快到了!

我们需要一种进行字符串比较的方法,事实证明gdb具有内置函数$ _streq(str1,str2)可以完全满足我们的需求。 异常的名称混乱不清,但是您可以尝试猜测它或查看此答案的附录。现在假设它是“ St9bad_alloc”。

最终指示是:<​​/ p>

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

寻找在路径中具有调试和.so扩展名的内容。在我的电脑中,我有/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一切并运行我之前编写的小型可执行文件即可。当您进入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"}

答案 6 :(得分:2)

我不确定这是否是最新修复程序,但是对于GDB GNU gdb (Debian 9.1-2) 9.1,我已经成功使用了catch throw std::logical_error。我不希望过早地对此进行概括,但是现在这有可能在GDB中正确运行(2020年4月)。

答案 7 :(得分:1)

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

假设您有以下代码(我使用void从gdb doc获取类似于__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调用时停止,但不会在第一个调用

上停止

我认为,他们在docs中的含义是你设置了函数__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.

答案 8 :(得分:0)

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

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