ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Pwndbg 开发者笔记:多调试器架构、事件系统与缓存机制深度指南

Pwndbg 开发者笔记:多调试器架构、事件系统与缓存机制深度指南 Pwndbg 开发者笔记多调试器架构、事件系统与缓存机制深度指南【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg本篇指南以 docs/contributing/dev-notes.md 为骨架展开面向想要为 Pwndbg 贡献代码、或者希望深入理解其内部架构的开发者。读完本文你将掌握 Pwndbg 的模块边界划分规则Debugger API 与aglib的分层、事件驱动与缓存机制的工作原理、异常调试手段以及一份代码同时运行在 GDB 与 LLDB 之上背后的设计约束。一、开发者日常在 GDB / LLDB 中直接与 Pwndbg 交互Pwndbg 内部高度依赖 Python开发者可以随时在调试器会话中直接调用 Pwndbg 的函数做实验在 GDB 中使用pi启动 Python 解释器或使用py python 代码单行执行在 LLDB 中输入script进入交互式 Python REPL注意需要先import pwndbg。这是验证函数行为、调试自己新写代码的最快路径。例如在 GDB 中执行py import pwndbg; print(pwndbg.aglib.proc.pid())即可获取当前被调试进程的 PID。二、模块边界铁律调试器相关代码不能越界Pwndbg 是多调试器项目因此对代码放置位置有严格约束不要从pwndbg.dbg实际模块为pwndbg/dbg_mod/之外的任何模块访问调试器专属功能——即任何使用gdb、lldb或gdblib模块的代码。使用aglib替代gdblib。gdblib正处于被移除的过程中对应 issue #2489两者接口近乎一致迁移通常只是把pwndbg.gdblib.X改成pwndbg.aglib.X。如果发现gdblib有而aglib缺失的功能应当开 issue 反馈。所有内存访问应通过 pwndbg/aglib/memory.py 中的函数完成进程属性通过 pwndbg/aglib/proc.py 获取例如pwndbg.aglib.proc.pid()需要pwndbg.dbg.Value实例的场景见 pwndbg/aglib/typeinfo.py后文专节说明。唯一的例外是某些命令只在单一调试器语境下才有意义例如强依赖 GDB 行为的命令这种命令可以直连调试器但必须明确标记为调试器专属并将加载行为正确地 gate 在对应调试器之后最好与 pwndbg/commands/ 下的其他命令分开存放。三、多调试器支持Debugger API接口 实现Pwndbg 对 GDB 与 LLDB 的双重支持主要靠位于 pwndbg/dbg_mod/ 的Debugger API实现。它定义了一组精简的调试原语供 Pwndbg 其余部分在其之上构建功能由两部分组成接口interface抽象类与类型勾勒出可供使用的功能形状实现implementations分别在 GDBpwndbg/dbg_mod/gdb/与 LLDBpwndbg/dbg_mod/lldb/之上实现这些接口。可以把它理解为调试器无关版的lldb/gdbPython 模块——相比两者它在精神上更接近lldb。3.1 入口与核心对象Debugger API 的入口是pwndbg.dbg大多数与进程相关的方法通过Process对象访问。除非你非常清楚自己在做什么否则一律使用pwndbg.dbg.selected_inferior()返回的对象。此外值得关注的是若当前没有存活进程selected_inferior()会抛出NoInferior异常见 pwndbg/dbg_mod/init.py断点/观察点句柄StopPoint支持上下文管理器with语句块结束时自动移除天然适合实现临时断点BreakpointLocation(address)与WatchpointLocation(address, size, watch_read, watch_write)分别表示断点与观察点的位置规格break_at()根据传入对象类型自动创建断点或观察点并支持stop_handler回调决定命中后是否停下、internal标记隐藏内部断点。3.2 GDB / LLDB / Debugger API 三栏对照原文档给出了同一诉求在三种 API 下的写法这里完整保留在地址处设置断点# GDB gdb.Breakpoint(*address) # LLDB lldb.target.BreakpointCreateByAddress(address) # Debugger API inf pwndbg.dbg.selected_inferior() inf.break_at(BreakpointLocation(address))查询符号地址# GDB int(gdb.lookup_symbol(name).value().address) # LLDB lldb.target.FindSymbols(name).GetContextAtIndex(0).symbol.GetStartAddress().GetLoadAddress(lldb.target) # Debugger API inf.lookup_symbol(name)在地址处设置观察点# GDB gdb.Breakpoint(f(char[{size}])*{address}, gdb.BP_WATCHPOINT) # LLDB lldb.target.WatchAddress(address, size, ...) # Debugger API inf.break_at(WatchpointLocation(address, size))在 Debugger API 中符号查找还支持SymbolLookupTypeANY/FUNCTION/VARIABLE过滤、prefer_static优先静态符号、objfile_endswith限定对象文件等扩展参数WatchpointLocation则要求读、写至少监控其一构造函数内有assert保证。3.3 两条重要设计纪律为所有调试器补齐实现向 Debugger API 添加任何新功能都必须为所有受支持的调试器提供实现——即使某功能在某个调试器上不支持也要用实现把它正确地门控掉接口保持精简接口越精简代码重复越少。经验法则如果某个接口的所有实现都预期共享代码那它更适合放进aglib——先把功能拆解为原语再把原语加入 Debugger API。四、aglib调试器无关的高级功能层aglib位于 pwndbg/aglib/承载对单个命令而言过于宽泛、但又可在多个调试器间共享的功能QEMU 处理、ELF 与动态段解析、操作系统相关功能、基于 Capstone 的反汇编、堆分析等对应 pwndbg/aglib/disasm/、pwndbg/aglib/elf.py、pwndbg/aglib/heap/、pwndbg/aglib/qemu.py 等模块。由于历史原因aglib的定位是在调试器无关的地基上导出与gdblib完全相同的功能以加速向调试器无关接口的移植。你可以把它想象成一个pwndbglib它拿 Debugger API 提供的原语构建出 Pwndbg 中那些更复杂、更有趣的功能。也就是说层次关系是调试器GDB / LLDB ↓ 原生 API pwndbg.dbg_modDebugger API接口 各调试器实现 ↓ 调试原语 pwndbg.aglib高级共享功能 ↓ pwndbg.commands面向用户的命令五、事件系统让代码响应调试器状态变化Pwndbg 拥有自己的事件系统可在调试器事件发生时触发对应代码。事件类型与触发条件定义在EventType枚举中注册回调则使用pwndbg.dbg.event_handler装饰器两者都在 pwndbg/dbg_mod/init.py 中定义与文档化。5.1 事件类型EventType枚举定义了以下事件含源码 docstring 语义事件值触发时机START0创建或附加被调试进程后、其执行开始前的某个时刻STOP1进程执行被挂起后、控制权交还用户交互调试前EXIT2进程被 detach 或执行结束后GDB 中此期间不允许调用info programMEMORY_CHANGED3用户交互式修改被调试进程内存时REGISTER_CHANGED4用户交互式修改寄存器时CONTINUE5用户请求在挂起后继续执行时小心使用此时调试器可能视程序为运行中许多操作受限见 issue #3683NEW_MODULE6调试器遇到新加载的模块GDB 术语中称为objfile时SUSPEND_ALL-1特殊值可监听所有事件5.2 优先级与挂起EventHandlerPriority枚举决定同一事件多个处理器被调用的顺序取值需按递增顺序定义CACHE_CLEAR 0第一件事是清缓存避免处理过期数据→UPDATE_ARCH_AND_TYPEINFO 10先初始化架构与类型信息→SAVE_SIGNAL 20保存 siginfo 供 context 展示→STANDARD 100默认值。文档建议优先使用描述性名称如CACHE_CLEAR而不是笼统的LOW/HIGHsuspend_events/resume_events可临时挂起某类事件的投递挂起期间触发的事件会被直接忽略ctx_suspend_events提供对应的上下文管理器写法。5.3 context 展示与 prompt hookPwndbg 的仪表盘/上下文展示由 pwndbg/commands/context.py 完成它通过 GDB 与 LLDB 各自的 prompt hook 被触发。这两个 hook 分别定义在 pwndbg/gdblib/prompt.pyGDB 侧最终通过prompt_hook调用与 pwndbg/dbg_mod/lldb/hooks.pyLLDB 侧prompt_hook函数在第 102 行附近定义并在第 116 行被赋给dbg.prompt_hook。也就是说每次调试器回到提示符、准备接受下一条命令时context 都会刷新一次。六、缓存机制Memoization用装饰器换取性能Pwndbg 有一套自己的缓存机制通过 Python 装饰器使用核心实现在 pwndbg/lib/cache.py。它基于直到被调试进程发生某类事件才失效的策略——例如进程因 SIGINT 或断点停止、加载新库/新对象文件等。6.1 核心用法cache_until(*event_names)装饰器接受若干事件名例如pwndbg.lib.cache.cache_until(stop, objfile) def some_expensive_query(): ...可用的缓存失效事件与CacheUntilEvent标志一一对应stop、exit、objfile、start、cont、thread、forever。STOP缓存不仅在进程停下时清空用户修改内存或寄存器时同样会触发。传入未知事件名会直接抛ValueError同一函数禁止重复缓存装饰器会检测并报错应改用多事件名。事件标志可位或组合成事件集合EventSet。6.2 使用约束函数所有参数值必须是可哈希的例如var: MyUnhashableType | None None就不允许遇到不可哈希参数会打印提示并assert失败返回值不应是可变对象也不得被修改——源码中对返回list的情况会打印警告被缓存的函数会附带.cache属性可调用clear_function_cache()按需清空对方法会清空所有实例的缓存。6.3 调试支持pwndbg/lib/cache.py内置了缓存命中/未命中/清空的日志开关debug标志按位组合debug_name可按函数名过滤由 pwndbg/commands/memoize.py 命令暴露给用户全局IS_CACHING可一键关闭缓存用于调试。从源码结构看缓存还可以按事件类型单独禁用IS_CACHING_DISABLED_FOR应在调试器启动阶段设置。七、装饰器与异常处理健壮性的三板斧7.1 only_after_first_prompt在首个提示符显示之前很多 Pwndbg 功能尚不可用。用 pwndbg/decorators.py 中的pwndbg.decorators.only_after_first_prompt装饰器包裹函数即可阻止其在首提示符前执行并可通过value_before参数指定提前调用时的返回值。同模块的suppress_errors装饰器则让函数出错时返回 fallback 值可选择性打印警告与 traceback。7.2 命令异常处理Pwndbg 有一个处理命令抛出异常的包装器定义在 pwndbg/exception.py。其调试体验依赖两个配置项set exception-verbose on命令异常时打印完整堆栈对应exception-verbose参数set exception-debugger on命令异常时直接进入交互式事后调试器对应exception-debugger参数默认尝试使用ipdb回退到pdb。此外 pwndbg/commands/bugreport.py 提供的bugreport --run-browser可生成 bug 报告并打开浏览器pwndbg/commands/reload.py 与 pwndbg/commands/dev.pyreinit-pwndbg、memoize、profiler、log-level等开发者命令见 docs/commands/developer/也是日常迭代时的高频工具。八、pwndbg.dbg.Value 与 typeinfo类型探测的边界Pwndbg 的不少功能需要pwndbg.dbg.Value实例。问题在于GDB 和 LLDB 都无法让用户自定义类型只能询问调试器这个二进制里有没有识别出某个特定类型。Pwndbg 在 pwndbg/aglib/typeinfo.py 中完成这一探测多数情况下工作正常。已知缺陷对带调试符号编译的 Go 二进制类型探测可能无法正常工作。Value类型系统本身Type、TypeCode、TypeField、Value等抽象都在 pwndbg/dbg_mod/init.py 中定义供 GDB/LLDB 两套实现各自落地。九、移植外部工具署名与许可证如果要把一个公开工具移植进 Pwndbg请务必在 CREDITS.md 中注明原作者/灵感来源并附上原工具或文章的链接确认原工具许可证适合移植例如 MIT避免许可冲突。十、最低支持版本一份代码跑遍 LTSPwndbg 的目标是完整支持所有尚未 EOL 的 Ubuntu LTS 发行版其他平台尽力而为。当前具体约束是所有代码须在 Ubuntu 22.04 与 24.04 上、使用GDB 12.1 及以上版本正常运行因此最低支持的 Python 版本是3.10不能使用任何未 backport 到 3.10 的新语法/特性在较新版本 GDB 上使用而旧版本无法提供替代实现的 API应正确降级并把该功能因 GDB 版本原因不可用明确告知用户而不是让代码报错。为强制这一约束仓库的lint.sh脚本会运行vermin与 pyproject.toml确保代码没有使用 Python 3.10 不支持的特性——这也是提交 PR 前本地必须通过的检查之一。结语从能用到懂架构综合来看Pwndbg 的工程核心是一组清晰的分层约定dbg_mod调试器原语→aglib共享高级功能→commands用户命令外加事件驱动与缓存贯穿其中。为 Pwndbg 写代码时牢记四条准则即可快速上手调试器专属功能不出pwndbg.dbg、优先写aglib、新增 API 必须给所有调试器补实现、新代码必须兼容 Python 3.10 / GDB 12.1。需要动手实践时可进一步参考 docs/contributing/adding-a-command.md新增命令、docs/contributing/adding-a-parameter.md新增配置项与 docs/contributing/setup-pwndbg-dev.md搭建开发环境。【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表