深入ftrace kprobe原理解析

linux kprobe使用 kprobe 阅读详情

Linux krpobe调试技术是内核开发者专门为了编译跟踪内核函数执行状态所涉及的一种轻量级内核调试技术,利用kprobe技术,内核开发人员可以在内核的绝大多数指定函数中动态插入探测点来收集所需的调试状态信息而基本不影响内核原有的执行流程。本章的是基于5.15内核来学习kprobe的相关内容,主要包括以下内容

  • kprobe技术产生的背景
  • 主要针对ARM64 kpobes的技术实现原理,实现方式
  • 对于ftrace中的kprobe是如何实现的
  • kpobe可以做什么,可以解决哪些问题

1 kprobe技术背景

对于开发者,我们在内核或者模块的调试过程中,往往需要知道一些函数的执行流程,何时被调用,执行过程中的入参和返回值是什么等等,比较简单的做法就是在内核代码对应的位置添加日志信息,但是这种方式往往需要重新编译内核或者模块,烧写或者替换模块,操作较为复杂甚至可能会破坏原有的代码执行过程。

所以针对这种情况,内核提供了一种调试机制kprobe,提供了一种方法,能够在不修改现有代码的基础上,灵活的跟踪内核函数的执行。

它的基本工作原理是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。

kprobe 是一种动态调试机制,用于debugging,动态跟踪,性能分析,动态修改内核行为等,2004年由IBM发布,是名为Dprobes工具集的底层实现机制[1][2],2005年合入Linux kernel。probe的含义是像一个探针,可以不修改分析对象源码的情况下,获取Kernel的运行时信息。kprobe一直在X86系统上使用,ARM64的平台支持在2015年合入kernel [8],kprobe提供了三种形式的探测点,

  • 一种最基本的kprobe:能够在指定代码执行前,执行后进行探测,但此时不能访问被探测函数内的相关变量信息,内核代码的任何指令处
  • 一种是jprobe:用于探测某一个函数的入口,并且能够访问对应的函数参数,这个目前已经不再使用
  • 一种是kretprobe:用于完成指定函数返回值的探测功能,内核函数的退出点

其中最基本的就是kprobe机制,jprobe以及kretprobe的实现都依赖于kprobe,kprobe是linux内核的一个重要的特性,是其他内核调试工具(perf,systemtap)的基础设施,同时内核BPF也是依赖于kprobe,它是利用指令插桩原理,截获指令流,并在指令执行前后插入hook函数,其如下:

在这里插入图片描述

所以kprobe的实现原理是把制定地址(探测点)的指令替换成一个可以让cpu进入debug模式的指令,使执行路径暂停,跳转到probe处理函数后收集,修改信息,然后再跳转回来继续执行的过程。

如果需要知道内核函数是否被调用、被调用上下文、入参以及返回值,比较简单的方式是加printk,但是效率低,利用kprobe技术,用户可以自定义自己的回调函数,可以再几乎所有的函数中动态插入探测点。

首先kprobe是最基本的探测方式,是实现后两种的基础,它可以再任意的位置放置探测点(就连函数内部的某条指令处也可以),提供了探测点的调用前,调用后和内存访问出错3种回调方式,分别是- -

  • per_handler:将在被探测指令执行前回调
  • post_handler:将在被探测指令执行完毕后回调(注意不是被探测函数)

对于kretprobe从名字就可以看出,它同样是基于kprobe实现,用于获取被探测函数的返回值

2 ARM64 kprobe的工作原理

实现kprobes 接口的数据结构和函数已在文件<include/linux/kprobes.h>中定义。下面的数据结构描述了一个 kprobe

struct kprobe {
	struct hlist_node hlist;  /* 所有注册的kprobe都会添加到kprobe_table哈希表中,hlist成员用来链接到某个槽位中 */

	/* list of kprobes for multi-handler support */
	struct list_head list;     /* 链接一个地址上注册的多个kprobe */

	/*count the number of times this probe was temporarily disarmed */
	unsigned long nmissed;    /* 记录当前的probe没有被处理的次数 */
  /* 一个是用户在注册前指定探测点的基地址(加上偏移得到真实的地址),
   * 另一个是在注册后保存探测点的实际地址, 如果没有指定,必须指定探测的位置的符号信息 */
	/* location of the probe point */
	kprobe_opcode_t *addr;    /* 探测点地址 */
  /* 名称和地址不能同时指定,否则注册时会返回EINVAL错误 */
	/* Allow user to indicate symbol name of the probe point */
	const char *symbol_name;  /* 探测点函数名 */

	/* Offset into the symbol */
	unsigned int offset;      /* 探测点在函数内的偏移 */
  /* 断点异常触发之后,开始单步执行原始的指令之前被调用 */
	/* Called before addr is executed. */
	kprobe_pre_handler_t pre_handler;  
  /* 在单步执行原始的指令后会被调用 */
	/* Called after addr is executed, unless... */
	kprobe_post_handler_t post_handler;  /* 后处理函数 */
  /* 原始指令,在被替换为断点指令(X86下是int 3指令)前保存。 */
	/* Saved opcode (which has been replaced with breakpoint) */
	kprobe_opcode_t opcode;            

	/* copy of the original instruction */
	struct arch_specific_insn ainsn;   /* 保存平台相关的被探测指令和下一条指令 */

