当前位置: 首页 > news >正文

Linux使用kprobes跟踪内核函数

一、kprobes模块代码

创建一个名为kprobes_debug.c的文件,拷贝下面的内容,注意粘贴时使用粘贴模式,即:set paste

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>/* 定义kprobe结构体 */
static struct kprobe kp;/* 前置处理函数:在探测点指令执行前调用 */
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{printk(KERN_INFO "Register dump for sys_ioctl:\n");printk(KERN_INFO "  ebx: 0x%lx\n", regs->ebx);printk(KERN_INFO "  ecx: 0x%lx\n", regs->ecx); printk(KERN_INFO "  edx: 0x%lx\n", regs->edx);printk(KERN_INFO "  esi: 0x%lx\n", regs->esi);printk(KERN_INFO "  edi: 0x%lx\n", regs->edi);printk(KERN_INFO "  ebp: 0x%lx\n", regs->ebp);printk(KERN_INFO "  esp: 0x%lx\n", regs->esp);printk(KERN_INFO "  eip: 0x%lx\n", regs->eip);dump_stack();return 0;
}/* 后置处理函数:在探测点指令执行后调用 */
static void handler_post(struct kprobe *p, struct pt_regs *regs,unsigned long flags)
{//printk(KERN_INFO "<%s> post_handler: p->addr = 0x%p\n",//       p->symbol_name, p->addr);
}/* 错误处理函数:当处理函数或单步执行出现异常时调用 */
static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{//printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn\n",//       p->addr, trapnr);return 0;
}static int __init kprobe_init(void)
{int ret;kp.addr = (kprobe_opcode_t *)0xc0266d1f;/* 设置处理函数 */kp.pre_handler = handler_pre;kp.post_handler = handler_post;kp.fault_handler = handler_fault;/* 注册kprobe */ret = register_kprobe(&kp);if (ret < 0) {printk(KERN_ERR "register_kprobe failed, returned %d\n", ret);return ret;}printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);return 0;
}static void __exit kprobe_exit(void)
{unregister_kprobe(&kp);printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}module_init(kprobe_init);
module_exit(kprobe_exit);
MODULE_LICENSE("GPL");

使用 kprobes 机制在内核指定地址(0xc0266d1f)动态插入探测点,用于监控该地址指令的执行前后的寄存器状态


1. 核心功能

  • kprobes 是 Linux 内核提供的一种动态追踪机制,允许在几乎任何内核地址插入探测点,捕获执行时的上下文(如寄存器、栈信息)
  • 本模块在地址 0xc0266d1f 处注册了一个 kprobe,并在指令执行前打印寄存器值,执行后调用后置处理函数

2. 代码逐段解析

2.1.头文件引入

#include <linux/kernel.h>       // 内核基础功能(如 printk)
#include <linux/module.h>      // 内核模块宏(如 module_init/exit)
#include <linux/kprobes.h>     // kprobes 相关API

2.2.定义 kprobe 结构体

static struct kprobe kp;
  • struct kprobekprobes 的核心数据结构,用于配置探测点地址和处理函数

2.3.前置处理函数 (handler_pre)

static int handler_pre(struct kprobe *p, struct pt_regs *regs) {printk(KERN_INFO "Register dump for sys_ioctl:\n");printk(KERN_INFO "  ebx: 0x%lx\n", regs->ebx);// ... 打印其他寄存器(ecx, edx, esi, edi, ebp, esp, eip)dump_stack(); // 打印内核调用栈return 0;
}
  • 触发时机:在探测点指令执行前调用
  • 参数
    • p:指向当前 kprobe 结构体的指针
    • regs:保存 CPU 寄存器状态的 pt_regs 结构体(包含 ebx, ecx, edx 等寄存器值)
  • 功能
    • 打印所有通用寄存器的值(x86 架构)
    • dump_stack() 输出内核调用栈,帮助定位代码路径
  • 返回值
    • 0 表示继续执行;非零会触发 handler_fault

2.4.后置处理函数 (handler_post)

static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) {// 当前为空,可用于分析指令执行后的状态
}
  • 触发时机:在探测点指令执行后调用。
  • 参数
    • flags:执行过程中的标志位(如中断状态)

