OpenBSD with qemu

久しぶりに実家へ行ったら、縁側に枯草が干してあった。義母さん、これ何ですか?って、思わず聞いてしまったぞ。

お灸にするよもぎとの事。デイサービスで行っている、足が痛い、膝が痛いと言う仲間に勧められて、貰ってきたとの事。コエンザイムじゃ駄目なんですか?って言うと、ハナタカさんの女房が、すり減った関節は、何やったって回復しないわよ。富士フィルムの研究員に聞いてみたいぞ。

お灸に使う艾(もぐさ)の作り方

もぐさひねり初級編<動画で知る!鍼と灸>

医療大学がユーチューブに上げていた。他にも色々見つかる。これって、民間療法だな。 東洋医学と言えば、中国4000年の歴史を誇る、漢方薬。

今猛威を奮っている新型コロナウイルス(COVID-19)に効くやつは無いんですか? TVに出て来る映像は、西洋医学ばかり。 もう漢方では太刀打ち出来ないのかな?

オイラーが幼少の頃は体が弱く、よく風邪をひいた。おばちゃんが、ほうずきを干したやつを、煎じて飲まされたな。それから、体が丈夫になるようにと、ヤツメウナギやらマムシの黒焼きやら、、効いたような効かなかったような。。。

guile in WSL

先日debianが10.3になったんで、Windows10のWSLに入れてるdebianもupgradeしたんよ。 元々このシステムはミニマムなやつに、非常用のemacsぐらいしか入れていないので、upgradeされる物も極わずか。

で、upgradeされるパッケージの中に、何故かlibguileが候補として挙がってた。何かにくっついて入ってきたのもだな。ならば、ちょいとguileを足せば、オイラーも恩恵にあずかれるのではなかろうか。入れてあげた。

無事に動いたんで、WSLがどれぐらい遅いか確認してみた。

scheme@(guile-user)> (time (fibo 40))
clock utime stime cutime cstime gctime
16.29 16.17  0.02   0.00   0.00   0.00
$2 = 165580141

guile-2.2.4の結果です。ちなみにCentOSのguile-3.0でjit off の時の結果は、14.75だった。自前でguileをコンパイルして3.0にすれば、jitのターボチャージャーが働いて、5.0ぐらいにはなろうと予想する。

けどね、それをやるにはコンパイル環境やら用意しなきゃならん。gule-devを入れようとしたら、gccがわんさかと押し寄せて来る事が分かったので、入れるの止めた。

今入ってるのは、非常用のscheme環境と割り切っておこう。そうしないと取り留めつかなくなりますからね。

qemu on OpenBSD

以前やろうとして中断してたやつをやってみる。OpenBSDの中にqemuを入れて、そこでゲストOSとしてOpneBSDを動かしkernelの観察日記をつけるあれ。

環境はdebianに作ってあったので、それをそのまま持ってきて起動。

o32$ ./boot
qemu-system-i386: cannot set up guest memory 'pc.ram': Cannot allocate memory

メモリー不足で動かんってのが、前回までの粗筋。さてどうする?

qemu-system-i386 -m 256  -net nic -net user,hostfwd=tcp::2022-:22 disk

メモリーを256Mも割り当てしなくても十分に動くでしょ。128Mにしたら起動した。 でも遅いな。 64Bitのシステムなら、もっと早く動くんではなかろうか?

OpenBSD(amd64) with qemu

果たして64Bitシステムで32Bit用のカーネルをコンパイル出来るのか? FAQを調べてみたら、どうやら出来そうだ。これはもうやってみる鹿。カーネルとかをコンパイルするんなら、wobjのグループに属してるべきなんてtipsを知ったぞ。

ちょいと過去の記事を漁ってみたら、 2015年にOpenBSD 探検って取り上げてる。それから2018年にもAudio in OpenBSDってので、qumeで動かし、kernelの挙動を確認するやつをね。その時は、OpenBSDでコンパイルしたやつをウブに持って行って、そこで動かしてた。

今度は、そんな面倒かけずにOpenBSD内で完結させる作戦だ。まずはコンパイルだな。

obsd# cd /usr/src/sys/arch/i386/conf
obsd# cp GENERIC DEBUG
obsd# vi DEB
makeoptions DEBUG="-g"      ## <-- ADD this line
obsd# config DEB
obsd# cd ../compile/DEB
obsd# make depend
obsd# COPTS="-O0" make

