打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Displaying Stack Frames in gdb with Python

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 $rspWatchpoint 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 fooHardware watchpoint 2: foo(gdb) disable 2(gdb) b main.cpp:28Breakpoint 3 at 0x55555555468e: file main.cpp, line 28(gdb) commands 3Type 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   %rbpmov    %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 codePrintFrame()

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

(gdb) source ../gdb_util.py(gdb) commands 2Type 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

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.

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Linux环境下的C/C+基础调试技术2——程序控制
利用gdb在汇编指令级调试C程序
GDB调试之暂停
用PDB库调试Python程序
QT线程引发Backtrace stopped: previous frame identical to this frame (corrupt stack?)
Kernel DebuggingTricks
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服