linux内核调试工具之kprobe

news2024/11/19 14:33:19

目录

一、内核调试的痛点

二、kprobe的优点

三、kprobe探测点的要点

四、探测点的开销与优化

五、内核配置

六、API

七、程序架构

八、实例


一、内核调试的痛点


        内核调试,添加打印信息。在运行过程中想看某个函数的变量,需要重新编译内核。这样破坏了执行的过程。

二、kprobe的优点


        kprobe 可以在系统运行期间,自定义回调函数,动态插入探测点。当内核执行到探测函数时,会调用回调函数。同时,也可以动态移除探测函数。

   探测点的类型

  •  pre_handler:在被探测函数执行前回调
  •  post_handler:在被探测函数执行
  •  falut_handler:探测期间发生的错误,但在新内核中被删除

 

三、kprobe探测点的要点

  • 可以探测的函数,一般来说,可以探测任意函数,尤其是可以探测中断函数。
  • 在kernel/kprobes.c和arch/*/kernel/kprobes.c 用于实现自身的函数不能探测
  •  do_page_fault与 notifier_call_chain 会出现错误。在register_*probe 注册时会返回-EINVAL
  • 内联函数,不能保证都探测,因为gcc可能会优化某些函数
  • 回调函数,通过修改内核的数据结构或pt_regs结构体中的数据,可以修改被探测函数的环境。如在测试中安装bug修复信息或者注入错误代码。
  • kprobe会避免在处理探测点函数时 再次调用另一个探测点的回调函数。如在printk上注册的探测点,在它的回调函数中可能再次调用printk函数,此时将不再触发printk探测点的回调。
  • 在注册和销毁的过程中,不要使用mutexs或allocate memory函数
  • 多个回调函数可以同时在不同的CPU上触发。
  • 探测函数运行在禁用抢占或禁用中断的情况下运行,在这种情况下中断函数不能调用会放弃CPU的函数。
  • 如何函数的调用次数和返回次数不等,则在类似的函数上注册kretprobe不会达到预期的效果。如do_exit()
  • 在进入和退出一个函数时,CPU运行再非当前任务所有的栈上,往该函数上注册kretprobe可能会导致不可预料的后果。因此X86_64结构下为__switch_to()注册kretprobe会返回-EINVAL

四、探测点的开销与优化


2005年使用的典型CPU,kprobe命中需要0.5到1.0微秒来处理,返回探测命中的时间通常比kprobe命中的时间长50-75%

k = kprobe; r = return probe; kr = kprobe + return probe
on same function
i386: Intel Pentium M, 1495 MHz, 2957.31 bogomips
k = 0.57 usec; r = 0.92; kr = 0.99
x86_64: AMD Opteron 246, 1994 MHz, 3971.48 bogomips
k = 0.49 usec; r = 0.80; kr = 0.82
ppc64: POWER5 (gr), 1656 MHz (SMT disabled, 1 virtual CPU per physical CPU)
k = 0.77 usec; r = 1.26; kr = 1.45


优化的kprobe命中需要0.07到0.1微秒才能处理

k = unoptimized kprobe, b = boosted (single-step skipped), o = optimized kprobe,
r = unoptimized kretprobe, rb = boosted kretprobe, ro = optimized kretprobe.
i386: Intel(R) Xeon(R) E5410, 2.33GHz, 4656.90 bogomips
k = 0.80 usec; b = 0.33; o = 0.05; r = 1.10; rb = 0.61; ro = 0.33
x86-64: Intel(R) Xeon(R) E5410, 2.33GHz, 4656.90 bogomips
k = 0.99 usec; b = 0.43; o = 0.06; r = 1.24; rb = 0.68; ro = 0.30

五、内核配置

CONFIG_KPROBES
CONFIG_KALLSYMS
CONFIG_KALLSYMS_ALL
CONFIG_DEBUG_INFO

六、API

#include <linux/kprobes.h>
#include <linux/ptrace.h>

//注册
int register_kprobe(struct kprobe *kp);

//探测前回调函数
int pre_handler(struct kprobe *p, struct pt_regs *regs);

//探测后回调函数
void post_handler(struct kprobe *p, struct pt_regs *regs,
                  unsigned long flags);

int disable_kprobe(struct kprobe *kp);

int enable_kprobe(struct kprobe *kp);


void unregister_kprobes(struct kprobe **kps, int num);

七、程序架构

#include <linux/kprobes.h>
#include <linux/ptrace.h>

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{

}

static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{
  
}

static struct kprobe kpb;
static int __init kprobe_init(void)
{
        kpb.pre_handler = handler_pre;
        kpb.post_handler = handler_post;
        kpb.symbol_name = kprobe_func;
        if (register_kprobe(&kpb)) {
                pr_alert("register_kprobe failed!\n“);
                return -EINVAL;
        }
}

static void __exit kprobe_exit(void)
{
        unregister_kprobe(&kpb);
        pr_info("bye, unregistering kernel probe @ '%s'\n", kprobe_func);
}

module_init(kprobe_init);
module_exit(kprobe_exit);


八、实例

使用kprobe跟踪do_sys_openat2,并输出打开的文件名参数

内核中do_sys_openat2的原型

static long do_sys_openat2(int dfd, const char __user *filename,
               struct open_how *how)
{

}

1、内核模块程序

kprobe.c源码


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/kprobes.h>
#include <linux/ptrace.h>
#include <linux/uaccess.h>

#include <linux/jiffies.h>
#include <linux/ktime.h>


MODULE_AUTHOR("wy");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_VERSION("0.1");

static spinlock_t lock;
static struct kprobe kpb;
static u64 tm_start, tm_end;
static char *fname;

//接收脚本传递的参数 kprobe_func
#define MAX_FUNCNAME_LEN  64
static char kprobe_func[MAX_FUNCNAME_LEN];
module_param_string(kprobe_func, kprobe_func, sizeof(kprobe_func), 0);
MODULE_PARM_DESC(kprobe_func, "function name to attach a kprobe to");

#define pr_fmt(fmt) "%s:%s(): " fmt, KBUILD_MODNAME, __func__

//显示时间差
#define SHOW_DELTA(later, earlier)  do {    \
    if (time_after((unsigned long)later, (unsigned long)earlier)) { \
            s64 delta_ns = ktime_to_ns(ktime_sub(later, earlier));      \
        pr_info("delta: %lld ns", delta_ns);       \
                if (delta_ns/1000 >= 1)                    \
                        pr_info(" %lld us", delta_ns/1000);    \
                if (delta_ns/1000000 >= 1)                 \
                        pr_info(" %lld ms", delta_ns/1000000); \
    } else  \
        pr_warn("SHOW_DELTA(): *invalid* earlier > later?\n");  \
} while (0)


//探测前的执行
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
	char *param_fname_reg;

	param_fname_reg = (char __user *)regs->si;
#if 1
    //拷贝数据
	if (!strncpy_from_user(fname, param_fname_reg, PATH_MAX + 1))
#else
	/* 使用 copy_from_user() 会产生调度 导致CPU挂掉*/
	if (!copy_from_user(fname, (const char __user *)regs->si,
			    strnlen_user((const char __user *)regs->si, PATH_MAX + 1)))
