【原创】Linux kprobe 实现原理 图文详解 (三) kprobe跳转优化

linux kprobe使用 kprobe 阅读详情

目录

一、背景介绍

二、kprobe 跳转优化 实现原理

1、跳转优化的指令槽准备阶段

2、后台线程kprobe_optimizer替换被探测指令处的BRK指令

3、优化后的kprobe探测点被执行到时的处理流程

三、kprobe跳转优化的一些限制

四、具体代码


(代码:linux 6.3.1,架构:arm64)

One look is worth a thousand words.  —— Tess Flanders

相关链接:

【原创】Linux kprobe 实现原理 图文详解 (一) kprobe实现原理

【原创】Linux kprobe 实现原理 图文详解 (二) kretprobe实现原理

【原创】Linux kprobe 实现原理 图文详解 (四) 指令替换、cpu stopper

一、背景介绍

        之前两篇文章中(kprobe实现原理、kretprobe实现原理),详细讲述了kprobe与kretprobe的实现原理。两者都有一个共同点:需要借助brk断点指令,触发 2次 同步异常,借助异常处理的流程,去执行注册的钩子函数。

        但是,频繁地触发同步异常会给性能造成严重的影响(陷入、陷出时,寄存器保存恢复等操作,会造成较大的开销)。因此,linux内核社区在2009年12月,提出了kprobe jump optimization 方案。

        该方案的思路是利用跳转指令,代替brk断点异常处理的方式,直接跳转到 kprobe注册 的 “钩子”处继续执行。该方案使得优化的kprobe会带来10倍的性能提升,以下是linux官方提供的一些测试结果:

        在2005年常见的CPU上,处理命中kprobe要花费0.5~1.0微妙;命中kretprobe比命中kprobe多花费50%~75%的时间。

        通常命中已优化的kprobe,花费0.07 ~ 0.1微妙来处理。

        本文主要聚焦arm64架构下kprobe的跳转优化实现,对于x86这种cisc架构的实现原理也是一样的,仅一些细节会有些出入,对x86下实现感兴趣的小伙伴可以自行去研究下相关代码。

二、kprobe 跳转优化 实现原理

        kprobe跳转优化方案的核心思想:摒弃brk断点指令触发异常带来的额外开销,使用相对跳转的方式,来代替异常处理流程中调用 kprobe钩子函数 的方式。大体流程如下:

        1)在 “执行到被探测指令” 时,跳转到一块内核准备好的 “detour buffer指令槽代码” 执行;

        2)在 “detour buffer指令槽代码” 中,直接执行 “kprobe注册的钩子函数”;

        3)执行完 “kprobe注册的钩子函数” 后,接着执行 “被探测指令”;

        4)最后,通过相对跳转,返回到 “被探测指令地址 + 4” 处,回到原本的执行流。

        以上过程中,并没有涉及到同步异常,通过 “相对跳转” 的方式节省了很多brk带来的开销。好了,接下来就一起看下所谓的 “kprobe跳转优化” 的详细实现原理。

        主要分成3个部分:跳转优化的指令槽准备阶段、后台线程kprobe_optimizer替换被探测指令处的BRK指令、优化后的kprobe探测点被执行到时的处理流程;

1、跳转优化的指令槽准备阶段

        1)分配一块用于存放跳转优化指令的指令槽;

        2)将跳转优化的代码拷贝至上面分配的指令槽;

        3)调整拷贝到指令槽中的 “跳转优化指令optprobe_template” 的内容,如:将这块指令中“标签2”地址上的内容修改为 “optimized_callback函数”地址,用于optprobe_template汇编代码跳转到该处执行pre_handler等操作;

        4)将 “kprobe探测指令” 内容,设置到指令槽中相应的位置(用于执行完kprobe钩子后,直接执行被探测指令);

        5)将指令槽最后一条指令,设置为 bl相对跳转指令,用于跳转回 “被探测指令地址 + 4”处;

        6)将这个要优化的kprobe任务,添加到全局optimizing_list队列中;

        7) 唤醒内核线程kprobe_optimizer,去执行执行替换的工作;

        可以看出,kprobe跳转优化分成2个步骤,第1个步骤是准备跳转优化的指令槽内容,而实际的指令替换工作是交由内核线程去负责完成的。

        上述提到的 “跳转优化指令” 如下所示:

        以上这块汇编代码的内容,会被拷贝到分配好的指令槽中,然后由arch_prepare_optimized_kprobe函数负责对这块汇编代码的内容进行一些调整。

2、后台线程kprobe_optimizer替换被探测指令处的BRK指令

        内核线程kprobe_optimizer,被唤醒后,会遍历并执行 3个 全局队列(unoptimizing_list、optimizing_list、freeing_list)中的任务。第1步中放入optimizing_list队列的任务,被取出后,会生成 1条 “bl相对跳转指令”,用于从被探测指令地址跳转到第1步准备好的指令槽处。

        这条相对跳转指令,会被设置到被探测指令处,替换上面的brk指令;这样一来,当执行到kprobe被探测指令时,程序就会跳转到上述准备好的指令槽中,继续执行了。

3、优化后的kprobe探测点被执行到时的处理流程

        假设kprobe探测的代码段如下:

        执行到优化过的kprobe探测指令后,处理逻辑如下:

        1)当程序执行到“跳转优化过的kprobe被探测指令”时, 会执行相对跳转指令, 跳转到预先设置好的指令槽处, 执行optprobe_template汇编代码

        2)optprobe_template首先会将CPU上下文保存到内核栈上, 然后通过blx指令, 跳转到提前设置好的optimized_callback函数继续运行

     3)optimized_callback函数中会设置当前CPU的kprobe状态, 然后执行原始kprobe的pre_handler函数, 执行完后ret返回到optprobe_template汇编代码中

        4)在optprobe_template中, 将一开始进入时的CPU上下文从内核栈中恢复出来, 然后执行真正的被探测指令

        5)当被探测指令执行完毕后, 跳转回 “被探测指令+4” 处继续运行原本的代码路径

