DTrace (3)
Table of Contents
get old OpenBSD
古い奴は、下記の様にして頂くとサーバーの管理者に教えてもらった。感謝し ます。
ob$ cvs -qd anoncvs@anoncvs.jp.openbsd.org:/cvs checkout -rOPENBSD_2_0 -P src
ちゃんと来てるか、確認。
ob$ cd arch/ ob$ ls CVS/ amiga/ arm32/ hp300/ m68k/ mvme68k/ pc532/ sparc/ vax/ alpha/ arc/ atari/ i386/ mac68k/ mvme88k/ pmax/ sun3/ ob$ cat CVS/Tag TOPENBSD_2_0
まだamd64とかは無い時代。時代をスライドさせると、新らしい石が登場し始 めた。やがて、古い奴は退場してくんだね。諸行無常。
ob$ cvs -q checkout -rOPENBSD_3_0 -P src ob$ ls sys/arch/ CVS/ arm/ hppa/ loongson/ macppc/ powerpc/ sh/ alpha/ arm64/ i386/ luna88k/ mips64/ powerpc64/ sparc64/ amd64/ armv7/ landisk/ m88k/ octeon/ riscv64/
dtrace kernel side
1回休んじゃったけど、dtraceの続きをやる。今回はカーネル側を探検。 ただ探検するってんじゃ、物見遊山に終ってしまうので、焦点を明確にしてお く。
dev/random/fortuna.c: random_fortuna_pre_read(void)
; SDT_PROBE2(random, fortuna, event_processor, debug, fortuna_state.fs_reseedcount, fortuna_state.fs_pool); ;; line 515 173: 80 3d 00 00 00 00 00 cmpb $0x0, 0x0 17a: 0f 85 17 02 00 00 jne 0x397 <random_fortuna_pre_read+0x397> : ; SDT_PROBE2(random, fortuna, event_processor, debug, fortuna_state.fs_reseedcount, fortuna_state.fs_pool); 397: a1 24 00 00 00 movl 0x24, %eax 39c: 85 c0 testl %eax, %eax 39e: 0f 84 dc fd ff ff je 0x180 <random_fortuna_pre_read+0x180> 3a4: 6a 00 pushl $0x0 3a6: 6a 00 pushl $0x0 3a8: 6a 00 pushl $0x0 3aa: 68 00 00 00 00 pushl $0x0 3af: ff 35 80 0d 00 00 pushl 0xd80 3b5: 50 pushl %eax 3b6: ff 15 00 00 00 00 calll *0x0 3bc: 83 c4 18 addl $0x18, %esp 3bf: e9 bc fd ff ff jmp 0x180 <random_fortuna_pre_read+0x180>
埋め込まれたプローブをコールするかの判定は0番地が定数の0か確認。値が有っ たら、有効。そして、何と0番地にある値をアドレスと見做してcallすると読 める。禁断の0番地を使っていいのか?
実際にgdbしたら、該当部分はdtraceのon/offに関係なく、固有の値になって た。逆アセしたのは、オブジェクトファイルだ。別ファイルで定義されてるも のは、場所だけ空けてあるんだ。リンクされて、一意に値が決定される。この 理解がすっかり抜け落ちていたぞ。
dtrace on qemu
実際にqemuで動作させてみる。
Welcome to FreeBSD! on QEMU root@qemu:~ # kldstat Id Refs Address Size Name 1 5 0x800000 1861c90 kernel 2 1 0x10e00000 6000 intpm.ko 3 1 0x10e06000 4000 smbus.ko root@qemu:~ # dtrace -n random::: dtrace: description 'random:::' matched 1 probe ^Z Suspended root@qemu:~ # bg [1] dtrace -n random::: & root@qemu:~ # kldstat Id Refs Address Size Name 1 27 0x800000 1861c90 kernel 2 1 0x10e00000 6000 intpm.ko 3 1 0x10e06000 4000 smbus.ko 4 1 0x10e0a000 3000 dtraceall.ko 5 8 0x10e0d000 5000 opensolaris.ko 6 8 0x10e12000 3c000 dtrace.ko 7 1 0x10e4e000 3000 dtmalloc.ko 8 1 0x10e51000 5000 dtnfscl.ko 9 1 0x10e56000 5000 fbt.ko 10 1 0x10e5b000 59000 fasttrap.ko 11 1 0x10eb4000 4000 sdt.ko 12 1 0x10eb8000 d000 systrace.ko 13 1 0x10ec5000 4000 profile.ko
初回はモジュールがロードされるんで、結構時間がかかる。えっ、モジュールっ て、これまた難儀そうな仕掛けだなあ。
root@qemu:~ # dd if=/dev/urandom of=z count=1 1+0 records in 1+0 records out 512 bytes transferred in 0.002288 secs (223778 bytes/sec) root@qemu:~ # 0 60266 event_processor:debug
このカーネルをgdbするんで、ssh接続とかは実質使用不可だ。切れない接続で あるコンソール上で、fg,bgを使うのさ。
gdb log
ホスト側で、ゲストのカーネルをコントロールする。大事なのとしてカーネル をその場で逆アセしたい。結果をログしたいぞ。
set logging file /tmp/LOG set logging on disassmble hoge set logging off
普通にやると、途中で画面STOPになるんで、.gdbinitに set pagination off を記述しておくと良い。
ana
逆アセしたコードを手掛かりに、謎のルーチンを検索。 call *0x1abc828
(gdb) disassemble 0x1abc828 Dump of assembler code for function sdt_probe_func: 0x01abc828 <+0>: push %eax 0x01abc829 <+1>: jmp 0x1abc84d <M_SENDFILE_uninit_sys_uninit+13> 0x01abc82b <+3>: adc %eax,(%ecx)
もう一つ、スイッチっぽい奴を確認。バイナリーでも、ちゃんと名前を拾い出 してくれるgdbは偉いなあ。
(gdb) x/w 0x1abc82c 0x1abc82c <sdt_probes_enabled>: 1
やっぱり感が有るんで enable dtrace した時、どんな経路でスイッチが入る か、gdbに監視してもらいましょ。
(gdb) watch sdt_probes_enabled Hardware watchpoint 1: sdt_probes_enabled (gdb) c Continuing. Hardware watchpoint 1: sdt_probes_enabled Old value = false New value = true (gdb) bt #0 0x112b6243 in ?? () #1 0x1123ca45 in ?? () #2 0x1123bb1a in ?? () #3 0x1123b7ac in ?? () #4 0x11240b01 in ?? () #5 0x00eda13b in devfs_ioctl (ap=0xcb98a4c) at /usr/src/sys/fs/devfs/devfs_vnops.c:942 #6 0x01106ab7 in VOP_IOCTL (vp=<optimized out>, command=<optimized out>, data=<optimized out>, fflag=<optimized out>, cred=<optimized out>, td=<optimized out>) at ./vnode_if.h:636 #7 0x01106ab7 in vn_ioctl (fp=0xccd9078, com=3221780486, data=0xcb98b68, active_cred=0x11b49e00, td=0xccef780) #8 0x00eda6ec in devfs_ioctl_f (fp=0xccd9078, com=3221780486, data=0xcb98b68, cred=0x11b49e00, td=0xccef780) at /usr/src/sys/fs/devfs/devfs_vnops.c:873 #9 0x0108f1a0 in fo_ioctl (fp=0xccd9078, data=0xcb98b68, td=0x1, com=<optimized out>, active_cred=<optimized out>) at /usr/src/sys/sys/file.h:360 #10 kern_ioctl (td=0xccef780, fd=7, com=3221780486, data=0xcb98b68 "\240\260\242 ") at /usr/src/sys/kern/sys_generic.c:801 #11 0x0108ef19 in sys_ioctl (td=0xccef780, uap=0xccefa18) at /usr/src/sys/kern/sys_generic.c:709 #12 0x0143deff in syscallenter (td=<optimized out>) at /usr/src/sys/i386/i386/../../kern/subr_syscall.c:188 #13 syscall (frame=0xcb98ce8) at /usr/src/sys/i386/i386/trap.c:1161 #14 0xffc03479 in ?? () #15 0x0000003b in ?? () #16 0x0000003b in ?? () #17 0x0040a6f4 in ?? () Backtrace stopped: previous frame inner to this frame (corrupt stack?)
今度は、exit dtrace してみる。
(gdb) bt #0 0x112b6243 in ?? () #1 0x1123ca45 in ?? () #2 0x1123bb1a in ?? () #3 0x1123b7ac in ?? () #4 0x11240b01 in ?? () #5 0x00eda13b in devfs_ioctl (ap=0xcb98a4c) at /usr/src/sys/fs/devfs/devfs_vnops.c:942 #6 0x01106ab7 in VOP_IOCTL (vp=<optimized out>, command=<optimized out>, data=<optimized out>, fflag=<optimized out>, cred=<optimized out>, td=<optimized out>) at ./vnode_if.h:636 #7 0x01106ab7 in vn_ioctl (fp=0xccd9078, com=3221780486, data=0xcb98b68, active_cred=0x11b49e00, td=0xccef780) #8 0x00eda6ec in devfs_ioctl_f (fp=0xccd9078, com=3221780486, data=0xcb98b68, cred=0x11b49e00, td=0xccef780) at /usr/src/sys/fs/devfs/devfs_vnops.c:873 #9 0x0108f1a0 in fo_ioctl (fp=0xccd9078, data=0xcb98b68, td=0x1, com=<optimized out>, active_cred=<optimized out>) at /usr/src/sys/sys/file.h:360 #10 kern_ioctl (td=0xccef780, fd=7, com=3221780486, data=0xcb98b68 "\240\260\242 ") at /usr/src/sys/kern/sys_generic.c:801 #11 0x0108ef19 in sys_ioctl (td=0xccef780, uap=0xccefa18) at /usr/src/sys/kern/sys_generic.c:709 #12 0x0143deff in syscallenter (td=<optimized out>) at /usr/src/sys/i386/i386/../../kern/subr_syscall.c:188 #13 syscall (frame=0xcb98ce8) at /usr/src/sys/i386/i386/trap.c:1161 #14 0xffc03479 in ?? () #15 0x0000003b in ?? () #16 0x0000003b in ?? () #17 0x0040a6f4 in ?? () Backtrace stopped: previous frame inner to this frame (corrupt stack?)
どうやらデバイスっぽいな。初回はモジュールをロードしたりデバイスを作成 するって大仕事を熟すんで、起動まで時間がかかるんだな。qemuは適当に遅い んで挙動が良く判るよ。
root@qemu:/tmp # ls -l /dev/dtrace/ total 0 crw------- 1 root wheel 0x5e Sep 8 23:47 dtmalloc crw------- 1 root wheel 0x5a Sep 8 23:47 dtrace crw------- 1 root wheel 0x60 Sep 8 23:47 fasttrap crw------- 1 root wheel 0x5f Sep 8 23:47 fbt crw-rw---- 1 root wheel 0x5d Sep 8 23:47 helper crw------- 1 root wheel 0x61 Sep 8 23:47 profile
慌ててゲストを作成したんで、時計がUTCになってた。ホスト側から、
etc/localtimeを取り寄せたよ。それでも駄目なら、touch
/etc wall_cmos_clock
しておくと良い。
see code
kern_stc.c
/* * Hook for the DTrace probe function. The SDT provider will set this to * dtrace_probe() when it loads. */ sdt_probe_func_t sdt_probe_func = sdt_probe_stub; volatile bool __read_frequently sdt_probes_enabled; /* * This is a stub for probe calls in case kernel DTrace support isn't * enabled. It should never get called because there is no DTrace support * to enable it. */ void sdt_probe_stub(uint32_t id, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4) { printf("sdt_probe_stub: unexpectedly called\n"); kdb_backtrace(); }
上で出てきたコードを追跡すると、こんな所にやってくるなあ。 謎のアセンブラルーチン。
│ >0x1122eb50 push %ebp │ │ 0x1122eb51 mov %esp,%ebp │ │ 0x1122eb53 push %ebx │ │ 0x1122eb54 push %edi │ │ 0x1122eb55 push %esi │ │ 0x1122eb56 sub $0x144,%esp │ │ 0x1122eb5c cmpb $0x0,0x1abce84 │ │ 0x1122eb63 jne 0x11230ba5 │ │ 0x1122eb69 mov 0x8(%ebp),%esi │ │ 0x1122eb6c call 0x112478f8 │ │ 0x1122eb71 mov %fs:0x0,%ecx │ │ 0x1122eb78 mov 0x328(%ecx),%ecx │ │ 0x1122eb7e movb $0x1,0x2(%ecx) │ │ 0x1122eb82 mov 0x1124da90,%ecx │ │ 0x1122eb88 mov -0x4(%ecx,%esi,4),%ecx │ │ 0x1122eb8c mov %fs:0x24,%edx
呼ばれた状況。515行って、確かに、SDTの所なんだけど。
(gdb) bt 3 #0 0x1122eb50 in ?? () #1 0x00d61cd1 in read_random (random_buf=0x3b00b70, len=32) at /usr/src/sys/dev/random/randomdev.c:261 #2 0x0110e7ff in chacha20_randomstir (chacha20=chacha20@entry=0x3c4f000) at /usr/src/sys/libkern/arc4random.c:131
無名の関数と言う事なら、自動生成してるに違いない。
sakae@fb:/sys/cddl/dev/dtrace $ grep cmpb -l -r . ./amd64/dtrace_asm.S ./x86/dis_tables.c ./i386/dtrace_asm.S
sakae@fb:/sys/cddl/dev/dtrace $ ls aarch64/ dtrace_ioctl.c i386/ amd64/ dtrace_load.c mips/ arm/ dtrace_modevent.c powerpc/ dtrace_anon.c dtrace_sysctl.c riscv/ dtrace_cddl.h dtrace_test.c x86/ dtrace_debug.c dtrace_unload.c dtrace_hacks.c dtrace_vtime.c
共通のコードと石特有のコードに分離して、定義されてるんだな。
x86/ dis_tables.c
に巨大なテーブルが用意されてる。これが無名なアセンブ
ル・コードではなかろうか。
example kld
/usr/share/examples/kld このあたりを読んでからかな? 特に、cdev/module あたりに注目。
d_open_t mydev_open; d_close_t mydev_close; d_ioctl_t mydev_ioctl; d_read_t mydev_read; d_write_t mydev_write;
こんなデバイスの5大機能を準備。それを利用できる様にするには、ロードが 必要になる。cdevmod.c
cdev_load(module_t mod, int cmd, void *arg) switch (cmd) { case MOD_LOAD: /* Do any initialization that you should do with the kernel */ /* if we make it to here, print copyright on console*/ printf("\nSample Loaded kld character device driver\n"); make_dev_args_init(&mda); mda.mda_devsw = &my_devsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_WHEEL; mda.mda_mode = 0600; err = make_dev_s(&mda, &sdev, "cdev"); break; case MOD_UNLOAD: printf("Unloaded kld character device driver\n"); destroy_dev(sdev); break; /* Success*/
モジュールをロードした時にデバイスが作成される。アンロードするとデバイ スは消滅する。ダイナミックにデバイスが出現したり消滅したりするとな。
dtraceコマンドでは人知れずデバイスが作成される。このデバイスを通じて、 ユーザーランドと交信するんだな。果してR/Wで済ましているものだろうか? それともオイラーの知らない別の手段が有るのだろうか? 以前に見つけた資料によると、IOCTL ムニャムニャと説明してたなあ。
ioctl(2)
SYNOPSIS #include <sys/ioctl.h> int ioctl(int fd, unsigned long request, ...); DESCRIPTION The ioctl() system call manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl() requests. The argument fd must be an open file descriptor.
dtraceを実装するにつけ、専用のシステムコールを強要するってのは最低の手 段だ。それに代わるものとして、デバイスを作成し、それをioctlしちゃえっ て戦略っぽい。何でも出来そうな予感がするぞ。
fdと要求番号と引数を指定するんか。ならば、どんなfdが利用されるのだろう?
oot@qemu:/tmp # fstat -p 815 USER CMD PID FD MOUNT INUM MODE SZ|DV R/W root dtrace 815 text / 59456 -r-xr-xr-x 31972 r root dtrace 815 ctty /dev 54 crw------- ttyu0 rw root dtrace 815 wd / 173185 drwxrwxrwt 512 r root dtrace 815 root / 2 drwxr-xr-x 512 r root dtrace 815 0 /dev 54 crw------- ttyu0 rw root dtrace 815 1 /dev 54 crw------- ttyu0 rw root dtrace 815 2 /dev 54 crw------- ttyu0 rw root dtrace 815 3 /dev 90 crw------- dtrace/dtrace rw root dtrace 815 4 /dev 96 crw------- dtrace/fasttrap rw
dtraceの状況。fasttrapなんてイカにもっぽいのが有ったので、
sys_iostat
でキャッチしようと思ったら、雑音が多すぎて断念。
Breakpoint 3, kern_ioctl (td=0xccedb40, fd=3, com=1079539723, data=0xcb75b68 "") at /usr/src/sys/kern/sys_generic.c:727 727 AUDIT_ARG_FD(fd);