ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

libbccをmrubyにポートしている話

たまには「〜話」メソッドでブログを書く。あと6月は二回ブログを書けたのでめでたい。

さて、タイトルのようなことをしています。

前提としてeBPFという、カーネル内で動く特定の目的のプログラムを比較的高速・安全に書くための技術があるのですが、そのプログラムはバイトコードベースなので、eBPFを扱うプログラムをもう少し人間的な形式で書きたくなります。せめてC言語とか。そのためのコンパイラがlibbccです。

github.com

大きくはkprobe、uprobe、kernel tracepointといったカーネルイベントのトレースをしたり、XDPというネットワーク周りのトレースをしたり挙動を変更する処理を書くことができたりします。

以下の記事が詳しいと思います。

mmi.hatenablog.com

qiita.com

知人の id:mrtc0 さんという人がeBPF関係の中でもUSDTという、プログラムの任意の箇所に任意のプローブ(もともと探針、みたいな意味ですね)を埋め込む(それも元のプログラムへ影響を最小限にして埋め込める)機能があるのですが、その検証をしていたりします。

blog.ssrf.in

今回、mrubyでその範囲の機能だけでもポートできた気がするので紹介します。

まずmrubyのプログラムにUSDT Probeを埋め込むためのmrbgemを作ってます。

github.com

exampleにもあるんですがこういうコードを書くと、とりあえずprobeを送り込めます。このmruby側のAPIは仮なので変えるかもですが...。

puts Process.pid
i = 0
loop do
  Probe.probe(i)
  i += 1
  sleep 1
end

これを起動しておく。

$ ./mruby/bin/mruby example/probe.rb                                        
19975

つづいて、mruby-bccというmrbgemを利用して、こういうコードを書きました。

github.com

pid = ARGV[0]

bpf_text = <<CLANG
#include <uapi/linux/ptrace.h>
int do_trace(struct pt_regs *ctx) {
    long buf, tgt;
    bpf_usdt_readarg(1, ctx, &buf);
    bpf_probe_read(&tgt, sizeof(tgt), (void *)&buf);
    bpf_trace_printk("%ld\\n", tgt);
    return 0;
};
CLANG

u = BCC::USDT.new(pid: pid.to_i)
u.enable_probe(probe: "mruby", fn_name: "do_trace")

b = BCC::BPF.new(text: bpf_text, usdt_context: u)

printf("%-18s %-16s %-6s %s\n", "TIME(s)", "COMM", "PID", "mruby-probe")

b.trace_fields do |task, pid, cpu, flags, ts, msg|
  printf("%-18.9f %-16s %-6d %s", ts, task, pid, msg)
end

このコード、分かると思いますが、Python版(標準添付)のbccラッパーのAPIを強く意識したものに、敢えてしています。

これを特権ありで実行すると、さっき起動したmruby-probeのプログラムから随時送られてくるprobeをトレースして表示できるようになります。

$ sudo ./mruby/bin/mruby example/usdt.rb 19975
TIME(s)            COMM             PID    mruby-probe
82312.907943000    mruby            19975  16
82313.908693000    mruby            19975  17
82314.918178000    mruby            19975  18
82315.918618000    mruby            19975  19
82316.919467000    mruby            19975  20
82317.928667000    mruby            19975  21
82318.938109000    mruby            19975  22
82319.948643000    mruby            19975  23
82320.949394000    mruby            19975  24
82321.950322000    mruby            19975  25
82322.951750000    mruby            19975  26

ひとまず USDT/uprobe に対応していますが、kprobeなども同じように対応できるはずなので、徐々にPython側のコードを見ながらポートしていく予定。そもそも、eBPFで何ができるかの全貌がまだわかっていないので、勉強しながら。

あと、コードを見ると分かるとおり最終的なエンドポイントが全てCになっているのでそこもなんとか整理したい。

そういう感じでeBPFやっていきましょう。