I’d been meaning to explore the GDB Python API for some time when I saw an interesting tweet that posed a problem I thought it could solve.

The poster was looking for a tool to draw “ASCII art” of the state of the stack whenever it changed during program execution. This should be doable if gdb can supply two critical features:

  1. Break on stack changes
  2. Describe frame contents

Stack Change Breakpoint

This turns out to be a matter of setting a watchpoint (i.e. a data breakpoint) on the stack pointer rsp:

(gdb) watch $rsp
Watchpoint 2: $rsp
(gdb) continue

Unfortunately rsp changes constantly, so restricting these breakpoints to the code you really care about is essential. I found two techniques to be most useful:

Enable/Disable

You can use a pair of breakpoints to enable (or disable!) other breakpoints within a region by attaching commands, like this:

(gdb) watch foo
Hardware watchpoint 2: foo
(gdb) disable 2
(gdb) b main.cpp:28
Breakpoint 3 at 0x55555555468e: file main.cpp, line 28
(gdb) commands 3
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>enable 2
>c
>end

That enables the watchpoint starting at main.cpp:28. You can add the reverse (disable) to mark the end of the range of interest, if desired.

Conditions

It also helped to make the rsp watchpoint conditional on the function of interest, to avoid getting frames from deep inside library functions. gdb will tell you the current function when you print the value of $rip:

(gdb) p $rip
$2 = (void (*)(void)) 0x555555554678 <main()+8>

We can use this value in convenience functions supplied for our use by gdb. These are not part of the Python API but can be used in gdb CLI expressions. I used two to get the printed representation of rip and then check it against a regex representing my target function:

(gdb) watch $rsp if $_regex($_as_string($rip), ".* <main")

Because it uses the instruction pointer to distinguish among functions, we will still see some inlined function calls to other code, but it’s still a big improvement.

Displaying Frame Contents

Next we need to use the Python API to create a command that displays stack frames attractively. Our gateway is the gdb.Frame class and the gdb.newest_frame() method, from which we can access a lot of other information describing the current function, code block, register values, and stack state.

One of the most important pieces of information is the current value of the frame pointer rbp. This register - assuming the code was compiled with -fno-omit-frame-pointer - helps us locate the saved return address and stack pointer from the prior frame. That’s because the first two instructions in the function will be:

push   %rbp
mov    %rsp,%rbp

and rbp will be untouched from that point. That produces a stack layout like this (grows downward):

saved rip  i.e., the return address from callq
saved rbp <- rbp points here
 
other saved registers
 
 
locals
  <- top of stack (rsp points here)


Function arguments - if present and not in registers - will appear in the previous stack frame, above the return address. So our picture of the frame extends from the argument with the highest address, through the saved return address and rbp, to the current rsp. If we record the locations of the arguments and locals for the current function, along with their sizes, we can mark the stack locations accordingly. My implementation of this approach is here.

Creating a gdb Command

All that remains is to make this accessible from the gdb command line and hook it into our rsp watchpoint. gdb docs describe this in detail but the main idea is to subclass gdb.Command and provide a custom invoke method that does the work. Mine looks basically like this:

class PrintFrame (gdb.Command):
    """Display the stack memory layout for the current frame"""

    def __init__ (self):
        super (PrintFrame, self).__init__ ("pframe", gdb.COMMAND_STACK)

    def invoke (self, arg, from_tty):
        print(FramePrinter(gdb.newest_frame()))   # call my code

PrintFrame()

That registers a new command called pframe we can invoke when the watchpoint is hit:

(gdb) source ../gdb_util.py
(gdb) commands 2
Type commands for breakpoint(s) 2, one per line.
End with a line saying just "end".
>pframe
>end

Now every time the stack pointer changes we get a nice display of the stack frame. It’s not exactly “ASCII art” but I think it’s pretty informative:

Result

pframe output

This example from my repo shows one dword argument s and one 3-dword local variable bar on the stack, along with the standard frame contents and some unknown storage (probably for temporaries and out of scope locals).

Summary

With the power of gdb’s Python API, plus some tricks, we can produce a dynamic display of the current stack frame for debugging purposes.