	/*
	 * Indicates various status flags.
	 * Protected by kprobe_mutex after this kprobe is registered.
	 */
	u32 flags;                        /* 状态标记 */
};

所以对于kprobe的使用比较简单,只需要指定探测点地址,或者使用符号名+偏移的方式,定义xxx_handler,注册即可,注册后,探测指令被替换,可以使用kprobe_enable/disable函数动态开关

2.1 kprobe初始化

下面我们来看看 kprobe 的初始化过程,kprobe 的初始化由 init_kprobes() 函数kernel/kprobes.c实现:

static int __init init_kprobes(void)
{
	int i, err = 0;
  /* 初始化用于存储 kprobe 模块的哈希表 */
	/* FIXME allocate the probe table, currently defined statically */
	/* initialize all list heads */
	for (i = 0; i < KPROBE_TABLE_SIZE; i++)
		INIT_HLIST_HEAD(&kprobe_table[i]);
  /* 初始化 kprobe 的黑名单函数列表(不能被 kprobe 跟踪的函数列表) */
	err = populate_kprobe_blacklist(__start_kprobe_blacklist,
					__stop_kprobe_blacklist);
	if (err) {
		pr_err("kprobes: failed to populate blacklist: %d\n", err);
		pr_err("Please take care of using kprobes.\n");
	}

	if (kretprobe_blacklist_size) {
		/* lookup the function address from its name */
		for (i = 0; kretprobe_blacklist[i].name != NULL; i++) {
			kretprobe_blacklist[i].addr =
				kprobe_lookup_name(kretprobe_blacklist[i].name, 0);
			if (!kretprobe_blacklist[i].addr)
				printk("kretprobe: lookup failed: %s\n",
				       kretprobe_blacklist[i].name);
		}
	}

	/* By default, kprobes are armed */
	kprobes_all_disarmed = false;

#if defined(CONFIG_OPTPROBES) && defined(__ARCH_WANT_KPROBES_INSN_SLOT)
	/* Init kprobe_optinsn_slots for allocation */
	kprobe_optinsn_slots.insn_size = MAX_OPTINSN_SIZE;
#endif
  /* 初始化CPU架构相关的环境(x86架构的实现为空) */
	err = arch_init_kprobes();
	if (!err)
		err = register_die_notifier(&kprobe_exceptions_nb); /* 注册die通知链*/
	if (!err)
		err = register_module_notifier(&kprobe_module_nb);  /* 注册模块通知链 */
 
	kprobes_initialized = (err == 0);

	if (!err)
		init_test_probes();
	return err;
}
early_initcall(init_kprobes);

2.2 注册一个kprobe实例

内核是通过register_kprobe完成一个kprobe实例的注册,其详细实现过程在kernel/kprobes.c,如下所示

