このページの目次

ftrace と bpftrace によるユーザーランドトレース

カバー範囲: uprobe → USDT (DTraceプローブ) → bpftrace ユーザーランドスクリプト → perf probe (ユーザーランド) → ユーザーランド動的トレースの全体像 対象: Linux + bpftrace/bcc/perf

uprobe: ユーザーランド関数プローブ

# uprobe: 任意のユーザーランド関数のエントリ/リターンにプローブポイントを配置
# 構文: uprobe:<バイナリ>:<関数>

# 例: bash の readline をトレース:
echo 'p:bash_readline /bin/bash:readline' \
     > /sys/kernel/debug/tracing/uprobe_events
echo 1 > /sys/kernel/debug/tracing/events/uprobes/bash_readline/enable
cat /sys/kernel/debug/tracing/trace_pipe
# → bash が readline を呼び出すたびに記録される

# 引数付き:
echo 'p:bash_readline /bin/bash:readline cmd=+0(%si):string' \
     > uprobe_events
# cmd: 関数リターン後の第1引数 (ARMでは %si=x0、x86では %rdi)

# uretprobe: 関数リターン時に実行
echo 'r:bash_readline_ret /bin/bash:readline ret=$retval' \
     > uprobe_events

USDT: 事前に埋め込まれた静的プローブポイント

// USDT (User Statically Defined Tracing): DTraceプローブに類似
// コンパイル時に埋め込まれ、実行時はオーバヘッドゼロ (NOP) → 有効化されるまで

#include <sys/sdt.h>  // systemtap-sdt-dev (Debian) または dev-util/systemtap (Gentoo)

DTRACE_PROBE2(myapp, request_start, request_id, url);
// → NOPスレッドとしてコンパイルされ、実行時に uprobe によって INT3 ブレークポイントに置換される

// バイナリ内の USDT 埋め込みを確認:
bpftrace -l 'usdt:/path/to/binary:*'
readelf -n /path/to/binary | grep NT_STAPSDT

bpftrace: ユーザーランドトレース

# 1行でトレース:
bpftrace -e 'uprobe:/bin/bash:readline { printf("readline: %s\n", str(reg("di"))); }'

# ヒストグラム:
bpftrace -e 'uprobe:/lib64/libc.so.6:malloc { @size = hist(arg0); }'

# 統計:
bpftrace -e 'uretprobe:/lib64/libc.so.6:malloc /
             retval != 0/ { @bytes[comm] = sum(arg0); }'

# プロセス別:
bpftrace -e 'uprobe:/usr/bin/nginx:ngx_http_process_request {
             @requests[pid] = count(); }'

# 関数の実行時間をトレース:
bpftrace -e 'uprobe:/lib64/libc.so.6:malloc { @start[tid] = nsecs; }
             uretprobe:/lib64/libc.so.6:malloc /@start[tid]/ {
               @lat_us = hist((nsecs - @start[tid]) / 1000);
               delete(@start[tid]);
             }'

# open 呼び出しをトレース (ファイル名付き):
bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
             printf("%s %s\n", comm, str(args->filename)); }'

bcc ユーザーランドツール

# bcc: Python + BPF の高レベルラッパー
# コンパイル済みツール ( /usr/share/bcc/tools/ に配置):

execsnoop        # exec() シリーズ: 誰が誰をフォークしたか
opensnoop        # open() トレース: ファイル名、戻り値、実行時間
bitesize         # プロセス別の IO サイズ統計 (ヒストグラム)
filelife         # 一時ファイルのライフサイクル
statsnoop        # stat() トレース

# 用途: BPF プログラムを書かずに、プリコンパイル済みツールを直接使用可能

カーネルトレースとの組み合わせ

# ユーザーランドからカーネルランドまでの完全なチェーン:
bpftrace -e '
uprobe:/lib64/libpthread.so.0:pthread_mutex_lock {
    @lock_start[tid] = nsecs;
}
uretprobe:/lib64/libpthread.so.0:pthread_mutex_lock {
    $duration = (nsecs - @lock_start[tid]) / 1000;
    if ($duration > 10000) {  // > 10μs → futex_wait が発生した可能性
        printf("slow lock: %s %d μs\n", comm, $duration);
    }
    delete(@lock_start[tid]);
}
tracepoint:syscalls:sys_enter_futex {
    // ここでの tid は上記と関連付け可能 → futex 起因の遅延か確認
}'

デバッグテクニック

# バイナリ内でプローブ可能な関数を探す:
nm -D /usr/lib64/libc.so.6 | grep ' T ' | head -20   # 出力シンボル
objdump -T /usr/lib64/libc.so.6 | grep -E '\.text'    # コードセクションのシンボル

# インストール済みの uprobe を確認:
cat /sys/kernel/debug/tracing/uprobe_events
cat /sys/kernel/debug/tracing/kprobe_events

# perf プローブポイントを確認:
perf probe -l

# クリーンアップ:
echo > /sys/kernel/debug/tracing/uprobe_events

参考

  • ツール⁠: bpftrace (github.com/bpftrace), bcc (github.com/iovisor/bcc)
  • ドキュメント⁠: man perf-probe, Brendan Gregg のブログ
  • LWN: 「Uprobes and USDT」、「Dynamic tracing with bpftrace」

キーワード: uprobe, uretprobe, USDT, DTRACE_PROBE, bpftrace, bcc, user-space tracing, SDT