三、kprobe跳转优化的一些限制

        1)只支持优化不含post_handler的kprobe;

        2)对于开启内核抢占(CONFIG_PREEMPT=n)的内核,不支持kprobe优化;

        3)对于ARM架构,“被探测指令”与“detour buffer指令槽”之间的距离不能超过32MiB(因为ARM相对跳转指令允许的范围就是32MiB);

四、具体代码

1、注册kprobe时, 准备跳转优化的汇编代码

/* For optimized_kprobe buffer */
struct kprobe_insn_cache kprobe_optinsn_slots = {
	.mutex = __MUTEX_INITIALIZER(kprobe_optinsn_slots.mutex),
	.alloc = alloc_optinsn_page,
	.free  = free_optinsn_page,
	.sym   = KPROBE_OPTINSN_PAGE_SYM,
	.pages = LIST_HEAD_INIT(kprobe_optinsn_slots.pages),
	/* .insn_size is initialized later 
	 * .insn_size = MAX_OPTINSN_SIZE	// MAX_OPTINSN_SIZE: (optprobe_template_end - optprobe_template_entry)
	 */
	.nr_garbage = 0,
};

struct arch_optimized_insn {
	/* copy of the original instructions.  Different from x86, ARM kprobe_opcode_t is u32. */
#define MAX_COPIED_INSN	DIV_ROUND_UP(RELATIVEJUMP_SIZE, sizeof(kprobe_opcode_t))
	kprobe_opcode_t copied_insn[MAX_COPIED_INSN]

	/* detour code buffer */
	kprobe_opcode_t *insn
	/*
	 * We always copy one instruction on ARM, so size will always be 4, and unlike x86, there is no need for a size field.
	 * 即: x86的arch_optimized_insn结构体中会多一个size字段
	 */
}

/*
 * Internal structure for direct jump optimized probe
 */
struct optimized_kprobe {
	struct kprobe kp					/*  */
	struct list_head list				/* list for optimizing queue */
	struct arch_optimized_insn optinsn	/* 用于跳转优化的汇编指令块 */
};

/* 全局待跳转优化kprobe队列 */
static LIST_HEAD(optimizing_list);


