背景
在分析 Android 应用/系统的一些问题时,可能会想知道上层的一些行为,在内核中是否触发了我们预期的行为,以及这个行为是怎么触发的。
kprobe_events 使用
kprobe_events 是 Linux 内核提供的一种调试方案,可以跟踪一个内核函数是如何被调用的,以及函数入参,返回值等,详细介绍可阅览官方文档 docs.kernel.org/trace/kprob… ,本文主要关注如何快速使用 kprobe events。
下面以跟踪函数 binder_add_freeze_work 为例,演示如何快速使用 kprobe_events。
step 1
确定想要观察的函数,并确定 /proc/kallsyms 中包含了该函数。
cat /proc/kallsyms | grep binder_add_freeze_work
0000000000000000 t binder_add_freeze_work
step 2
在 /sys/kernel/tracing 目录下执行如下命令:
echo 'p:binder_add_freeze_work_probe binder_add_freeze_work' > kprobe_events
其中 [binder_add_freeze_work_probe] 也可以为其它字符串
step 3
在 /sys/kernel/tracing 目录下执行如下命令:
echo 1 > events/kprobes/enable
echo stacktrace > trace_options
echo 1 > tracing_on
其中第一条命令表示打开 kprobe_events 跟踪点,第二条命令表示跟踪点命中时输出调用栈,第三条命令表示打开 Linux 内核 ftrace 输出开关。
step 4
在 /sys/kernel/tracing 目录下执行如下命令:
cat trace_pipe | grep -A 10 binder_add_freeze_work_probe
得到如下输出结果:
CachedAppOptimi-2299 ( 1778) [006] d.... 401134.287737: binder_add_freeze_work_probe: (binder_add_freeze_work+0x0/0x2f4)
CachedAppOptimi-2299 ( 1778) [006] d.... 401134.287738: <stack trace>
=> binder_add_freeze_work
=> __arm64_sys_ioctl
=> invoke_syscall
=> el0_svc_common
=> do_el0_svc
=> el0_svc
=> el0t_64_sync_handler
=> el0t_64_sync
可以看到 CachedAppOptimizer 这个线程调用了函数 binder_add_freeze_work,调用栈也一并被输出出来。
实战案例
最近遇到了一个和文件锁有关的动画卡顿问题,在分析过程中需要了解内核中如下函数传入的 seq_file 的 size 是多少。
fs/locks.c
static void *locks_start(struct seq_file *f, loff_t *pos)
__acquires(&blocked_lock_lock)
{
struct locks_iterator *iter = f->private;
iter->li_pos = *pos + 1;
percpu_down_write(&file_rwsem);
spin_lock(&blocked_lock_lock);
return seq_hlist_start_percpu(&file_lock_list.hlist, &iter->li_cpu, *pos);
}
struct seq_file 的定义如下:
include/linux/seq_file.h
struct seq_file {
char *buf;
size_t size; <------------------- 需要看下这个成员是多少
size_t from;
size_t count;
size_t pad_until;
loff_t index;
loff_t read_pos;
struct mutex lock;
const struct seq_operations *op;
int poll_event;
const struct file *file;
void *private;
};
可以看到 size 成员在 seq_file 中的偏移量为 8 字节,所以可以通过如下命令,将 locks_start 函数入参 seq_file 的 size 值打印出来:
/sys/kernel/tracing # echo 'p:locks_start_probe locks_start +8(%x0)' > kprobe_events
/sys/kernel/tracing # echo 1 > events/kprobes/enable
/sys/kernel/tracing # echo 1 > tracing_on
/sys/kernel/tracing # cat trace_pipe | grep locks_start
其中,x0 表示 locks_start 的第一个参数 struct seq_file *f,是一个指针变量;%x0 表示解引用 f,相当于 C 语言代码 *f。
得到输出如下,表明每次 locks_start 传入的 seq_file 的 size 为 4 KB。
CachedAppOptimi-18562 ( 18401) [001] d.... 176843.738641: locks_start_probe: (locks_start+0x0/0x64) arg1=0x1000
CachedAppOptimi-18562 ( 18401) [007] d.... 176843.738998: locks_start_probe: (locks_start+0x0/0x64) arg1=0x1000
CachedAppOptimi-18562 ( 18401) [007] d.... 176843.739202: locks_start_probe: (locks_start+0x0/0x64) arg1=0x1000
CachedAppOptimi-18562 ( 18401) [007] d.... 176843.739294: locks_start_probe: (locks_start+0x0/0x64) arg1=0x1000