企业网站开发费用,帝国cms 网站描述的全局变量,山东网站建设制作公司,卓智网络科技有限公司目录 用途#xff1a;一、技术背景1.1 kprobes的特点与使用限制1.2 kprobe原理 二、 基于kprobe探测模块的探测方式2.1、struct kprobe结构体2.2 kprobe API函数2.3 示例代码参考资料#xff1a; 用途#xff1a;
判断内核函数是否被调用#xff0c;获取调用上下文、入参以… 目录 用途一、技术背景1.1 kprobes的特点与使用限制1.2 kprobe原理 二、 基于kprobe探测模块的探测方式2.1、struct kprobe结构体2.2 kprobe API函数2.3 示例代码参考资料 用途
判断内核函数是否被调用获取调用上下文、入参以及返回值。
一、技术背景
如果需要知道内核函数是否被调用、被调用上下文、入参以及返回值比较简单的方法是加printk但是效率低。
利用kprobe技术用户可以自定义自己的回调函数可以再几乎所有的函数中动态插入探测点。
当内核执行流程执行到指定的探测函数时会调用该回调函数用户即可收集所需的信息了同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息不再需要继续探测则同样可以动态的移除探测点。
kprobes技术包括的3种探测手段分别时kprobe、jprobe和kretprobe。
首先kprobe是最基本的探测方式是实现后两种的基础它可以在任意的位置放置探测点就连函数内部的某条指令处也可以它提供了探测点的调用前、调用后和内存访问出错3种回调方式分别是pre_handler、post_handler和fault_handler其中pre_handler函数将在被探测指令被执行前回调post_handler会在被探测指令执行完毕后回调注意不是被探测函数fault_handler会在内存访问出错时被调用jprobe基于kprobe实现它用于获取被探测函数的入参值最后kretprobe从名字种就可以看出其用途了它同样基于kprobe实现用于获取被探测函数的返回值。
1.1 kprobes的特点与使用限制
1、kprobes允许在同一个被被探测位置注册多个kprobe但是目前jprobe却不可以同时也不允许以其他的jprobe回掉函数和kprobe的post_handler回调函数作为被探测点。
2、一般情况下可以探测内核中的任何函数包括中断处理函数。不过在kernel/kprobes.c和arch/*/kernel/kprobes.c程序中用于实现kprobes自身的函数是不允许被探测的另外还有do_page_fault和notifier_call_chain
3、如果以一个内联函数为探测点则kprobes可能无法保证对该函数的所有实例都注册探测点。由于gcc可能会自动将某些函数优化为内联函数因此可能无法达到用户预期的探测效果
4、一个探测点的回调函数可能会修改被探测函数运行的上下文例如通过修改内核的数据结构或者保存与struct pt_regs结构体中的触发探测之前寄存器信息。因此kprobes可以被用来安装bug修复代码或者注入故障测试代码
5、kprobes会避免在处理探测点函数时再次调用另一个探测点的回调函数例如在printk()函数上注册了探测点则在它的回调函数中可能再次调用printk函数此时将不再触发printk探测点的回调仅仅时增加了kprobe结构体中nmissed字段的数值
6、在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存
7、kprobes回调函数的运行期间是关闭内核抢占的同时也可能在关闭中断的情况下执行具体要视CPU架构而定。因此不论在何种情况下在回调函数中不要调用会放弃CPU的函数如信号量、mutex锁等
8、kretprobe通过替换返回地址为预定义的trampoline的地址来实现因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址
9、如果一个函数的调用此处和返回次数不相等则在类似这样的函数上注册kretprobe将可能不会达到预期的效果例如do_exit()函数会存在问题而do_execve()函数和do_fork()函数不会
10、如果当在进入和退出一个函数时CPU运行在非当前任务所有的栈上那么往该函数上注册kretprobe可能会导致不可预料的后果因此kprobes不支持在X86_64的结构下为__switch_to()函数注册kretprobe将直接返回-EINVAL。
1.2 kprobe原理
kprobe工作具体流程见下图 1、当用户注册一个探测点后kprobe首先备份被探测点的对应指令然后将原始指令的入口点替换为断点指令该指令是CPU架构相关的如i386和x86_64是int3arm是设置一个未定义指令目前的x86_64架构支持一种跳转优化方案Jump Optimization内核需开启CONFIG_OPTPROBES选项该种方案使用跳转指令来代替断点指令 2、当CPU流程执行到探测点的断点指令时就触发了一个trap在trap处理流程中会保存当前CPU的寄存器信息并调用对应的trap处理函数该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数kprobe会向该函数传递注册的struct kprobe结构地址以及保存的CPU寄存器信息 3、随后kprobe单步执行前面所拷贝的被探测指令具体执行方式各个架构不尽相同arm会在异常处理流程中使用模拟函数执行而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行 4、在单步执行完成后kprobe执行用户注册的post_handler回调函数 5、最后执行流程回到被探测指令之后的正常流程继续执行。 二、 基于kprobe探测模块的探测方式
2.1、struct kprobe结构体
内核提供了struct kprobe表示一个探测点以及一系列API接口用户可以通过这些接口实现回调函数并实现struct kprobe结构然后将它注册到内核的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 */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;------------------------------------被探测点指令执行之后调用的回调函数。kprobe_fault_handler_t fault_handler;----------------------------------在执行pre_handler、post_handler或单步执行被探测指令时出现内存异常则会调用该回调函数。kprobe_break_handler_t break_handler;----------------------------------在执行某一kprobe过程中出发了断点指令后会调用该函数用于实现jprobe。kprobe_opcode_t opcode;------------------------------------------------保存的被探测点原始指令。struct arch_specific_insn ainsn;---------------------------------------被复制的被探测点的原始指令用于单步执行架构强相关。u32 flags;-------------------------------------------------------------状态标记。
};2.2 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探测点的名称、地址、偏移2.3 示例代码
下面以内核中samples/kprobes/kprobe_example.c为例介绍如何使用kprobe进行内核函数探测。 该kprobe实例实现了_do_fork的探测该函数会在fork系统调用或者kernel_kthread创建内核线程时被调用。 对原%p修改为%pF后可读性更强。可以显示函数名称以及偏移量。
#include linux/kernel.h
#include linux/module.h
#include linux/kprobes.h#define MAX_SYMBOL_LEN 64
static 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)
{
#ifdef CONFIG_X86pr_info(%s pre_handler: p-addr %pF, ip %lx, flags 0x%lx\n,p-symbol_name, p-addr, regs-ip, regs-flags);
#endif
#ifdef CONFIG_ARM64pr_info(%s pre_handler: p-addr %pF, pc 0x%lx, pstate 0x%lx\n,p-symbol_name, p-addr, (long)regs-pc, (long)regs-pstate);
#endif/* 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)
{
#ifdef CONFIG_X86pr_info(%s post_handler: p-addr %pF, flags 0x%lx\n,p-symbol_name, p-addr, regs-flags);
#endif
#ifdef CONFIG_ARM64pr_info(%s post_handler: p-addr %pF, pstate 0x%lx\n,p-symbol_name, p-addr, (long)regs-pstate);
#endif
}/** 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 dont 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);模块的编译Makefile如下
obj-m : kprobe_example.oCROSS_COMPILEKDIR : /lib/modules/$(shell uname -r)/build
all:make -C $(KDIR) M$(PWD) modules
clean:rm -f *.ko *.o *.mod.o *.mod.c .*.cmd *.symvers modul*执行结果如下
[ 9363.905687] Planted kprobe at _do_fork0x0/0x3f0
[ 9366.924852] _do_fork pre_handler: p-addr _do_fork0x0/0x3f0, ip ffffffff88a86a61, flags 0x246
[ 9366.924858] _do_fork post_handler: p-addr _do_fork0x0/0x3f0, flags 0x246
[ 9366.932935] _do_fork pre_handler: p-addr _do_fork0x0/0x3f0, ip ffffffff88a86a61, flags 0x246
[ 9366.932938] _do_fork post_handler: p-addr _do_fork0x0/0x3f0, flags 0x246
[ 9366.957594] kprobe at _do_fork0x0/0x3f0 unregistered可以通过sudo cat /proc/kallsyms | grep l_do_fork来验证地址和符号是否对应。 若没有sudo看不到真实的地址。 参考资料
Linux kprobe调试技术使用Linux内核调试技术——kprobe使用与实现