/* struct kprobe结构体,里面包含指令地址或者函数名地址和函数内偏移 */
int register_kprobe(struct kprobe *p)
{
	int ret;
	struct kprobe *old_p;
	struct module *probed_mod;
	kprobe_opcode_t *addr;
  /* 获取被探测点的地址,指定了sysmbol name,则kprobe_lookup_name从kallsyms中获取;
   * 指定了offsete + address,则返回address + offset */
	/* Adjust probe address from symbol */
	addr = kprobe_addr(p);
	if (IS_ERR(addr))
		return PTR_ERR(addr);
	p->addr = addr;
  /* 判断同一个kprobe是否被重复注册 */
	ret = warn_kprobe_rereg(p);
	if (ret)
		return ret;

	/* User can pass only KPROBE_FLAG_DISABLED to register_kprobe */
	p->flags &= KPROBE_FLAG_DISABLED;
	p->nmissed = 0;
	INIT_LIST_HEAD(&p->list);
  /* 1. 判断被注册的函数是否位于内核的代码段内,或位于不能探测的kprobe实现路径中 
   * 2. 判断被探测的地址是否属于某一个模块,并且位于模块的text section内
   * 3. 如果被探测的地址位于模块的init地址段内,但该段代码区间已被释放,则直接退出 */
	ret = check_kprobe_address_safe(p, &probed_mod);
	if (ret)
		return ret;

	mutex_lock(&kprobe_mutex);
  /* 判断在同一个探测点是否已经注册了其他的探测函数 */
	old_p = get_kprobe(p->addr);
	if (old_p) {
		/* Since this may unoptimize old_p, locking text_mutex. */
    /* 如果已经存在注册过的kprobe,则将探测点的函数修改为aggr_pre_handler
     * 将所有的handler挂载到其链表上,由其负责所有handler函数的执行 */
		ret = register_aggr_kprobe(old_p, p);
		goto out;
	}

	cpus_read_lock();
	/* Prevent text modification */
	mutex_lock(&text_mutex);
  /* 分配特定的内存地址用于保存原有的指令 */
	ret = prepare_kprobe(p);
	mutex_unlock(&text_mutex);
	cpus_read_unlock();
	if (ret)
		goto out;
  /* 将kprobe加入到相应的hash表内 */
	INIT_HLIST_NODE(&p->hlist);
	hlist_add_head_rcu(&p->hlist,
		       &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
  /* 将探测点的指令码修改为arm_kprobe */
	if (!kprobes_all_disarmed && !kprobe_disabled(p)) {
		ret = arm_kprobe(p);
		if (ret) {
			hlist_del_rcu(&p->hlist);
			synchronize_rcu();
			goto out;
		}
	}

	/* Try to optimize kprobe */
	try_to_optimize_kprobe(p);
out:
	mutex_unlock(&kprobe_mutex);

	if (probed_mod)
		module_put(probed_mod);

	return ret;
}

其主要包括以下几个步骤:

  • 探测点地址的计算: 该函数主要用来指定位置注册探测点,首先使用kprobe_addr计算需要插入探测点的地址,这个会设置到kprobe的addr成员,注册后通过这个成员和offset就可以拿到探测位置的地址。利用这个特性,你可以通过kprobe来获取内核中某一个函数的运行时地址
    • 如果没有指定探测地址,而是指定了符号信息,则调用kprobe_lookup_name在内核符号表中查找符号对应的地址,在找到对应的符号地址后,加上偏移就得到探测点的实际位置
    • 如果只是指定了探测点的地址,则会将这个地址直接加上偏移返回
  • 检测探测点地址: 计算探测点的地址后,接下来就需要检查这个地址是否可以被探测
    • 跟踪点是否已经被 ftrace 跟踪,如果是就返回错误(kprobe 与 ftrace 不能同时跟踪同一个地址)
    • kprobe只能用作内核函数的探测,所以在注册前必须检查探测点的地址是否是在内核地址空间中,探测点的地址要么在内核影响中(_stext 和 etext之间,如果是在相同启动阶段(sinittext 和_einittext之间),具体实现在kernel_text_address代码中
    • 跟踪点是否在 kprobe 的黑名单中,如果是就返回错误
    • 如果探测点的地址在一个内核模块中,需要增加对该模板的引用,以防止模块提前卸载,如果模块已经开始卸载,此时也是不能注册探测点

在这里插入图片描述

  • 保存被跟踪指令的值: 内核通过调用prepare_kprobe函数来保持被跟踪的指令,而 prepare_kprobe() 最终会调用 CPU 架构相关的 arch_prepare_kprobe() 函数来完成任务
  • 注册kprobe: 系统中所有的kprobe实例都保存在kprobe_table这个哈希表中,
    • 如果调用get_kprobe()能找到一个kprobe实例,说明已经在当前的探测点注册了一个kprobe,这种情况下会调用register_aggr_kprobe()来处理。
    • 如果当前的探测点没有注册过kprobe,则调用arm_kprobe将被探测位置的指令保持到kprobe的ainsn成员中,并且被探测位置的第一条指令保存到opcode成员中

对于arch_prepare_kprobe,看指令是否是一些分支等特殊指令,需要特别处理。如果是正常可以probe的指令,调用arch_prepare_ss_slot把探测点的指令备份到slot page里,把下一条指令存入struct arch_probe_insn结构的restore成员里,在post_handler之后恢复执行。

arch_prepare_krpobe无误后把kprobe加入kprobe_table哈希链表。

然后调用arch_arm_kprobe替换探测点指令为BRK64_OPCODE_KPROBES指令。

int __kprobes arch_prepare_kprobe(struct kprobe *p)
{
	unsigned long probe_addr = (unsigned long)p->addr;
  /* 地址应该为4的整数倍 */
	if (probe_addr & 0x3)
		return -EINVAL;

	/* copy instruction */
	p->opcode = le32_to_cpu(*p->addr);  /* 大端小端转换,将地址进行转换成PC能识别的地址 */
  /* 检测地址是否在异常代码段中 */
	if (search_exception_tables(probe_addr))
		return -EINVAL;
  /* 取出探测点的汇编指令 */
	/* decode instruction */
	switch (arm_kprobe_decode_insn(p->addr, &p->ainsn)) {
	case INSN_REJECTED:	/* insn not supported */
		return -EINVAL;
  /* 异常处理 */
	case INSN_GOOD_NO_SLOT:	/* insn need simulation */
		p->ainsn.api.insn = NULL;
		break;

	case INSN_GOOD:	/* instruction uses slot */
		p->ainsn.api.insn = get_insn_slot();
		if (!p->ainsn.api.insn)
			return -ENOMEM;
		break;
	}

	/* prepare the instruction */
	if (p->ainsn.api.insn)
		arch_prepare_ss_slot(p);   /* 将指令存放到slot中,记录吓一条指令到p->ainsn.api.insn */
	else
		arch_prepare_simulate(p);  /* 异常处理,如分支指令特殊处理 */

	return 0;
}

整个过程如下图所示:

在这里插入图片描述

最终会调用arm_kprobe,将指令3替换成一条BRK64异常处理指令,当CPU执行到这个跟踪点的时候,将会触发断点中断,这时候就会走到异常处理函数中,对于x86,这个是一条int 3指令,我们来看看针对ARM64,是如何处理的,其最终会调到arch_arm_kprobe,最终会替换成BRK64_OPCODE_KPROBES指令。

/* arm kprobe: install breakpoint in text */
void __kprobes arch_arm_kprobe(struct kprobe *p)
{
	void *addr = p->addr;               /* 原地址 */
	u32 insn = BRK64_OPCODE_KPROBES;    /* 替换后的指令 */

	aarch64_insn_patch_text(&addr, &insn, 1);
}

在这里插入图片描述

2.3 触发kprobe探测和回调

kprobe的触发和处理是通过brk exception和single step单步exception执行的,每次的处理函数中会修改被异常中断的上下文(struct pt_regs)的指令寄存器,实现执行流的跳转。ARM64对于异常处理的注册在arch/arm64/kernel/debug-monitors.c, 是arm64的通用debug模块,kgdb也基于这个模块。请参考Query: ARM64: Behavior of el1_dbg exception while executing el0_dbg

void __init debug_traps_init(void)
{
	/* 单步异常处理 */
	hook_debug_fault_code(DBG_ESR_EVT_HWSS, single_step_handler, SIGTRAP,
			      TRAP_TRACE, "single-step handler");
  /* 断点异常处理 */
	hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,
			      TRAP_BRKPT, "BRK handler");
}

通过hook_debug_fault_code动态定义了异常处理的钩子函数brk_handler,它将在断点异常处理函数中被调用

hook_debug_fault_code是替换arch/arm64/mm/fault.c 中的debug_fault_info异常表项:

在这里插入图片描述

对于ARM64的异常处理,当brk断点异常触发后悔执行不同的回调处理,进入异常会跳转到arch/arm64/kernel/entry.S的sync异常处理,此处会跳转到el1_sync

在这里插入图片描述

将 entry_handler 1, t, 64, sync宏展开得到调用el1t_64_sync_handler的处理函数,在arch/arm64/kernel/entry-common.c中处理,是通过read_sysreg(esr_el1)来处理对应的异常

在这里插入图片描述

最终会调用do_debug_exception处理debug异常

在这里插入图片描述

sr_el1的bit27~bit29指示了debug异常类型,对应debug_fault_info数组的索引,此处可知debug异常类型为0x6,对应DBG_ESR_EVT_BRK,由初始化函数debug_traps_init可知inf->fn为brk_handler

在这里插入图片描述

brk_handler会调用call_break_hook,它实际是根据具体的某种断点异常类型来回调不同的hook,主要是根据ESR_EL1.ISS.Comment进行区分,也就是不同的ESR_EL1.ISS.Comment对应不同的hook。

在这里插入图片描述

在初始化时register_kernel_break_hook会向kernel_break_hook链表注册不同的hook,这包括kprobes_break_hook和kprobes_break_ss_hook。list_for_each_entry_rcu(hook, list, node)主要通过遍历kernel_break_hook链表,根据debug断点异常类型找到匹配的hook。

在这里插入图片描述

在这里插入图片描述

可以看出kprobe_handler里先是进入pre_handler,然后通过setup_singlestep设置single-step相关寄存器,为下一步执行原指令时发生single-step异常做准备

在这里插入图片描述

2.4 单步执行

进入异常态后,首先执行pre_handler,然后利用CPU提供的单步调试(single-step)功能,设置好相应的寄存器,将下一条指令设置为插入点处本来的指令,从异常态返回

这个里面使用reenter检查机制,对于SMP,中断等可能有kprobe的重入,允许kpobe发生嵌套

在这里插入图片描述

setup_singlestep() 执行完毕后,程序继续执行保存的被探测点的指令,由于开启了单步调试模式,执行完指令后会继续触发异常,单步执行探测点的指令后,会触发单步异常,进入single_step_handler,调用kprobe_breakpoint_ss_handler,主要任务是恢复执行路径,调用用户注册的post_handler

在这里插入图片描述

kprobe的实现原理是把指定地址(探测点)的指令替换成一个可以让cpu进入debug模式的指令,使执行路径暂停,跳转到probe 处理函数后收集、修改信息,再跳转回来继续执行。

X86中使用的是int3指令,ARM64中使用的是BRK指令进入debug monitor模式。

在这里插入图片描述

3 kprobe event实现原理

首先我们跟function一样,从我们的配置开始,krpobe event和功能一样,那么大部分的实现是一样的,最关键的不同就是怎么使用新的插桩方法来创建event。使用向“/sys/kernel/debug/tracing/kprobe_events”文件中echo命令的形式来创建krpobe event。来查看具体的代码实现:

在这里插入图片描述

经过层层调用,最终到__trace_kprobe_create函数,其主要的实现如下:

在这里插入图片描述

对于alloc_trace_kproe,可以看到kretprobe模式下的桩函数:kretprobe_dispatcher(),而kprobe模式下的插桩函数为kprobe_dispatcher

在这里插入图片描述

其最终也会通过__register_trace_kprobe注册kprobe和kpretprobe,其最终的原理也是基本类似

4 kprobe的使用方法

最早的时候,使用kprobe一般都是编写内核驱动,在模块中定义pre-handler和post-handler函数,然后调用kprobe的API(register_kprobe)来进行注册kprobe。加载模块后,pre-handler和post-handler中的printk就会打印定制的信息到系统日志中,目前有三种使用kporbe的接口

  • kprobe API:使用register_kprobe
  • 基于Ftrace的/sys/kernel/debug/tracing/kprobe_events接口,通过写特定的配置文件
  • perf_event_open:通过perf工具,perf 的probe命令提供了添加动态探测点的功能, 参看 kernel/tools/perf/Documentation/perf-probe.txt,
  • 在最新的内核上,BPF tracing也是通过这种方式,后面再学习这种方法

kprobes的最大使用者都是一些tracer的前端工具,比如perf、systemtap、BPF 跟踪(BCC和bpftrace)

由于第一种方式灵活而且功能更为强大,对于方法一,大家请参考示例

要编写一个 kprobe 内核模块,可以按照以下步骤完成:

  • 第一步:根据需要来编写探测函数,如 pre_handler 和 post_handler 回调函数。
  • 第二步:定义 struct kprobe 结构并且填充其各个字段,如要探测的内核函数名和各个探测回调函数。
  • 第三步:通过调用 register_kprobe 函数注册一个探测点。
  • 第四步:编写 Makefile 文件。
  • 第五步:编译并安装内核模块。

对于方式二,用户通过/sys/kernel/debug/tracing/目录下的trace等属性文件来探测用户指定的函数,用户可添加kprobe支持的任意函数并设置探测格式与过滤条件,无需再编写内核模块,使用更为简便,但需要内核的debugfs和ftrace功能的支持,详细的请参考内核文档kprobetrace

使用前确定内核CONFIG打开:CONFIG_KPROBE_EVENT=y

  • /sys/kernel/tracing/kprobe_events:添加断点接口
  • /sys/kernel/tracing/events/kprobes/enabled:断点使能开关
  • /sys/kernel/tracing/trace:查看trace日志接口

4.1 查看"vfs_open"当前打开文件名

如果你使用了“‘p:’ or ‘r:’+event name” > kprobe_events命令,新的kprobe event将会被添加,可以看到新events对应的文件夹tracing/events/kprobes/,包含‘id’, ‘enabled’, ‘format’ and ‘filter’文件。

在这里插入图片描述

  • enable:使能
  • filter:过滤想要的信息
  • trigger:事件发生时触发其他功能,例如function功能
  • format:环形队列缓冲区的格式
  • id: event对应的id

在这里插入图片描述

echo 1 > /sys/kernel/tracing/events/kprobes/myprobe/enable
echo 1 > /sys/kernel/tracing/tracing_on

在这里插入图片描述

要查看哪些进程触发了这些kprobe,可以通过trace、trace_pipe接口查看,输出格式如下,最左边是进程名,如果是<…>,可能是因为cat的时候,那个进程号对应的进程已经不存在了,第二个是进程PID,触发kprobe的时候记录的。FUNCTION就是触发的那个kprobe的名字,后面括号里是触发的时候代码位置,如果是“r”类型的kprobe,会显示返回到了什么代码位置。代码位置中的行号是反汇编对应的行号。

# 设置kprobe规则,获取vfs_open函数第一个参数path中的文件name 
cd /sys/kernel/tracing
echo 'p vfs_open name=+0x38(+0x8($arg1)):string namep=+0(+0x28(+0x8($arg1))):string' > ./kprobe_events 
# 使能上述的kprobe 
echo 1 > ./events/kprobes/p_vfs_open_0/enable 
# 使能数据写入到 Ring 缓冲区 
echo 1 > tracing_on

在这里插入图片描述

通过offset和类型打印,实现结构体内部成员的打印,但是需要知道寄存器和参数的对应关系和结构体成员的偏移。[13]提到了新的function_event机制,可以直接传递参数名。例如我们想获取net_device的stats信息,获取数据结构偏移的例子:打印ip_rcv的网络设备名和收发包数

在这里插入图片描述

$ aarch64-linux-gnu-gdb vmlinux
(gdb) ptype/o struct net_device

在这里插入图片描述

gdb) print (int)&((struct net_device *)0)->stats
$7 = 296