取り合えずdebug用のカーネルをコンパイル。エラーになったぞ。

#radeondrm*     at pci?         # ATI Radeon DRM driver
#drm0           at radeondrm? primary 1
#drm*           at radeondrm?
#wsdisplay0     at radeondrm? primary 1
#wsdisplay*     at radeondrm? mux -1

こんな具合に、RedeonをGENERICからコメントアウトしたよ。そしたらコンパイル成功した。 本当にi386用が出来ているか、確認。

ob$ file bsd.gdb
bsd.gdb: ELF 32-bit LSB executable, Intel 80386, version 1

i386が進化してamd64になったんで、大丈夫なんだな。49Mの巨大なサイズだったよ。 Makefileを探ってみたら、

.if ${MACHINE} == "amd64"
CFLAGS+=        -m32
AFLAGS+=        -m32
LDFLAGS=        -melf_i386
LINKFLAGS+=     ${LDFLAGS}
.endif

こんな記述を発見。clangなコンパイラーでも、こういう事が出来るのね。こういうのクロスコンパイラーと呼んでいいものですか?

次は、i386の入ったdiskを作る。

qemu-img create -f qcow2 disk  1G
qemu-system-i386 -m 256M -net nic -net user -cdrom install66.iso disk

心配してた256M問題も、何なくクリアして動いてくれた。X関係は入れないので(それとソース関係も)、1Gあれば足りるでしょ。swapは無くてもいいんだけど、警告が出てくるのがいやなので、猫の額程(50m)を割り当てたよ。

尚、初回の起動は、sshdのキーを作成したり、デフォルトのダエモン君が起動したりするので、いやになる程待たされる。一息ついてくるのが良い。

これで、起動して、不必要なデーモンが立ち上がらないように設定。

vm$ cat /etc/rc.conf.local
smtpd_flags="NO"
ntpd_flags="NO"
slaacd_flags="NO"
sndiod_flags="NO"
pflogd_flags="NO"

それから、起動時にlibとkernelの再配置をしないようにコメントアウト。これをしないと、loginプロンプトが出てくるまで、膨大な時間がかかる。