register_kprobe(struct kprobe *p) {
	...
	prepare_kprobe(p)
	...
	if (!kprobes_all_disarmed && !kprobe_disabled(p)) {
		arm_kprobe(struct kprobe *kp = p) {
			cpus_read_lock()
			mutex_lock(&text_mutex)
			
			__arm_kprobe(struct kprobe *p = kp) {
				/* Find the overlapping optimized kprobes. */
				struct kprobe *_p = get_optimized_kprobe(p->addr)
				if (unlikely(_p))
					/* Fallback to unoptimized kprobe */
					unoptimize_kprobe(_p, true)
				
				arch_arm_kprobe(p)
				
				### Optimize kprobe jump
				optimize_kprobe(p)	/* Try to optimize (add kprobe to a list) */
			}
			
			mutex_unlock(&text_mutex)
			cpus_read_unlock()
		}
	}
	
	/*【try_to_optimize_kprobe 函数功能】
	 * 新分配一个aggr_kprobe用于跳转优化本次注册的kprobe, 分配指令槽并设置好optprobe_tmplate跳转优化汇编代码, 然后将aggr_kprobe->optimized_kprobe对象
	 * 添加到全局optimizing_list队列并唤醒optimizing_work去执行跳转优化
	 * 
	 *【try_to_optimize_kprobe 实现逻辑】
	 * 1) 新分配一个aggr_kprobe
	 * 2) 继承kprobe探测指令地址
	 * 3) 分配用于跳转优化的指令槽, 将optprobe_template代码拷贝置该指令槽, 并设置这块跳转优化的汇编代码
	 * 4) 根据新分配aggr_kprobe对象, 找到optimized_kprobe对象
	 * 5) 检查用于jump optimization的detour buffer是否已经分配并设置好
	 * 6) 初始化新分配的aggr_kprobe, 将原本的kprobe对象内容拷贝至新分配的aggr_kprobe中
	 * 7) 判断本个kprobe是否可以进行跳转优化, 若可以的话, 将optimized_kprobe对象添加到全局optimizing_list队列并唤醒optimizing_work去执行跳转优化
	 * 
	 */
	try_to_optimize_kprobe(struct kprobe *p = p) {
		struct kprobe *ap;
		struct optimized_kprobe *op
		
		/* Impossible to optimize ftrace-based kprobe. */
		is_ftrace_kprobe = kprobe_ftrace(p) {
			return p->flags & KPROBE_FLAG_FTRACE
		}
		if (is_ftrace_kprobe)
			return
		
		/* For preparing optimization, jump_label_text_reserved() is called. */
		cpus_read_lock()
		jump_label_lock()
		mutex_lock(&text_mutex)

		/* 1) 新分配一个aggr_kprobe */
		ap = alloc_aggr_kprobe(p) {
			struct optimized_kprobe *op
			op = kzalloc(sizeof(struct optimized_kprobe), GFP_KERNEL)
			
			INIT_LIST_HEAD(&op->list)
			
			/* 2) 继承kprobe探测指令地址 */
			op->kp.addr = p->addr
			
			/* 3) 分配用于跳转优化的指令槽, 将optprobe_template代码拷贝置该指令槽, 并设置这块跳转优化的汇编代码 */
			__prepare_optimized_kprobe(op, p) {
				if (!kprobe_ftrace(p)) {
					/*【arch_prepare_optimized_kprobe函数功能】
					 * 分配用于跳转优化的指令槽, 将optprobe_template代码拷贝置该指令槽, 并设置这块跳转优化的汇编代码;
					 * 这块跳转优化的代码会先执行pre_handler, 然后执行被探测指令, 最后跳转回被探测指令+4处
					 *
					 *【arch_prepare_optimized_kprobe 实现逻辑】
					 * 1) 分配用于跳转优化的“optprobe_template汇编代码”的指令槽空间
					 *   (指令槽大小: kprobe_optinsn_slots.insn_size = MAX_OPTINSN_SIZE = optprobe_template_end - optprobe_template_entry)
					 * 2) 检查kpeobe探测指令处, 与optprobe_template code之间的距离是否超过32MiB(因为ARM相对跳转允许的范围就是32MiB)
					 * 3) 将用于跳转优化的汇编代码(即: optprobe_template)拷贝到新分配的这块指令槽中
					 * 4) 修改optprobe_template汇编代码中的sub sp指令内容, 用于预留保存CPU上下文(即: struct pt_regs)的栈内存
					 * 5) 修改optprobe_template汇编代码中的add r3, sp指令内容, 用于计数sub sp指令执行前原始的栈指针位置并记录到r3中
					 * 6) 将optimized_kprobe对象指针设置到“optprobe_template汇编代码的标签1处”, 后续作为optimized_callback的入参
					 * 7) 将optimized_callback函数地址, 设置到“optprobe_template汇编代码的标签2处”, 用于optprobe_template汇编代码跳转到该处执行pre_handler等操作
					 * 8) 判断被探测指令是否可以直接在optprobe_template中直接执行
					 *   (判断条件: 被探测指令是否使用了PC寄存器, 若未使用则可以直接执行被探测指令, 不需要任何模拟执行)
					 * 9) 若可以直接执行被探测指令, 则手动计算一条跳转(branch)指令, 用于从 “optprobe_template_restore_end标签处” 跳转到 “被探测指令+4位置处”
					 * 10) 对于可以直接执行被探测指令的场景, 修改optprobe_template_restore_begin标签处用于“恢复CPU上下文”的指令, 修改为不恢复PC
					 * 11) 将被探测指令(orig->opcode)写入optprobe_template_restore_orig_insn标签处, 用于在optprobe_template中直接执行被探测指令
					 * 12) 将用于跳转回“被探测指令+4处”的指令, 写到optprobe_template_restore_end标签处, 用于返回到原本的程序执行流
					 * 13) 刷新指令槽对应的icache
					 * 14) 将用于跳转优化的汇编代码块首地址, 保存到optimized_kprobe->optinsn.insn中, 后续由kprobe_optimizer负责将op->optinsn.insn保存的指令设置到被探测指令处
					 */
					arch_prepare_optimized_kprobe(struct optimized_kprobe *op = op, struct kprobe *orig = p) {
						kprobe_opcode_t *code
						unsigned long rel_chk
						unsigned long val
						unsigned long stack_protect = sizeof(struct pt_regs)

						/*  TODO  */
						ret = can_optimize(struct kprobe *kp = orig) {
							if (kp->ainsn.stack_space < 0)
								return 0
							/* 255 is the biggest imm can be used in 'sub r0, r0, #<imm>'. Number larger than 255 needs special encoding. */
							if (kp->ainsn.stack_space > 255 - sizeof(struct pt_regs))
								return 0
							return 1
						}
						if (!ret)
							return -EILSEQ
						
						/* 1) 分配用于跳转优化的“optprobe_template汇编代码”的指令槽空间
						 * 指令槽大小: kprobe_optinsn_slots.insn_size = MAX_OPTINSN_SIZE = optprobe_template_end - optprobe_template_entry */
						code = get_optinsn_slot() {
							return __get_insn_slot(struct kprobe_insn_cache *c = &kprobe_optinsn_slots)
						}
						
						/* 2) 检查kpeobe探测指令处, 与optprobe_template code之间的距离是否超过32MiB(因为ARM相对跳转允许的范围就是32MiB)
						 *    Verify if the address gap is in 32MiB range, because this uses a relative jump. Kprobe opt use a 'b' instruction to branch to optinsn.insn.
						 *
						 * ARM branch instruction:
						 *   31  28 27           24 23             0
						 *  +------+---+---+---+---+----------------+
						 *  | cond | 1 | 0 | 1 | 0 |      imm24     |
						 *  +------+---+---+---+---+----------------+
						 *
						 * imm24 is a signed 24 bits integer.
						 * So the maximum forward branch should be: (0x007fffff << 2) = 0x01fffffc =  0x1fffffc
						 * The maximum backword branch should be:   (0xff800000 << 2) = 0xfe000000 = -0x2000000
						 */
						rel_chk = (unsigned long)((long)code - (long)orig->addr + 8) & 0xfe000003
						if ((rel_chk != 0) && (rel_chk != 0xfe000000)) {
							/* Different from x86, we free code buf directly instead of calling __arch_remove_optimized_kprobe() because 
							 * we have not fill any field in op. */
							free_optinsn_slot(code, 0)
							return -ERANGE
						}
						
						/* 3) 将用于跳转优化的汇编代码(即: optprobe_template)拷贝到新分配的这块指令槽中;  Copy arch-dep-instance from template. */
						memcpy(code, (unsigned long *)optprobe_template_entry, TMPL_END_IDX * sizeof(kprobe_opcode_t))
						
						stack_protect += orig->ainsn.stack_space
						
						/* 4) 修改optprobe_template汇编代码中的sub sp指令内容, 用于预留保存CPU上下文(即: struct pt_regs)的栈内存
						 * Create a 'sub sp, sp, #<stack_protect>' */
						code[TMPL_SUB_SP] = __opcode_to_mem_arm(0xe24dd000 | stack_protect)
						
						/* 5) 修改optprobe_template汇编代码中的add r3, sp指令内容, 用于计数sub sp指令执行前原始的栈指针位置并记录到r3中
						 * Create a 'add r3, sp, #<stack_protect>' */
						code[TMPL_ADD_SP] = __opcode_to_mem_arm(0xe28d3000 | stack_protect)
						
						/* 6) 将optimized_kprobe对象指针设置到“optprobe_template汇编代码的标签1处”, 后续作为optimized_callback的入参
						 * Set probe information */
						val = (unsigned long)op
						code[TMPL_VAL_IDX] = val
						
						/* 7) 将optimized_callback函数地址, 设置到“optprobe_template汇编代码的标签2处”, 用于optprobe_template汇编代码跳转到该处执行pre_handler等操作
						 * Set probe function call */
						val = (unsigned long)optimized_callback
						code[TMPL_CALL_IDX] = val
						
						/* 8) 判断被探测指令是否可以直接在optprobe_template中直接执行.
						 *   (判断条件: 被探测指令是否使用了PC寄存器, 若未使用则可以直接执行被探测指令, 不需要任何模拟执行)
						 * If possible, copy insn and have it executed during restore */
						orig->ainsn.kprobe_direct_exec = false
						ret = can_kprobe_direct_exec(m = orig->ainsn.register_usage_flags) {
							return !test_bit(ARM_REG_PC, &(m))		// #define ARM_REG_PC	15
						}
						if (ret) {
							/* 9) 若可以直接执行被探测指令, 则手动计算一条跳转(branch)指令, 用于从 “optprobe_template_restore_end标签处” 跳转到 “被探测指令+4位置处”
							 * 
							 * (final_branch指令: 在optprobe_template中执行完pre_handler后, 并接着执行完被kprobe探测的指令后, 跳转回被探测指令的下一条指令接着运行)
							 */
							kprobe_opcode_t final_branch = arm_gen_branch(pc = (unsigned long)(&code[TMPL_RESTORE_END]), addr = (unsigned long)(op->kp.addr) + 4) {
								return __arm_gen_branch(pc, addr, bool link = false, bool warn = true) {
									return __arm_gen_branch_arm(pc = &code[TMPL_RESTORE_END], addr = (op->kp.addr) + 4, link = false, warn = true) {
										unsigned long opcode = 0xea000000
										long offset
										
										if (link)
											opcode |= 1 << 24
										
										offset = (long)addr - (long)(pc + 8)
										if (unlikely(offset < -33554432 || offset > 33554428)) {
											return 0
										}
										offset = (offset >> 2) & 0x00ffffff
										
										return opcode | offset
									}
								}
							}//arm_gen_branch
							if (final_branch != 0) {
								/* 10) 对于可以直接执行被探测指令的场景, 修改optprobe_template_restore_begin标签处用于“恢复CPU上下文”的指令, 修改为不恢复PC
								 * Replace original 'ldmia sp, {r0 - r15}' with 'ldmia {r0 - r14}', restore all registers except pc. */
								code[TMPL_RESTORE_BEGIN] = __opcode_to_mem_arm(0xe89d7fff)
								
								/* 11) 将被探测指令(orig->opcode)写入optprobe_template_restore_orig_insn标签处, 用于在optprobe_template中直接执行被探测指令
								 * The original probed instruction */
								code[TMPL_RESTORE_ORIGN_INSN] = __opcode_to_mem_arm(orig->opcode)

								/* 12) 将用于跳转回“被探测指令+4处”的指令, 写到optprobe_template_restore_end标签处, 用于返回到原本的程序执行流
								 * Jump back to next instruction */
								code[TMPL_RESTORE_END] = __opcode_to_mem_arm(final_branch)
								orig->ainsn.kprobe_direct_exec = true
							}
						}
						/* 13) 刷新指令槽对应的icache */
						flush_icache_range((unsigned long)code, (unsigned long)(&code[TMPL_END_IDX]))
						
						/* 14) 将用于跳转优化的汇编代码块首地址, 保存到optimized_kprobe->optinsn.insn中,
						 *     后续由kprobe_optimizer负责将op->optinsn.insn保存的指令设置到被探测指令处 */
						op->optinsn.insn = code
						return 0
					}//arch_prepare_optimized_kprobe
				}
			}//__prepare_optimized_kprobe
			return &op->kp
		}//alloc_aggr_kprobe
		
		/* 4) 根据新分配aggr_kprobe对象, 找到optimized_kprobe对象 */
		op = container_of(ap, struct optimized_kprobe, kp)
		
		/* 5) 检查用于jump optimization的detour buffer是否已经分配并设置好 */
		ret = arch_prepared_optinsn(struct arch_optimized_insn *optinsn = &op->optinsn) {
			/* ARM can always optimize an instruction when using ARM ISA, except instructions like 'str r0, [sp, r1]'
			 * which store to stack and unable to determine stack space consumption statically. */
			return optinsn->insn != NULL
		}
		if (!ret)
			goto out
		
		/* 6) 初始化新分配的aggr_kprobe, 将原本的kprobe对象内容拷贝至新分配的aggr_kprobe中 */
		# 注: 对于optimize kprobe, 新分配的aggr_kprobe的pre_hander实际上是不会被执行到的!执行的是opt_pre_handler->kprobe's pre_handler
		init_aggr_kprobe(struct kprobe *ap = ap, struct kprobe *p = p)
		{
			/* Copy the insn slot of 'p' to 'ap'. */
			copy_kprobe(p, ap) {					// 将orig_kprobe(即:p)的insn slot指令槽拷贝给新分配的aggr_kprobe(即:ap)
				memcpy(&ap->opcode, &p->opcode, sizeof(kprobe_opcode_t))
				memcpy(&ap->ainsn, &p->ainsn, sizeof(struct arch_specific_insn))
			}
			
			ap->addr  = p->addr						// 拷贝探测指令地址, flag等属性
			ap->flags = p->flags & ~KPROBE_FLAG_OPTIMIZED
			
			ap->pre_handler = aggr_pre_handler		// *** 为aggr_kprobe设置pre_handler ***
			
			if (p->post_handler && !kprobe_gone(p))
				ap->post_handler = aggr_post_handler// *** 为aggr_kprobe设置post_handler ***
			
			INIT_LIST_HEAD(&ap->list)				// 初始化用于挂载到aggr_kprobe->list上的node节点
			INIT_HLIST_NODE(&ap->hlist)				// 初始化用于挂载到全局哈希表kprobe_table上的node节点
			
			list_add_rcu(&p->list, &ap->list)		// 将orig_kprobe(即:p)头插到新分配的aggr_kprobe(即:ap)的list链表上
			hlist_replace_rcu(&p->hlist, &ap->hlist)// 将全局哈希表kprobe_table中的orig_kprobe替换成新分配的aggr_kprobe
		}//init_aggr_kprobe
		
		/*【optimize_kprobe函数功能】
		 * 7) 判断本个kprobe是否可以进行跳转优化, 若可以的话, 将optimized_kprobe对象添加到全局optimizing_list队列并唤醒optimizing_work去执行跳转优化
		 * 
		 * This just kicks optimizer thread. */
		optimize_kprobe(struct kprobe *p = ap) {
			struct optimized_kprobe *op
			
			/* 通过查看optinsn->insn是否为NULL, 判断optimize kprobe的跳转优化代码是否准备完毕 */
			ready = kprobe_optready(p) {
				if (kprobe_aggrprobe(p)) {
					op = container_of(p, struct optimized_kprobe, kp)
					return arch_prepared_optinsn(struct arch_optimized_insn *optinsn = &op->optinsn) {
						return optinsn->insn != NULL
					}
				}
				return 0
			}
			if (!ready || !kprobes_allow_optimization || (kprobe_disabled(p) || kprobes_all_disarmed))
				return
			
			/* 若kprobe有post_handler, 则不能被优化
			 * kprobes with 'post_handler' can not be optimized */
			if (p->post_handler)
				return
			
			op = container_of(p, struct optimized_kprobe, kp)
			/* Check there is no other kprobes at the optimized instructions */
			if (arch_check_optimized_kprobe(op) < 0)		// ARM always return 0
				return
			
			/* Check if it is already optimized. */
			if (op->kp.flags & KPROBE_FLAG_OPTIMIZED) {
				if (optprobe_queued_unopt(op)) {
					list_del_init(&op->list)
				}
				return
			}
			op->kp.flags |= KPROBE_FLAG_OPTIMIZED
			
			if (WARN_ON_ONCE(!list_empty(&op->list)))
				return
			
			/* 将optimized_kprobe对象添加到全局optimizing_list队列中 */
			list_add(&op->list, &optimizing_list)
			
			/* 唤醒optimizing_work工作线程, 去替换kprobe指令进行跳转优化 */
			kick_kprobe_optimizer() {
				schedule_delayed_work(struct delayed_work *dwork = &optimizing_work, unsigned long delay = OPTIMIZE_DELAY)
					queue_delayed_work(system_wq, dwork, delay)
			}
		}//optimize_kprobe
	out:
		mutex_unlock(&text_mutex)
		jump_label_unlock()
		cpus_read_unlock()
	}//try_to_optimize_kprobe
	
	return 0
}

