Linux-5.10.13 kprobe源码分析

Posted 全波形反演

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-5.10.13 kprobe源码分析相关的知识,希望对你有一定的参考价值。

1. 引言

1.1. kprobe API

内核使用kprobe,可以使用register_kprobe()/unregister_kprobe()进行注册/卸载,还可以临时关闭/使能探测点。

int register_kprobe(struct kprobe *p);//注册kprobe探测点void unregister_kprobe(struct kprobe *p);//卸载kprobe探测点int register_kprobes(struct kprobe **kps, int num);//注册多个kprobe探测点void unregister_kprobes(struct kprobe **kps, int num);//卸载多个kprobe探测点int disable_kprobe(struct kprobe *kp);//暂停指定定kprobe探测点int enable_kprobe(struct kprobe *kp);//恢复指定kprobe探测点void dump_kprobe(struct kprobe *kp);//打印指定kprobe探测点的名称、地址、偏移

对于kretprobe同样有一套接口:

int register_kretprobe(struct kretprobe *rp);void unregister_kretprobe(struct kretprobe *rp);int register_kretprobes(struct kretprobe **rps, int num);void unregister_kretprobes(struct kretprobe **rps, int num);int disable_kretprobe(struct kretprobe *kp);int enable_kretprobe(struct kretprobe *kp);

在5.10.13代码samples\kprobes\kprobe_example.c中,probe的是kernel_clone函数,在我的环境中3.10.0-1062.el7.x86_64没有这个sym(可以使用cat /proc/kallsyms查看),我使用do_fork替换(do_fork不至于被调用太多,但又不会没有)。

1.2. samples\kprobes\kprobe_example.c

