用 python gdb 客製化 backtrace 的結果
需求
想要瞭解模組之間函式呼叫的關係時, 與其一層層比對多個類別之間的呼叫關係, 不如直接在最後一個呼叫函式放中斷點, 直接顯示 backtrace。但是當函式裡有太多參數或 template 時, backtrace 的 frame 訊息會變得很長, 不易閱讀。我的目的只是找出呼叫的函式名稱、檔名和行數, 函式帶的參數反而是困擾。
作法一: 用 gdb.execute()
一個簡單的作法是截取 gdb 的輸出, 然後解析文字去掉不要的部份:
import gdb
import re
class ShorternBacktraceCommand(gdb.Command):
'''Show a backtrace without argument info in each frame.'''
def __init__(self):
super(ShorternBacktraceCommand, self).__init__ ("bt",
gdb.COMMAND_SUPPORT,
gdb.COMPLETE_NONE)
def invoke(self, arg, from_tty):
if not arg:
arg = ''
raw = gdb.execute("backtrace %s" % arg, True, True)
lines = raw.split('\n')
for i, line in enumerate(lines):
if not line:
continue
tokens = line.split()
# first line format: e.g., #0 A::hello (...) at a.cpp:8
# the rest : e.g., #2 0x0..0 in A::foo (...) at a.cpp:18
func_index = 1 if i == 0 else 3
print ('\033[1;33m%2s\033[m %s at %s'
'' % (tokens[0], tokens[func_index], tokens[-1]))
ShorternBacktraceCommand()
Btw, 上面的作法還順便幫行首的標號上色。
但是, 使用 cgdb 時會無法運作, 理由是 cgdb 使用 GDB MI, gdb.execute('backtrace') 的結果不是原本看到的格式, 難以解析。
作法二: 用 gdb.Frame() API
只好改用中規中矩的方式逐一讀取 frame, 取出需要的資訊:
import gdb
class ShorternBacktraceCommand(gdb.Command):
'''Show a backtrace without argument info in each frame.'''
def __init__(self):
super(ShorternBacktraceCommand, self).__init__ ("bt",
gdb.COMMAND_SUPPORT,
gdb.COMPLETE_NONE)
def invoke(self, arg, from_tty):
num = 0;
try:
num = int(arg)
except Exception, e:
pass
lines = []
f = gdb.newest_frame()
fn = 0
while f is not None:
symtab_and_line = gdb.Frame.find_sal(f)
frame_name = gdb.Frame.name(f)
if frame_name:
args = [
fn,
frame_name,
symtab_and_line.symtab.filename,
symtab_and_line.line,
]
else:
args = [fn, '??', 'unknown', 0]
lines.append('#%2d %s at %s:%s' % tuple(args))
f = gdb.Frame.older(f)
fn += 1
if num > 0:
lines = lines[:num]
elif num < 0:
lines = lines[len(lines) + num:]
for line in lines:
print line
ShorternBacktraceCommand()
將上面的 script 存到 /path/to/gdb/scripts/backtrace.py, 接著在 $HOME/.gdbinit 裡加入以下設定:
python
sys.path.insert(0, '/path/to/gdb/scripts')
import backtrace
end
之後就能用 bt 顯示精簡後的 backtrace 了, 也方便手動複製貼上到筆記裡。以下是一個輸出例子:
(gdb) bt
# 0 A::hello at a.cpp:8
# 1 A::bar at a.cpp:13
# 2 A::foo at a.cpp:18
# 3 main at a.cpp:25
Btw, 若是需求比較簡單, 可以試看看 Print Settings, 有些選項可以改變 backtrace 顯示的訊息。
參考資料:
- The Cliffs of Inanity › 2. Writing a new gdb command
- Pruning backtrace output with gdb script - Stack Overflow
- Frames In Python - Debugging with GDB
- Symbols In Python - Debugging with GDB
- Symbol Tables In Python - Debugging with GDB