DTrace (2)

Table of Contents

rm -P

前回、乱数発生器を調べていたら、ユーザーにはarc4randomをお勧めしますと 説明されてた。どんな風に使うの? そんなの、あり余るユーザーランドのソー スを見ればいいんでないかい。gamesとかで多用されてるのは頷けるんだけど、 それに紛れてrmでも利用されてたぞ。

コードは、こんなの。

int pass(int fd, off_t len, char *buf, size_t bsize) {
        size_t wlen;

        for (; len > 0; len -= wlen) {
                wlen = len < bsize ? len : bsize;
                arc4random_buf(buf, wlen);
                if (write(fd, buf, wlen) != wlen)
                        return (0);
        }
        return (1);
}

慌ててmanしてみたら、

-P      Attempt to overwrite regular writable files before deleting them.
        Files are overwritten once with a random pattern.  Files with
        multiple links will be unlinked but not overwritten.

ファイルを削除する時、本体部分を乱数でメチャメチャにしちゃう機能が提供 されてるんだなあ。普通ファイルを削除すると言っても、台帳から登録を抹消 するだけ。本体はそのまま残っている。悪い人にかかると容易に復元出来てし まう。それを避けようという、親分のオモテナシガ発露してんだなあ。

man arc4randomすると、面白い記述が見付かったぞ。

HISTORY
     These functions first appeared in OpenBSD 2.1.

     The original version of this random number generator used the RC4 (also
     known as ARC4) algorithm.  In OpenBSD 5.5 it was replaced with the
     ChaCha20 cipher, and it may be replaced again in the future as
     cryptographic techniques advance.  A good mnemonic is "A Replacement Call
     for Random".

FreeBSDの場合は、上記に加えて、こんな追記も有ったぞ。

The arc4random() random number generator was first introduced in
FreeBSD 2.2.6.  The ChaCha20 based implementation was introduced in
FreeBSD 12.0, with obsolete stir and addrandom interfaces removed at the
same time.

ChaCha20って、何か面白そう。後で調べてみるか。

random for FreeBSD

FreeBSD方面のTRNGって、どうなってる? OpenBSDでやったように、ドライバー を炙り出して、そこから辿って行けばいいかなと思ったら、個々のドライバー は列挙されなかった。仕組みが違うのかなあ? 何か手掛りが欲しいぞと。

思い出したのは、中断してたDTrace。多分、手掛りは得られるだろう。 乱数発生器を使って、それに反応するかだな。

# dtrace -P random
dtrace: description 'random' matched 1 probe
CPU     ID                    FUNCTION:NAME
  0  60667            event_processor:debug
^C

# dwatch -P random
INFO Watching 'random:::' ...
2024 Aug 28 06:50:18 1001.1001 dd[7678]: dd if=/dev/urandom of=zzz count=1
^C
# dwatch -l random
PROVIDER:MODULE:FUNCTION:NAME
fbt:kernel:random:entry

見事に反応があった。後は、DTraceがどう実装されてるか、見てくだけ(回り 道のような気がするが、一石二鳥を期待します)。

one liner

DTraceの復習です。直ぐに忘れてしまうからね。

dtraceと言ったら、一行野郎でしょ。 DTrace on FreeBSD に、有用な一行野郎が結集している。

DTrace/One-Liners

長いスクリプトは御免だ。一行でピリリと締まった事をするのが、醍醐味。 昔々perlが流行する前の頃、awkを発見して、こりゃ便利と回りに風潮したら、 にわかに一行野郎が増殖したな。懐かしいこったい。

dtrace src

それからソース・コードだな。

ユーザーランド側

/usr/src/cddl/usr.sbin/dtrace/
/usr/src/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c

カーネル側

/usr/src/sys/cddl/compat/opensolaris/kern/opensolaris_dtrace.c
/usr/src/sys/cddl/contrib/opensolaris/uts/common/dtrace/dtrace.c
/usr/src/sys/contrib/openzfs/module/os/freebsd/spl/spl_dtrace.c
/usr/src/sys/fs/nfsclient/nfs_clkdtrace.c
/usr/src/sys/kern/kern_dtrace.c
/usr/src/sys/netinet/in_kdtrace.c
/usr/src/sys/netinet/sctp_kdtrace.c
/usr/src/sys/security/audit/audit_dtrace.c

FreeBSDのソースは寄贈品やライセンス別に元コードが保持されていて、本体 を探すのが面倒だ。まあ、原著者を尊重してるんだろうから、これはこれで、 文句を言う筋合いじゃなかろうけど。。

他の資料として、こういうのも参照しておく。

Attachment 'dtracebsdcan2008jb.pdf'

DTrace Tools (pdf)

SDT

