https://www.ibm.com/developerworks/cn/linux/l-kprobes.html

使用 Kprobes 调试内核

Prasanna S. Panchamukhi, 开发人员,Linux Technology Center, IBM India Software Labs

2004 年 9 月 19 日

将 printk 插入到运行中的 Linux 内核

使用 printk 收集 Linux ™ 内核的调试信息是一个众所周知的方法 —— 而使用了 Kprobes,不需要经常重新引导和重新编译内核就可以完成这一任务。Kprobes 与 2.6 内核结合起来提供了一个动态插入 printk's 的轻量级、无干扰而且强大的装置。记录调试信息(比如内核栈追踪、内核数据结构和寄存器)日志从来没有这么简单过!

Kprobes 是 Linux 中的一个简单的轻量级装置,让您可以将断点插入到正在运行的内核之中。 Kprobes 提供了一个强行进入任何内核例程并从中断处理器无干扰地收集信息的接口。使用 Kprobes 可以 轻松地收集处理器寄存器和全局数据结构等调试信息。开发者甚至可以使用 Kprobes 来修改 寄存器值和全局数据结构的值。

为完成这一任务,Kprobes 向运行的内核中给定地址写入断点指令,插入一个探测器。 执行被探测的指令会导致断点错误。Kprobes 钩住(hook in)断点处理器并收集调试信息。Kprobes 甚至可以单步执行被探测的指令。
安装

要安装 Kprobes,需要从 Kprobes 主页下载最新的补丁(参阅 参考资料 中的链接)。 打包的文件名称类似于 kprobes-2.6.8-rc1.tar.gz。解开补丁并将其安装到 Linux 内核:

$tar -xvzf kprobes-2.6.8-rc1.tar.gz
$cd /usr/src/linux-2.6.8-rc1
$patch -p1 < ../kprobes-2.6.8-rc1-base.patch

Kprobes 利用了 SysRq 键,这个 DOS 时代的产物在 Linux 中有了新的用武之地(参阅 参考资料)。您可以在 Scroll Lock键左边找到 SysRq 键;它通常标识为 Print Screen。要为 Kprobes 启用 SysRq 键,需要安装 kprobes-2.6.8-rc1-sysrq.patch 补丁:

$patch -p1 < ../kprobes-2.6.8-rc1-sysrq.patch

使用 make xconfig/ make menuconfig/ make oldconfig 配置内核,并 启用 CONFIG_KPROBES 和 CONFIG_MAGIC_SYSRQ 标记。 编译并引导到新内核。您现在就已经准备就绪,可以插入 printk 并通过编写简单的 Kprobes 模块来动态而且无干扰地 收集调试信息。
编写 Kprobes 模块

对于每一个探测器,您都要分配一个结构体 struct kprobe kp; (参考 include/linux/kprobes.h 以获得关于此数据结构的详细信息)。
清单 1. 定义 pre、post 和 fault 处理器

/* pre_handler: this is called just before the probed instruction is
  * executed.
  */
int handler_pre(struct kprobe *p, struct pt_regs *regs) {
printk("pre_handler: p->addr=0x%p, eflags=0x%lx\n",p->addr,
regs->eflags);
return 0;
}
 /* post_handler: this is called after the probed instruction is executed
  * (provided no exception is generated).
  */
void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) {
printk("post_handler: p->addr=0x%p, eflags=0x%lx \n", p->addr,
regs->eflags);
}
 /* fault_handler: this is called if an exception is generated for any
  * instruction within the fault-handler, or when Kprobes
  * single-steps the probed instruction.
  */
int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) {
printk("fault_handler:p->addr=0x%p, eflags=0x%lx\n", p->addr,
regs->eflags);
return 0;
}

获得内核例程的地址

在注册过程中,您还需要指定插入探测器的内核例程的地址。使用这些方法中的任意一个来获得内核例程 的地址:

从 System.map 文件直接得到地址。
    例如,要得到 do_fork 的地址,可以在命令行执行 $grep do_fork /usr/src/linux/System.map 。
    使用 nm 命令。
    $nm vmlinuz |grep do_fork
    从 /proc/kallsyms 文件获得地址。
    $cat /proc/kallsyms |grep do_fork
    使用 kallsyms_lookup_name() 例程。
    这个例程是在 kernel/kallsyms.c 文件中定义的,要使用它,必须启用 CONFIG_KALLSYMS 编译内核。 kallsyms_lookup_name() 接受一个字符串格式内核例程名, 返回那个内核例程的地址。例如: kallsyms_lookup_name("do_fork");