cd /sys/kernel/debug/tracing/
echo 'p:net ip_rcv name=+0(%x1):string rx_pkts=+296(%x1):u64 tx_pkts=+280(%x1):u64 ' > kprobe_events
echo 1 > events/kprobes/enable

4.2 设置了一个kretprobe,用来记录返回值

root@rlk:/sys/kernel/tracing# echo 0 > tracing_on
root@rlk:/sys/kernel/tracing# echo 0 > ./events/kprobes/p_vfs_open_0/enable
root@rlk:/sys/kernel/tracing# echo 'p vfs_open name=+0x38(+0x8($arg1)):string namep=+0(+0x28(+0x8($arg1))):string' > ./kprobe_events
root@rlk:/sys/kernel/tracing# echo 'r vfs_open ret_val=$retval' >> kprobe_events
root@rlk:/sys/kernel/tracing# echo 1 > events/kprobes/p_vfs_open_0/enable
root@rlk:/sys/kernel/tracing# echo 1 > events/kprobes/r_vfs_open_0/enable
root@rlk:/sys/kernel/tracing# echo 1 > tracing_on
root@rlk:/sys/kernel/tracing# echo 0 > tracing_on
root@rlk:/sys/kernel/tracing# cat trace_pipe 

在这里插入图片描述

4.3 filter:捕获"vfs_open"查看指定文件的信息的事件

