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

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

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


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

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.

Tags:

C++

Debugging

Gdb