#include <linux/kernel.h>#include <linux/module.h>#include <linux/kprobes.h>
#define MAX_SYMBOL_LEN 64static char symbol[MAX_SYMBOL_LEN] = "do_fork";module_param_string(symbol, symbol, sizeof(symbol), 0644);
/* For each probe you need to allocate a kprobe structure */static struct kprobe kp = {//定义实例kp并初始化symbol_name为"_do_fork",将探测_do_fork函数。 .symbol_name = symbol,};
/* kprobe pre_handler: called just before the probed instruction is executed */static int handler_pre(struct kprobe *p, struct pt_regs *regs){ pr_info("<%s> pre_handler: p->addr = %pF, ip = %lx, flags = 0x%lx\n", p->symbol_name, p->addr, regs->ip, regs->flags);
/* A dump_stack() here will give a stack backtrace */ return 0;}
/* kprobe post_handler: called after the probed instruction is executed */static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags){ pr_info("<%s> post_handler: p->addr = %pF, flags = 0x%lx\n", p->symbol_name, p->addr, regs->flags);}
/* * fault_handler: this is called if an exception is generated for any * instruction within the pre- or post-handler, or when Kprobes * single-steps the probed instruction. */static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr){ pr_info("fault_handler: p->addr = %pF, trap #%dn", p->addr, trapnr); /* Return 0 because we don't handle the fault. */ return 0;}
static int __init kprobe_init(void){ int ret; kp.pre_handler = handler_pre;//初始化kp的三个回调函数。 kp.post_handler = handler_post; kp.fault_handler = handler_fault;
ret = register_kprobe(&kp);//注册kp探测点到内核。 if (ret < 0) { pr_err("register_kprobe failed, returned %d\n", ret); return ret; } pr_info("Planted kprobe at %pF\n", kp.addr); return 0;}
static void __exit kprobe_exit(void){ unregister_kprobe(&kp); pr_info("kprobe at %pF unregistered\n", kp.addr);}
module_init(kprobe_init)module_exit(kprobe_exit)MODULE_LICENSE("GPL");

下面将结合samples\kprobes\kprobe_example.c实例代码,对kprobe源代码进行分析。

2. 数据结构

2.1. struct kprobe

struct kprobe { /* */ struct hlist_node hlist; /* 被用于kprobe全局hash,索引值为被探测点的地址。*/
/* list of kprobes for multi-handler support */ struct list_head list; /* 用于链接同一被探测点的不同探测kprobe。*/
/*count the number of times this probe was temporarily disarmed 如果 kprobe 嵌套,增加nmissed字段的数值*/ unsigned long nmissed; /* */
/* location of the probe point */ kprobe_opcode_t *addr; /* 被探测点的地址。*/
/* Allow user to indicate symbol name of the probe point */ const char *symbol_name; /* 被探测函数的名称。*/
/* Offset into the symbol */ unsigned int offset; /* 被探测点在函数内部的偏移,用于探测函数内核的指令,如果该值为0表示函数的入口。*/
/* Called before addr is executed. 在被探测指令被执行前回调*/ kprobe_pre_handler_t pre_handler;
/* Called after addr is executed, unless... 在被探测指令执行完毕后回调(注意不是被探测函数)*/ kprobe_post_handler_t post_handler;
/* * ... called if executing addr causes a fault (eg. page fault). * Return 1 if it handled fault, otherwise kernel will see it. */ kprobe_fault_handler_t fault_handler; /* 在内存访问出错时被调用 */
/* 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; /* 状态标记。*/};

其中回调函数的执行流程为:


2.2. struct kretprobe

struct kretprobe { struct kprobe kp; kretprobe_handler_t handler; kretprobe_handler_t entry_handler; int maxactive; int nmissed; size_t data_size; struct hlist_head free_instances; raw_spinlock_t lock;};

3. API

3.1. register_kprobe - 注册kprobe

首先调用kprobe_addr查找函数名。

3.1.1. kprobe_addr - 获取内核符号地址

调用_kprobe_addr

static kprobe_opcode_t *kprobe_addr(struct kprobe *p){ return _kprobe_addr(p->addr, p->symbol_name, p->offset);}

调用关系为:

register_kprobe kprobe_addr _kprobe_addr kprobe_lookup_name kallsyms_lookup_name kallsyms_expand_symbol 1.首先从内核函数中查找                    module_kallsyms_lookup_name 2.如果没找到再从模块中查找

函数kallsyms_sym_address如下,遍历内核符号:

static unsigned long kallsyms_sym_address(int idx){ if (!IS_ENABLED(CONFIG_KALLSYMS_BASE_RELATIVE)) return kallsyms_addresses[idx];
/* values are unsigned offsets if --absolute-percpu is not in effect */ if (!IS_ENABLED(CONFIG_KALLSYMS_ABSOLUTE_PERCPU)) return kallsyms_relative_base + (u32)kallsyms_offsets[idx];
/* ...otherwise, positive offsets are absolute values */ if (kallsyms_offsets[idx] >= 0) return kallsyms_offsets[idx];
/* ...and negative offsets are relative to kallsyms_relative_base - 1 */ return kallsyms_relative_base - 1 - kallsyms_offsets[idx];}


下面只给出原文目录,详情请点击阅读原文。

3.1.2. check_kprobe_address_safe - 检测kprobe是否安全

3.1.3. prepare_kprobe

3.1.4. arm_kprobe - 处理

3.1.5. arch_arm_kprobe - 用int3替换代码段

3.1.6. __text_poke - 找到代码段页,用opcode替换

3.2. register_kprobes - 略

3.3. unregister_kprobe - 注销kprobe

3.4. unregister_kprobes - 注销kprobes

4. 参考和相关链接

点击阅读原文阅读完整文章。

以上是关于Linux-5.10.13 kprobe源码分析的主要内容,如果未能解决你的问题,请参考以下文章

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

Linux内核 eBPF基础:perf基础perf_event_open系统调用内核源码分析

Android 插件化VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )(代

Linux kprobe初探

Android 逆向Dalvik 函数抽取加壳 ( 类加载流程分析 | ClassLoader#loadClass 函数分析 | BaseDexClassLoader#findClass 分析 )(代

异常及源码分析org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeE(代