オイラーがいの一番で知りたいのは、プローブがどのように埋め込まれている かだ。適切な資料が見付かったぞ。

DTrace SDT Kernel Probes

実例が出てた。

#include <sys/param.h>
#include <sys/queue.h>
#include <sys/sdt.h>

SDT_PROVIDER_DEFINE(foobar);
SDT_PROBE_DEFINE2(foobar, source_file1, foo, entry, entry, "int", "char *");
SDT_PROBE_DEFINE1(foobar, source_file1, foo, return, return, "int");
SDT_PROBE_DEFINE(foobar, source_file2, bar, entry, entry);
SDT_PROBE_DEFINE(foobar, source_file2, bar, my_error_condition_name, my-error-condition-name);
SDT_PROBE_DEFINE(foobar, source_file2, bar, return, return);

こんなマクロを使って、定義するとな。

int
foo(int a, const char *b) {
    int c;

    SDT_PROBE2(foobar, source_file1, foo, entry, a, b);

    if (a == 3) {
         SDT_PROBE1(foobar, source_file1, foo, return, 1);
         return 1;
    }

    c = a*a;
    printf("%s\f", b);

    SDT_PROBE1(foobar, source_file1, foo, return, c);
    return (c);
}

void
bar(void) {

    SDT_PROBE0(foobar, source_file2, bar, entry);

    ...
    if (trigger_error()) {
        /*
         * Some kind of "milestone", we want to be able
         * to trigger a dtrace action in case this happens.
         */
        SDT_PROBE0(foobar, source_file2, bar, my_error_condition_name);
        return;
    }
    ...

    SDT_PROBE0(foobar, source_file2, bar, return);
}

これが実例だ。

大文字はマクロって慣習なんで、どんな風に展開されるか確認だな。cc -E example.cとして展開させるも、そのまま表われてくる。マクロじゃない別の 使われ方をしてるの? 悩んでいてもしょうがない。

実際のカーネルで埋め込みを確認。

sakae@fb:/sys $ grep SDT_PROVIDER_DEFINE -r .
./fs/ext2fs/ext2_alloc.c:SDT_PROVIDER_DEFINE(ext2fs);
./fs/fuse/fuse_main.c:SDT_PROVIDER_DEFINE(fusefs);
./security/mac/mac_framework.c:SDT_PROVIDER_DEFINE(mac);
./security/mac/mac_framework.c:SDT_PROVIDER_DEFINE(mac_framework);
./contrib/dev/iwlwifi/iwl-devtrace.c:SDT_PROVIDER_DEFINE(iwlwifi);
./netinet/in_kdtrace.c:SDT_PROVIDER_DEFINE(ip);
./netinet/in_kdtrace.c:SDT_PROVIDER_DEFINE(tcp);
 :
./kern/kern_priv.c:SDT_PROVIDER_DEFINE(priv);
./kern/sched_ule.c:SDT_PROVIDER_DEFINE(sched);
./kern/kern_proc.c:SDT_PROVIDER_DEFINE(proc);
./kern/kern_lockstat.c:SDT_PROVIDER_DEFINE(lockstat);
./kern/sched_4bsd.c:SDT_PROVIDER_DEFINE(sched);
 :

そして、実際にrandomを検出するプローブの埋め込み先。

sakae@fb:/sys $ grep SDT_PROBE_DEFINE -r . | grep random
./dev/random/fortuna.c:SDT_PROBE_DEFINE2(random, fortuna, event_processor, debug, "u_int", "struct fs_pool *");

実際には、こんな風に定義されてた。

/* Probes for dtrace(1) */
#ifdef _KERNEL
SDT_PROVIDER_DECLARE(random);
SDT_PROVIDER_DEFINE(random);
SDT_PROBE_DEFINE2(random, fortuna, event_processor, debug, "u_int", "struct fs_pool *");
#endif /* _KERNEL */

random_fortuna_pre_read(void)
{
   :
#ifdef _KERNEL
        SDT_PROBE2(random, fortuna, event_processor, debug, fortuna_state.fs_reseedcount, fortuna_state.fs_pool);
#endif

gdb するぞ

こんな駆動方法で、とりあえず実行。

# dwatch -d -P random >dd
# less dd
# dtrace -s dd
2024 Aug 29 07:28:26 0.0 gdb141[7922]: gdb -q dtrace -p 7920

アタッチして、待機場所を特定。

(gdb) bt
#0  _umtx_op_err () at /usr/src/lib/libthr/arch/i386/i386/_umtx_op_err.S:37
#1  0x207136e7 in _thr_umtx_timedwait_uint (mtx=0x2071735c, id=0, clockid=0,
    abstime=0xffbfecac, shared=0) at /usr/src/lib/libthr/thread/thr_umtx.c:234
