Android 系统调试技巧 —— 使用 kprobe_events 分析内核的行为

14 阅读3分钟

背景

在分析 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