然后在 init_moudle 中注册您的探测器:
清单 2. 注册一个探测器

/* specify pre_handler address
  */
kp.pre_handler=handler_pre;
 /* specify post_handler address
  */
kp.post_handler=handler_post;
 /* specify fault_handler address
  */
kp.fault_handler=handler_fault;
 /* specify the address/offset where you want to insert probe.
  * You can get the address using one of the methods described above.
  */
kp.addr = (kprobe_opcode_t *) kallsyms_lookup_name("do_fork");
 /* check if the kallsyms_lookup_name() returned the correct value.
  */
if (kp.add == NULL) {
printk("kallsyms_lookup_name could not find address
for the specified symbol name\n");
return 1;
}
 /* or specify address directly.
  * $grep "do_fork" /usr/src/linux/System.map
  * or
  * $cat /proc/kallsyms |grep do_fork
  * or
  * $nm vmlinuz |grep do_fork
  */
kp.addr = (kprobe_opcode_t *) 0xc01441d0;
 /* All set to register with Kprobes
  */
        register_kprobe(&kp);

一旦注册了探测器,运行任何 shell 命令都会导致一个对 do_fork 的调用,您将可以在控制台上或者运行 dmesg 命令来查看您的 printk。做完后要记得注销探测器:

unregister_kprobe(&kp);

下面的输出显示了 kprobe 的地址以及 eflags 寄存器的内容:

$tail -5 /var/log/messages

Jun 14 18:21:18 llm05 kernel: pre_handler: p->addr=0xc01441d0, eflags=0x202
Jun 14 18:21:18 llm05 kernel: post_handler: p->addr=0xc01441d0, eflags=0x196
获得偏移量

您可以在例程的开头或者函数中的任意偏移位置插入 printk(偏移量必须在指令范围之内)。 下面的代码示例展示了如何来计算偏移量。首先,从对象文件中反汇编机器指令,并将它们 保存为一个文件:

$objdump -D /usr/src/linux/kernel/fork.o > fork.dis

其结果是:
清单 3. 反汇编的 fork

000022b0 <do_fork>:
    22b0:       55                      push   %ebp
    22b1:       89 e5                   mov    %esp,%ebp
    22b3:       57                      push   %edi
    22b4:       89 c7                   mov    %eax,%edi
    22b6:       56                      push   %esi
    22b7:       89 d6                   mov    %edx,%esi
    22b9:       53                      push   %ebx
    22ba:       83 ec 38                sub    $0x38,%esp
    22bd:       c7 45 d0 00 00 00 00    movl   $0x0,0xffffffd0(%ebp)
    22c4:       89 cb                   mov    %ecx,%ebx
    22c6:       89 44 24 04             mov    %eax,0x4(%esp)
    22ca:       c7 04 24 0a 00 00 00    movl   $0xa,(%esp)
    22d1:       e8 fc ff ff ff          call   22d2 <do_fork+0x22>
    22d6:       b8 00 e0 ff ff          mov    $0xffffe000,%eax
    22db:       21 e0                   and    %esp,%eax
    22dd:       8b 00                   mov    (%eax),%eax

要在偏移位置 0x22c4 插入探测器,先要得到与例程的开始处相对的偏移量 0x22c4 - 0x22b0 = 0x14 ,然后将这个偏移量添加到 do_fork 的地址 0xc01441d0 + 0x14 。(运行 $cat /proc/kallsyms | grep do_fork 命令以获得 do_fork 的地址。)

您还可以将 do_fork 的相对偏移量 0x22c4 - 0x22b0 = 0x14 添加到 kallsyms_lookup_name("do_fork"); 的输入,即: 0x14 + kallsyms_lookup_name("do_fork");
转储内核数据结构

现在,让我们使用修改过的用来转储数据结构的 Kprobe post_handler 来转储运行在系统上的所有作业的一些组成部分:
清单 4. 用来转储数据结构的修改过的 Kprope post_handler

void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) {
struct task_struct *task;
read_lock(&tasklist_lock);
for_each_process(task) {
printk("pid =%x task-info_ptr=%lx\n", task->pid,
task->thread_info);
printk("thread-info element status=%lx,flags=%lx, cpu=%lx\n",
task->thread_info->status, task->thread_info->flags,
task->thread_info->cpu);
}
read_unlock(&tasklist_lock);
}