# 设置过滤条件,name中包含test字段
echo 'name ~ "*test*"' > ./events/kprobes/p_vfs_open_0/filter

在这里插入图片描述

4.4 trigger:包含"test"字段的文件的事件会触发"stacktrace"堆栈打印

# 包含"test"字段的文件的事件会触发"stacktrace"堆栈打印
echo 'stacktrace if name ~ "*test*"' > ./events/kprobes/p_vfs_open_0/trigger  

在这里插入图片描述

5 总结

至此,我们知道Kprobe实现的本质是breakpoint和single-step的结合,这一点和大多数调试工具一样,比如kgdb/gdb。实现动态内核的注入,其主要流程如下:

  • 当 kprobe 被注册后,内核会将对应地址的指令进行拷贝并替换为断点指令(比如 X86 中的 int 3)
  • 随后当内核执行到对应地址时,中断会被触发从而执行流程会被重定向到我们注册的 pre_handler 函数
  • 当对应地址的原始指令执行完后,内核会再次执行 post_handler从而实现指令级别的内核动态监控。也就是说,kprobe 不仅可以跟踪任意带有符号的内核函数,也可以跟踪函数中间的任意指令。

6 参考文档

Linux 内核调试利器 | kprobe 的使用

Linux内核调试利器|kprobe 原理与实现