2、后台线程kprobe_optimizer对指定kprobe进行优化

optimizing_work(struct work_struct *work)
A.K.A
kprobe_optimizer(struct work_struct *work) {
	mutex_lock(&kprobe_mutex)
	cpus_read_lock()
	mutex_lock(&text_mutex)
	
	/* 1) 将unoptimizing_list队列上挂载的optimized_kprobe对象都unoptimize掉, 将被探测指令替换会BRK指令 */
	do_unoptimize_kprobes() {
		struct optimized_kprobe *op, *tmp
		
		if (!list_empty(&unoptimizing_list)) {
			arch_unoptimize_kprobes(struct list_head *oplist = &unoptimizing_list, struct list_head *done_list = &freeing_list) {
				struct optimized_kprobe *op, *tmp
				
				list_for_each_entry_safe(op, tmp, oplist, list) {
					arch_unoptimize_kprobe(op) {
						/* install breakpoint in text */
						arch_arm_kprobe(struct kprobe *p = &op->kp) {
							void *addr = p->addr
							u32 insn = BRK64_OPCODE_KPROBES
							aarch64_insn_patch_text(&addr, &insn, 1)
						}
					}
					list_move(&op->list, done_list)
				}
			}//arch_unoptimize_kprobes
		}
		
		list_for_each_entry_safe(op, tmp, &freeing_list, list) {
			op->kp.flags &= ~KPROBE_FLAG_OPTIMIZED
			if (kprobe_disabled(&op->kp) && !kprobe_gone(&op->kp))
				arch_disarm_kprobe(&op->kp)
			if (kprobe_unused(&op->kp)) {
				hlist_del_rcu(&op->kp.hlist)
			} else
				list_del_init(&op->list)
		}
	}//do_unoptimize_kprobes
	
	/* 2) 确保所有活跃的中断在调用 synchronize_rcu() 的时候已经完成 */
	synchronize_rcu_tasks()
	
	/* 3) 优化optimizing_list队列上挂载的所有optimized_kprobe对象, 将被探测指令处的BRK替换成“相对跳转到op->optinsn.insn指令槽”的相对跳转指令 */
	do_optimize_kprobes() {
		if (kprobes_all_disarmed || !kprobes_allow_optimization || list_empty(&optimizing_list))
			return
		
		arch_optimize_kprobes(&optimizing_list) {
			struct optimized_kprobe *op, *tmp
			
			list_for_each_entry_safe(op, tmp, oplist, list) {
				unsigned long insn
				
				/* Backup instructions which will be replaced by jump address */
				memcpy(op->optinsn.copied_insn, op->kp.addr, RELATIVEJUMP_SIZE)		// define RELATIVEJUMP_SIZE   4
				
				### 生成: “从kprobe探测指令跳转到Jump Optimization code(即: op->optinsn.insn)” 的跳转指令
				insn = arm_gen_branch((unsigned long)op->kp.addr, (unsigned long)op->optinsn.insn)
				
				/* Make it a conditional branch if replaced insn is consitional */
				insn = (__mem_to_opcode_arm(op->optinsn.copied_insn[0]) & 0xf0000000) | (insn & 0x0fffffff)
				
				### >>>>>>>>>>>>>>>>  将原本kprobe设置的brk指令替换为“跳转到optprobe_template指令块的跳转指令”  <<<<<<<<<<<<<<<<<<
				kprobes_remove_breakpoint(void *addr = op->kp.addr, insn) {
					struct patch p = {
						.addr = addr,
						.insn = insn,
					}
					stop_machine_cpuslocked(__kprobes_remove_breakpoint, &p, cpu_online_mask) {
						/* Detail see above */
					}
				}
				
				list_del_init(&op->list)
			}
		}//arch_optimize_kprobes
	}//do_optimize_kprobes
	
	/* 4) 回收所有挂载在freeing_list队列上的optimized_kprobe对象 */
	do_free_cleaned_kprobes() {
		struct optimized_kprobe *op, *tmp
		
		list_for_each_entry_safe(op, tmp, &freeing_list, list) {
			list_del_init(&op->list)
			if (WARN_ON_ONCE(!kprobe_unused(&op->kp))) {
				/* This must not happen, but if there is a kprobe still in use, keep it on kprobes hash list. */
				continue
			}
			free_aggr_kprobe(struct kprobe *p = &op->kp) {
				struct optimized_kprobe *op
				op = container_of(p, struct optimized_kprobe, kp)
				
				arch_remove_optimized_kprobe(op) {
					__arch_remove_optimized_kprobe(struct optimized_kprobe *op = op, int dirty = 1) {
						if (op->optinsn.insn) {
							free_optinsn_slot(op->optinsn.insn, dirty)
							op->optinsn.insn = NULL
						}
					}
				}
				arch_remove_kprobe(p) {
					if (p->ainsn.api.insn) {
						free_insn_slot(p->ainsn.api.insn, 0)
						p->ainsn.api.insn = NULL
					}
				}
				
				kfree(op)
			}
		}
	}//do_free_cleaned_kprobes
	
	mutex_unlock(&text_mutex)
	cpus_read_unlock()
	/* Step 5: Kick optimizer again if needed */
	if (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list))
		kick_kprobe_optimizer()
	
	mutex_unlock(&kprobe_mutex)
}

