あらすじ
前回の記事で、以下のような構成要素がBPFプログラムに最低限必要な情報だと結論づけました。
必要なセクション strtab BPFプログラムセクション with 正しい名前 ライセンス symtab 必要なシンボルテーブル BPFプログラムの関数名 ライセンスの場所
今回は実際にバイナリを作ってロードしてみます。
ツールを作っている話
RubyKaigi takeout 2021 で話すと思うんですが(まだトーク詳細はアップされていません)、 Rucy という名前の、Pure RubyのスクリプトだけでBPFのバイナリツールが作成できるSDKを開発しています(CO-REにできるかはまだ調査中です)。
ご存知の通り RbBCC を作っていたのですが、昨今のBPFツールの潮流の変化に対応して新しいプロジェクトを始めたということです。例えば:
その下準備の一つとして、RubyスクリプトからBPFバイナリが作成できる必要があります。ちなみにRustの場合、例えば RedBPF のようなものがあります。参考:
Rucy の進捗: Ruby DSLからELFファイルを作る
今のところ、「Ruby DSLからELFファイルを作る」ことができるようになっています。
冒頭の必要なセクションとシンボルテーブルを定義するDSLは以下のような感じ。
include Rucy ELFFile.define do |elf| elf.header do |e| e.type ET_REL e.machine EM_BPF e.section do |scn| scn.name ".strtab" scn.type SectionType::STRTAB end e.section do |scn| scn.name "license" scn.symname "__license" scn.type SectionType::LICENSE scn.data "GPL\x00" end e.section do |scn| scn.name "cgroup/dev" scn.symname "my_prog" scn.type SectionType::PROG scn.data "\xb7\x00\x00\x00\x00\x00\x00\x00" + "\x95\x00\x00\x00\x00\x00\x00\x00" end e.section do |scn| scn.name ".symtab" scn.type SectionType::SYMTAB end end end
真ん中の謎のバイナリ、つまりBPFのIRが気になる感じです。ここのコンパイラはまだ完成していないので、いったんハンドアセンブルしたものです。
rucyのワークスペースに rucy-elfgen
というものがあるので、以下のようにサンプルコードを走らせます。
rucy-elfgen$ cargo run --example elf_dsl -- \ examples/elf_dsl.rb ~/test.bin
これで ~/test.bin
にELFフォーマットのバイナリができる。
$ llvm-readelf -a ~/test.bin | head -25 ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: EM_BPF Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 216 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 64 (bytes) Number of section headers: 5 Section header string table index: 1 There are 5 section headers, starting at offset 0xd8: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 [ 1] .strtab STRTAB 0000000000000000 000040 000036 00 0 0 1 [ 2] license PROGBITS 0000000000000000 000076 000004 00 WA 0 0 1 [ 3] cgroup/dev PROGBITS 0000000000000000 000080 000010 00 AX 0 0 8 [ 4] .symtab SYMTAB 0000000000000000 000090 000048 18 1 1 8 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), p (processor specific) Elf file type is REL (Relocatable file) Entry point 0x0 There are 0 program headers, starting at offset 0 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align Section to Segment mapping: Segment Sections... None .strtab license cgroup/dev .symtab There are no relocations in this file. Symbol table '.symtab' contains 3 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 __license 2: 0000000000000000 16 FUNC GLOBAL DEFAULT 3 my_prog There are no section groups in this file.
DSL での定義と見比べてください。正しいフォーマットのELF形式です。
また、BPFプログラムも正常に組み込まれています。
$ llvm-objdump -d ~/test.bin /home/vagrant/test.bin: file format elf64-bpf Disassembly of section cgroup/dev: 0000000000000000 <my_prog>: 0: b7 00 00 00 00 00 00 00 r0 = 0 1: 95 00 00 00 00 00 00 00 exit
手製BPFプログラムをロードする
この先日の実験の際と同じプログラムを(ファイル名だけ変えて)走らせてみます。
$ sudo mkdir /sys/fs/cgroup/unified/test-bpf-based-device-cgroup $ sudo ./load_dev_cgroup $ sudo bpftool prog ... 29: cgroup_device name my_prog tag a04f5eef06a7f555 gpl loaded_at 2021-08-12T09:12:35+0000 uid 0 xlated 16B jited 37B memlock 4096B
ロードに成功しました。今回は、実質どんなデバイスにも retrun 0
を返すようなプログラムのため、どんなデバイスにもアクセスできないはずです。
$ echo $$ | sudo tee /sys/fs/cgroup/unified/test-bpf-based-device-cgroup/cgroup.procs 1858 $ head -c 10 /dev/urandom | hexdump -C head: cannot open '/dev/urandom' for reading: Operation not permitted
フィルタリングに成功しているようです。 $ echo $$ | sudo tee /sys/fs/cgroup/unified/cgroup.procs
のように当該グループから外れれば、元のように /dev/urandom にアクセス可能な状態に戻ります。
ということで(rodataやリロケーションなど、考慮すべきことは他にもたくさんありますが...) Ruby スクリプトからvalidなBPFバイナリを作成できるようになりました。
あとは、中身のBPFのinsnをRubyのコードから生成できるようになればよかったですね、という感じなので、引き続きやっていくしかない。
9月中旬にそんなお話ができるよう、引き続き調査を続けます。