Linux内核调试技术——kprobe使用与实现(四)

trace系列4 - kprobe学习笔记

【原创】Kernel调试追踪技术之 Kprobe on ARM64

https://evilpan.com/2022/01/03/kernel-tracing/

Linux内核调试技术——kretprobe使用与实现

浅析Linux追踪技术之kprobe:基于kprobes的Event Tracing 常规的Event Tracing(事件追踪)是通过散落在Linux内核代码各处的Tracepoint来实现的,这些Tracepoint数量有限,在出现问题时,往往不能覆盖到要追踪的函数;而利用kprobes机制,则可以实现对所有支持kprobes插入的代码点进行追踪。这种方式是 kprobe 和 ftrace 结合使用,通过 kprobe 来优化 ftrace 来跟踪函数的调用。 阅读详情

相关推荐

通过Ftrace使用kprobe技术

Linux内核kprobe调试技术分析摘要 本文介绍了Linux内核5.10版本中的kprobe调试技术,这是一种轻量级的内核函数跟踪方法。kprobe允许动态插入探测点,收集函数参数、局部变量和执行时间等信息。文章重点讲解了基于ftrace的kprobe使用方法,包括配置选项、事件节点操作和具体使用示例。详细说明了kprobe事件添加的语法格式,包括函数探测(p)、返回探测(r)的参数设置方法,以及寄存器、内存地址、栈空间等数据的获取方式。通过echo命令演示了如何添加sys_openat2等函数的探测点

lunhui2016的博客 990

Linux kprobe的使用

Linux kprobe的简单使用

小立仔 5331

深入解析Linux热补丁技术:从kprobe到ftrace的实现原理与架构限制

本文深入解析Linux热补丁技术(Livepatch)的实现原理与架构限制,详细介绍了从kprobe的动态插桩到ftrace的函数追踪技术。通过分析热补丁的工作流程、一致性模型及不同架构的支持差异,帮助开发者理解如何在运行时无缝替换内核函数,确保高可用服务器环境中的系统稳定性。

weixin_30352645的博客 423

Linux kprobe原理

Linux kprobe原理,以及其优化简介

小立仔 3974

Linux内核追踪实战:ftrace、perf与eBPF定位性能瓶颈与异常

Linux系统运行中的性能下降、内核态CPU占用异常或驱动行为诡异,常让开发者束手无策。内核追踪技术通过动态插桩与静态埋点,在不修改代码、不重启系统的前提下,深入内核函数调用路径,精准定位问题根因。ftrace作为内核自带的轻量级追踪框架,可用于观察函数调用关系;perf基于硬件计数器和采样,擅长捕捉CPU热点;eBPF则提供灵活的动态观测能力,配合kprobe、tracepoint、kallsyms等机制,可覆盖从性能分析到安全排查的广泛场景。理解探针原理、符号解析与内核配置项,有助于在实际工程中快速选型

weixin_30512965的博客 240

Linux内核追踪与分析:ftrace、perf、kprobe与eBPF实战指南

在系统性能优化与故障排查中,Linux内核的可观测性一直是工程师关注的焦点。从传统的静态打印到动态追踪,内核提供了ftrace、perf、kprobe/uprobe以及eBPF等一系列工具,帮助开发者定位CPU热点、调度延迟、IO等待等深层问题。理解这些技术的工作原理与适用场景,能够显著提升排障效率。本文结合实战案例,梳理了从函数调用追踪到性能采样、再到可编程观测的完整路径,并给出了工具选型建议与常见踩坑点,为Linux运维、驱动开发及内核研发人员提供一份实用的技术地图。

weixin_42531128的博客 267

linux kprobe实现原理

linux kprobes kprobes出现了非常长的时间,最早是2.6.9版本中,最早能够追溯到2004年,距离现在15年,一直没有为人所知,但却是隐藏在诸多技术后的一个基础组件,例如ftrace,perf,SystemTap,LTTng还有最近非常火热的ebpf. kprobes是什么? 它是kernel probes(我翻译成探针),是一种debug机制,它是由IBM开发的,最初计划用来和...

faxiang1230的专栏 1829

kprobe/kretprobe内核动态插桩原理与实战详解

内核动态追踪是性能分析与故障排查的关键技术手段,其中kprobe/kretprobe作为经典的动态插桩机制,允许在不重新编译内核、不重启系统的前提下,于指定函数入口、返回点甚至任意指令位置挂载回调,从而观测入参、返回值与调用耗时。其底层原理依赖断点指令替换与返回地址重定向,配合寄存器现场保存,实现精准的运行时观测。相比eBPF,kprobe对老内核更友好,无需复杂工具链即可完成系统调用追踪、内核函数耗时统计、失败率分析等任务,是内核开发者、SRE与性能调优工程师的高效工具。本文从kprobe的核心机制入手,