vm$ grep reorder /etc/rc
reorder_libs() {
        echo -n 'reordering libraries:'
### reorder_libs
### /usr/libexec/reorder_kernel &

大体、こんな環境で動いている。

vm$ uname -a
OpenBSD vm.my.domain 6.6 DEB#0 i386
vm$ ps awx
  PID TT  STAT        TIME COMMAND
    1 ??  I        0:01.19 /sbin/init
92597 ??  IU       0:00.08 dhclient: em0 [priv] (dhclient)
 8726 ??  Ip       0:00.10 dhclient: em0 (dhclient)
31432 ??  IpU      0:00.30 syslogd: [priv] (syslogd)
36726 ??  Sp       0:00.62 /usr/sbin/syslogd
72106 ??  I        0:00.15 /usr/sbin/sshd
25249 ??  Sp       0:00.43 /usr/sbin/cron
80478 ??  I        0:01.95 sshd: sakae [priv] (sshd)
84518 ??  S        0:01.23 sshd: sakae@ttyp0 (sshd)
62837 p0  Sp       0:00.51 -ksh (ksh)
22034 p0  R+pU     0:00.07 ps -awx
 6582 C0  I+pU     0:00.19 /usr/libexec/getty std.9600 ttyC0
85758 C1  I+pU     0:00.26 /usr/libexec/getty std.9600 ttyC1
25696 C5  I+pU     0:00.23 /usr/libexec/getty std.9600 ttyC5
vm$ df -k
Filesystem  1K-blocks      Used     Avail Capacity  Mounted on
/dev/wd0a     1014302    758314    205274    79%    /

あっ、大事なカーネルの事を忘れていた。bsd.oldは、インストール時のオリジナルな奴。bsdの方は、今回作成したやつ。drmな奴を削ったにも関わらずに肥大化してるのはdebug情報を内在してるからかな。

vm$ ls -l /bsd*
-rwx------  2 root  sakae  17210279 Feb 12 06:22 /bsd
-rwx------  2 root  sakae  17210279 Feb 12 06:22 /bsd.booted
-rwx------  1 root  wheel  13942980 Feb 11 15:53 /bsd.old
-rw-------  1 root  wheel   8860963 Feb 11 15:41 /bsd.rd

起動は、下記スクリプト

ob$ cat gdb-boot
qemu-system-i386 -m 256 -s -net nic -net user,hostfwd=tcp::2022-:22 disk

何が味噌と言うか、gdbなんだようと言うのは、さりげなく紛れ込んでいる、-s にある。

これ、OSが起動中のtopで捉えたcpu利用状況。

load averages:  0.74,  0.21,  0.08                      ob.localdomain 06:09:26
36 processes: 35 idle, 1 on processor                                  up  0:35
CPU0:  3.8% user,  0.0% nice,  0.4% sys,  0.0% spin,  0.0% intr, 95.8% idle
CPU1: 37.1% user,  0.0% nice,  2.8% sys,  0.0% spin,  0.0% intr, 60.1% idle
CPU2: 37.5% user,  0.0% nice,  2.6% sys,  0.0% spin,  0.0% intr, 59.9% idle
CPU3:  4.6% user,  0.0% nice,  1.0% sys,  0.0% spin,  0.0% intr, 94.4% idle
Memory: Real: 185M/975M act/tot Free: 991M Cache: 393M Swap: 0K/518M

cpu1,2に跨って、負荷がかかっている。qemuは負荷が重いので協力して箏に当たっているのかな? kernelのコンパイルの時は、cpu1,2,3の順番でプロセスが動いていたぞ。cpu0は、マスターなんで、指図するだけか。

qemuのあれ

pkgから入れたqemuのバージョン

ob$ qemu-system-i386 -version
QEMU emulator version 4.1.0
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

そして、manからgdb関係のオプションを抜粋

       -S  Do not start CPU at startup (you must type 'c' in the monitor).

       -gdb dev
           Wait for gdb connection on device dev. Typical connections will
           likely be TCP-based, but also UDP, pseudo TTY, or even stdio are
           reasonable use case. The latter is allowing to start QEMU from
           within gdb and establish the connection via a pipe:

                   (gdb) target remote | exec qemu-system-i386 -gdb stdio ...

       -s  Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port
           1234.

qemuの起動スクリプトに、-S(大文字に注意)を付けると、CPUの電源onから、gdbの配下に入る事が出来る。すなわちOSの起動を追跡出来るんだ。(まあ、根性無いからやらないけど)

遊び方

先に、gdb-bootでOSを起動しておく。loginになるまで時間がかかる(約2分)。 適当にログイン。qemuのコンソールは、Xの上に立ち上がるぞ。そこにloginが無難だ。

ssh -p2022 localhost

するって手もあるけど、gdbが動いちゃうとTCP通信が途絶しちゃうんで注意。

やおら、

ob$ pwd
/usr/obj/sys/arch/i386/compile/DEB
ob$ cat .gdbinit
target remote :1234

gdbを起動した時の自動実行ファイルを用意しとく。このdirに降りてくるには、wobjのグループになってれば良い。

ob$ gdb -q bsd.gdb
Reading symbols from bsd.gdb...done.
acpicpu_idle () at /usr/src/sys/dev/acpi/acpicpu.c:1188
1188                    break;
(gdb) b microtime
Breakpoint 1 at 0xd08e6956: file /usr/src/sys/kern/kern_tc.c, line 234.
(gdb) c
Continuing.

gdbを起動すると、kernelの空回りルーチンの何処かで止まる。止めたい所にBPを置いてから継続。

Breakpoint 1, microtime (tvp=0xf3638bb8) at /usr/src/sys/kern/kern_tc.c:234
234             bintime(&bt);
(gdb) bt
#0  microtime (tvp=0xf3638bb8) at /usr/src/sys/kern/kern_tc.c:234
#1  0xd068bf7d in sys_gettimeofday (p=0xd1c39900, v=0xf3638cb4,
    retval=0xf3638cac) at /usr/src/sys/kern/kern_time.c:336
#2  0xd06bae59 in mi_syscall (p=0xd1c39900, code=67,
    callp=0xd10f91cc <sysent+804>, argp=0xf3638cb4, retval=0xf3638cac)
    at /usr/src/sys/sys/syscall_mi.h:92
#3  0xd06bab16 in syscall (frame=0xf3638cf0)
    at /usr/src/sys/arch/i386/i386/trap.c:597