#2  0x207098c6 in _thr_sleep (curthread=0x207bb000, clockid=0,
    abstime=0xffbfecac) at /usr/src/lib/libthr/thread/thr_kern.c:197
#3  0x2070488d in cond_wait_user (cvp=0x207c0020, mp=0x20a00004,
    abstime=<optimized out>, cancel=<optimized out>)
    at /usr/src/lib/libthr/thread/thr_cond.c:318
#4  cond_wait_common (cond=<optimized out>, mutex=<optimized out>,
    abstime=0xffbfecac, cancel=0) at /usr/src/lib/libthr/thread/thr_cond.c:378
#5  0x20704c0a in _thr_cond_timedwait (cond=0x207e5004, mutex=0x207e5000,
    abstime=0xffbfecac) at /usr/src/lib/libthr/thread/thr_cond.c:406
#6  0x205a0335 in pthread_cond_timedwait_exp (p0=0x207e5004, p1=0x207e5000,
    p2=0xffbfecac) at /usr/src/lib/libc/gen/_pthread_stubs.c:279
#7  0x204cb3d1 in dtrace_sleep (dtp=0x207d4000)
    at /usr/src/cddl/contrib/opensolaris/lib/libdtrace/common/dt_work.c:110
#8  0x00405810 in main (argc=3, argv=0xffbfed98)
    at /usr/src/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c:1971

最初から丁寧に見ていくなら、下記。

# gdb -q dtrace
Reading symbols from dtrace...
Reading symbols from /usr/lib/debug//usr/sbin/dtrace.debug...
(gdb) b main
Breakpoint 1 at 0x40495c: file /usr/src/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c, line 1313.
(gdb) r -n random:::
Starting program: /usr/sbin/dtrace -n random:::

Breakpoint 1, main (argc=3, argv=0xffbfecdc)
    at /usr/src/cddl/contrib/opensolaris/cmd/dtrace/dtrace.c:1313
1313            g_ofp = stdout

もう少し詳細にって事で、コンパイルの辺り。

for (i = 0; i < g_cmdc; i++)
        exec_prog(&g_cmdv[i]);
(gdb) p g_cmdv[0]
$2 = {
  dc_func = 0x406070 <compile_str>,
  dc_spec = DTRACE_PROBESPEC_NAME,
  dc_arg = 0xffbfee58 "random:::",
  dc_name = 0xffbfee58 "random:::",
  dc_desc = 0x401900 "description",
  dc_prog = 0x207c0880,
  dc_ofile = '\000' <repeats 1023 times>
}

コンパイルされるのか。大変だな。

                default:
=>                      if (!g_impatient && dtrace_errno(g_dtp) != EINTR)
                                dfatal("processing aborted");
                }

このあたりで、情報を受信して、結果を表示してるっぽい。もう少し詳細をつ めたいな。

make buildkernel

ちょっと趣を変えて、オブジェクトでは、どんな表現になってるか知りたい。 それには、カーネルをコンパイルして、その残骸をobjdump -dS してみれば良 いのかな。

cd /usr/src してから実行するんだったな。もう20年ぶりぐらいだから、すっかり 忘れていて、Makefileを眺めてしまったぞ。build(7)も参考に。

--------------------------------------------------------------
>>> Kernel build for GENERIC completed on Fri Aug 30 06:37:21 JST 2024
--------------------------------------------------------------
>>> Kernel(s)  GENERIC built in 2943 seconds, ncpu: 1
--------------------------------------------------------------

コンパイルに50分近くかかるって、ドンダケーーー。もうやるもんじゃないな。 どんどんと肥大化してくからな。

-rw-r--r--  1 root  wheel      1949 Aug 30 06:04 vers.c
-rw-r--r--  1 root  wheel         2 Aug 30 06:04 version
-rw-r--r--  1 root  wheel      2716 Aug 30 06:04 vers.o
-rwxr-xr-x  1 root  wheel  91198828 Aug 30 06:04 kernel.full*
-rwxr-xr-x  1 root  wheel  71127744 Aug 30 06:04 kernel.debug*
-rwxr-xr-x  1 root  wheel  23530004 Aug 30 06:04 kernel*
drwxrwxr-x  3 root  wheel       512 Aug 30 06:04 modules/

時間がかかるのは、同時にdebug用も作成してるから? それともカーネル関連 のツールを作成してるから?

sakae@fb:/usr/obj/usr/src/i386.i386/sys/GENERIC $ objdump -dS fortuna.o
00000000 <random_fortuna_pre_read>:
     :
