尧图精选

MongoDB/WiredTiger 自定义 GDB 调试脚本体系:从 load_gdb_scripts.py 自动加载到危险指针、插入列表转储

🕒 发布时间:2026/9/17 4:08:38 📁 来源:尧图网络
MongoDB/WiredTiger 自定义 GDB 调试脚本体系从 load_gdb_scripts.py 自动加载到危险指针、插入列表转储【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo本文基于 WiredTiger 存储引擎仓库中的src/third_party/wiredtiger/tools/gdb/README.md展开介绍 MongoDB 内置 WiredTiger 调试用的自定义 GDB 脚本体系脚本目录组织方式、通过source手动加载与-DENABLE_SHARED1共享库自动加载的两种机制、hazard pointer 与 insert list 等实际调试命令的实现原理以及按官方推荐模式编写并注册新 GDB 脚本的完整流程。读完本文你可以直接在 mongod/WiredTiger 调试会话中使用这些脚本转储危险指针、跳过列表skiplist和行存内部页键并能独立扩展新的调试命令。体系总览目录职责与加载器定位该 GDB 脚本体系位于 tools/gdb目录职责划分非常明确见 README顶层目录只保留加载器脚本 load_gdb_scripts.py各个具体调试脚本统一放在gdb_scripts/子目录中当前包含hazard_pointers.pyPython 脚本转储/查找危险指针hazard pointerdump_insert_list.pyPython 脚本遍历 WT_INSERT_HEAD 跳过列表dump_row_int.gdbGDB 命令文件非 Python转储行存内部页row store internal page的键。load_gdb_scripts.py 本身并不实现调试功能它是一个入口聚合器先把脚本所在目录追加进 Python 路径再导入各 Python 脚本模块最后用gdb.execute(source ...)加载 GDB 命令文件。其核心逻辑集中在 load_gdb_scripts.py#L46-L55# Add the build directory to our path so we can import from the gdb_scripts subfolder. build_dir os.path.dirname(__file__) sys.path.append(build_dir) # Load gdb scripts written in python. import gdb_scripts.hazard_pointers import gdb_scripts.dump_insert_list # load gdb scripts written in scheme. gdb.execute(fsource {build_dir}/gdb_scripts/dump_row_int.gdb)可以看到新增脚本时必须修改加载器这一步README 明确要求正是体现在这里Python 脚本需要importGDB 命令文件需要gdb.execute(source ...)。两种加载方式手动 source 与共享库自动加载方式一在 GDB 中手动加载README 给出的基本用法只有一行在 GDB 会话内执行source /path/to/load_gdb_scripts.py由于加载器内部使用os.path.dirname(__file__)动态解析自身路径脚本无需改任何路径即可在任意目录下 source前提是gdb_scripts/子目录与加载器同级。方式二-DENABLE_SHARED1 编译后自动加载如果以-DENABLE_SHARED1编译 WiredTiger打开 GDB 时脚本会被自动加载无需手动 source。这个机制利用的是 GDB 官方约定的objfile-gdb.py自动加载规则GDB 在加载某个可执行文件或共享库时如果发现同目录下存在与库同名、后缀为-gdb.py的脚本就会自动执行它。这一点在 load_gdb_scripts.py#L31-L38 的注释中有完整说明# gdb can also auto-run this script if it is located in the same folder # as the .so file and has an identical name with -gdb.py appended. For example: # build/ # libwiredtiger.so.11.2.0 # libwiredtiger.so.11.2.0-gdb.py // this script自动加载的工程化落点由 CMake 函数完成见 gdb_autoloader_setup.cmake。其逻辑L3-L21为当WT_LINUX且ENABLE_SHARED同时满足时构建两个自定义目标copy-autoload-script把load_gdb_scripts.py复制进构建目录并重命名为libwiredtiger.so.${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-gdb.py使名称恰好满足 GDB 自动加载的命名约定copy-runtime-files把整个gdb_scripts/目录复制到构建目录保证加载器在构建目录内也能找到子脚本否则打印 NOT setting up gdb script auto-loading。注释同时说明了限制自动加载要求产出WiredTiger.so动态库且当前假定只有 Linux 主机上有 gdb因此在非 Linux 或未开ENABLE_SHARED的构建中该功能不生效——此时只能使用手动source方式。适用前提归纳自动加载仅对 Linux -DENABLE_SHARED1的构建成立静态库/其他平台构建下请在 GDB 中手动 source 加载器。现有脚本逐一解析hazard_pointers.py跨线程定位危险指针持有者load_gdb_scripts.py 注册的第一个 Python 脚本 hazard_pointers.py 解决一个典型的并发诊断问题哪些线程/会话正持有所需页上的 hazard pointer这直接决定页能否被淘汰、锁能否推进。它的核心辅助函数find_sessions()L41-L62实现思路颇有巧思记录当前 frame遍历gdb.inferiors()[0]的所有线程并逐个thread.switch()在每条线程内从gdb.newest_frame()开始向上逐帧回溯每帧执行info arg session一旦某帧的函数参数中出现session 0x...就记下该线程号与WT_SESSION_IMPL地址并停止全部线程处理完后恢复原始 frame返回(线程, session 指针)列表。基于此注册了两个 GDB 命令均为gdb.COMMAND_DATAdump_hazard_pointersL66-L80对每个 active 会话按session[hazards][inuse]遍历session[hazards][arr]数组打印所有ref ! 0的 hazard slot。直接输出的是 WiredTiger 会话结构体WT_SESSION_IMPL的真实字段因此字段名与源码定义严格一致。find_hazard_pointers_for 0x地址L84-L104反向查询——给定一个ref地址列出所有持有指向该地址 hazard pointer 的线程与会话。它先校验参数必须是单个0x开头的十六进制值否则打印用法示例find_hazard_pointers_for 0xffff8c5792a0。两个命令类在文件末尾L107-L108通过调用构造函数完成向 GDB 注册——这正是 README 中新 Python 脚本推荐的写法模式见后文创建新脚本一节。dump_insert_list.py遍历 WT_INSERT_HEAD 跳过列表并落盘dump_insert_list.py 用于转储行存叶子页的插入列表skiplist。文件头注释L29-L38给出用法与约束# It takes a WT_INSERT_HEAD structure and a key format. In order to use it # in gdb it must first be sourced. Example usage: # source dump_insert_list.py # dump_insert_list WT_INSERT_HEAD,S # # It supports 3 key formats: u, i, S.实现上有几个值得注意的工程细节只走最底层walk_level()L76-L87固定以level 0遍历head[0]链表因为底层覆盖了全部节点同时为每个节点解析其next[0..9]各层指针遇到0x0即该节点的最高层完整还原该节点的跳过列表高度信息。键的变长定位get_key()不直接读WT_INSERT结构体内的键而是依据insert[u][key]中的offset/size用gdb.selected_inferior().read_memory(int(insert) offset, size)从变长存储区WT_VAR_SIZE 布局读原始字节——这是 WiredTiger 页内变长字段的典型内存布局。键格式解码decode_key()对S字符串格式做key.decode()u可变字节串与i整型格式保留原始值非法格式会打印错误并进入usage()。输出落盘由于插入列表内容往往超出终端容量结果写入工作目录的dump.txt而非直接打印L89-L100每行格式为Key: ... Address: ... Next array: [...]。dump_row_int.gdb用 GDB 命令文件转储内部页键dump_row_int.gdb 演示了 README 提到的另一类脚本形态——纯 GDB 命令文件用define定义命令命令为dump_row_int_keys page遍历page-u-intl-__index.entries个索引项并逐个打印键。其核心是模拟源码中对__WT_PAGE_INDEX键指针的解码逻辑脚本 L20-L27 用注释引用了对应的 C 宏取ikey指针值v若最低位WT_IK_FLAG0x01置位说明是内联键键实体位于页面磁盘镜像中key_offset (v 0xFFFFFFFF) 1size v 32对应WT_IK_DECODE_KEY_OFFSET/WT_IK_DECODE_KEY_LEN最终地址为(uint8_t *)page-dsk key_offset打印前缀DSK:否则是内存中的 WT_IKEY数据指针位于结构体偏移 8 处$keyp ikey 8长度取WT_IKEY-size打印前缀MEM:最后逐字节printf %c输出键内容并对索引号/长度做了简单的列对齐填充。这个脚本的价值在于它把源码中索引键到底指向磁盘页还是内存副本的分支逻辑原样翻译成 GDB 条件表达式调试时可以直观看到每个子页边界键的物理位置DSK:或MEM:。wt_debug_script_update.py数据句柄与页结构的深度转储工具gdb_scripts/wt_debug_script_update.py 是一个尚未被加载器自动纳入的辅助脚本文件内注释FIXME-WT-9286标明其后续计划改写为gdb.Command风格适合按需单独source。它能做的事包括get_data_handle(conn, file:xxx.wt, [checkpoint_name])遍历conn[dhqh]数据句柄队列TAILQ按名称与可选的 checkpoint 名如WiredTigerCheckpoint.5匹配目标句柄dump_handle(dh)从 btree root 页开始递归转储内部页/叶子页对叶子页同时输出磁盘镜像跳过WT_PAGE_HEADER_SIZE28 字节 WT_BLOCK_HEADER_SIZE12 字节头后的原始字节修改信息modify行存叶子页的 insert 列表u2.row_leaf.insert与每个条目的 update 链u2.row_leaf.update[i]update 链转储时会读取WT_UPDATE的size/data当类型为 3 时用bson.decode_all把值解析成文档并打印UUID 子类型 4 会进一步转成 UUID 文本TOMBSTONE/RESERVEsize 为 0条目会跳过内存读取以避免读到未初始化指针。文件头给出的示例用法L33-L40# To dump data from the original file handle. # coll_dh get_data_handle(conn, file:collection-5550--7194480883124807592.wt, NULL) # dump_handle(coll_dh)创建新的 GDB 脚本官方推荐模式README 的 Creating new scripts 一节给出了扩展规范结合源码可以完整复述为三步第一步选择脚本形态。两种形态均可Python 脚本推荐模仿 hazard_pointers.py 的模式——每个命令对应一个继承gdb.Command的类在__init__中调用super().__init__(命令名, gdb.COMMAND_DATA)注册命令类别在invoke(self, arg, from_tty)中实现逻辑GDB 命令文件.gdb用define 命令名 ... end编写适合纯内存表达式操作、不想依赖 Python 运行时的场景参考 dump_row_int.gdb。第二步在文件末尾注册命令。Python 脚本必须在文件底部显式调用构造函数完成注册如 hazard_pointers.py#L106-L108 的dump_hazard_pointers()/find_hazard_pointer_for()以及 dump_insert_list.py#L102-L103 的dump_insert_list()——仅import模块并不会产生 GDB 命令这一步最容易被遗漏。第三步更新加载器。无论哪种形态都要在 load_gdb_scripts.py 中登记Python 脚本加一行import gdb_scripts.模块名GDB 命令文件加一行gdb.execute(fsource {build_dir}/gdb_scripts/文件名)。这样手动 source 与共享库自动加载两条路径才都能覆盖到新脚本。一个可参考的完整最小骨架按 hazard_pointers.py 模式提炼import gdb class dump_my_struct(gdb.Command): def __init__(self): super(dump_my_struct, self).__init__(dump_my_struct, gdb.COMMAND_DATA) def invoke(self, arg, from_tty): # 例如gdb.parse_and_eval(arg).reinterpret_cast( # gdb.lookup_type(WT_SESSION_IMPL).pointer()).dereference() ... # 注册source 本文件后立即生效 dump_my_struct()在 MongoDB 侧的延伸应用这套 WiredTiger GDB 工具并非孤立存在MongoDB 仓库自身的调试工具目录 buildscripts/gdb 中wt_dump_table.py 沿用了完全相同的技术路线——通过gdb.lookup_type(WT_CONNECTION_IMPL)查找类型、gdb.parse_and_eval(session-iface-connection)拿到连接对象再按数据句柄名如collection-2--4547167393143767234形式对应file:ident.wt转储整棵页树并对 update 链做 BSON 解码输出见 wt_dump_table.py#L13-L27 的用法注释。同目录下的 mongo.py、mongo_printers.py 等则面向 mongod 整体调试。可以推断WiredTiger 侧的脚本hazard pointer 查询、insert list 转储与 MongoDB 侧的wt_dump_table互补前者深入单个结构/页的底层布局后者面向按集合名一键转储的粗粒度排障。实际调试 mongod 崩溃或页损坏时两者配合使用可以覆盖从连接→数据句柄→btree→页→update 链的完整链路。小结WiredTiger 的 GDB 脚本体系以 README 描述的最小结构为基础顶层加载器load_gdb_scripts.pygdb_scripts/子目录配合 CMake 在 Linux -DENABLE_SHARED1构建中完成的libwiredtiger.so.*-gdb.py重命名复制实现了打开 GDB 即用的自动加载脚本层面则同时示范了 Pythongdb.Command子类 末尾注册与 GDB 命令文件define两种形态并用 hazard pointer 全线程扫描、WT_INSERT_HEAD 跳过列表转储、内部页内联/内存键解码等真实场景证明了这套体系的实战价值。扩展新命令时遵循建类→末尾注册→登记加载器三步即可无缝接入手动与自动两条加载路径。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →