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);

etc