#endif
		return -EFAULT;

	pr_info("FILE being opened: reg:0x%px   fname:%s\n",
		(void *)param_fname_reg, fname);

	spin_lock(&lock);
	tm_start = ktime_get_real_ns();
	spin_unlock(&lock);

	return 0;
}

//探测之后的输出
static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{
	spin_lock(&lock);
	tm_end = ktime_get_real_ns();
    //计算时间
	SHOW_DELTA(tm_end, tm_start);
	spin_unlock(&lock);
}


static int __init kprobe_init(void)
{
	if (kprobe_func[0] == '\0') {
		pr_warn("expect a valid kprobe_func=<func_name> module parameter");
		return -EINVAL;
	}
    //申请内存空间,用来存储do_sys_openat2参数名
	fname = kzalloc(PATH_MAX + 1, GFP_ATOMIC);
	if (unlikely(!fname))
		return -ENOMEM;
    
	kpb.pre_handler = handler_pre;
	kpb.post_handler = handler_post;
	kpb.symbol_name = kprobe_func;
    //注册
	if (register_kprobe(&kpb)) {
		pr_alert("kernel fun register_kprobe failed!\n", kprobe_func);
		return -EINVAL;
	}
	pr_info("registering kernel probe @ '%s'\n", kprobe_func);
	spin_lock_init(&lock);

	return 0;		/* success */
}

static void __exit kprobe_exit(void)
{
	kfree(fname);
	unregister_kprobe(&kpb);
	pr_info("bye, unregistering kernel probe @ '%s'\n", kprobe_func);
}

module_init(kprobe_init);
module_exit(kprobe_exit);

Makefile

FNAME_C := kprobe

KDIR ?= /lib/modules/$(shell uname -r)/build

CC     := $(CROSS_COMPILE)gcc
PWD            := $(shell pwd)
obj-m          += ${FNAME_C}.o