3、跳转优化的kprobe, 触发时, 执行对应pre_handler

optimized_callback(struct optimized_kprobe *op, struct pt_regs *regs) {
	unsigned long flags
	struct kprobe *p = &op->kp
	struct kprobe_ctlblk *kcb
	
	/* Save skipped registers */
	regs->ARM_pc = (unsigned long)op->kp.addr
	regs->ARM_ORIG_r0 = ~0UL
	
	local_irq_save(flags)
	
	kcb = get_kprobe_ctlblk() {
		return this_cpu_ptr(&kprobe_ctlblk)
	}
	
	is_running = kprobe_running() {
		return __this_cpu_read(current_kprobe)
	}
	if (is_running) {
		kprobes_inc_nmissed_count(&op->kp) {
			if (!kprobe_aggrprobe(p)) {
				p->nmissed++
			} else {
				list_for_each_entry_rcu(kp, &p->list, list)
					kp->nmissed++
			}
		}//kprobes_inc_nmissed_count
	} else {
		__this_cpu_write(current_kprobe, &op->kp)
		kcb->kprobe_status = KPROBE_HIT_ACTIVE
		
		opt_pre_handler(struct kprobe *p = &op->kp, struct pt_regs *regs = regs) {
			struct kprobe *kp
			list_for_each_entry_rcu(kp, &p->list, list) {
				if (kp->pre_handler && likely(!kprobe_disabled(kp))) {
					set_kprobe_instance(kp) {
						__this_cpu_write(kprobe_instance, kp)
					}
					
					### 执行kprobe的pre_handler!
					kp->pre_handler(kp, regs)
				}
				reset_kprobe_instance() {
					__this_cpu_write(kprobe_instance, NULL)
				}
			}
		}//opt_pre_handler
		__this_cpu_write(current_kprobe, NULL)
	}
	
	if (!p->ainsn.kprobe_direct_exec) {
		op->kp.ainsn.insn_singlestep(p->opcode, &p->ainsn, regs)
	}
	
	local_irq_restore(flags)
}//optimized_callback

