Uprobe-tracer: 基于 Uprobe 的事件追踪

作者:

Srikar Dronamraju

概述

基于 Uprobe 的追踪事件类似于基于 kprobe 的追踪事件。要启用此功能,请使用 CONFIG_UPROBE_EVENTS=y 构建内核。

与 kprobe 事件追踪器类似,此功能不需要通过 current_tracer 激活。 相反,通过 /sys/kernel/tracing/uprobe_events 添加探针点,并通过 /sys/kernel/tracing/events/uprobes/<EVENT>/enable 启用它。

但是,与 kprobe 事件追踪器不同,uprobe 事件接口期望用户计算对象中探针点的偏移量。

您也可以使用 /sys/kernel/tracing/dynamic_events 而不是 uprobe_events。该接口还将提供对其他动态事件的统一访问。

uprobe_tracer 概要

p[:[GRP/][EVENT]] PATH:OFFSET [FETCHARGS] : Set a uprobe
r[:[GRP/][EVENT]] PATH:OFFSET [FETCHARGS] : Set a return uprobe (uretprobe)
p[:[GRP/][EVENT]] PATH:OFFSET%return [FETCHARGS] : Set a return uprobe (uretprobe)
-:[GRP/][EVENT]                           : Clear uprobe or uretprobe event

GRP           : Group name. If omitted, "uprobes" is the default value.
EVENT         : Event name. If omitted, the event name is generated based
                on PATH+OFFSET.
PATH          : Path to an executable or a library.
OFFSET        : Offset where the probe is inserted.
OFFSET%return : Offset where the return 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 userspace)
 @+OFFSET     : Fetch memory at OFFSET (OFFSET from same file as PATH)
 $stackN      : Fetch Nth entry of stack (N >= 0)
 $stack       : Fetch stack address.
 $retval      : Fetch return value.(\*1)
 $comm        : Fetch current task comm.
 +|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*2)(\*3)
 \IMM         : Store an immediate value to the argument.
 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), hexadecimal types
                     (x8/x16/x32/x64), "string" and bitfield are supported.

(\*1) only for return probe.
(\*2) this is useful for fetching a field of data structures.
(\*3) Unlike kprobe event, "u" prefix will just be ignored, because uprobe
      events can access only user-space memory.

类型

fetch-args 支持多种类型。 Uprobe 追踪器将按给定的类型访问内存。前缀“s”和“u”表示这些类型分别是带符号的和无符号的。“x”前缀表示它是无符号的。追踪的参数以十进制(“s”和“u”)或十六进制(“x”)显示。在没有类型转换的情况下,根据架构使用“x32”或“x64”(例如,x86-32 使用 x32,而 x86-64 使用 x64)。 字符串类型是一种特殊类型,它从用户空间获取一个“以 null 结尾”的字符串。位域是另一种特殊类型,它接受 3 个参数:位宽、位偏移和容器大小(通常为 32)。语法是

b<bit-width>@<bit-offset>/<container-size>

对于 $comm,默认类型是“string”; 任何其他类型都是无效的。

事件分析

您可以通过 /sys/kernel/tracing/uprobe_profile 检查每个事件的探针命中总数。 第一列是文件名,第二列是事件名称,第三列是探针命中次数。

使用示例

  • 添加一个探针作为新的 uprobe 事件,如下所示向 uprobe_events 写入新定义(在可执行文件 /bin/bash 中 0x4245c0 的偏移量处设置一个 uprobe)

    echo 'p /bin/bash:0x4245c0' > /sys/kernel/tracing/uprobe_events
    
  • 添加一个探针作为新的 uretprobe 事件

    echo 'r /bin/bash:0x4245c0' > /sys/kernel/tracing/uprobe_events
    
  • 取消设置注册的事件

    echo '-:p_bash_0x4245c0' >> /sys/kernel/tracing/uprobe_events
    
  • 打印出已注册的事件

    cat /sys/kernel/tracing/uprobe_events
    
  • 清除所有事件

    echo > /sys/kernel/tracing/uprobe_events
    

以下示例展示了如何在探针的文本地址处转储指令指针和 %ax 寄存器。在 /bin/zsh 中探测 zfree 函数

# cd /sys/kernel/tracing/
# cat /proc/`pgrep zsh`/maps | grep /bin/zsh | grep r-xp
00400000-0048a000 r-xp 00000000 08:03 130904 /bin/zsh
# objdump -T /bin/zsh | grep -w zfree
0000000000446420 g    DF .text  0000000000000012  Base        zfree

0x46420 是加载在 0x00400000 的对象 /bin/zsh 中 zfree 的偏移量。因此,uprobe 的命令将是

# echo 'p:zfree_entry /bin/zsh:0x46420 %ip %ax' > uprobe_events

而 uretprobe 的命令将是相同的

# echo 'r:zfree_exit /bin/zsh:0x46420 %ip %ax' >> uprobe_events

注意

用户必须显式计算对象中探针点的偏移量。

我们可以通过查看 uprobe_events 文件来查看已注册的事件。

# cat uprobe_events
p:uprobes/zfree_entry /bin/zsh:0x00046420 arg1=%ip arg2=%ax
r:uprobes/zfree_exit /bin/zsh:0x00046420 arg1=%ip arg2=%ax

可以通过查看文件 events/uprobes/zfree_entry/format 来查看事件的格式。

# cat events/uprobes/zfree_entry/format
name: zfree_entry
ID: 922
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:int common_padding;                 offset:8;  size:4; signed:1;

     field:unsigned long __probe_ip;           offset:12; size:4; signed:0;
     field:u32 arg1;                           offset:16; size:4; signed:0;
     field:u32 arg2;                           offset:20; size:4; signed:0;

print fmt: "(%lx) arg1=%lx arg2=%lx", REC->__probe_ip, REC->arg1, REC->arg2

定义后,默认情况下每个事件都被禁用。 要追踪这些事件,您需要通过以下方式启用它

# echo 1 > events/uprobes/enable

让我们开始追踪,休眠一段时间并停止追踪。

# echo 1 > tracing_on
# sleep 20
# echo 0 > tracing_on

此外,您可以通过以下方式禁用该事件

# echo 0 > events/uprobes/enable

您可以通过 /sys/kernel/tracing/trace 查看追踪的信息。

# cat trace
# tracer: nop
#
#           TASK-PID    CPU#    TIMESTAMP  FUNCTION
#              | |       |          |         |
             zsh-24842 [006] 258544.995456: zfree_entry: (0x446420) arg1=446420 arg2=79
             zsh-24842 [007] 258545.000270: zfree_exit:  (0x446540 <- 0x446420) arg1=446540 arg2=0
             zsh-24842 [002] 258545.043929: zfree_entry: (0x446420) arg1=446420 arg2=79
             zsh-24842 [004] 258547.046129: zfree_exit:  (0x446540 <- 0x446420) arg1=446540 arg2=0

输出显示 uprobe 被 pid 24842 触发,ip 为 0x446420,ax 寄存器的内容为 79。uretprobe 被触发,ip 为 0x446540,对应的函数入口为 0x446420。