日記

日本語の勉強のためのブログ

【libbpf/C言語】eBPFのuprobeでprintfをフックしてみる

以下のステップで進む。

  1. uprobeの概要、使用法
  2. eBPFプログラムの作成
  3. ユーザ空間プログラムの作成
  4. コンパイル、動作確認

1. uprobeの概要、使用法

eBPFにはシステムコールや関数にフックする機能があるが、用途によって大きく2つに分かれる。

今回はprintfにアタッチしたいのでuprobeを使うことになる。

さて、eBPFで関数をフックするには2つのプログラムが必要となる(これはkprobe/uprobe共通)。ここでは便宜上「eBPFプログラム」と「ユーザ空間プログラム」と呼ぶことにする。

  • eBPFプログラム:フックした際の処理を記述するプログラム
  • ユーザ空間プログラム:eBPFプログラムをカーネルにロードし、関数にアタッチするプログラム

これら2つのプログラムが揃って初めてフックが可能となることに注意。

2. eBPFプログラムの作成

ファイル名はhook.bpf.c

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

SEC("uprobe//lib/x86_64-linux-gnu/libc.so.6:printf")
int BPF_KPROBE(printf_hook, const char *str) {
  bpf_printk("hello, printf!: %s\n", str);
  return 0;
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";

3. ユーザ空間プログラムの作成

ファイル名はhook.c

#include <stdio.h>
#include <unistd.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "hook.skel.h"

int main() {
  int err = 0;

  struct hook_bpf *skel = hook_bpf__open_and_load();
  if (!skel) {
    printf("error: failed to open BPF object\n");
    return 1;
  }

  LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts, .func_name = "printf",
              .retprobe = false);
  skel->links.printf_hook = bpf_program__attach_uprobe_opts(
      skel->progs.printf_hook, -1, "/lib/x86_64-linux-gnu/libc.so.6", 0,
      &uprobe_opts);

  for (;;) {
    sleep(1);
  }

  hook_bpf__destroy(skel);
  return 0;
}

4. コンパイル、動作確認

Makefile

TARGET = hook
ARCH = $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')

BPF_OBJ = ${TARGET:=.bpf.o}
USER_C = ${TARGET:=.c}
USER_SKEL = ${TARGET:=.skel.h}

all: $(TARGET) $(BPF_OBJ)
.PHONY: all 

$(TARGET): $(USER_C) $(USER_SKEL) 
    - rm -f $(TARGET).bpf-*.o.tmp
    - rm -f $(TARGET).bpf.o.temp-stream*
    gcc -Wall -o $(TARGET) $(USER_C) -L「ここにlibbpfのパスを入れる(例:-L/home/user/libbpf/src)」 -l:libbpf.a -lelf -lz

%.bpf.o: %.bpf.c vmlinux.h
    clang \
        -target bpf \
        -D __TARGET_ARCH_$(ARCH) \
        -I/usr/include/$(shell uname -m)-linux-gnu \
        -Wall \
        -O2 -g -o $@ -c $<
    llvm-strip -g $@

$(USER_SKEL): $(BPF_OBJ)
    bpftool gen skeleton $< > $@

vmlinux.h:
    bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
    
clean:
    - rm $(BPF_OBJ)
    - rm $(TARGET)
    - rm -f $(TARGET).bpf-*.o.tmp
    - rm -f $(TARGET).bpf.o.temp-stream*

テスト用のファイル(hello.c

#include <stdio.h>

int main() {
  int tmp = 1;
  printf("hello! %d\n", tmp);
  printf("hello! %d\n", tmp);
  return 0;
}

tmuxなどで3画面開き、以下を順に行う。

  • 1画面目はsudo makeしてからsudo ./hook
  • 2画面目はsudo cat /sys/kernel/tracing/trace_pipe
  • 3画面目はgcc -Wall hello.cしてから./a.out

a.outを実行して、2画面目にhello, printf!: hello! %dと出たらOK!