How to set a breakpoint in GDB where the function returns?

后端 未结 7 1321
孤独总比滥情好
孤独总比滥情好 2020-11-27 06:10

I have a C++ function which has many return statements at various places. How to set a breakpoint at the return statement where the function actually returns ?

And

相关标签:
7条回答
  • You can use reverse debugging to find out where function actually returns. Finish executing current frame, do reverse-step and then you should stop at just returned statement.

    (gdb) record
    (gdb) fin
    (gdb) reverse-step
    
    0 讨论(0)
  • 2020-11-27 06:43

    Break without argument sets a breakpoint at the current line.

    There is no way for a single breakpoint to catch all return paths. Either set a breakpoint at the caller immediately after it returns, or break at all return statements.

    Since this is C++, I suppose you could create a local sentry object, and break on its destructor, though.

    0 讨论(0)
  • 2020-11-27 06:43

    If you can change the source code, you might use some dirty trick with the preprocessor:

    void on_return() {
    
    }
    
    #define return return on_return(), /* If the function has a return value != void */
    #define return return on_return()  /* If the function has a return value == void */
    
    /* <<<-- Insert your function here -->>> */
    
    #undef return
    

    Then set a breakpoint to on_return and go one frame up.

    Attention: This will not work, if a function does not return via a return statement. So ensure, that it's last line is a return.

    Example (shamelessly copied from C code, but will work also in C++):

    #include <stdio.h>
    
    /* Dummy function to place the breakpoint */
    void on_return(void) {
    
    }
    
    #define return return on_return()
    void myfun1(int a) {
        if (a > 10) return;
        printf("<10\n");
        return;   
    }
    #undef return
    
    #define return return on_return(),
    int myfun2(int a) {
        if (a < 0) return -1;
        if (a > 0) return 1;
        return 0;
    }
    #undef return
    
    
    int main(void)
    {
        myfun1(1);
        myfun2(2);
    }
    

    The first macro will change

    return;
    

    to

    return on_return();
    

    Which is valid, since on_return also returns void.

    The second macro will change

    return -1;
    

    to

    return on_return(), -1;
    

    Which will call on_return() and then return -1 (thanks to the ,-operator).

    This is a very dirty trick, but despite using backwards-stepping, it will work in multi-threaded environments and inlined functions, too.

    0 讨论(0)
  • 2020-11-27 06:49

    Break on all retq of current function

    This Python command puts a breakpoint on every retq instruction of the current function:

    class BreakReturn(gdb.Command):
        def __init__(self):
            super().__init__(
                'break-return',
                gdb.COMMAND_RUNNING,
                gdb.COMPLETE_NONE,
                False
            )
        def invoke(self, arg, from_tty):
            frame = gdb.selected_frame()
            # TODO make this work if there is no debugging information, where .block() fails.
            block = frame.block()
            # Find the function block in case we are in an inner block.
            while block:
                if block.function:
                    break
                block = block.superblock
            start = block.start
            end = block.end
            arch = frame.architecture()
            pc = gdb.selected_frame().pc()
            instructions = arch.disassemble(start, end - 1)
            for instruction in instructions:
                if instruction['asm'].startswith('retq '):
                    gdb.Breakpoint('*{}'.format(instruction['addr']))
    BreakReturn()
    

    Source it with:

    source gdb.py
    

    and use the command as:

    break-return
    continue
    

    You should now be at retq.

    Step until retq

    Just for fun, another implementation that stops when a retq is found (less efficient of because no hardware support):

    class ContinueReturn(gdb.Command):
        def __init__(self):
            super().__init__(
                'continue-return',
                gdb.COMMAND_RUNNING,
                gdb.COMPLETE_NONE,
                False
            )
        def invoke(self, arg, from_tty):
            thread = gdb.inferiors()[0].threads()[0]
            while thread.is_valid():
                gdb.execute('ni', to_string=True)
                frame = gdb.selected_frame()
                arch = frame.architecture()
                pc = gdb.selected_frame().pc()
                instruction = arch.disassemble(pc)[0]['asm']
                if instruction.startswith('retq '):
                    break
    ContinueReturn()
    

    This will ignore your other breakpoints. TODO: can be avoided?

    Not sure if it is faster or slower than reverse-step.

    A version that stops at a given opcode can be found at: https://stackoverflow.com/a/31249378/895245

    0 讨论(0)
  • 2020-11-27 06:56

    break without arguments stops execution at the next instruction in the currently selected stack frame. You select strack frames via the frame or up and down commands. If you want to debug the point where you are actually leaving the current function, select the next outer frame and break there.

    0 讨论(0)
  • 2020-11-27 06:57

    Contrary to answers so far, most compilers will create a single return assembly instruction, regardless of how many return statements are in the function (it is convenient for the compiler to do that, so there is only a single place to perform all the stack frame cleanup).

    If you wanted to stop on that instruction, all you have to do is disas and look for retq (or whatever the return instruction for your processor is), and set a breakpoint on it. For example:

    int foo(int x)
    {
      switch(x) {
       case 1: return 2;
       case 2: return 3;
       default: return 42;
      }
    }
    
    int main()
    {
      return foo(0);
    }
    
    
    (gdb) disas foo
    Dump of assembler code for function foo:
       0x0000000000400448 <+0>: push   %rbp
       0x0000000000400449 <+1>: mov    %rsp,%rbp
       0x000000000040044c <+4>: mov    %edi,-0x4(%rbp)
       0x000000000040044f <+7>: mov    -0x4(%rbp),%eax
       0x0000000000400452 <+10>:    mov    %eax,-0xc(%rbp)
       0x0000000000400455 <+13>:    cmpl   $0x1,-0xc(%rbp)
       0x0000000000400459 <+17>:    je     0x400463 <foo+27>
       0x000000000040045b <+19>:    cmpl   $0x2,-0xc(%rbp)
       0x000000000040045f <+23>:    je     0x40046c <foo+36>
       0x0000000000400461 <+25>:    jmp    0x400475 <foo+45>
       0x0000000000400463 <+27>:    movl   $0x2,-0x8(%rbp)
       0x000000000040046a <+34>:    jmp    0x40047c <foo+52>
       0x000000000040046c <+36>:    movl   $0x3,-0x8(%rbp)
       0x0000000000400473 <+43>:    jmp    0x40047c <foo+52>
       0x0000000000400475 <+45>:    movl   $0x2a,-0x8(%rbp)
       0x000000000040047c <+52>:    mov    -0x8(%rbp),%eax
       0x000000000040047f <+55>:    leaveq 
       0x0000000000400480 <+56>:    retq   
    End of assembler dump.
    (gdb) b *0x0000000000400480
    Breakpoint 1 at 0x400480
    (gdb) r
    
    Breakpoint 1, 0x0000000000400480 in foo ()
    (gdb) p $rax
    $1 = 42
    
    0 讨论(0)
提交回复
热议问题