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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6835728/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 20:44:52  来源:igfitidea点击:

How to break when a specific exception type is thrown in GDB?

c++gdb

提问by StackedCrooked

According to the documentation I can break on specific exception type by using conditional breakpoints. However the syntax for the condition isn't very clear to me:

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

condition bnum <expression>

Looking at the expression syntax I think this is the pattern I need:

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

{type} addr

{type} addr

However, I don't know what I should pass for the addrargument. I tried the following:

但是,我不知道我应该为这个addr论点传递什么。我尝试了以下方法:

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

But it doesn't work (gdb breaks on all exception types).

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

Can anyone help?

任何人都可以帮忙吗?



Update

更新

我也尝试过@Adam 的建议,但它导致了一条错误消息:

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

Without boost::namespace:

没有boost::命名空间:

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



Workaround

解决方法

打破bad_function_callbad_function_call工程的建设者。

回答by Adam Rosenfield

EDIT

编辑

The documentation suggests that catch throw <exceptname>can be used to break whenever an exception of type <exceptname>is thrown; however, that doesn't seem to work in practice.

文档建议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.

回答by Francois

When gdb command 'catch throw' fails, try this workaround :
(tested with Linux g++ 4.4.5/gdb 6.6)
1/ Add this code anywhere in the program to debug :

当 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/ In gdb you will then be able to filter exceptions with :

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

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

where N is the breakpoint number, and exception_name is the name of exception for which we wish to break.

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

回答by ?οδεMεδι?

From what I have understood from the question here, you want to break when a specific exception boost::bad_function_callis thrown in your application.

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

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

So when the temporary object boost::bad_function_callis constructed in preparation for the throw; gdb will break out!

所以当临时对象boost::bad_function_call被构造为准备时throw;gdb会爆发!

I have tested this and it does work. If you precisely know the way the exception object is being constructed then you can set breakpoint on the specific constructor, otherwise as shown in the example below, you can omit the arguments prototype list, and gdbwill set break points on all different flavours of the constructor.

我已经测试过了,它确实有效。如果您确切地知道异常对象的构造方式,那么您可以在特定的构造函数上设置断点,否则如下面的示例所示,您可以省略参数原型列表,并将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>

回答by Xin Liu

Another approach is to rely on the tinfoargument available when the catch point is triggered, which is a pointer to the object returned by typeid(type).

另一种方法是依赖于tinfo触发捕获点时可用的参数,它是指向由 返回的对象的指针typeid(type)

So say if I want to catch exception std::bad_allocbeing thrown, I could just do:

所以说如果我想捕获std::bad_alloc抛出的异常,我可以这样做:

> p &typeid(std::bad_alloc)
>  = (__cxxabiv1::__si_class_type_info *) 0x8c6db60 <typeinfo for std::bad_alloc>
> catch throw if tinfo == 0x8c6db60

回答by mirko ferrati

Let's assume you have the following code.cpp with a thread that throws an exception:

假设您有以下 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;
}

Compile it with using the following CMakeLists.txt

使用以下 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)

Now you can play with, running it will give you an abort because of bad_alloc. Before going on, it's better if you install libstd debug symbols, sudo apt-get install libstdc++6-5-dbgor whatever version you have.

现在你可以玩了,运行它会让你因为 bad_alloc 而中止。在继续之前,最好安装 libstd 调试符号sudo apt-get install libstdc++6-5-dbg或您拥有的任何版本。

Debug compilation

调试编译

If you are compiling in Debugyou can follow this answer https://stackoverflow.com/a/12434170/5639395because constructors are usually defined.

如果您正在编译,则Debug可以按照此答案https://stackoverflow.com/a/12434170/5639395 进行操作,因为通常会定义构造函数。

Release compilation

发布编译

If you are compiling in DebWithRelInfoyou may not be able to find a proper constructor where to put your breakpoint because of the compiler optimization. In this case, you have some other options. Let's continue.

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

Source code change solution

源代码更改解决方案

If you can change the source code easily, this will work https://stackoverflow.com/a/9363680/5639395

如果您可以轻松更改源代码,这将起作用https://stackoverflow.com/a/9363680/5639395

Gdb catch throw easy solution

gdb catch throw 简单的解决办法

If you don't want to change the code, you can try to see if catch throw bad_allocor in general catch throw exception_nameworks.

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

Gdb catch throw workaround

Gdb catch throw 解决方法

I will build on top of this answer https://stackoverflow.com/a/6849989/5639395We will add a breakpoint in gdb in the function __cxxabiv1::__cxa_throw. This function takes a parameter called tinfothat has the information we need to conditionally check for the exception we care about.

我将建立在这个答案之上https://stackoverflow.com/a/6849989/5639395我们将在函数的 gdb 中添加一个断点__cxxabiv1::__cxa_throw。这个函数接受一个被调用的参数tinfo,该参数包含我们有条件地检查我们关心的异常所需的信息。

We want something like catch throw if exception==bad_alloc, so how to find the proper comparison? It turns out that tinfois a pointer to a structure that has a variable called __nameinside. This variable has a string with the mangled name of the exception type.

