BPFプログラムからカーネル内のデータ構造にアクセスする方法
kprobeやtracepointなどにアタッチしたBPFプログラムは,BPF_FUNC_probe_read()
関数でカーネル内のデータ構造にアクセスすることができます.これはprobe_kernel_read()
のラッパーで,もし変な領域にアクセスしようした場合(page faultが発生した場合)は-EFAULTが返ります.
また,BPF_FUNC_get_current_task()
でcurrentのtask_structを得ることができます.
例として,/bin/catが実行されたときに,そのプロセスのvm_area_struct
の中身をダンプするプログラムを書いてみます.bccを利用して以下のように書けます.
#!/usr/bin/env python import bcc text = r""" #include <linux/ptrace.h> #include <linux/sched.h> int probe(struct pt_regs *ctx) { int i; struct task_struct *t = (struct task_struct *)bpf_get_current_task(); struct vm_area_struct *vma = t->mm->mmap; #pragma clang loop unroll(full) for (i = 0; i < 20; i++) { if (vma != NULL) { unsigned long inode = vma->vm_file->f_inode->i_ino; bpf_trace_printk("%lx-%lx %ld\n", vma->vm_start, vma->vm_end, inode); vma = vma->vm_next; } } return 0; } """ def main(): b = bcc.BPF(text=text, debug=0) b.attach_uprobe(name="/bin/cat", sym="main", fn_name="probe") b.trace_print() if __name__ == "__main__": main()
ここで注意点としては,(古いカーネルだと)BPFプログラムはループを扱えないので*1,pragmaを利用して展開する必要があります.またbreak文を含むようなプログラムはverifierにおこられるので,ちょっと書き方に工夫がいります.
bccでは,ポインタ参照は原則bpf_probe_read()
関数呼び出しになります*2.ここで,上記プログラムではポインタがNULLか確認していません*3.これで大丈夫?と思うかもしれませんが,実際のポインタ参照はbpf_probe_read()
を利用していて,もし読み出しに失敗した場合はbccは事前にバッファをクリアするので得られる値は0になります.アドレス0に対するbpf_probe_read()
は失敗するでしょうが,プログラムがクラッシュするといったことはないので,これで大丈夫です.結果としてはvma->vm_file
がNULLならinodeは0になります.
上記プログラムを実行しておいて*4,以下のようにcatを実行すると,
% cat /proc/self/maps 55e4824bf000-55e4824c7000 r-xp 00000000 08:02 2621482 /bin/cat 55e4826c6000-55e4826c7000 r--p 00007000 08:02 2621482 /bin/cat 55e4826c7000-55e4826c8000 rw-p 00008000 08:02 2621482 /bin/cat 55e4835b0000-55e4835d1000 rw-p 00000000 00:00 0 [heap] 7f1b8bfcf000-7f1b8c1b6000 r-xp 00000000 08:02 6685444 /lib/x86_64-linux-gnu/libc-2.27.so 7f1b8c1b6000-7f1b8c3b6000 ---p 001e7000 08:02 6685444 /lib/x86_64-linux-gnu/libc-2.27.so 7f1b8c3b6000-7f1b8c3ba000 r--p 001e7000 08:02 6685444 /lib/x86_64-linux-gnu/libc-2.27.so 7f1b8c3ba000-7f1b8c3bc000 rw-p 001eb000 08:02 6685444 /lib/x86_64-linux-gnu/libc-2.27.so 7f1b8c3bc000-7f1b8c3c0000 rw-p 00000000 00:00 0 7f1b8c3c0000-7f1b8c3e7000 r-xp 00000000 08:02 6685432 /lib/x86_64-linux-gnu/ld-2.27.so 7f1b8c41a000-7f1b8c43c000 rw-p 00000000 00:00 0 7f1b8c43c000-7f1b8c5d7000 r--p 00000000 08:02 4326139 /usr/lib/locale/locale-archive 7f1b8c5d7000-7f1b8c5d9000 rw-p 00000000 00:00 0 7f1b8c5e7000-7f1b8c5e8000 r--p 00027000 08:02 6685432 /lib/x86_64-linux-gnu/ld-2.27.so 7f1b8c5e8000-7f1b8c5e9000 rw-p 00028000 08:02 6685432 /lib/x86_64-linux-gnu/ld-2.27.so 7f1b8c5e9000-7f1b8c5ea000 rw-p 00000000 00:00 0 7fffea906000-7fffea927000 rw-p 00000000 00:00 0 [stack] 7fffea9fc000-7fffea9ff000 r--p 00000000 00:00 0 [vvar] 7fffea9ff000-7fffeaa00000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
以下の出力が得られます*5.BPFプログラム側からvma_structの情報が取得できていることが分かります.
<...>-8164 [001] .... 512025.107366: 0: 55e4824bf000-55e4824c7000 2621482 <...>-8164 [001] .... 512025.107381: 0: 55e4826c6000-55e4826c7000 2621482 <...>-8164 [001] .... 512025.107382: 0: 55e4826c7000-55e4826c8000 2621482 <...>-8164 [001] .... 512025.107383: 0: 7f1b8bfcf000-7f1b8c1b6000 6685444 <...>-8164 [001] .... 512025.107384: 0: 7f1b8c1b6000-7f1b8c3b6000 6685444 <...>-8164 [001] .... 512025.107384: 0: 7f1b8c3b6000-7f1b8c3ba000 6685444 <...>-8164 [001] .... 512025.107385: 0: 7f1b8c3ba000-7f1b8c3bc000 6685444 <...>-8164 [001] .... 512025.107390: 0: 7f1b8c3bc000-7f1b8c3c0000 0 <...>-8164 [001] .... 512025.107391: 0: 7f1b8c3c0000-7f1b8c3e7000 6685432 <...>-8164 [001] .... 512025.107394: 0: 7f1b8c5d7000-7f1b8c5d9000 0 <...>-8164 [001] .... 512025.107395: 0: 7f1b8c5e7000-7f1b8c5e8000 6685432 <...>-8164 [001] .... 512025.107396: 0: 7f1b8c5e8000-7f1b8c5e9000 6685432 <...>-8164 [001] .... 512025.107399: 0: 7f1b8c5e9000-7f1b8c5ea000 0 <...>-8164 [001] .... 512025.107402: 0: 7fffea906000-7fffea927000 0 <...>-8164 [001] .... 512025.107405: 0: 7fffea9fc000-7fffea9ff000 0 <...>-8164 [001] .... 512025.107408: 0: 7fffea9ff000-7fffeaa00000 0
またbpftraceなら以下のように書けます.
#include <linux/sched.h> #include <linux/fs.h> uprobe:/bin/cat:main { $vma = (struct vm_area_struct*)curtask->mm->mmap; unroll(20) { if ($vma != 0) { $inode = $vma->vm_file->f_inode->i_ino; printf("%lx-%lx %ld\n", $vma->vm_start, $vma->vm_end, $inode); $vma = $vma->vm_next; } } }
(ハイライトが欲しい...)
使い所が限られますが,あの情報ここにあったっけ?という時にカーネルモジュールを書かなくても確認できます.