以上就是 kprobe跳转优化 的全部内容,感谢大伙的收看 !~

双臂复合机器人眼在手外标定实战:从原理到D435标定流程详解 本文详细解析了双臂复合机器人中眼在手外标定的核心原理与实战流程。通过阐述坐标系转换与AX=XB方程,重点介绍了使用Intel RealSense D435相机进行标定的完整步骤,包括环境搭建、数据采集、计算与验证,并提供了关键的避坑指南,帮助工程师实现相机与机械臂基座间的精确坐标对齐。 阅读详情

相关推荐

vivado 编译ADI AD9361 TCL脚本生成hdl工程

(4)输入"source ../../scripts/adi_make.tcl"命令(仅双引号内部命令,不包含双引号),个人理解:该命令用来加载当前所需要编译工程所需要各功能模块的信息,因为每个工程需要不同的功能模块,比如有的需要gpio模块,有的不需要gpio模块,这些模块上前面路径的library也同样以TCL脚本形式存在,当有需要时才进行编译,怎么知道当前工程需要什么模块呢就是通过"source ../../scripts/adi_make.tcl"(仅双引号内部命令,不包含双引号)命令实现。

weixin_40586423的博客 802

Linux kprobe的使用

Linux kprobe的简单使用

小立仔 5331

基于Python+OpenCV中文及符号检测

