On registration, the kprobe probe places a breakpoint (or jump, if optimized) instruction at the start of the probed instruction. When the breakpoint is hit, a trap occurs, the registers are saved, and control passes to kprobes, which calls the pre-handler. It then single steps the breakpoint and calls the post-handler. If a fault occurs, the fault handler is called. Handlers can be null if desired.
A kprobe probe can be inserted either in a function symbol or into an address, using the offset field, but not in both.
For example, to place a kprobe probe in the open syscall, we would use the meta-bsp-custom/recipes-kernel/open-kprobe/files/kprobe_open.c custom module as follows:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/kprobes.h> static struct kprobe kp = { .symbol_name = "do_sys_open", }; static int handler_pre(struct kprobe *p, struct pt_regs *regs) { pr_info("pre_handler: p->addr = 0x%p, lr = 0x%lx," " sp = 0x%lx ", p->addr, regs->ARM_lr, regs->ARM_sp); /* A dump_stack() here will give a stack backtrace */ return 0; } static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { pr_info("post_handler: p->addr = 0x%p, status = 0x%lx ", p->addr, regs->ARM_cpsr); } static int handler_fault(struct kprobe *p, struct pt_regs *regs,
int trapnr) { pr_info("fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr); /* Return 0 because we don't handle the fault. */ return 0; } static int kprobe_init(void) { int ret; kp.pre_handler = handler_pre; kp.post_handler = handler_post; kp.fault_handler = handler_fault; ret = register_kprobe(&kp); if (ret < 0) { pr_err("register_kprobe failed, returned %d ", ret); return ret; } pr_info("Planted kprobe at %p ", kp.addr); return 0; } static void kprobe_exit(void) { unregister_kprobe(&kp); pr_info("kprobe at %p unregistered ", kp.addr); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE("GPL");
We compile it with a Yocto recipe, as explained in the Building external kernel modules recipe in Chapter 2, The BSP Layer. Here is the code for the meta-bsp-custom/recipes-kernel/open-kprobe/open-kprobe.bb Yocto recipe file:
SUMMARY = "kprobe on do_sys_open kernel module." LICENSE = "GPLv2" LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-
2.0;md5=801f80980d171dd6425610833a22dbe6" inherit module PV = "0.1" SRC_URI = " file://kprobe_open.c file://Makefile " S = "${WORKDIR}"
With the Makefile file in meta-bsp-custom/recipes-kernel/open-kprobe/files/Makefile being as follows:
obj-m := kprobe_open.o SRC := $(shell pwd) all: $(MAKE) -C "$(KERNEL_SRC)" M="$(SRC)" modules_install: $(MAKE) -C "$(KERNEL_SRC)" M="$(SRC)" modules_install clean: rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c rm -f Module.markers Module.symvers modules.order rm -rf .tmp_versions Modules.symvers
Copy it to a target running the same kernel it has been linked against, and load it with the following:
$ insmod kprobe_open.ko Planted kprobe at 8010da84
We can now see the handlers printing in the console when a file is opened:
pre_handler: p->addr = 0x8014d608, lr = 0x8014d858, sp = 0xd8b99f98 pre_handler: p->addr = 0x8014d608, lr = 0x8014d858, sp = 0xd89abf98 post_handler: p->addr = 0x8014d608, status = 0x800e0013 post_handler: p->addr = 0x8014d608, status = 0x80070013