使用Python扩展gdb

gdb是功能强大、最常使用的调试工具之一。对于绝大多数任务,它都能胜任,并且工作的很好。
对于个性化的需求,gdb提供了十分便捷的python api,结合python简洁的语法,我们可以高效地实现我们的想法。

gdb提供的设施

gdb对扩展api进行了抽象,提供如下模块接口:

抽象模块 详细信息
Basic Python Basic Python Functions
Exception Handling How Python exceptions are translated
Values From Inferior Python representation of values
Types In Python Python representation of types
Pretty Printing API Pretty-printing values
Selecting Pretty-Printers How GDB chooses a pretty-printer
Writing a Pretty-Printer Writing a Pretty-Printer
Type Printing API Pretty-printing types
Frame Filter API Filtering Frames
Frame Decorator API Decorating Frames
Writing a Frame Filter Writing a Frame Filter
Unwinding Frames in Python Writing frame unwinder
Xmethods In Python Adding and replacing methods of C++ classes
Xmethod API Xmethod types
Writing an Xmethod Writing an xmethod
Inferiors In Python Python representation of inferiors (processes)
Events In Python Listening for events from GDB
Threads In Python Accessing inferior threads from Python
Recordings In Python Accessing recordings from Python
Commands In Python Implementing new commands in Python
Parameters In Python Adding new GDB parameters
Functions In Python Writing new convenience functions
Progspaces In Python Program spaces
Objfiles In Python Object files
Frames In Python Accessing inferior stack frames from Python
Blocks In Python Accessing blocks from Python
Symbols In Python Python representation of symbols
Symbol Tables In Python Python representation of symbol tables
Line Tables In Python Python representation of line tables
Breakpoints In Python Manipulating breakpoints using Python
Finish Breakpoints in Python Setting Breakpoints on function return using Python
Lazy Strings In Python Python representation of lazy strings
Architectures In Python Python representation of architectures

我们使用Commands模块接口实现一个新的gdb命令

前提

gdb编译时必须使用了”–with-python”选项。可以启动gdb后,使用“show configuration”查看是否启用了该选项。

新gdb命令

用途

gdb的“x”命令可以用来查看一定范围的内存信息

1
2
3
4
5
(gdb) x/32xb 0x555555557d90
0x555555557d90 <_ZTV1A+16>: 0x84 0x52 0x55 0x55 0x55 0x55 0x00 0x00
0x555555557d98 <_ZTV1A+24>: 0x9e 0x52 0x55 0x55 0x55 0x55 0x00 0x00
0x555555557da0 <_ZTV1A+32>: 0xce 0x51 0x55 0x55 0x55 0x55 0x00 0x00
0x555555557da8 <_ZTV1A+40>: 0xda 0x51 0x55 0x55 0x55 0x55 0x00 0x00

上面的命令是把内存地址0x555555557d90后连续32字节的数据打印出来。结果的左侧是
内存地址,“<_ZTV1A+16>”是指_ZTV1A这个符号地址 + 16字节 = 0x555555557d90。由于该结果是在x86平台得到的,CPU是小端模式,所以0x555555557d90内保存的数据是0x555555555284。

在gdb执行“info symbol”命令

1
2
(gdb) info symbol 0x555555555284
A::~A() in section .text of /tmp/a.out

即,0x555555555284就是A::~A()函数的地址

我们希望,能将上述两个步骤(x和info symbol命令)结合到一起。

checksymbol命令

首先应该“import gdb”导入gdb模块。如果你在普通python会话下使用这一语句,会提示“找不到gdb模块”,即使你将gdb安装目录下的python gdb模块路径加入到PYTHONPATH,仍然会提示“找不到_gdb模块”。gdb启动时会将一个C模块加载到python解释器,所以“import gdb”只能在gdb会话中使用。

参考gdb官方手册 https://sourceware.org/gdb/onlinedocs/gdb/Commands-In-Python.html
,我们需要继承gdb.Command类,然后重写几个关键接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import gdb

class CheckSymbol(gdb.Command):
def __init__(self):
# 调用父类构造函数,第一个参数就是在gdb下使用的命令名,第二个参数指定该命令属于哪一类gdb命令
super(self.__class__, self).__init__('checksymbol', gdb.COMMAND_USER)

# 当在gdb下输入命令并回车就会调用该方法,第一个参数是字符串,该命令后面输入的所有参数
def invoke(self, para_str, from_tty):
# gdb内部函数,根据空格将参数字符串分组
paras = gdb.string_to_argv(para_str)
if len(paras) != 2:
print('need 2 para, address length')
return

addr = paras[0]
_len = paras[1]

# gdb内部函数,执行gdb命令,如果to_string为True则将命令输出返回,否则输出到gdb控制台
raw_out = gdb.execute('x/{}xb {}'.format(_len, addr), to_string=True)
for line in raw_out.split('\n'):
if not line:
continue
one_line = line.split('\t')
addr_detail = one_line[0]
# 由于小端模式,将数据倒序
symbol_addr = '0x' + ''.join([i.replace('0x', '') for i in one_line[-1:0:-1]])
# gdb内部函数,查找地址对应的符号
symbol_detail = gdb.execute('info symbol ' + symbol_addr, to_string=True).rstrip('\n')
# 将数据输出到gdb控制台
print(addr_detail, '\t', symbol_addr, '\t', symbol_detail)

# 向gdb注册该命令
CheckSymbol()

以上我们就实现了checksymbol命令。

首先启动gdb,在gdb控制台source python文件

1
2
3
4
5
6
(gdb) source ~/checksymbol.py
(gdb) checksymbol 0x555555557d90 32
0x555555557d90 <_ZTV1A+16>: 0x0000555555555284 A::~A()
0x555555557d98 <_ZTV1A+24>: 0x000055555555529e A::~A()
0x555555557da0 <_ZTV1A+32>: 0x00005555555551ce A::test()
0x555555557da8 <_ZTV1A+40>: 0x00005555555551da A::fa()

对比上面x命令的结果,checksymbol命令的结果给出的符号信息,更有利于debug和研究学习。

目前checksymbol命令的参数不能是表达式,例如“checksymbol &a 32”。gdb也提供了内部函数来满足这种需求。

可以按如下修改

1
2
addr = gdb.parse_and_eval(paras[0])
_len = gdb.parse_and_eval(paras[1])

Ref