【问题标题】:How to break when a specific exception type is thrown in GDB?在 GDB 中抛出特定异常类型时如何中断?
【发布时间】:2011-07-26 19:52:14
【问题描述】:

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

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对象的构造函数上设置断点怎么样?

标签: c++ gdb


【解决方案1】:

编辑

文档建议catch throw &lt;exceptname&gt; 可用于在抛出&lt;exceptname&gt; 类型的异常时中断;但是,这在实践中似乎行不通。

(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.

【讨论】:

  • 参见这里:设置 Catchpoints (sourceware.org/gdb/onlinedocs/gdb/Set-Catchpoints.html)
  • 使用 catch throw &lt;exceptname&gt; 会导致错误消息“Junk at end of arguments”。
  • @StackedCrooked:哎呀,你是对的。这就是我在没有实际尝试的情况下查看文档的结果。
  • 文档实际上是说不能指定C++异常;通过在 C++ 标题下省略 catch exception &lt;exceptionname&gt;。作为另一个例子,根据您发布的文档,仅 Ada 支持在引发未处理的异常时捕获它们。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    根据我从这里的问题了解到,当您的应用程序中抛出特定异常 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>
    

    【讨论】:

    • 这很好用。感谢您提供简单易用的解决方法。
    • 要在异常的所有构造函数上设置断点,您可以使用 regex-break 命令,例如: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 感谢您指出这一点;我已经更新了答案。
    【解决方案4】:

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

    【讨论】:

    • 如何让 &typeid(std::bad_alloc) 在 gdb 中工作?我的只是说No symbol "typeid" in current context.
    【解决方案5】:

    假设您有以下 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 简单的解决方案

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

    Gdb catch throw 解决方法

    我会在这个答案https://stackoverflow.com/a/6849989/5639395 的基础上再接再厉 我们将在函数 __cxxabiv1::__cxa_throw 的 gdb 中添加一个断点。该函数接受一个名为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-&gt;__name , "St9bad_alloc")

    或同等的

    break __cxxabiv1::__cxa_throw if $_streq(tinfo-&gt;__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】:

      正如其他人已经提到的那样,此功能在实践中不起作用。但作为解决方法,您可以在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()。你如何编译你的程序?
      • 我的系统是Fedora 12,gcc版本4.4.4 20100630。我是这样编译的g++ -g test.cpp
      • 我还安装并加载了 libstdc++ 的调试信息。在 Fedora 上,这是通过命令 debuginfo-install libstdc++ 完成的。我认为这就是您看不到__cxa_throw 参数的原因。检查系统中 libstdc++ 的调试信息。
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        假设您有以下代码(我使用 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 调用时停止,但不是在第一个调用时停止

        我认为,他们在文档中的意思是您在函数 __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 我不能在 __cxa_raise 处中断(函数“__cxa_raise”未定义)虽然有函数 __cxa_allocate_exception 和 __cxa_throw。问题是这两个函数都是在没有参数的情况下定义的,因此无法对异常类型进行条件中断。我在互联网上做了一些研究,似乎 gdb 文档中存在一个错误很长时间了。我用“nm”命令验证了可执行文件不包含 __raise_exception 符号。虽然不确定这个错误。
        • @mmilewski:哎呀,是的,我的意思是__cxa_throw,而不是__cxa_raise(我的另一个失败)。您仍然可以创建一个条件断点来查看堆栈/寄存器以确定异常的类型,但这非常笨拙且依赖于平台。
        【解决方案9】:

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

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

        【讨论】:

          猜你喜欢
          • 2022-06-16
          • 1970-01-01
          • 2011-03-05
          • 2017-05-20
          • 2015-12-17
          • 1970-01-01
          • 2014-12-06
          • 2015-11-13
          • 1970-01-01
          相关资源
          最近更新 更多