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

后端 未结 9 1150
迷失自我
迷失自我 2020-12-02 07:40

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:

相关标签:
9条回答
  • 2020-12-02 08:01

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

    #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

    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-dbg or whatever version you have.

    Debug compilation

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

    Release compilation

    If you are compiling in DebWithRelInfo you 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.

    Source code change solution

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

    Gdb catch throw easy solution

    If you don't want to change the code, you can try to see if catch throw bad_alloc or in general catch throw exception_name works.

    Gdb catch throw workaround

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

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

    So we can do something like: 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".

    The final instruction is:

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

    or equivalent

    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:

    apt search libstd | grep dbg | grep installed

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

    Now check the files installed:

    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.

    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_error etc.

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

    Throw it in gdb and inspect the name in there

    This is easy, just start gdb, catch throw everything and run the small executable I wrote before. When you are inside gdb, you can issue a p *tinfo and look for the __name description from gdb.

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

    0 讨论(0)
  • 2020-12-02 08:02

    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 (?)

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

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

    to break at foo(2) you use following commands

    (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

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

     /* 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).

    Unfortunately

     (gdb) break __raise_exception
    

    results with (g++ 4.5.2)

     Function "__raise_exception" not defined.
    
    0 讨论(0)
  • 2020-12-02 08:05

    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 :

    #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 :

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

    0 讨论(0)
  • 2020-12-02 08:07

    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_error successfully. I would hate to generalise prematurely, but it is possible this now works correctly in GDB (April 2020).

    0 讨论(0)
  • 2020-12-02 08:08

    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.

    (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.
    
    0 讨论(0)
  • 2020-12-02 08:08

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

    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)

    0 讨论(0)
提交回复
热议问题