Android 系统调试技巧 —— 使用 uprobe_events 分析用户态的行为

14 阅读3分钟

背景

在分析 Android 应用/系统的一些问题时,有时可能需要得知应用是否有直接或间接调用了系统的某个 native 函数,或调用某个函数时传入的参数情况。

uprobe_events 实战

uprobe_events 是 Linux 内核提供的一种调试方案,可以跟踪一个用户态函数(或是函数内的某行代码)是否有被调用,以及函数入参等,详细介绍可阅览官方文档 docs.kernel.org/trace/uprob… ,本文主要关注如何快速使用 uprobe_events。

下面以跟踪系统内的进程都打开了什么文件为例,演示如何快速使用 uprobe_events。

step 1

使用 readelf 获取 open64 函数在 libc.so 中的偏移量:

flame:/ # readelf -s /apex/com.android.runtime/lib64/bionic/libc.so | grep open64
   786: 000000000008bc10   980 FUNC    GLOBAL DEFAULT   15 freopen64
   904: 000000000008cb10   172 FUNC    GLOBAL DEFAULT   15 funopen64
  1154: 000000000007a7f0   404 FUNC    GLOBAL DEFAULT   15 open64
  1225: 000000000008b878   392 FUNC    GLOBAL DEFAULT   15 fopen64
  6160: 000000000007a7f0   404 FUNC    GLOBAL DEFAULT   15 open64
  6652: 000000000008b878   392 FUNC    GLOBAL DEFAULT   15 fopen64
  6653: 000000000008bc10   980 FUNC    GLOBAL DEFAULT   15 freopen64
  6677: 000000000008cb10   172 FUNC    GLOBAL DEFAULT   15 funopen64

可以看到偏移量为 0x7a7f0。

step 2

在 /sys/kernel/tracing 目录下执行如下命令:

echo 'p:open_probe /apex/com.android.runtime/lib64/bionic/libc.so:0x7a7f0 pathname=+0(%x0):string' > uprobe_events

其中 [open_probe] 可以替换为其它字符串。+0(%x0):string 表示将 open64 函数的第一个参数作为字符串输出。open64 函数的实现如下,其第一个参数代表文件路径。

bionic/libc/bionic/open.cpp

int open(const char* pathname, int flags, ...) {
  mode_t mode = 0;

  if (needs_mode(flags)) {
    va_list args;
    va_start(args, flags);
    mode = static_cast<mode_t>(va_arg(args, int));
    va_end(args);
  }

  return FDTRACK_CREATE(__openat(AT_FDCWD, pathname, force_O_LARGEFILE(flags), mode));
}
__strong_alias(open64, open);

step 3

在 /sys/kernel/tracing 目录下执行如下命令:

echo 1 > events/uprobes/enable
echo 1 > tracing_on

其中第一条命令表示打开 uprobe_events 跟踪点,第二条命令表示打开 Linux 内核 ftrace 输出开关。

step 4

在 /sys/kernel/tracing 目录下执行如下命令:

cat trace_pipe | grep open_probe

从桌面启动一个 App,得到如下输出结果(节选):

    RenderThread-24323 [005] .... 17932.382490: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/present"
    RenderThread-24323 [005] .... 17932.382534: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu0/cpu_capacity"
    RenderThread-24323 [005] .... 17932.382546: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu1/cpu_capacity"
    RenderThread-24323 [005] .... 17932.382556: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu2/cpu_capacity"
    RenderThread-24323 [005] .... 17932.382564: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu3/cpu_capacity"
    RenderThread-24323 [005] .... 17932.382573: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu4/cpu_capacity"
    RenderThread-24323 [005] .... 17932.382581: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu5/cpu_capacity"
    RenderThread-24323 [005] .... 17932.382589: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu6/cpu_capacity"
    RenderThread-24323 [005] .... 17932.382597: open_probe: (0x76e789a7f0) pathname="/sys/devices/system/cpu/cpu7/cpu_capacity"
    RenderThread-24323 [007] .... 17932.383114: open_probe: (0x76e789a7f0) pathname="/sys/class/kgsl/kgsl-3d0/gpu_model"

可以看到 RenderThread open 了一些 cpu 和 gpu 有关的节点。