c++stdshared-ptrsmart-pointerslldb

LLDB: Set breakpoint on std::shared_ptr<T> destructor


How can one set a symbolic breakpoint in lldb on ~shared_ptr, the destructor?


Context: We wanted to break there to inspect a stack overflow of a naïvely implemented forward linked list in a demo program, where throwing away the list with 100,000 elements would overflow because each node's destructor triggered it's ->next destructor.

Sample file

#include <memory>

class Test {
public:
    Test() : a(1) { }
    
    int a;
};

int main() {
    auto sptr = std::make_shared<Test>();
    return 0;
}

Breakpoints tried (the last one was suggested by auto-completion):

(lldb) br set --name std::__1::shared_ptr<Test>::~shared_ptr
WARNING:  Unable to resolve breakpoint to any actual locations.
(lldb) br set --name std::__1::shared_ptr<Test>::~shared_ptr[abi:v160006]()
WARNING:  Unable to resolve breakpoint to any actual locations.

Solution

  • There are several ways to do this.

    Test with two instantiations

    First, just to make the test a little more interesting by having more than one instantiation of shared_ptr, I changed the sample to:

    #include <iostream>
    #include <memory>
    
    struct Test {
        ~Test() { std::cout << "in ~Test\n"; }
    };
    
    struct Test2 {
        ~Test2() { std::cout << "in ~Test2\n"; }
    };
    
    int main() {
        auto sptr = std::make_shared<Test>();
        auto sptr2 = std::make_shared<Test2>();
        return 0;
    }
    

    Then I compile and start the debugger with:

    $ clang++ -o prog -g -Wall prog.cc
    $ lldb ./prog
    

    1. Use breakpoint set --name

    The breakpoint set --name command sets a breakpoint on all functions with a given name:

    (lldb) breakpoint set --name ~shared_ptr
    Breakpoint 1: 2 locations.
    (lldb) breakpoint list
    Current breakpoints:
    1: name = '~shared_ptr', locations = 2
      1.1: where = prog`std::shared_ptr<Test2>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001440], unresolved, hit count = 0 
      1.2: where = prog`std::shared_ptr<Test>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001460], unresolved, hit count = 0 
    

    It also works with a more complete name, but the __1 stuff should not be there:

    (lldb) br set --name std::__1::shared_ptr<Test>::~shared_ptr
    Breakpoint 1: no locations (pending).
    WARNING:  Unable to resolve breakpoint to any actual locations.
    (lldb) br set --name std::shared_ptr<Test>::~shared_ptr
    Breakpoint 2: where = prog`std::shared_ptr<Test>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = 0x0000000000001460
    

    Note: br is an alias (via the unique prefix mechanism) for breakpoint.

    2. Use breakpoint set --method

    The breakpoint set --method <name> command sets a breakpoint on all methods with the given name:

    (lldb) breakpoint set --method ~shared_ptr
    Breakpoint 1: 2 locations.
    (lldb) breakpoint list
    Current breakpoints:
    1: name = '~shared_ptr', locations = 2
      1.1: where = prog`std::shared_ptr<Test2>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001440], unresolved, hit count = 0 
      1.2: where = prog`std::shared_ptr<Test>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001460], unresolved, hit count = 0 
    

    3. Use breakpoint set --func-regex

    The breakpoint set --func-regex <regex> command sets a breakpoint on all functions whose name matches the regex:

    (lldb) breakpoint set --func-regex "^std::shared_ptr<.*>::~shared_ptr\(\)$"
    Breakpoint 1: 2 locations.
    (lldb) breakpoint list
    Current breakpoints:
    1: regex = '^std::shared_ptr<.*>::~shared_ptr\(\)$', locations = 2
      1.1: where = prog`std::shared_ptr<Test>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001460], unresolved, hit count = 0 
      1.2: where = prog`std::shared_ptr<Test2>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001440], unresolved, hit count = 0 
    

    This is more cumbersome to use but offers greater precision.

    4. Break at the line in the header

    With b <file>:<line>, you can put breakpoints into all instantiations of a method at that location:

    (lldb) b shared_ptr.h:103
    warning: (x86_64) /home/scott/wrk/learn/lldb/break-in-shared-ptr-dtor/prog 0x000034a5: DW_AT_specification(0x000000000000088e) has no decl
    
    warning: (x86_64) /home/scott/wrk/learn/lldb/break-in-shared-ptr-dtor/prog 0x000034c6: DW_AT_specification(0x0000000000000110) has no decl
    
    Breakpoint 1: 2 locations.
    (lldb) breakpoint list
    Current breakpoints:
    1: file = 'shared_ptr.h', line = 103, exact_match = 0, locations = 2
      1.1: where = prog`std::shared_ptr<Test2>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001440], unresolved, hit count = 0 
      1.2: where = prog`std::shared_ptr<Test>::~shared_ptr() + 16 at shared_ptr.h:103:11, address = prog[0x0000000000001460], unresolved, hit count = 0 
    

    This is reasonably easy and precise if you know where the method is implemented. I don't know why it spews those "has no decl" warnings, but it's not unusual for LLDB to do that sort of thing.

    Note: b and breakpoint are different commands!

    5. Break in a callee

    With any of these methods, you can instead put a breakpoint in one of the destructors defined in your own code:

    (lldb) breakpoint set --method ~Test
    Breakpoint 1: where = prog`Test::~Test() + 12 at prog.cc:5:25, address = 0x0000000000001e2c
    (lldb) run
    Process 588466 launched: '/home/scott/wrk/learn/lldb/break-in-shared-ptr-dtor/prog' (x86_64)
    in ~Test2
    Process 588466 stopped
    * thread #1, name = 'prog', stop reason = breakpoint 1.1
        frame #0: 0x0000555555555e2c prog`Test::~Test(this=0x000055555556cec0) at prog.cc:5:25
       2    #include <memory>
       3   
       4    struct Test {
    -> 5        ~Test() { std::cout << "in ~Test\n"; }
       6    };
       7   
       8    struct Test2 {
    (lldb)  symbol table for <Unknown>.....K       [weird msg, I hit Ctrl-C]
    error: No auto repeat.
    (lldb) bt
    * thread #1, name = 'prog', stop reason = breakpoint 1.1
      * frame #0: 0x0000555555555e2c prog`Test::~Test(this=0x000055555556cec0) at prog.cc:5:25
        frame #1: 0x0000555555555e19 prog`void __gnu_cxx::new_allocator<Test>::destroy<Test>(this=0x000055555556cec0, __p=0x000055555556cec0) at new_allocator.h:153:10
        frame #2: 0x0000555555555dbd prog`void std::allocator_traits<std::allocator<Test>>::destroy<Test>(__a=0x000055555556cec0, __p=0x000055555556cec0) at alloc_traits.h:497:8
        frame #3: 0x0000555555555bd6 prog`std::_Sp_counted_ptr_inplace<Test, std::allocator<Test>, (__gnu_cxx::_Lock_policy)2>::_M_dispose(this=0x000055555556ceb0) at shared_ptr_base.h:557:2
        frame #4: 0x0000555555555503 prog`std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release(this=0x000055555556ceb0) at shared_ptr_base.h:155:6
        frame #5: 0x00005555555554ba prog`std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count(this=0x00007fffffffe4e0) at shared_ptr_base.h:730:11
        frame #6: 0x0000555555555579 prog`std::__shared_ptr<Test, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr(this=0x00007fffffffe4d8) at shared_ptr_base.h:1169:31
        frame #7: 0x0000555555555465 prog`std::shared_ptr<Test>::~shared_ptr(this=nullptr) at shared_ptr.h:103:11
        frame #8: 0x000055555555524f prog`main at prog.cc:16:1
        frame #9: 0x00007ffff7a9c0b3 libc.so.6`__libc_start_main + 243
        frame #10: 0x000055555555514e prog`_start + 46
    

    Maybe stopping in a callee suffices, or maybe seeing the desired symbol and its file name in the backtrace helps accomplish the goal.

    Documentation link

    I got most of this info by reading the Breakpoint commands section of the GDB to LLDB command map, plus experimentation.