2.5.错误处理函数 (handler_fault)

static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) {// 当前为空,可用于处理探测点执行时的异常(如缺页)return 0;
}
  • 触发时机:当 handler_pre 或指令单步执行触发异常(如访问非法地址)。
  • 参数
    • trapnr:异常编号(如 #PF 表示缺页)。

2.6.模块初始化 (kprobe_init)

static int __init kprobe_init(void) {int ret;kp.addr = (kprobe_opcode_t *)0xc0266d1f; // 硬编码探测地址kp.pre_handler = handler_pre;kp.post_handler = handler_post;kp.fault_handler = handler_fault;ret = register_kprobe(&kp); // 注册kprobeif (ret < 0) {printk(KERN_ERR "register_kprobe failed, returned %d\n", ret);return ret;}printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);return 0;
}
  • 关键操作
    1. 设置探测地址 0xc0266d1f(需根据实际内核版本调整)
    2. 绑定处理函数(pre_handler, post_handler, fault_handler
    3. 调用 register_kprobe() 注册探测点
    4. 失败时打印错误并返回

2.7.模块退出 (kprobe_exit)

static void __exit kprobe_exit(void) {unregister_kprobe(&kp); // 注销kprobeprintk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}
  • 调用 unregister_kprobe() 清理探测点,避免内核污染。

2.8.模块声明

module_init(kprobe_init);
module_exit(kprobe_exit);
MODULE_LICENSE("GPL"); // 声明模块许可证(GPL兼容)

3.关键注意事项

  • 0xc0266d1f 是内核虚拟地址,可能因内核版本/配置不同而变化。
  • 获取方式,比如想要跟踪sys_ioctl内核函数,可以这样获取它的地址:
cat /proc/kallsyms | grep sys_ioctl

预期输出:

c017bbc4 T sys_ioctl

c017bbc4 就是我们要跟踪的地址

调试信息:

  • dump_stack() 的输出可通过 dmesg 查看,帮助分析调用链

性能影响

  • kprobes 会轻微降低内核性能,避免在高频路径上使用

二、Makefile文件

ifneq ($(KERNELRELEASE),)# 在内核构建系统中(由 kbuild 调用时)obj-m := kprobes_debug.o
elseKERNELDIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)default:@echo "[DEBUG] 正在执行内核模块编译..."@echo "[DEBUG] MAKE = $(MAKE)"                   # 打印 MAKE 变量@echo "[DEBUG] uname -r = $(shell uname -r)"    # 打印当前内核版本@echo "[DEBUG] KERNELRELEASE = $(KERNELRELEASE)"$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean:@echo "[DEBUG] 正在清理编译文件..."$(MAKE) -C $(KERNELDIR) M=$(PWD) cleanrm -f *.ko *.mod.c *.mod.o *.o
  • obj-m := kprobes_debug.o:这是最重要的部分,它告诉内核的 kbuild 系统将kprobes_debug.o 构建为可加载内核模块(kprobes_debug.o)。

    • 如果你的模块由多个源文件组成(比如 main.chelper.c),则需要这样写:

      obj-m := mymodule.o
      mymodule-objs := main.o helper.o
      
  • KERNELDIR:指定内核源码树的路径。示例中 ?= 表示如果该变量未设置,则使用 /lib/modules/$(shell uname -r)/build,这通常是当前运行内核的源码或头文件链接。

    • 注意必须确保 KERNELDIR 指向的内核源码版本与你当前运行的内核版本一致,否则编译出的模块可能无法加载。
  • 编译命令 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

    • -C $(KERNELDIR):让 make 先切换到内核源码目录,读取顶层的 Makefile
    • M=$(PWD):然后告诉 kbuild系统回到当前模块源码所在的目录执行编译。
    • modules:指定编译目标为模块。
  • ifneq ($(KERNELRELEASE),):这个条件判断用于处理 Makefile两次执行。 第一次在当前目录,KERNELRELEASE 未定义,执行 else 部分,调用内核源码的 Makefile。内核的 Makefile 会设置 KERNELRELEASE 变量,并再次回到当前 Makefile 时,KERNELRELEASE 已被设置,于是执行 obj-m := kprobes_debug.o 这一行。

1.模块加载和卸载

加载模块:

sudo insmod kprobes_debug.ko

​ 加载后,模块的初始化函数会被调用,其中printk 信息会输出到内核日志。使用 dmesg 命令查看:

dmesg | tail -n 20

​ 使用 lsmod 命令 grep 查找你的模块

lsmod | grep kprobes_debug

卸载模块:

sudo rmmod kprobes_debug

​ 同样,模块的退出函数 会被调用,信息也会输出到内核日志。使用 dmesg | tail -n 20 再次查看。

三、结果验证

在终端执行命令

sudo ethtool eth0

dmesg命令会有如下输出

Register dump for sys_ioctl:ebx: 0x8946ecx: 0xd7870e08edx: 0xee546d88esi: 0xbffff9b0edi: 0x8946ebp: 0xe61b3f68esp: 0xc02a5c97eip: 0xc0265c21[<c01047d2>] dump_stack+0x16/0x18[<f89f8083>] handler_pre+0x83/0x8a [kprobes_debug][<c011443f>] kprobe_exceptions_notify+0x11c/0x219[<c012cd71>] notifier_call_chain+0x1c/0x35[<c01055b2>] do_int3+0x37/0x6c[<c01044e2>] int3+0x1e/0x2c[<c025b802>] sock_ioctl+0x249/0x25d[<c017bd7e>] sys_ioctl+0x1ba/0x1d8[<c0103919>] sysenter_past_esp+0x52/0x75

我们可以根据寄存器获取函数参数,例如:ebx寄存器是函数第一个参数,值是0x8946,代表ethtool相关命令,其次,调用栈可以显示函数的具体代码位置,比如,我们想获取sock_ioctl函数具体是执行的哪一行代码,可以这样做

sudo gdb /usr/src/linux-2.6.10/vmlinux /proc/kcore

然后输入

list *0xc025b802

预期输出

(gdb) list *0xc025b802
0xc025b802 is in sock_ioctl (net/socket.c:902).
897					err = dlci_ioctl_hook(cmd, argp);
898					up(&dlci_ioctl_mutex);
899				}
900				break;
901			default:
902				err = sock->ops->ioctl(sock, cmd, arg);
903				break;
904		}
905		lock_kernel();
906	
(gdb) 
http://www.dtcms.com/a/447709.html

