pwndbg kdmesg 命令详解:在 GDB/LLDB 中读取 Linux 内核环形缓冲区
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
导读
kdmesg是 pwndbg 内核调试命令族(CommandCategory.KERNEL)中的一员,用于在 GDB/LLDB 调试会话中直接读取 Linux 内核的 printk 环形缓冲区(kernel ring buffer),即用户态dmesg命令所查看的同一份内核日志数据。它依托 pwndbg 的类型化内存读写能力,从内核内存中的printk_ringbuffer结构体解析每条日志记录,让内核逆向与漏洞利用研究者在断点处无需退出调试器即可查看内核日志。读完本文,你将掌握kdmesg的完整用法、参数语义、适用前提,以及它在 pwndbg/commands/kdmesg.py 中的底层实现原理。
命令概述与用法
kdmesg的官方帮助文档位于 docs/commands/kernel/kdmesg.md,其用法如下:
usage: kdmesg [-h] [-T]该命令的功能描述为:Displays the kernel ring buffer (dmesg) contents(显示内核环形缓冲区即 dmesg 内容)。
可选参数
| 短参数 | 长参数 | 帮助信息 |
|---|---|---|
-h | --help | 显示帮助信息并退出 |
-T | --ctime | 以人类可读的时间戳格式打印(Print human-readable timestamps) |
参数定义位于源码 pwndbg/commands/kdmesg.py,-T/--ctime是一个store_true开关,传入后作为ctime: bool = False进入kdmesg()函数。
适用前提:三个硬性条件
kdmesg不是在任何调试场景下都能执行,它被三个装饰器约束(见 pwndbg/commands/kdmesg.py),装饰器实现位于 pwndbg/commands/init.py:
@OnlyWhenQemuKernel:仅当在 QEMU 中调试 Linux 内核时允许执行。实现中通过pwndbg.aglib.qemu.is_qemu_kernel()判断,否则报错This command may only be run when debugging the Linux kernel in QEMU.。这决定了kdmesg面向的是虚拟化内核调试场景(例如内核漏洞利用研究常用的 QEMU + GDB stub)。@OnlyWhenPagingEnabled:仅当内核分页(paging/MMU)已启用时允许执行。实现调用pwndbg.aglib.kernel.paging_enabled()(见 pwndbg/aglib/kernel/init.py),它会按架构检查寄存器状态:x86-64 检查 CR0.PG 等分页位、aarch64 检查 MMU 使能位、rv64 检查satp.MODE高四位是否非零。分页未启用时无法将虚拟地址转换为物理地址,因而无法读取内核内存。@OnlyWithKernelDebugInfo:仅当调试的内核镜像包含.debug_info段时允许执行。has_debug_info()(见 pwndbg/aglib/kernel/init.py)会解析当前目标文件(vmlinux)的 ELF 段,确认是否存在.debug_info节。这意味着你调试的内核必须带有完整的 DWARF 调试信息。
此外,命令类别注册为CommandCategory.KERNEL,属于内核命令组,与kbase、kversion、kcmdline、kdmesg等同组命令并列。
内核版本要求:仅支持 5.10+ 的新环形缓冲区实现
需要特别强调的是:kdmesg只支持 Linux 5.10+ 引入的"新"printk 环形缓冲区实现。Linux 5.10 通过提交(commitd594d8f411d4)将 printk 从旧的连续缓冲区迁移到了基于描述符(descriptor)和文本数据环(text data ring)的struct printk_ringbuffer设计,相关结构体定义于内核源码kernel/printk/printk_ringbuffer.h。该实现直接读取的是这些结构体,而不是调用dmesg之类的用户态命令。
因此,当你在较老的内核上执行kdmesg时,可能找不到对应的结构体或符号,导致命令失败——这一点会在下文"故障排查"中进一步说明。
工作原理:从 printk_ringbuffer 到日志行
整个命令的执行流程(pwndbg/commands/kdmesg.py)可分为四个阶段:
1. 定位printk_rb_static全局符号
命令首先通过pwndbg.aglib.symbol.lookup_symbol_addr("printk_rb_static")查找内核静态实例化的struct printk_ringbuffer(即内核代码中printk_rb_static全局变量)。若符号缺失,会提示:
The printk_rb_static symbol was not found. This may indicate that the kernel is an older version or that the symbol is not available in the current build.2. 读取环形缓冲区的两个核心子结构
struct printk_ringbuffer包含两个关键成员,源码通过pwndbg.aglib.memory.get_typed_pointer_value读取:
desc_ring(struct prb_desc_ring):描述符环,保存每条消息的描述符元数据。源码从中读取count_bits(描述符数量 =1 << count_bits)、descs(描述符数组基址)、infos(信息数组基址)、head_id与tail_id(环的头尾游标)。text_data_ring(struct prb_data_ring):文本数据环,保存消息正文。源码从中读取size_bits(数据环大小 =1 << size_bits)与data(数据区基址)。
3. 从 tail 到 head 遍历记录
描述符 ID 是一个带标志位的原子计数器:源码通过desc_flags_shift = size_long * 8 - 2与desc_flags_mask提取状态位、desc_id_mask提取纯 ID,然后从tail_id开始逐个遍历到head_id。
对每条描述符:
- 读取
struct prb_desc的state_var.counter,仅当状态为desc_committed(1)或desc_finalized(2)时才打印,跳过未提交的记录; - 根据
text_blk_lpos的begin/next计算出消息正文在数据环中的偏移(对text_data_sz取模实现环形回绕),处理 wrap-around 与数据块被丢弃(begin & 1 == 1)的情况; - 从
struct printk_info读取text_len与ts_nsec(纳秒时间戳),用pwndbg.aglib.memory.read读取正文并utf8解码(损坏字节用errors="replace"容错)。
4. 时间戳格式化
这是-T/--ctime参数发挥作用的地方,两条输出路径:
- 默认模式:直接打印相对开机时间的浮点秒数,格式为
[{ts_nsec / 1e9:12.6f}] 消息内容,即 12 位宽度、6 位小数; -T模式:查找tk_core符号,读取struct tk_data中的timekeeper.xtime_sec(系统墙上时钟的 epoch 秒),把每条记录的ts_nsec / 1e9 + epoch_time交给time.ctime()转换为人类可读的[...]时间戳,如[Mon Sep 14 10:00:00 2026]。
若tk_core符号或struct tk_data类型缺失,-T模式会报错退出;整个流程中任何调试器读取错误都会被捕获并打印ERROR: ...。
使用示例
假设你正在 QEMU 中调试一个带调试信息的 vmlinux:
pwndbg> kdmesg [ 0.000000] Linux version 6.1.0 (build@host) (gcc-12) #1 SMP ... [ 0.000082] BIOS-provided physical RAM map: [ 0.000118] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable ... pwndbg> kdmesg -T [Mon Sep 14 08:12:05 2026] Linux version 6.1.0 (build@host) (gcc-12) #1 SMP ...在漏洞利用研究场景中,可在内核函数断点(如b do_exit或b do_execveat_common)命中后直接查看最近的 Oops 堆栈或驱动告警日志,而无需切换到宿主机的dmesg——尤其当内核通过 QEMU 启动、宿主机dmesg中看不到 guest 内核日志时,该命令是唯一的便捷途径。
局限性与故障排查
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
提示may only be run when debugging the Linux kernel in QEMU | 目标不是 QEMU 内核(例如纯用户态程序或本地调试) | 确认调试场景为 QEMU 内核调试 |
提示may only be run when paging is enabled | 断点命中时内核分页尚未开启(如早期启动阶段)或架构寄存器状态异常 | 在分页启用后的阶段下断点 |
提示may only be run when debugging a Linux kernel with debug info | vmlinux 缺少.debug_info段 | 使用带 DWARF 调试信息的 vmlinux,或参考OnlyWithKernelDebugInfo提示编译带调试符号的内核 |
提示printk_rb_static symbol was not found | 内核版本低于 5.10(旧 printk 实现)或符号被裁剪 | 更换 5.10+ 内核,确认内核未被strip |
-T模式报tk_core symbol was not found/struct tk_data is not defined | 调试信息中缺少时间维护相关符号/类型 | 回退到默认时间戳模式,或补齐调试信息 |
ERROR: ...输出 | 调试器读取内核内存失败 | 确认OnlyWhenPagingEnabled前提成立,检查内存访问权限 |
小结
kdmesg是 pwndbg 内核调试工具集中将"用户态调试器"与"内核运行时状态"连接起来的实用命令:它直接在内核内存中解析 Linux 5.10+ 的printk_ringbuffer,输出与dmesg等价的内核日志,并借助-T参数将单调时间戳转换为人类可读时间。理解其三个前置装饰器、printk_ringbuffer的双环结构与状态位过滤逻辑,能帮助你在 QEMU 内核调试与漏洞利用研究场景中更可靠地使用它。该命令与同目录下的 kcmdline、kversion、kbase 等命令共同构成 pwndbg 的内核侦察工具箱,可配合使用以快速建立对目标内核运行环境的整体认知。
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考