一、项目背景与意义在图像处理、文档识别、自动翻译等场景中,中文及符号的准确检测是一个重要且复杂的任务。传统的字符检测方法往往对英文等拉丁字母系的识别效果较好,但对中文及特殊符号的识别准确率较低。本项目旨在利用Python编程语言结合OpenCV库,开发一个能够高效、准确地检测图像中中文及符号的系统,为相关领域的研究和应用提供技术支撑。二、技术实现图像预处理:利用OpenCV的图像处理功能,对输入的图像进行灰度化、二值化、滤波去噪等预处理操作,以提高中文及符号检测的准确性[1]。

2301_79809972的博客 1115

Linux kprobe原理

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

小立仔 3974

手机OIS马达拆解:从苹果的悬丝到三星的滚珠,不同方案如何影响你的拍照体验?

本文深度解析手机OIS马达技术,对比苹果悬丝式、三星滚珠式和LG记忆合金式三种主流方案的机械原理与成像体验差异。通过详实的数据和测试结果,揭示不同OIS技术如何影响防抖性能、模组厚度及长期可靠性,为摄影爱好者和技术爱好者提供实用选购建议。

weixin_42554408的博客 286

【QT】基于QSerialPort的跨平台串口通信实战指南

本文是一份基于Qt QSerialPort模块的跨平台串口通信实战指南。详细介绍了从硬件基础、QSerialPort核心类使用、参数配置、设备自动发现到数据异步读写和错误处理的完整流程,并通过构建一个功能齐全的串口调试助手实例,帮助开发者快速掌握在Windows、Linux和macOS系统下进行高效、稳定的串口应用开发。

a1b2c3d的博客 975

Linux内核调试技术之kprobes:基本原理与使用

Linux kprobes技术是一种可以跟踪内核函数执行状态的轻量级内核调试技术,利用kprobes技术可以在运行的内核中动态的插入探测点,当内核运行到该探测点后可以执行用户预定义的回调函数,以收集所需的调试状态信息而基本不影响内核原有的执行流程。{return 0;}.symbol_name = "do_fork", // 要追踪的内核函数为 do_fork.pre_handler = handler_pre // pre_handler 回调函数。

Aspiresky的专栏 3161

STM32H750_QSPI_Flash_Configuration_With_CubeMX

本文详细介绍了如何使用STM32CubeMX配置STM32H750的QSPI Flash接口,包括硬件连接、CubeMX参数设置、代码生成与初始化流程优化。通过实战示例展示了Flash ID读取、页编程与数据读取等常用操作,并提供了调试技巧与性能优化建议,帮助开发者高效实现高速外部存储扩展。

weixin_29176179的博客 266

kprobe 原理详细分析

内核调试机制 kprobe 原理详细分析,使用 kprobe 可以在内核任意位置设置 hook 并跟踪调试

看我眼色行事的博客 1640

图解ADAS电源系统架构:看懂这张图,搞懂前装摄像头的供电秘密!

别以为ADAS系统就是“摄像头 + SoC”,其实最难搞的是供电系统,尤其是要满足车规级ASIL-D安全等级时。供电时序、电压稳定性、电源监控、冗余通信链路,一个都不能少。😤一旦电源选型不对,轻则系统卡顿,重则全车失控,不是闹着玩的。

weixin_43199439的博客 662

ABAP链接外围数据库SQL实战案例

这是一个ABAP 直接读取SQL数据库数据的实例,以此种方式可以将SAP与外围的系统做一些集成,比如生产,SAP本身生产这部分是弱项,管不了那么细,很多车间的数据都是在MES系统,如管理上需要则可以将MES的一些数据自动读取过来,结合SAP本身的业务数据,做一些分析是比较方便的。 1、首先在SAP里建立数据库链接:DBCO ,直接增加一条记录,填上数据库的相关信息:IP地址、数据库名、账号SA、密码这些。 2、开发程序,设置成自动运行的计划,固定时间自动读取SQL里的数据,保存到SAP自定义表.

heng0757的专栏 1510

【原创】Linux kprobe 实现原理 图文详解 (一) kprobe实现原理

图文详解 linux kprobe 底层实现原理

h_b__k的博客 2108

【零基础入门unity游戏开发——2D篇】大大简化了地形创建流程,减少Rule Tile的使用 —— AutoTile自动平铺瓦片的使用介绍

Unity 2D游戏开发中的AutoTile技术详解 摘要:本文详细介绍了Unity 2D游戏开发中的AutoTile技术,这是一种智能瓦片系统,能够自动处理地形和场景拼接。文章首先讲解了AutoTile的基本概念和优势,然后分步骤指导读者如何安装2D Tilemap Extras包、创建AutoTile、设置参数和绘制遮罩。通过实际案例演示了AutoTile的使用方法,并比较了AutoTile与Rule Tile的适用场景。最后指出AutoTile虽然使用方便,但对素材要求较高,开发者应根据项目需求选择合

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 714

【原创】Linux kprobe 实现原理 图文详解 (二) kretprobe实现原理