我们想要像catch throw if exception==bad_alloc这样的东西,那么如何找到合适的比较呢?事实证明这tinfo是一个指向__name内部有一个变量的结构的指针。此变量有一个字符串,其中包含异常类型的重整名称。

So we can do something like: catch throw if tinfo->__name == mangled_exception_name

所以我们可以这样做:catch throw if tinfo->__name == mangled_exception_name

We are almost there!

我们就快到了!

We need a way to do string comparison, and it turns out gdb has a built-in function $_streq(str1,str2) that does exactly what we need. The mangled name of the exception is a little harder to find, but you can try to guess it or check the Appendix of this answer. Let's assume for now it is "St9bad_alloc".

我们需要一种方法来进行字符串比较,结果证明 gdb 有一个内置函数 $_streq(str1,str2) 可以完全满足我们的需求。异常的错位名称有点难找到,但您可以尝试猜测它或查看此答案的附录。现在让我们假设它是“St9bad_alloc”。

The final instruction is:

最后的指令是:

catch throw if $_streq(tinfo->__name , "St9bad_alloc")

catch throw if $_streq(tinfo->__name , "St9bad_alloc")

or equivalent

或同等学历

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

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

How to find the name of your exception

如何查找异常的名称

You have two options

你有两个选择

Look for the symbol in the library

在库中查找符号

Assuming that you installed the libstd debug symbols, you can find the library name like this:

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

apt search libstd | grep dbg | grep installed

apt search libstd | grep dbg | grep installed

The name is something like this libstdc++6-5-dbg

名字是这样的 libstdc++6-5-dbg

Now check the files installed:

现在检查安装的文件:

dpkg -L libstdc++6-5-dbg

dpkg -L libstdc++6-5-dbg

Look for something that has a debug in the path, and a .so extension. In my pc I have /usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.21. Finally, look for the exception you want in there.

寻找在路径中有调试和 .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 bad_alloc

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

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

In my case I found something like 00000000003a4b20 V _ZTISt9bad_allocwhich suggested me to use "St9bad_alloc" as the name.

就我而言,我发现类似的东西00000000003a4b20 V _ZTISt9bad_alloc建议我使用“St9bad_alloc”作为名称。

Throw it in gdb and inspect the name in there

将其放入 gdb 并检查其中的名称

This is easy, just start gdb, catch throweverything and run the small executable I wrote before. When you are inside gdb, you can issue a p *tinfoand look for the __namedescription from gdb.

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

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

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"}

(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"}

回答by ks1322

As others already mentioned this functionality doesn't work in practice. But as workaround you can put condition on catch throw. When exception is thrown we come to __cxa_throwfunction. It has several parameters pointing to exception class, so we can set condition on one of them. In the sample gdb session below, I put condition on destparameter of __cxa_throw. The only problem is that value of dest(0x80486ec in this case) is unknown in advance. It can be known, for example, by first running gdb without condition on breakpoint.

正如其他人已经提到的,此功能在实践中不起作用。但作为解决方法,您可以将条件置于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)

Update

更新

You must also load debug info for libstdc++ for this workaround to work.

您还必须加载 libstdc++ 的调试信息才能使此变通方法起作用。

回答by Marco Craveiro

I'm not sure if this is a recent fix, but with GDB GNU gdb (Debian 9.1-2) 9.1, I have used catch throw std::logical_errorsuccessfully. I would hate to generalise prematurely, but it is possible this now works correctly in GDB (April 2020).

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

回答by ?ukasz Milewski

I think I can answer the part about setting conditional breaks. I won't answer question regarding exceptions as __raise_exception seems to not exist in g++ 4.5.2 (?)

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

Let's assume that you have following code (I use void to get something similar to __raise_exception from gdb doc)

假设您有以下代码(我使用 void 从 gdb doc 中获取类似于 __raise_exception 的内容)

void foo(void* x) {

}

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

to break at foo(2) you use following commands

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

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

If you run with

如果你跑

(gdb) r

you will see that it stops on the second foo call, but not on the first one

您会看到它在第二次 foo 调用时停止,但不会在第一个调用时停止

I think, what they meant in docs is that you set break on function __raise_exception (very implementation dependent)

我认为,他们在文档中的意思是你在函数 __raise_exception 上设置了中断(非常依赖于实现)

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

and then set conditional break on id as described above (you have to somehow determine what is id for yours exception type).

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

Unfortunately

很遗憾

 (gdb) break __raise_exception

results with (g++ 4.5.2)

结果与 (g++ 4.5.2)

 Function "__raise_exception" not defined.

回答by xmx

In case the problem is that there is no valid stack trace (not breaking in raise), it seems to be a problem when re-compiling without re-starting gdb. ( i.e. calling "make" inside the gdb console).

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

After having re-started gdb, it breaks correctly in raise.c (my versions : GNU gdb 8.1.0.20180409-git, gcc 7.4.0, GNU make 4.1)

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