1.kprobe是什么?
kprobe是一种动态探测技术,不用重新编译内核,在运行系统上插入模块方式,或者基于ftrace方式,动态的添加探测点(实现自己的回调函数),可以探测内核函数的参数和返回值。
kprobes的特点与使用限制: (1)kprobes允许在同一个被探测位置注册多个kprobe;
(2)一般情况下,可以探测内核中的任何函数,包括中断处理函数。不过实现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。
2.kprobe原理
(1)当用户注册一个探测点后,kprobe首先备份被探测点的对应指令,然后将原始指令的入口点替换为断点指令,该指令是CPU架构相关的,如i386和x86_64是int3,arm是设置一个未定义指令(目前的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)最后,执行流程回到被探测指令之后的正常流程继续执行。
3.kprobe用法
kprobe按实现方式,有两种类型:一个是直接向内核动态插入探测点;一个是基于Ftrace框架实现;
2.1 原生态的kprobe动态探测
编写一个模块,动态加载进内核,从而实现动态探测; 内核提供了一个struct kprobe结构体,以及相关API接口,可以用过这些接口实现kprobe结构,并注册到内核的kprobe子系统;
2.1.1kprobe结构体分析:
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; ///probe调用的计数(保证一个probe不递归调用)
/* 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; ///在执行pre_handler、post_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.1.2几个常用的回调函数原型:
typedef int (*kprobe_pre_handler_t) (struct kprobe *, struct pt_regs *);
typedef void (*kprobe_post_handler_t) (struct kprobe *, struct pt_regs *,
unsigned long flags);
typedef int (*kprobe_fault_handler_t) (struct kprobe *, struct pt_regs *,
int trapnr);
typedef int (*kretprobe_handler_t) (struct kretprobe_instance *,
struct pt_regs *);
2.1.3kprobe相关API:
int register_kprobe(struct kprobe *p); ///向内核注册kprobe探测点
void unregister_kprobe(struct kprobe *p); ///卸载kprobe探测点
int register_kprobes(struct kprobe **kps, int num); ///注册探测函数向量,包含多个探测点
void unregister_kprobes(struct kprobe **kps, int num); ///卸载探测函数向量,包含多个探测点
int register_kretprobe(struct kretprobe *rp); ///向内核注册kretprobe探测点
void unregister_kretprobe(struct kretprobe *rp); ///卸载kretprobe探测点
int register_kretprobes(struct kretprobe **rps, int num); ///注册kretprobe函数向量,包含多个探测点
void unregister_kretprobes(struct kretprobe **rps, int num);///卸载探测函数向量,包含多个探测点
int disable_kprobe(struct kprobe *kp); ///临时暂停指定探测点的探测
int enable_kprobe(struct kprobe *kp); ///使能指定探测点的探测
2.1.3 内核提供案例演示:
内核提供了一个非常简单的案例,samples/kprobes/kprobe_example.c,实现探测kernel_clone函数;
// SPDX-License-Identifier: GPL-2.0-only
/*
* Here's a sample kernel module showing the use of kprobes to dump a
* stack trace and selected registers when kernel_clone() is called.
*
* For more information on theory of operation of kprobes, see
* Documentation/trace/kprobes.rst
*
* You will see the trace data in /var/log/messages and on the console
* whenever kernel_clone() is invoked to create a new process.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#define MAX_SYMBOL_LEN 64
static char symbol[MAX_SYMBOL_LEN] = "kernel_clone"; ///探测函数名
module_param_string(symbol, symbol, sizeof(symbol), 0644);
/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {
.symbol_name = symbol,
};
/* kprobe pre_handler: called just before the probed instruction is executed */
///探测点前执行函数
static int __kprobes handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86
pr_info("<%s> pre_handler: p->addr = 0x%p, ip = %lx, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
#ifdef CONFIG_PPC
pr_info("<%s> pre_handler: p->addr = 0x%p, nip = 0x%lx, msr = 0x%lx\n",
p->symbol_name, p->addr, regs->nip, regs->msr);
#endif
#ifdef CONFIG_MIPS
pr_info("<%s> pre_handler: p->addr = 0x%p, epc = 0x%lx, status = 0x%lx\n",
p->symbol_name, p->addr, regs->cp0_epc, regs->cp0_status);
#endif
#ifdef CONFIG_ARM64
pr_info("<%s> pre_handler: p->addr = 0x%p, pc = 0x%lx,"
" pstate = 0x%lx\n",
p->symbol_name, p->addr, (long)regs->pc, (long)regs->pstate);
#endif
#ifdef CONFIG_ARM
pr_info("<%s> pre_handler: p->addr = 0x%p, pc = 0x%lx, cpsr = 0x%lx\n",
p->symbol_name, p->addr, (long)regs->ARM_pc, (long)regs->ARM_cpsr);
#endif
#ifdef CONFIG_RISCV
pr_info("<%s> pre_handler: p->addr = 0x%p, pc = 0x%lx, status = 0x%lx\n",
p->symbol_name, p->addr, regs->epc, regs->status);
#endif
#ifdef CONFIG_S390
pr_info("<%s> pre_handler: p->addr, 0x%p, ip = 0x%lx, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->psw.addr, regs->flags);
#endif
/* A dump_stack() here will give a stack backtrace */
return 0;
}
/* kprobe post_handler: called after the probed instruction is executed */
static void __kprobes handler_post(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
#ifdef CONFIG_X86
pr_info("<%s> post_handler: p->addr = 0x%p, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->flags);
#endif
#ifdef CONFIG_PPC
pr_info("<%s> post_handler: p->addr = 0x%p, msr = 0x%lx\n",
p->symbol_name, p->addr, regs->msr);
#endif
#ifdef CONFIG_MIPS
pr_info("<%s> post_handler: p->addr = 0x%p, status = 0x%lx\n",
p->symbol_name, p->addr, regs->cp0_status);
#endif
#ifdef CONFIG_ARM64
pr_info("<%s> post_handler: p->addr = 0x%p, pstate = 0x%lx\n",
p->symbol_name, p->addr, (long)regs->pstate);
#endif
#ifdef CONFIG_ARM
pr_info("<%s> post_handler: p->addr = 0x%p, cpsr = 0x%lx\n",
p->symbol_name, p->addr, (long)regs->ARM_cpsr);
#endif
#ifdef CONFIG_RISCV
pr_info("<%s> post_handler: p->addr = 0x%p, status = 0x%lx\n",
p->symbol_name, p->addr, regs->status);
#endif
#ifdef CONFIG_S390
pr_info("<%s> pre_handler: p->addr, 0x%p, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->flags);
#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 = 0x%p, trap #%dn", p->addr, trapnr);
/* Return 0 because we don't handle the fault. */
return 0;
}
/* NOKPROBE_SYMBOL() is also available */
NOKPROBE_SYMBOL(handler_fault);
static int __init kprobe_init(void)
{
int ret;
///初始化探测函数
kp.pre_handler = handler_pre;
kp.post_handler = handler_post;
kp.fault_handler = handler_fault;
//向内核kprobe子系统注册一个探测点
ret = register_kprobe(&kp);
if (ret < 0) {
pr_err("register_kprobe failed, returned %d\n", ret);
return ret;
}
pr_info("Planted kprobe at %p\n", kp.addr);
return 0;
}
static void __exit kprobe_exit(void)
{
///卸载探测点
unregister_kprobe(&kp);
pr_info("kprobe at %p unregistered\n", kp.addr);
}
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
编译成模块,动态insmod进内核;相关配置; General architecture-dependent options->
Kernel hacking --->
Sample kernel code --->
[root@myQEMU mnt]# insmod kprobe_example.ko
Planted kprobe at (____ptrval____)
<kernel_clone> pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x60000005
<kernel_clone> post_handler: p->addr = 0x(____ptrval____), pstate = 0x60000005
[root@myQEMU mnt]# ls /
<kernel_clone> pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x80000005
<kernel_clone> post_handler: p->addr = 0x(____ptrval____), pstate = 0x80000005
bin etc linuxrc proc sys tracing
dev lib mnt sbin tmp usr
[root@myQEMU mnt]# dmesg
<kernel_clone> pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x80000005
<kernel_clone> post_handler: p->addr = 0x(____ptrval____), pstate = 0x80000005
...
Planted kprobe at (____ptrval____)
pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x60000005
post_handler: p->addr = 0x(____ptrval____), pstate = 0x60000005
pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x80000005
post_handler: p->addr = 0x(____ptrval____), pstate = 0x80000005
pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x80000005
post_handler: p->addr = 0x(____ptrval____), pstate = 0x80000005
pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x80000005
post_handler: p->addr = 0x(____ptrval____), pstate = 0x80000005
[root@myQEMU mnt]#
确认下探测函数:
[root@myQEMU mnt]# cat /proc/kallsyms |grep 0xffff8000100341a0
<kernel_clone> pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x80000005
<kernel_clone> post_handler: p->addr = 0x(____ptrval____), pstate = 0x80000005
<kernel_clone> pre_handler: p->addr = 0x(____ptrval____), pc = 0xffff8000100341a0, pstate = 0x80000005
<kernel_clone> post_handler: p->addr = 0x(____ptrval____), pstate = 0x80000005
[root@myQEMU mnt]#
2.2基于Ftrace的kprobe
这种方式用户通过/sys/kernel/debug/tracing/目录下的trace等属性文件来探测指定的函数,无需再编写内核模块,使用更为简便,但需要内核的debugfs和ftrace功能的支持。 FTRACE = y KPROBE_EVENT = y HAVE_KPROBES_ON_FTRACE = y KPROBES_ON_FTRACE = y
2.2.1主要关注的属性文件:
属性文件 | 表头 |
---|---|
配置属性文件 | kprobe_events |
查询属性文件 | trace和trace_pipe |
使能属性文件 | events/kprobes/<GRP>/<EVENT>/enabled |
过滤属性文件 | events/kprobes/<GRP>/<EVENT>/filter |
格式查询属性文件 | events/kprobes/<GRP>/<EVENT>/format |
事件统计属性文件 | kprobe_profile |
(1)kprobe_events:
设置事件,即添加探测点,支持三种格式:
p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] ——设置一个probe探测点
r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] ——设置一个return probe探测点
-:[GRP/]EVENT -—删除一个探测点
各个字段的含义如下:
字段 | 注释 | |
---|---|---|
GRP | Group name. | 指定后会在events/kprobes目录下生成对应名字的目录,一般不设 |
EVENT | Event name | 指定后会在events/kprobes/<GRP>目录下生成对应名字的目录 |
MOD | Module name which has given SYM | 模块名,一般不设 |
SYM[+offs] | Symbol+offset where the probe is inserted | 指定被探测函数和偏移 |
MEMADDR | Address where the probe is inserted | 指定被探测的内存绝对地址 |
FETCHARGS | Arguments. Each probe can have up to 128 args. | 指定要获取的参数信息 |
%REG | Fetch register REG | 获取指定寄存器值 |
@ADDR | Fetch memory at ADDR (ADDR should be in kernel) | 获取指定内存地址的值 |
@SYM[+|-offs] | Fetch memory at SYM +|- offs (SYM should be a data symbol) | 获取全局变量的值 |
$stackN | Fetch Nth entry of stack (N >= 0) | 获取指定栈空间值,即sp寄存器+N后的位置值 |
$stack | Fetch stack address. | 获取sp寄存器值 |
$retval | Fetch return value.(*) | 获取返回值,仅用于return probe |
+|-offs(FETCHARG) | Fetch memory at FETCHARG +|- offs address.(**) | 以下可以由于获取指定地址的结构体参数内容,可以设定具体的参数名和偏移地址 |
NAME=FETCHARG | Set NAME as the argument name of FETCHARG | |
FETCHARG:TYPE | Set TYPE as the type of FETCHARG. Currently, basic types | 设置参数的类型,可以支持字符串和比特类型(u8/u16/u32/u64/s8/s16/s32/s64), "string" and bitfield are supported. |
(2)events/kprobes/<GRP>/<EVENT>/enabled
开启探测:echo 1 > events/kprobes/<GRP>/<EVENT>/enabled
暂停探测:echo 0 > events/kprobes/<GRP>/<EVENT>/enabled
(3)events/kprobes/<GRP>/<EVENT>/filter
该属性文件用于设置过滤条件,可以减少trace中输出的信息,它支持的格式和c语言的表达式类似,支持 ==,!=,>,<,>=,<=判断,并且支持与&&,或||,还有()。
2.2.2实例:探测kernel_clone:
(1)添加探测点
echo 'p:myprobe kernel_clone clone_flags=%x0 stack_start=%x1 stack_size=%x2 parent_tidptr=%x3 child_tidptr=+0($stack)' > /sys/kernel/debug/tracing/kprobe_events
(2)查看格式
[root@myQEMU tracing]# cat events/kprobes/myprobe/format
name: myprobe
ID: 1333
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:unsigned long __probe_ip; offset:8; size:8; signed:0;
field:u64 clone_flags; offset:16; size:8; signed:0;
field:u64 stack_start; offset:24; size:8; signed:0;
field:u64 stack_size; offset:32; size:8; signed:0;
field:u64 parent_tidptr; offset:40; size:8; signed:0;
field:u64 child_tidptr; offset:48; size:8; signed:0;
print fmt: "(%lx) clone_flags=0x%Lx stack_start=0x%Lx stack_size=0x%Lx parent_tidptr=0x%Lx child_tidptr=0x%Lx", REC->__probe_ip, REC->clone_flags, REC->stack_start, REC->stack_size, REC->parent_tidptr, REC->child_tidptr
[root@myQEMU tracing]#
(3)使能探测点
echo 1 > events/kprobes/myprobe/enable
(4)查看日志
[root@myQEMU tracing]# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 4/4 #P:1
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
sh-572 [000] d... 115.764845: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 122.796013: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 126.746282: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 137.690582: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
(5) 使能堆栈
[root@myQEMU tracing]# echo stacktrace > /sys/kernel/debug/tracing/trace_options
[root@myQEMU tracing]# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 9/9 #P:1
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
sh-572 [000] d... 115.764845: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 122.796013: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 126.746282: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 137.690582: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 138.834681: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 246.215843: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 289.473537: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 302.665952: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012adbdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012adbe20
sh-572 [000] d... 302.666924: <stack trace>
=> kernel_clone
=> __arm64_sys_clone
=> invoke_syscall.constprop.0
=> do_el0_svc
=> el0_svc
=> el0_sync_handler
=> el0_sync
(6)设置事件过滤
这里的格式参考前面的format;
[root@myQEMU tracing]# echo common_pid!=572 > events/kprobes/myprobe/filter
[root@myQEMU tracing]# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 6/6 #P:1
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
linuxrc-1 [000] d... 881.572321: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff80001000bdb0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff80001000be20
linuxrc-1 [000] d... 881.572352: <stack trace>
=> kernel_clone
=> __arm64_sys_clone
=> invoke_syscall.constprop.0
=> do_el0_svc
=> el0_svc
=> el0_sync_handler
=> el0_sync
sh-606 [000] d... 890.274144: myprobe: (kernel_clone+0x0/0x450) clone_flags=0xffff800012823db0 stack_start=0x0 stack_size=0x0 parent_tidptr=0x0 child_tidptr=0xffff800012823e20
sh-606 [000] d... 890.274203: <stack trace>
=> kernel_clone
=> __arm64_sys_clone
=> invoke_syscall.constprop.0
=> do_el0_svc
=> el0_svc
=> el0_sync_handler
=> el0_sync
(7)查询统计信息
后面两列分别表示命中,未命中:
[root@myQEMU tracing]# cat /sys/kernel/debug/tracing/kprobe_profile
myprobe 9 0
[root@myQEMU tracing]#
参考链接: https://blog.csdn.net/luckyapple1028/article/details/52972315 https://blog.csdn.net/luckyapple1028/article/details/54782659