all:
        make -C $(KDIR) M=$(PWD) modules
install:
        make
        sudo make -C $(KDIR) M=$(PWD) modules_install
clean:
        make -C $(KDIR) M=$(PWD) clean

2、用户态应用程序

测试程序打开/home/kprobe.c  测试在kprobe探测能否探测到

#include<stdio.h>
#include <unistd.h>
 
void main()
{
        FILE *fp = NULL;
        while(1)
        {
            fp = fopen("/home/kprobe.c","r+");
            if(fp == NULL)
                return;

            fclose(fp);
            usleep(10000);
        }
}

 gcc main.c  生成a.out

3、运行测试

运行用户程序

a.out

运行内核程序

insmod ./kprobe.ko kprobe_func=do_sys_openat2

运行输出截取,运行时很多的干扰可以使用 journalctl 

#journalctl -k >log.txt

#cat log.txt | grep "/home/kprobe.c"

将内核输出都写到log.txt中(dmesg只能写部分),使用grep检索所需要查询的信息。

10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_pre(): FILE being opened: reg:0x00007ffd9a320820   fname:/sys/module/3_kprobe/uevent
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_post(): delta: 37 ns
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_pre(): FILE being opened: reg:0x00007ffd9a320890   fname:/run/udev/data/+module:3_kprobe
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_post(): delta: 19 ns
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_pre(): FILE being opened: reg:0x000055cd74eb8007   fname:/home/kprobe.c
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_post(): delta: 40 ns

在输出信息中可以看到函数的地址、打开的文件名、函数执行的时间40ns等信息

参考

Kernel Probes (Kprobes) — The Linux Kernel documentation

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/612.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】指针(进阶)

目录一、字符指针二、数组指针2.1、数组指针的定义2.2、&数组名和数组名2.3、数组指针的使用三、数组传参、指针传参3.1、一维数组传参3.2、二维数组传参3.3、一级指针传参3.4、二级指针传参四、函数指针五、函数指针数组六、指向函数指针数组的指针七、回调函数一、字符指…

【C语言小游戏】详解三子棋,深刻掌握二维数组

前言&#xff1a; 大家好&#xff0c;我是良辰丫&#xff0c;今天带领大家实现一个C语言小游戏&#xff0c;主要运用的知识点为二维数组&#xff0c;希望这篇文章让大家对二维数组有更深刻的认识。 &#x1f49e;看似不起波澜的日复一日&#xff0c;会突然在某一天让人看到坚持…

【day14】【洛谷算法题】-P5711闰年判断-刷题反思集[入门2分支结构]

&#x1f338;大家好&#xff0c;我是花无缺&#xff0c;一枚热爱生活的新时代青年&#xff0c;感谢你的阅读&#x1f970;~ &#x1f468;‍&#x1f4bb;个人主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专…

PC - 史上最简单的远程访问群晖 NAS 方法

文章目录1、下载安装cpolar群晖套件1.1 注册cpolar账号1.2 下载cpolar群晖套件1.3 安装cpolar群晖套件2、创建隧道映射5000端口2.1 打开cpolar群晖套件2.2 创建远程访问隧道2.3 获取公网URL地址3、公网远程群晖NAS教大家一个新手小白都可以轻松掌握的远程群晖NAS方法&#xff0…

算法的时间复杂度和空间复杂度

文章目录算法的时间复杂度和空间复杂度算法效率算法的复杂度时间复杂度时间复杂度的概念大O的渐进表示法常见的时间复杂度计算举例空间复杂度常见复杂度对比复杂度的oj练习算法的时间复杂度和空间复杂度 算法效率时间复杂度空间复杂度常见的时间复杂度以及复杂度的oj练习 算法…

【题解】方格取数

&#x1f60a;博主目前也在学习&#xff0c;有错误欢迎指正&#x1f60a; &#x1f308;保持热爱 奔赴星海&#x1f308; 文章目录一、题目1、题目描述3、原题链接二、解题报告1、思路分析2、代码详解三、本题知识一、题目 1、题目描述 输入格式&#xff1a; 输入的第一行为一…

Java并发编程实战之互斥锁

文章目录Java并发编程实战之互斥锁如何解决原子性问题&#xff1f;锁模型Java synchronized 关键字Java synchronized 关键字 只能解决原子性问题&#xff1f;如何正确使用Java synchronized 关键字&#xff1f;锁和受保护资源的合理关联关系死锁预防死锁破坏占有且等待条件破坏…

字节一面:TCP 三次握手,问的好细!