weixin_33552698的博客 276

深入探索Linux kprobe机制:从Event Tracing到动态内核追踪实践

本文深入解析Linux kprobe机制,从Event Tracing基础到动态内核追踪实践,详细介绍了kprobe作为动态追踪工具的核心原理与应用场景。通过实战案例展示如何利用kprobe进行性能分析、异常捕获和调用链路追踪,并与ftrace、perf等工具协同工作,提升Linux内核调试效率。

weixin_26905487的博客 386

Linux内核模块GPL符号限制解析:从Unknown symbol到桥接方案

Linux内核模块的动态加载机制中,符号导出与许可证声明是决定模块能否运行的关键。MODULE_LICENSE宏用于声明模块许可证,而EXPORT_SYMBOL_GPL导出的符号仅允许GPL兼容模块引用。当非GPL模块试图静态引用这些符号时,加载器会拒绝解析,产生Unknown symbol错误。这一设计旨在平衡开源精神与闭源驱动的现实需求,通过分级开放接口实现生态兼容。工程实践中,闭源组件或厂商驱动常面临GPL-only符号的访问限制,社区因此出现了符号桥接(跳板模块)、运行时动态解析等应对思路。理解这些

weixin_42116058的博客 300

mmiotrace内核追踪实战:定位设备驱动MMIO性能瓶颈

在Linux系统调试中,设备驱动与硬件交互常通过内存映射I/O完成,而这类操作的性能问题往往难以用传统函数追踪定位。内核追踪技术提供了底层视角,通过记录CPU与设备寄存器间的每一次读写事件,开发者能够还原真实访问模式。mmiotrace作为一款专用的追踪器,依托ftrace框架捕获物理地址、访问宽度与时间戳,帮助分析驱动中的轮询过度、带宽占用或异常访问。其原理涉及环形缓冲区、中断上下文同步与per-CPU设计,实践时可通过tracefs或trace-cmd快速采集。借助事件解析脚本,可统计热点区间与读写比例

weixin_31858171的博客 99

ftrace hook 内核模块实战:从原理到代码实现

内核 hook 是安全监控与性能分析的基础技术,其核心在于在不修改源码的前提下拦截内核函数执行。传统 kprobe 基于断点异常,高频路径下开销显著;而 ftrace 利用编译期 mcount 桩与运行时指令改写,实现低开销函数级拦截。通过 ftrace_ops 注册回调,可动态启停、精准过滤,性能较 kprobe 提升近一个数量级。该技术广泛应用于内核模块热补丁、行为审计与调用链追踪等场景。本文围绕 ftrace hook 的底层机制、模块编写与实测调优展开,提供可落地的工程实践参考。

weixin_30647423的博客 243

Win64 驱动内核编程-16.WFP网络监控驱动(防火墙)

WFP驱动监控网络 WFP是微软推出来替代TDIHOOK、NDISHOOK等拦截网络通信的方案,WFP的框架非常庞大,在RING3和RING0各有一套类似的函数,令人兴奋的是,即使在R3使用WFP,也可以做到全局拦截访问网络。由于WFP的范围太广,实在难以一言概括,感...

墨鱼菜鸡 1028

Linux kprobes内核追踪全解析,PCB学习——STM32F103VET6-STM32接口部分。

kprobes是Linux内核提供的动态追踪机制,允许在不修改内核源码或重启系统的情况下,在函数的入口(kprobe)、返回处(kretprobe)或特定地址(jprobe,已弃用)插入探测点。其核心原理是通过替换目标指令为断点指令(如x86架构的。| 空处理 | 200-500 || 日志记录 | 1000-3000 || 复杂逻辑 | 5000+ |),触发陷阱后转入用户自定义的处理程序。| 操作类型 | 延迟增加(纳秒) |

2501_93981299的博客 389

基于Python实现基于遥感指数的水体提取

基于Python实现基于遥感指数的水体提取

gis收藏家的博客 6137

【UV打印机】PrintExp打印软件教程(二)-主界面介绍

PrintExp软件主界面如下图所示PrintExp软件的主界面主要包括主菜单区,工具栏区,界面显示区,状态栏。以下是上述部分的介绍。

Print World的专栏 6287

Linux内核架构与工作原理:从系统调用到eBPF调试实战

操作系统是现代软件的基石,而Linux内核作为其中最复杂的系统软件,其设计思想深刻影响着后端开发与运维实践。理解内核通常从“用户态与内核态”的隔离机制开始,系统调用成为两者通信的唯一合法通道,这种设计保证了安全与公平。内核通过宏内核架构实现高效内部协作,同时借助模块化维持灵活性;VFS虚拟文件系统、CFS调度器、页缓存等核心子系统共同构成了Linux高性能运行的底座。当线上出现CPU飙升、内存泄漏或网络延迟问题时,掌握ftrace、perf、eBPF等动态追踪工具,能极大提升排查效率。本文从架构设计到核心机

weixin_42522953的博客 314

【即插即用模块】Transformer篇 | CVPR 2025 | D2T:双域特征融合,完美适配缺陷检测/小目标检测和分割等频域细节敏感的任务!