目录 一、kretprobe 基本介绍 二、kretprobe 使用示例 三、kretprobe 实现原理 四、具体代码 (代码:linux 6.3.1,架构:arm64) One look is worth a thousand words. —— Tess Flanders 一、kretprobe 基本介绍 Kretprobe是一种基于Kprobe的探测技术,用于探测内核函数的入口点、返回点。在这两处可以调用用户注册的钩子函数,该技术一般用于获取:函数运行时间、函数返回

h_b__k的博客 2302

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【原创】Linux kprobe 实现原理 图文详解 (四) 指令替换、cpu stopper

本文主要介绍kprobe指令替换的细节,并介绍cpu stopper机制的实现原理

h_b__k的博客 1571

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

kprobe 的 3 种使用

知识背景:会编写基础内核模块 kprobe用处:用来跟踪、记录、打桩。最常见的使用就是需要打印内核函数的执行流程,比如排查问题时、或者学习内核流程时需要用到。 准备工作:找一份和自己linux系统相同版本的源码(方法自行Google,ubuntu、centos、redhat各自有各自方法) 内核模块跟踪指定函数 官方文档: https://www.kernel.org/doc/Documentation/kprobes.txt 在 内核源码目录samples/kprobes/有几个kprobe模块例子,.

Network, Linux Kernel 6205

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

kprobe实现原理解析

一、简介 kprobe是内核的动态探测工具,几乎可以探测任何一条内核指令。kprobe根据探测点类型可分为三种: kprobes, jprobes和kretprobes (也叫返回探测点)。 kprobes是可以被插入到内核的任何指令位置的探测点,jprobes则只能被插入到一个内核函数的入口,而kretprobes则是在指定的内核函数返回时才被执行。 kprobe...

hjkfcz的博客 3485

[monitor] 8. Linux kprobe(内核探针)

1、kprobe概念kprobe是一个动态地收集调试和性能信息的工具,它从Dprobe项目派生而来,是一种非破坏性工具,用户用它几乎可以跟踪任何函数或被执行的指令以及一些异步事件(如timer)。它的基本工作机制是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。kprobe实现了三种类型的探测点: kprobe

pwl999的博客 4279

深入ftrace kprobe原理解析

至此,我们知道Kprobe实现的本质是breakpoint和single-step的结合,这一点和大多数调试工具一样,比如kgdb/gdb。当 kprobe 被注册后,内核会将对应地址的指令进行拷贝并替换为断点指令(比如 X86 中的int 3)随后当内核执行到对应地址时,中断会被触发从而执行流程会被重定向到我们注册的函数当对应地址的原始指令执行完后,内核会再次执行从而实现指令级别的内核动态监控。也就是说,kprobe 不仅可以跟踪任意带有符号的内核函数,也可以跟踪函数中间的任意指令。

奇小葩 5457

ftrace,kprobe,tracepoint 入门

调试工具在开发过程中是必不可少的,特别是在定位 bug, 分析性能瓶颈的时候,调试工具可以给我们很大的帮助。在使用 c/c++ 做应用开发时,gdb 是我们经常使用的调试工具,在使用 gdb 时, 我们可以设置断点,查看调用栈,单步执行,修改或查看变量等。调试工具可以帮助我们直观地观察到软件的运行过程,进而使我们快速熟悉一个新的软件。比如在工作中,想要了解已有软件的架构,只靠看代码是很难了解透彻的,通过日志和调试工具可以提高学习效率。

weixin_38184628的博客 2899

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

Linux kprobes调试技术是内核开发者们专门为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。利用kprobes技术,内核开发人员可以在内核的绝大多数指定函数中动态的插入探测点来收集所需的调试状态信息而基本不影响内核原有的执行流程。

My Linux Journey 5万+

Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用示例

Linux内核 eBPF基础 kprobe原理源码分析:基本介绍与使用 荣涛 2021年5月11日

RToax 8404

Linux 下的一个全新的性能测量和调式诊断工具 Systemtap,第 1 部分: kprobe

http://www.ibm.com/developerworks/cn/linux/l-cn-systemtap1/index.html kprobe 的原理、编程接口、局限性和使用注意事项 杨 燚 (yang.y.yi@gmail.com), 计算机科学硕士, Intel 简介: 本系列文章详细地介绍了一个Linux下的全新的调式、诊断和性能测量工具Systemtap和它所依

hnllei的专栏 1605

kprobes技术介绍+原理,ebpf中的kprobe+运行情况+用户层代码+内核层代码+预编译代码(详细解释+语法介绍),修改.bpf.c入口处的函数,系统调用和内核函数的区别

kprobes技术介绍+原理,ebpf中的kprobe+运行情况+用户层代码+内核层代码+预编译代码(详细解释+语法介绍),修改.bpf.c入口处的函数,系统调用和内核函数的区别

m0_74206992的博客 2113

【性能工程 - eBPF 技术】什么是 kprobe 和 uprobe?一文详解 kprobe 和 uprobe 奥秘

详解 eBPF 中的 kprobe、 uprobe、kretprobe、 uretprobe。

Mercury_Lc的博客 1672

Linux基础 -- 内核性能分析之 kprobe 与 kretprobe

kprobe/kretprobe 是内核级的动态探针:不用改内核源码,运行时就能 hook 指定函数。kprobe = 入口拦截;kretprobe = 入口 + 返回成对,可以算耗时,是我们要的。只需开,不用开整套 ftrace 大缓冲。用 kretprobe 钩你的关键函数入口记 start_ns返回记 duration写入我们自己的 per-CPU 小 ring buffer(几十 KB 总占用)用 debugfs 导出来再加 enable 开关来做“按需抓一段窗口”

sz66cm 学习随笔 927
上一篇: 【原创】Linux kprobe 实现原理 图文详解 (二) kretprobe实现原理
下一篇: 【原创】Linux kprobe 实现原理 图文详解 (四) 指令替换、cpu stopper
码农hbk
博客等级 码龄9年 434粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值