大家好&#xff0c;我是小林。 有位读者在面试字节时&#xff0c;被问到这么个问题&#xff1a; 概括起来&#xff0c;是这两个问题&#xff1a; TCP 三次握手中&#xff0c;客户端收到的第二次握手中 ack 确认号不是自己期望的&#xff0c;会发生什么&#xff1f;是直接丢弃…

1024程序员节:从关注自身健康开始

今天是1024程序员节&#xff0c;我们已经历经了尽三年的疫情&#xff0c;健康是我们最应该关注的事情&#xff0c;在这个特别的日子里&#xff0c;希望程序员们都能更加爱惜自己的身体&#xff0c;少加班&#xff0c;多锻炼。 健身不仅是保持健康体魄的关键要素之一&#xff0c…

基于ssm高校科研管理系统-计算机毕业设计源码+LW文档

【摘 要】高校科研管理是一项重要而又繁琐的工作&#xff0c;有效的信息管理平台可以大大缓解科研管理压力&#xff0c;减少工作量。本文以石河子大学信息科学与技术学院为应用背景&#xff0c;开发教师教学信息与论文信息交流平台。该系统能对科研成果和课题进行较为全面的管理…

第十三届蓝桥杯C++B组国赛I题——齿轮 (AC)

目录1.齿轮1.题目描述2.输入格式3.输出格式4.样例输入5.样例输出6.样例说明6.数据范围7.原题链接2.解题思路3.Ac_code1.齿轮 1.题目描述 这天, 小明在组装齿轮。 他一共有 nnn 个齿轮, 第 iii 个齿轮的半径为 rir_{i}ri​, 他需要把这 nnn 个齿轮按一定 顺序从左到右组装起来…

[附源码]Java计算机毕业设计SSM公司办公自动化系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

10个实用的CSS样式之悬浮卡片

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位喜欢写作&#xff0c;计科专业大三菜鸟 &#x1f3e1;个人主页&#xff1a;starry陆离 &#x1f4da;订阅专栏&#xff1a;『10个实用的CSS样式』 10个实用的CSS样式之悬浮卡片1.简介2.布局设计3.样式美化3.1body美化3.2c…

隔离技术之端口隔离

端口隔离技术 端口隔离主要应用在同一个vlan内&#xff0c;不同的用户之间不可互相访问 好处&#xff1a; 可以避免广播风暴&#xff0c;节约了vlan的资源&#xff0c;提高了用户之间的安全性 比如一个用户的电脑中病毒了&#xff0c;不会影响到其他用户 端口隔离是基于端口&…

网络原理——No.2 传输层_TCP的连接管理(画图理解三次握手与四次挥手)

JavaEE传送门JavaEE 网络原理——传输层_UDP 网络原理——No.1 传输层_TCP的确认应答机制与超时重传 目录TCP的连接管理三次握手(建立连接)四次挥手(断开连接)TCP的连接管理 描述的就是 TCP 建立链接和断开链接的过程 TCP 的链接, 只是一个 “逻辑上的” “虚拟的连接” (只要…

qt学习笔记4:QMainWindow 菜单栏、工具栏、状态栏、铆接部件、

在创建基类的时候&#xff0c;有三大选择&#xff0c;一个是QWidge 空窗口&#xff0c; 另一个就是QMainWindow QMainWindow是一个为用户提供主窗口的类&#xff0c;包含一个菜单栏&#xff0c;多个工具栏&#xff0c;多个链接部件&#xff0c; 一个状态栏以及一个中心部件&…

《数据结构》(六)八大排序(上)

生活中大家从小到大处处可见排队&#xff0c;但是排队有哪些快速的方法你了解吗&#xff1f; 八大排序排序的基本概念插入排序直接插入排序基本思想代码直接插入排序总结希尔排序基本思想代码希尔排序总结选择排序直接选择排序基本思想&#xff1a;代码直接选择排序总结堆排序堆…

大数据基础之java常用API一

常用API1. Object类2. String类String案例1. Object类 构造方法空参构造全参构造 Object类: 是所有类的基类,或者说公共父类,每个类都直接或者间接的继承自Object,所以该类中有的方法,其他类中都有 构造方法: public Object(); 所有类的构造方法中都会默认调用super() 会逐级调…

C#里在子窗口与父窗口之间进行数据传送

在C#里经常需要在子窗口与父窗口之间进行数据传送,或者调用,虽然有很多方法可以实现,但是采用委托还是比较简单和直接的方式。 所以这次针对委托来演示一下怎么样实现这种功能。 下面先来创建一个带两窗口的例子,如下图所示: 接着来看一下,创建父窗口的代码: namespace…

【C++笔试强训】第十一天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6; &…