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 に、有用な一行野郎が結集している。
長いスクリプトは御免だ。一行でピリリと締まった事をするのが、醍醐味。 昔々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のソースは寄贈品やライセンス別に元コードが保持されていて、本体 を探すのが面倒だ。まあ、原著者を尊重してるんだろうから、これはこれで、 文句を言う筋合いじゃなかろうけど。。
他の資料として、こういうのも参照しておく。
SDT
オイラーがいの一番で知りたいのは、プローブがどのように埋め込まれている かだ。適切な資料が見付かったぞ。
実例が出てた。
#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流だな。