这个模块应该插入到 do_fork 的偏移位置。
清单 5. pid 1508 和 1509 的结构体 thread_info 的输出

$tail -10 /var/log/messages
Jun 22 18:14:25 llm05 kernel: thread-info element status=0,flags=0, cpu=1
Jun 22 18:14:25 llm05 kernel: pid =5e4 task-info_ptr=f5948000
Jun 22 18:14:25 llm05 kernel: thread-info element status=0,flags=8, cpu=0
Jun 22 18:14:25 llm05 kernel: pid =5e5 task-info_ptr=f5eca000

启用奇妙的 SysRq 键

为了支持 SysRq 键,我们已经进行了编译。这样来启用它:

$echo 1 > /proc/sys/kernel/sysrq

现在,您可以使用 Alt+SysRq+W 在控制台上或者到 /var/log/messages 中去查看所有插入的内核探测器。
清单 6. /var/log/messages 显示出在 do_fork 插入了一个 Kprobe

Jun 23 10:24:48 linux-udp4749545uds kernel: SysRq : Show kprobes
Jun 23 10:24:48 linux-udp4749545uds kernel:
Jun 23 10:24:48 linux-udp4749545uds kernel: [<c011ea60>] do_fork+0x0/0x1de

回页首
使用 Kprobes 更好地进行调试

由于探测器事件处理器是作为系统断点中断处理器的扩展来运行,所以它们很少或者根本不依赖于系统 工具 —— 这样可以被植入到大部分不友好的环境中(从中断时间和任务时间到禁用的上下文间切换和支持 SMP 的代码路径)—— 都不会对系统性能带来负面影响。

使用 Kprobes 的好处有很多。不需要重新编译和重新引导内核就可以插入 printk。为了进行调试可以记录 处理器寄存器的日志,甚至进行修改 —— 不会干扰系统。类似地,同样可以无干扰地记录 Linux 内核数据结构的日志,甚至 进行修改。您甚至可以使用 Kprobes 调试 SMP 系统上的竞态条件 —— 避免了您自己重新编译和重新引导的所有 麻烦。您将发现内核调试比以往更为快速和简单。
参考资料

您可以参阅本文在 developerWorks 全球站点上的 英文原文.
    在 Kprobes 主页可以找到更多信息、最新消息、README 和下载。 README 详尽地描述 了 kprobes 接口。
    Kprobes 开发自完整的 Dynamic Probes补丁。Dynamic Probes 使用 Kernel Hooks 来收集难以获得的诊断信息。
    v.2.5.26 版本的内核已经融入了对 Kprobes 的支持;参阅 Support for kernel probes( Kernel Traffic,2002 年 7 月 25 日)的声明以及简短评论。 KProbes 在 2.5.73 版本中增加了 out-of-line single-stepping。
    Kprobes 利用了 BIOS 中断 SysRq 键。 它可以成为一个 Magic SysRq 键, 用来实现防御间谍软件、不完全的重新引导、显示内存信息、杀死进程等等(多得多的)功能。 它适合用于调试; 不太适合用于生产环境中的机器,它会引发安全威胁。
    objdump 显示关于一个或多个对象文件的信息。要获得更多资料,请参阅 objdump 手册页 Linux 2.6 内核模块。
    Captain's Universe 曾经公布过一个关于 如何为内核 2.6 编译内核模块 的文档。
    下载内核 Module Utilities for 2.6 的源文件;它可以取代当前新内核的 modutils。它是 Rusty Russell 的众多实用补丁 之一。
    在 developerWorks Linux 专区 可以找到 更多为 Linux 开发者准备的参考资料。
    在 Developer Bookstore Linux 区中订购 打折出售的 Linux 书籍。
    从 developerWorks 的 Speed-start your Linux app 专区下载可以运行于 Linux 之上的经过挑选的 developerWorks Subscription 产品免费测试版本,包括 WebSphere Studio Site Developer、WebSphere SDK for Web services、WebSphere Application Server、DB2 Universal Database Personal Developers Edition、Tivoli Access Manager 和 Lotus Domino Server。要更快速地开始上手,请参阅针对各个产品的 how-to 文章和技术支持。