#4  0xd09afd1d in Xsyscall_untramp ()
#5  0xf3638cf0 in ?? ()
#6  0x1aa1a77b in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

loginした側で、dateって叩いてみたら、gettimeofdayのシステムコールが発せられ、その中で使われている関数に飛んできた。後は普通のアプリを探るのと一緒だ。

間違った所にBPを置いてしまって、さっぱりHitしない時は、C-c C-c すればgdbが再度現れるんで、正しい所にBPを置けばよい。使い終わったらdetachすれば良い。

ulimit

i386版でqemuを起動した時、メモリー足りないが出てた。リソース配布状況を調べてみる。

OpenBSD(amd64)

ob$ ulimit -a
time(cpu-seconds)    unlimited
file(blocks)         unlimited
coredump(blocks)     unlimited
data(kbytes)         1572864
stack(kbytes)        4096
lockedmem(kbytes)    668358
memory(kbytes)       2001980
nofiles(descriptors) 512
processes            256

OpenBSD(i386)

o32$ ulimit -a
time(cpu-seconds)    unlimited
file(blocks)         unlimited
coredump(blocks)     unlimited
data(kbytes)         524288
stack(kbytes)        4096
lockedmem(kbytes)    680932
memory(kbytes)       2039272
nofiles(descriptors) 512
processes            128

どうも、マシンによってえこひいきが有るな。特にdataの割り当て。と言う事で、ulimit -d 1572864 して、増やしてあげたら、無事に起動した。やれやれ。

bintime

sys_gettimeofdayが利用してる内部ルーチンにBPを置いて追跡。

(gdb) bt
#0  bintime (bt=0xf1d108e0) at /usr/src/sys/kern/kern_tc.c:210
#1  0xd08e6967 in microtime (tvp=0xf1d10938) at /usr/src/sys/kern/kern_tc.c:234
#2  0xd068bf7d in sys_gettimeofday (p=0xd17c3608, v=0xf1d10a34, retval=0xf1d10a2c) at /usr/src/sys/kern/kern_time.c:336
 :

この中では、自由変数(グローバル変数ね)が、いきなり参照されてる。(kern_tc.c)どんなデータかひとまず確認。どうやら、時間関係を保持してる共通の構造体だな。th_boottimeには、OSの起動時間が入っていた。th_microtimeなんかは、現在の時刻データだ。

(gdb) p *timehands
$3 = {
  th_counter = 0xd1103ce4 <hpet_timecounter>,
  th_adjtimedelta = 0,
  th_adjustment = 0,
  th_scale = 184467440736,
  th_offset_count = 4121071613,
  th_boottime = {
    sec = 1581542082,
    frac = 15284937090680771013
  },
  th_offset = {
    sec = 585,
    frac = 15469855674470014203
  },
  th_microtime = {
    tv_sec = 1581542668,
    tv_usec = 667220
  },
  th_nanotime = {
    tv_sec = 1581542668,
    tv_nsec = 667220656
  },
  th_generation = 15232,
  th_next = 0xd1105d54 <th0>
}

注意深く観察してると、10秒間隔ぐらいで呼び出されている。ひょっとしてcronの仕業? 元を断ってみるって事でcronを停止させた。

けど、やっぱり呼び出されている。でも、呼び出しのルーツが違う。カーネル内から呼び出されている。disk関係なんで、syncする為の内部スレッドからかな。

(gdb) bt
#0  bintime (bt=0xf1cddd80) at /usr/src/sys/kern/kern_tc.c:210
#1  0xd08e6907 in nanotime (tsp=0xf1cdddb8) at /usr/src/sys/kern/kern_tc.c:225
#2  0xd0cc41e9 in enqueue_randomness (val=40131) at /usr/src/sys/dev/rnd.c:294
#3  0xd04ca347 in disk_unbusy (diskp=0xd180b030, bcount=2048, blkno=6592, read=0) at /usr/src/sys/kern/subr_disk.c:1273
#4  0xd05a2f09 in wddone (v=0xd180b000) at /usr/src/sys/dev/ata/wd.c:571
#5  0xd0dfb398 in wdc_ata_bio_done (chp=0xd18230dc, xfer=0xd168bf60) at /usr/src/sys/dev/ata/ata_wdc.c:606
#6  0xd0df9b20 in wdc_ata_bio_intr (chp=0xd18230dc, xfer=0xd168bf60, irq=1) at /usr/src/sys/dev/ata/ata_wdc.c:553
#7  0xd04332eb in wdcintr (arg=0xd18230dc) at /usr/src/sys/dev/ic/wdc.c:968
#8  0xd078cee5 in pciide_compat_intr (arg=0xd18230dc) at /usr/src/sys/dev/pci/pciide.c:1945
#9  0xd06c69ab in intr_handler (frame=0xf1cde0a8, ih=0xd181b0c0) at /usr/src/sys/arch/i386/i386/machdep.c:4026
#10 0xd09b2a86 in Xintr_ioapic2_untramp ()

