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/ |
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