;       for (i = 0; i < RANDOM_FORTUNA_NPOOLS; i++) {
     15c: 43                            incl    %ebx
     15d: 83 c7 6c                      addl    $0x6c, %edi
     160: 83 c6 20                      addl    $0x20, %esi
     163: 89 b5 d8 fe ff ff             movl    %esi, -0x128(%ebp)
     169: 83 fb 20                      cmpl    $0x20, %ebx
     16c: 75 82                         jne     0xf0 <random_fortuna_pre_read+0xf0>
     16e: bb 20 00 00 00                movl    $0x20, %ebx
;       SDT_PROBE2(random, fortuna, event_processor, debug, fortuna_state.fs_reseedcount, fortuna_state.fs_pool);
     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>
;       return (!uint128_is_zero(fortuna_state.fs_counter));
     180: a1 88 0d 00 00                movl    0xd88, %eax
;       return (a.u128t_word0 == b.u128t_word0 &&

;       KFAIL_POINT_CODE(DEBUG_FP, random_fortuna_pre_read, {
     387: 84 c9                         testb   %cl, %cl
     389: 0f 85 5c ff ff ff             jne     0x2eb <random_fortuna_pre_read+0x2eb>
     38f: ff b5 dc fe ff ff             pushl   -0x124(%ebp)
     395: eb 89                         jmp     0x320 <random_fortuna_pre_read+0x320>
;       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>
     3c4: e8 fc ff ff ff                calll   0x3c5 <random_fortuna_pre_read+0x3c5>

Uum … さっぱり要領を得ないなあ。

kern/vfs_cache.c

もう少し簡単そうなのを調べてみる。

static void
cache_neg_hit_finish(struct namecache *ncp)
{

        SDT_PROBE2(vfs, namecache, lookup, hit__negative, ncp->nc_dvp, ncp->nc_name);
        counter_u64_add(numneghits, 1);
}

こんな風に展開された。

00001330 <cache_neg_hit_finish>:
; {
    1330: 55                            pushl   %ebp
    1331: 89 e5                         movl    %esp, %ebp
    1333: 53                            pushl   %ebx
    1334: 57                            pushl   %edi
    1335: 56                            pushl   %esi
    1336: 83 e4 f8                      andl    $-0x8, %esp
    1339: 83 ec 10                      subl    $0x10, %esp
;       SDT_PROBE2(vfs, namecache, lookup, hit__negative, ncp->nc_dvp, ncp->nc_name);
    133c: 80 3d 00 00 00 00 00          cmpb    $0x0, 0x0
    1343: 75 7c                         jne     0x13c1 <cache_neg_hit_finish+0x91>
;       counter_u64_add(numneghits, 1);
    1345: 8b 35 4c 00 00 00             movl    0x4c, %esi
          :
;       SDT_PROBE2(vfs, namecache, lookup, hit__negative, ncp->nc_dvp, ncp->nc_name);
    13c1: a1 24 00 00 00                movl    0x24, %eax
    13c6: 85 c0                         testl   %eax, %eax
    13c8: 0f 84 77 ff ff ff             je      0x1345 <cache_neg_hit_finish+0x15>
    13ce: 8d 51 1e                      leal    0x1e(%ecx), %edx
    13d1: 6a 00                         pushl   $0x0
    13d3: 6a 00                         pushl   $0x0
    13d5: 6a 00                         pushl   $0x0
    13d7: 52                            pushl   %edx
    13d8: ff 71 14                      pushl   0x14(%ecx)
    13db: 50                            pushl   %eax
    13dc: ff 15 00 00 00 00             calll   *0x0
    13e2: 83 c4 18                      addl    $0x18, %esp
    13e5: e9 5b ff ff ff                jmp     0x1345 <cache_neg_hit_finish+0x15>
    13ea: 90                            nop

最初のSDTは、プローブするか否かの判定。アセンブラーにして2命令たらずの 軽いコード。これでdtrace導入のオーバーヘッドを軽減してんだな。

2回目の奴が本命。コンパイラーが 一気に大文字で始まる関数を展開。まあ、ユーザーから見ればマクロの様に扱 かえるんだな。説明では、この部分をオブジェクトと呼んでいる。オブジェク ト指向が花だった時代の名残。

オブジェトの定義に先立って、クラス名に相当するのを SDT_PROVIDER_DEFINE で指定しなさい。具体的(?)なクラスは、 SDT_PROBE_DEFINE1 等で定義しておきなさいって方針なんだな。

SDT_PROVIDER_DEFINE(foobar);
SDT_PROBE_DEFINE2(foobar, source_file1, foo, entry, entry, "int", "char *");
SDT_PROBE_DEFINE1(foobar, source_file1, foo, return, return, "int");

後は型が決まっているので、実装あるのみ。 SDT_PROBE2() とかで、オブジェ クトを量産してね。。これぞ、Solaris流だな。

chacha20