GDB分析core dump时如何查看Python扩展中NamedTuple对象属性
假设定义了如下Python类:
class RawMessage(NamedTuple): data: Dict timestamp: float ...
该类的data字段为字典类型,其键和值大概率均为整数,但无法对此作出绝对确定的判断。
当前持有一份core dump核心转储文件,无关联的活动运行进程,在GDB中执行py-locals命令可查看到如下局部变量信息:
(gdb) py-locals ... msg = <RawMessage at remote 0x7fc91ea90ee0> ...
目前仅能获取到msg对象的内存地址,无法查看对象内部实际存储的内容,期望实现与Python环境中执行print(msg.data)等效的效果,直接读取消息内容,但当前仅能使用GDB进行操作,无可用的活跃Python运行环境。
已尝试执行如下命令,均未生效:
(gdb) py-print msg.data 'msg.data' not found (gdb) set $rawmessage = (PyObject*)0x7fc91ea90ee0 (gdb) p $rawmessage->data There is no member named data. (gdb) py msg Traceback (most recent call last): File "<string>", line 1, in <module> NameError: name 'msg' is not defined Error while executing Python code.
需要找到访问该对象内部信息的方式,优先支持运行简单Python代码查看内容的方案。
之前命令失效的核心原因有两个:一是py-locals输出的变量仅为调试扩展的打印结果,不会自动注入到py命令的执行上下文;二是NamedTuple本质是Python层面的tuple子类,C层的PyObject结构体没有对应命名字段的成员,直接强转后取成员必然失败。
可通过以下两种方式实现读取:
方法1:通过GDB Python API构造对象代理(支持执行任意Python代码)
cpython的GDB调试扩展自带内存地址到Python对象代理的转换能力,不需要依赖活跃Python进程,操作步骤如下:
- 确认
py-locals能正常输出即代表cpython调试扩展已加载,无需额外操作 - 优先在当前栈帧直接读取变量,执行如下命令:
(gdb) py # 读取当前栈帧的msg变量 msg = gdb.selected_frame().read_var("msg") # 等价于原生Python环境下print(msg.data) print("msg.data内容:", msg['data']) # 读取timestamp可直接取msg['timestamp'] # 支持遍历字典所有键值 for k, v in msg['data'].items(): print(f"键:{k}, 值:{v}") end
如果栈帧切换导致read_var读取失败,直接用已知内存地址构造对象即可:
(gdb) py msg_addr = 0x7fc91ea90ee0 # 将地址强转为PyObject指针,调试扩展会自动识别为Python对象代理 msg = gdb.Value(msg_addr).cast(gdb.lookup_type("PyObject").pointer()).dereference() # NamedTuple字段按定义顺序存在tuple存储区,索引0对应data,索引1对应timestamp data_dict = msg[0] print("data字段内容:", data_dict) end
方法2:纯GDB命令手动解析内存结构(无Python扩展适配时使用)
NamedTuple的C层存储结构和普通tuple完全一致,元组元素存在结构体尾部的柔性数组中,可直接读取内存:
- 先将对象地址强转为
PyTupleObject指针,结构体内部的ob_item是指向元素指针数组的首地址,数组顺序和NamedTuple定义的字段顺序完全一致 - 执行如下命令读取data字段:
# 强转为PyTupleObject类型指针 (gdb) set $msg = (PyTupleObject*)0x7fc91ea90ee0 # 取数组第一个元素,即为data字段对应的PyDictObject指针 (gdb) set $data = (PyDictObject*)$msg->ob_item[0] # 调用扩展命令打印字典内容 (gdb) py-print $data
注意:如果GDB提示找不到
PyTupleObject/PyDictObject类型,说明当前core dump对应的Python调试符号未加载,先安装对应版本的Python debug符号包即可。
内容的提问来源于stack exchange,提问作者awy