相关文章:

  • 公司网站优化哪家好做全屏网站图片显示不全
  • 春节网页设计素材重庆百度快照优化
  • 自建网站套现海外贸易在什么网站做
  • 义乌企业网站客户打不开网站
  • 中文网站开发工具wordpress文章首页设置
  • 企业网站建设计什么科目中国施工企业协会官网
  • 用爱站工具包如何做网站地图毕业ppt模板免费下载
  • logo设计网站官网wordpress link
  • 建立网站接受投注是什么意思做废铝的关注哪个网站好
  • 无极app定制开发公司网站模板三明市住房与建设局网站
  • 门户网站建设工作方案网页设计公司济南兴田德润优惠吗
  • 泰州专一做淘宝网站网络营销是什么工作主要干啥
  • 做网站是先做后台还是前端wordpress 培训
  • 怎样做企业学校网站本地wordpress如何传到服务器上
  • 百度站长联盟微信电商怎样开店
  • dw用设计视图做网站视频素材库网站下载
  • 做肝病科网站wordpress导航栏的文件在哪
  • 网站的发布方案有哪些免费大型网站
  • 网站开发信息文档宁波做网站首推荣盛网络
  • Redis-用户签到(BitMap)
  • 网站建设人力成本费用企业专业建站
  • PCIe协议之均衡篇之 3-TAP Coefficients的理解(一)
  • 西宁网站维护公司如何使用wordpress
  • 网站图片缩略图深圳住 建设局网站
  • 烟台芝罘区住房建设局网站程序员开源网站
  • 锟鹏建设招聘网站网站设计营销
  • 无锡网站建设兼职开发公司审计稽查的内容
  • 除了阿里巴巴还有什么网站做外贸的网络营销方案怎么做
  • 网站标题在哪里修改定制化网站开发报价
  • 手机壳定制网站制作商超运营与管理