使用 Kprobes 调试内核相关推荐

  1. qemu+gdb调试内核

    我们在内核调试的时候,会有很多方法,比如printk, ftrace, kprobe, ebpf和gdb等.比起其他的方法,gdb可以单步运行代码,实时获取变量信息等优势.但劣势也比较明显,比如效率较 ...

  2. Windows驱动开发学习笔记(二)—— 驱动调试内核编程基础

    Windows驱动开发学习笔记(二)-- 驱动调试&内核编程基础 基础知识 驱动调试 PDB(Program Debug Database) WinDbg 加载 PDB 实验:调试 .sys ...

  3. kgdb调试内核无法执行断点及kdb-22:Permisson denied

    之前在Centos8操作系统中,通过kgdb调试内核遇到无法执行断点及kdb-22:Permisson denied的问题.接下来,通过本篇文章讲述修改的配置参数. 章节预览: 1. 选择内核配置内核 ...

  4. ddd+kgdb调试内核

    一.linux主机需要安装的软件 (1) 安装patch工具 # apt-get install quilt (2) 安装DDD # apt-get install ddd (3) 编译.安装arm- ...

  5. Qemu 调试内核 出错 Remote 'g' packet reply is too long:

    按照博客http://blog.csdn.net/sunnybeike/article/details/6648815Qemu 调试内核, qemu的启动方式如下, qemu-system-x86_6 ...

  6. centos7 kdump、crash调试内核

    文章目录 前言 一.kdump 1.1 kdump定义 1.2 原理架构图 1.3 kdump配置 二.crash 2.1 crash简介 2.2 crash调试 vmcore 总结 参考资料 前言 ...

  7. linux内核单步调试,Linux内核驱动开发之KGDB单步调试内核(kgdboc方式)

    如何单步调试Linux内核一直困扰着linux驱动开发人员,内核有其代码量大.逻辑复杂.与硬件交互的特性.因此,有着不同于应用程序的调试方法,据统计Linux内核开 Linux内核驱动开发之KGDB原 ...

  8. linux 内核启动调试,内核开发和调试的启动时参数

    内核开发和调试的启动时参数 这些参数主要用在内核的开发和调试上,如果你不进行类似的工作,你可以简单的跳过本小节. 1.debug linux的日志级别比较多(详细信息可以参看linux/kernel. ...

  9. Windbg调试内核驱动方法

    一般说来,调速驱动程序分为两种: 1.存在PDB文件的调试: 这里的PDB文件其实就是调试符号文件,假如我们调试的这样的文件,我们可以再windbg中使用 :bp  驱动名!DriverEntry,这 ...

最新文章

  1. unsupported operand type(s) for + NoneType and int
  2. python个人博客搭建说明书_技术分享|利用Python Django一步步搭建个人博客(二)...
  3. SSH原理之图文详解
  4. LeetCode Gray Code 格雷码
  5. IE9以及IE9以下,无法执行innerHTML这一操作的解决方法
  6. (二)Linux下的crontab定时执行任务命令详解
  7. python中csv文件通过什么表示字符_python_写入csv文件时候无法进行原样写入(写入字符串中出现逗号,时候,csv文件自动分成两个单元格)...
  8. 深度学习在轨迹数据挖掘中的应用研究综述
  9. 2021抖音私域经营白皮书
  10. 推荐!京东开源姿态跟踪新框架LightTrack!
  11. mAP(mean Average Precision)应用(转)
  12. 帝国cms7.0调用指定栏目,指定顺序排列
  13. mp3 播放自动 html5,HTML5打造简易播放器:Chrome运行.mp3
  14. 古剑奇谭服务器维护,古剑奇谭ol7月10日更新维护公告 古剑网络版更新内容汇总...
  15. 【机器学习实战】1、机器学习主要任务
  16. Uva 11584 - Partitioning by Palindromes(预处理+DP)
  17. 一年级计算机课画画用什么,一年级孩子学画画该学哪种
  18. 谷歌提出Flan-T5,一个模型解决所有NLP任务
  19. 视频文件损坏怎么修复?简单的修复办法分享
  20. AsyncTask——AsyncTask串行and并行

热门文章

  1. RabbitMQ服务启动就自动停止解决方案
  2. Android 应用进程保活APP常驻内存研究方案
  3. 【深度学习】120G+训练好的word2vec模型(中文词向量)
  4. Ensemble Learning中的Bagging和Boosting
  5. 关于 Windows 10 如何扩展分区与合并分区
  6. 仿照Kafka,从零开始自实现 MQ
  7. 纯前端实现文件下载功能
  8. setup/teardown用法汇总
  9. 仿京东商城商品分类搜索功能
  10. Xcode 常用编译选项设置