本文提出了一种基于小波和原型增强的双域Transformer(WPFormer)用于像素级表面缺陷检测。该模型通过Dual-domain Transformer (D2T)模块融合频域和空间域特征:小波增强交叉注意力(WCA)分解高低频特征并加权平衡,增强缺陷细节捕捉;原型引导注意力(PCA)生成全局原型向量强化关键信息聚合。实验表明,该模块在缺陷检测任务中性能优于现有方法,通过频域细节增强和全局依赖建模有效提升了复杂场景下的检测效果。代码已开源,适用于对频域敏感的目标检测和分割任务。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 1367

驱动开发实践:字符设备、设备树与I2C总线驱动全解析

在嵌入式Linux系统中,设备驱动是连接内核与硬件的关键桥梁,而字符设备、设备树和I2C总线则是驱动开发中最常打交道的核心机制。理解file_operations结构体,就掌握了用户态与内核态通信的接口协议;通过设备树描述硬件资源,让驱动代码具备跨平台复用能力。这些技术不仅支撑着串口、GPIO、传感器等外设的稳定运行,也直接决定了产品从原型到量产过程中的调试效率。本文从字符设备框架的最小实现开始,剖析设备树节点与probe匹配的完整流程,并深入到I2C设备驱动的注册、通信与时序控制,最后结合动态加载、日志分

weixin_29193259的博客 230

VisionPro - 基础 - 模板匹配技术-Search/PMAlign/PatMax(6)-纹理屏蔽和重叠匹配

Note:对已经有了,1已经训练好的模板,2一组使能的自由度,3一个待测的实时图片,PatMax 就能模板匹配的给出变化的最佳描述。对一些综合的模板,这三个输入值往往是一起影响最后的变换结果信息。例如,如果我们训练了一个圆形的模板,然后搜索一个角度自由度包括一个圆,那么将会有无限个可能的角度满足要求。这些组合的模板匹配,被官方名为:退化系统,a degenerate system的结果被叫做a degenerate result 退化结果4.2 退化系统例子。

山云的专栏 1524

FPGA SoC 的 RISC-V 固件开发全攻略(三):RISC-V MCU 启动流程深入解析

本文是《FPGA SoC 的 RISC-V 固件开发全攻略》专栏第 3 篇。上一篇:第 2 篇《开发环境搭建与工具链避坑》 | 下一篇:第 4 篇《RISC-V 与 ARM Cortex-M 开发差异对比》

XavierKong的博客 39

userfaultfd内核机制解析:条件竞争漏洞利用的确定性控制

缺页异常是操作系统内存管理的核心机制,当进程访问未映射的虚拟地址时,内核通过do_page_fault分配物理页并建立映射。userfaultfd将这一过程用户态化,允许程序接管缺页事件处理权,使内核在访问特定内存区域时主动阻塞,直到用户态显式补页。这一机制将条件竞争漏洞利用从依赖时序运气的多线程冲撞,转变为可精确编排的确定性控制,广泛应用于内核漏洞研究、驱动安全测试和防御视角下的竞争窗口分析。

weixin_32430941的博客 256

图神经网络实战(6)——使用PyTorch构建图神经网络

图数据集通常比简单的连接集合更加丰富,因为节点和边可以具有表示分数、颜色、单词等特征。包含这些额外信息在输入数据中对于生成最佳嵌入至关重要。在本节中,我们将使用 Cora 和 Facebook Page-Page 数据集,首先将它们视为表格数据集,观察香草神经网络在节点特征上的表现如何。然后,尝试在神经网络中加入拓扑信息,实现图神经网络 (Graph Neural Networks, GNN) 架构:一个同时考虑节点特征和边的简单模型。最后,我们将比较两种架构的性能。

盼小辉丶的博客 2万+

无模块Linux内核植入:原理分析与检测防御实战

内核安全是服务器防护的基石,而后门技术正从传统的LKM模块加载向更隐蔽的内存级植入演进。理解内核权限模型与内存完整性机制,是构建有效防御的前提。攻击者绕过模块签名与lsmod检测,直接在内核内存中篡改系统调用表、劫持关键函数或滥用eBPF、ftrace等合法追踪机制,实现无文件、无模块的持久控制。这类技术隐蔽性极强,常规文件扫描与进程审计难以发现,给应急响应和主机入侵检测带来巨大挑战。通过校验sys_call_table完整性、监控内核代码段哈希、利用内存取证工具分析原始链表,并结合auditd与安全加固措

weixin_28622215的博客 26

SAP物料主数据归档详解(物理删除)

顾问们的回答肯定的统一的不能,只能打上删除标记。物料设置好需要删除的物料号这里我们需要删除的物料是AG88,其中,若不选“考虑无删除标记的物料”,需要将需要归档的物料MM06打删除标记。平时在操作系统过程中经常会不小心创建出很多无用的物料主数据,但是系统只能通过MM06打上删除标记,但是物料最终在系统中还是可以查询到的。6、执行已设置变式的归档程序,执行完毕,点下图中的“Job”,每次执行完归档程序会产生两条作业。然后设置好开始的参数和假脱机的参数,三个都是绿灯就可以执行归档的操作,物理上删除物料主数据。

我小时候很黑的博客 5508

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

上一篇: 深入ftrace function graph原理
下一篇: 深入ftrace uprobe原理和功能介绍
奇小葩
博客等级 码龄13年 1673粉丝 130原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值