これ以外にもsys_gettimeofdayが呼ばれているなあ。これユーザーランド側からの呼び出しだ。 はてcron以外に誰が呼び出している?思い付くのは、sshdが利用してるNICのハートビート間隔の確認かな?

kern_tc.c

冒頭から見て行くと、色々なヒントが見つかる。例えば

int sysctl_tc_hardware(void *, size_t *, void *, size_t);
int sysctl_tc_choice(void *, size_t *, void *, size_t);

こんなプロトタイプ宣言が有る。ここから、

ob$ sysctl -a | grep choice
kern.timecounter.choice=i8254(0) acpihpet0(1000) tsc(2000) acpitimer0(1000)
ob$ sysctl -a | grep hardware
kern.timecounter.hardware=tsc

こんな情報を引き出せた。意味する所は、カーネルが使う時間用のカウンターの選択肢は色々あるよ。その中で一番正確そうな、tscってのを使う事にしましたって事だな。

/*
 * Locks used to protect struct members, global variables in this file:
 *      I       immutable after initialization
 *      t       tc_lock
 *      w       windup_mtx
 */

struct timehands {
        /* These fields must be initialized by the driver. */
        struct timecounter      *th_counter;            /* [w] */
        int64_t                 th_adjtimedelta;        /* [tw] */
        int64_t                 th_adjustment;          /* [w] */
        u_int64_t               th_scale;               /* [w] */
        u_int                   th_offset_count;        /* [w] */
        struct bintime          th_boottime;            /* [tw] */
        struct bintime          th_offset;              /* [w] */
        struct timeval          th_microtime;           /* [w] */
        struct timespec         th_nanotime;            /* [w] */
        /* Fields not to be copied in tc_windup start with th_generation. */
        volatile u_int          th_generation;          /* [w] */
        struct timehands        *th_next;               /* [I] */
};

gdbで参照してみた構造体の意味付け。

数有るカウンターからtscが選ばれていた。ここら辺になって来ると、マシンのアーキテクチャに依存するんで、/sys/arch/i386に移動して、探ってみる。

ob$ find . -name '*.c' | xargs grep tsc
./i386/codepatch.c: * Copyright (c) 2014-2015 Stefan Fritsch <sf@sfritsch.de>
./i386/db_disasm.c:     { "invlpg", FALSE, NONE, op2(MEx,2), "swapgs\0rdtscp" },
./i386/db_disasm.c:/*31*/       { "rdtsc", FALSE, NONE,  0,           0 },
./i386/pctr.c:#define usetsc            (cpu_feature & CPUID_TSC)
./i386/pctr.c:  st->pctr_tsc = rdtsc();
./i386/pctr.c:  st->pctr_tsc = rdtsc();
./i386/pctr.c:  } else if (usetsc) {
./i386/pctr.c:                  if (usetsc)
./i386/pctr.c:                          st->pctr_tsc = rdtsc();
./i386/ucode.c: * Copyright (c) 2018 Stefan Fritsch <fritsch@genua.de>
./isa/clock.c:  __asm volatile("rdtsc" : "=A" (last_count));
./isa/clock.c:  __asm volatile("rdtsc" : "=A" (count));

i386/pctr.cが怪しそう。

#define usetsc          (cpu_feature & CPUID_TSC)

ほー、CPUに機能が搭載されてるかで利用の可否を決めてるっぽい。ってか、ない袖は振れぬ。

ob$ dmesg | grep TSC
cpu0: FPU,VME,DE,PSE,TSC,....
 :

ちゃんと機能が搭載されてました。

いやぁ、楽しいねぇ。たった一つのシステムコールから、こういう拡がりを得られるなんて。