OpenBSD 7.1 with qemu
qemu
OpenBSDの新しいのを待ち切れず、64Bit版を入れた。ゲストOSは7.1の32Bit版。だって、折角qemuの指南書があるんだから、気が変らないうちに入れちゃって事。指南書は前回見付たもの。説明はOpenBSD 7.0 でなされていたけど、qemuは足が速いんで指南書が陳腐化しないか心配だったのよ。
qemu-system-i386 -m 64 -monitor stdio \ -net nic -net user,hostfwd=tcp::2022-:22 -no-fd-bootchk -hda virtual.img
ポートフォワードで、ホスト側からもlogin可能。但しコンソールはVGAなので、大事なroot作業は容易にコピペ出来無い。そこで、 過去の記録を列挙してみた。
リナだと -serial pty とか出来たんだけどね。
qemu-ga
そこで指南書を見なおし。面白いものを発見。どうやら、シリアルを飛す事が出来るようだ。
ob$ cat /etc/qemu/qemu-ga.conf [general] method=isa-serial path=/dev/cua00
オリジナルではcua01となっているけど、ホストOSに実在するラインを使うように指示。そして、ダエモン君、qemu-gaが起動するように/etc/rc.confに登録。
ホスト側では/dev/tty00でgettyが走らないようにしておく(/etc/ttys)。そう、シリアルはゲストOSとの接続用に譲ってやるって事。
ob$ ls -l /dev/cua0* crw-rw---- 1 root dialer 8, 128 Oct 1 16:29 /dev/cua00 crw-rw---- 1 root dialer 8, 129 Sep 29 06:49 /dev/cua01 crw-rw---- 1 root dialer 8, 130 Sep 29 06:49 /dev/cua02 :
それから /dev/cua* は、rootとdialerのみが使えるようになってるので、/etc/groupを編集して、dialerにユーザー名を追加しておく。
ゲストOSの中では、シリアルlogin出来るようにし、更に/etc/boot.confに set tty com0 を設定しておく。
ゲストOSを起動するスクリプトは、先程のものと微妙に違う。
qemu-system-i386 -m 64 -nographic -no-fd-bootchk \ -net nic -net user,hostfwd=tcp::2022-:22 \ -hda virtual.img
これで起動すると、
SeaBIOS (version rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org) iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+03F914E0+03EF14E0 CA00 Booting from Hard Disk... Using drive 0, partition 3. Loading...... probing: pc0 com0 apm pci mem[639K 62M a20=on] disk: fd0 hd0+ >> OpenBSD/i386 BOOT 3.44 switching console to com>> OpenBSD/i386 BOOT 3.44 boot> 0 booting hd0a:/bsd: 10349079+2479108+241672+0+1130496 [709328+107+585136+629189]0 entry point at 0x201000 [ using 1924336 bytes of bsd ELF symbol table ] Copyright (c) 1982, 1986, 1989, 1991, 1993 The Regents of the University of California. All rights reserved. Copyright (c) 1995-2022 OpenBSD. All rights reserved. https://www.OpenBSD.org OpenBSD 7.1 (GENERIC) #151: Mon Apr 11 18:57:52 MDT 2022 deraadt@i386.openbsd.org:/usr/src/sys/arch/i386/compile/GENERIC real mem = 66469888 (63MB) avail mem = 48533504 (46MB) : starting local daemons: cron. Sun Oct 2 14:19:51 JST 2022 OpenBSD/i386 (qm.my.domain) (tty00) login: root Password: :
qemu-gaって初めてのアプリだな。
ob$ qemu-ga -h Usage: qemu-ga [-m <method> -p <path>] [<options>] QEMU Guest Agent 6.2.0 Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers -m, --method transport method: one of unix-listen, virtio-serial, isa-serial, or vsock-listen (virtio-serial is the default) -p, --path device/socket path (the default for virtio-serial is: /dev/vtcon/org.qemu.guest_agent.0, the default for isa-serial is: /dev/cua01). Socket addresses for vsock-listen are written as <cid>:<port>. :
リナの時にはそんなの無かったぞ。まて、調べてみるか。
sakae@deb:~/src/qemu/qga$ apt-file search qemu-ga qemu-guest-agent: /usr/sbin/qemu-ga qemu-guest-agent: /usr/share/man/man7/qemu-ga-ref.7.gz qemu-guest-agent: /usr/share/man/man8/qemu-ga.8.gz
なんだ、有るじゃん。ついでにqemuのソースを見ると、
sakae@deb:~/src/qemu/qga$ ls channel.h commands-posix-ssh.c installer/ vss-win32/ channel-posix.c commands-win32.c main.c vss-win32.c channel-win32.c cutils.c meson.build vss-win32.h commands.c cutils.h qapi-schema.json commands-common.h guest-agent-command-state.c service-win32.c commands-posix.c guest-agent-core.h service-win32.h
こんな風に存在している。そしていつもながら、リナのユーザー向けには手厚いサポートがあるなあ。悔しいけど世の中はリナックス中心に回ってますってのが、いやでも気付かされますよ。
失敗は成功の元
と、少々ネガティブな事を書いてしまった。ついでに最近の若者が嫌う失敗の事を晒しておく。 そう、上の説明ではすんなり動いた風に説明してるけど、そこまでの失敗事例を公開。
まあ、指南書は手取り足取りに説明されてる訳ではなくて、要点だけ説明されてるから動かなかったら裏読みしなければいけないって事だ。
ports/qemu/pkgの下に、小気味良くまとめられたREADMEがある。これを参考にDISKを作り、インストールした。とっても時間がかかるけど勝手にやってくれる事だから我慢我慢。
で、ノーマルな起動を実施したら、entry … と表示した所で画面更新が途絶えた。我慢してても変化が無いので諦めてqemuを殺した。電源プチューですな。
インストール時の設定で起動したら、VGAが出て来た。おまけでポートフォワードするようにしたのが、最初にあげたboot方法だ。これでもVGAのメニューでズームインとか出来るので、まあ普通に使える。でも、リナの -seriail pty 相当をしたい。
そこで行き着いたのは、qemu-gaだ。qemuを入れるとこれもつられて入ってくる。説明によると、rcctl enable qemu_ga
すれば、ダエモン君がインストールされるとな。でも、使うのは/dev/cua01との事。/dev/cua01ってハードには無い。よって、qemu-gaが死亡しちゃうんだ。
回避策が提示されてるけど、ちと不恰好でやりたくない。OpenBSDの設定は/etcに集中してる。FreeBSDだとportsで入れたのの設定は /usr/local/etc と分離された場所になる。論理の一環牲からはFreeBSDだろうけど、OpenBSDは大人の対応だと思うぞ。
そこで、調べてみたら、/etc/qemu/qemu-ga.conf に行き着いた。で、そこを修整。
でも相変わらず画面の更新は途中でSTOPする。これはもう、com0にメーッセージが流れてしまうのだろうとあたりを付けた。よって、ゲストOSのboot.confに設定を追加。
これでbootプロセス中のメッセージが流れてくるようにはなった。でも、最後のloginが出てこない。これはゲストOSでgetty が走っていないのだろう。よって、/etc/ttysを修整。 やっとリナに対抗出来る所まで漕ぎ着けた。
失敗駆動と言うかリナへの対抗意識だね。これが知力を高めるのさ。
なにはともあれ、qemuの使いかたをコンパクトにまとめてくれたパッケージャーさんには感謝です。
ob$ pwd /usr/local/share/doc/qemu ob$ ls _static/ genindex.html objects.inv specs/ user/ about/ index.html search.html system/ devel/ interop/ searchindex.js tools/
そして勿論qemuな人々にも感謝。膨大なドキュメントが付属してるんで、このポイントでpythonのWEBサーバーをたてて、Windows側からアクセスするのが吉。
DISKの圧縮
方法が公開されてたのでやってみる。debian(32bit)でも動かそうと思って転送したものを対象にする。元ねたは指南書にあった。圧縮した物を元の名前に変更してるんだけど、それは無視して中間結果を表示。
sakae@deb:~/sim/QEMU/org$ qemu-img convert -c -O qcow2 virtual.img v.tmp sakae@deb:~/sim/QEMU/org$ ls -lh v.tmp virtual.img -rw-r--r-- 1 sakae sakae 1.2G Oct 3 06:25 virtual.img -rw-r--r-- 1 sakae sakae 549M Oct 3 06:54 v.tmp
半分になった。勿論、普通に動くよ。
qm$ df -k Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/wd0a 2674670 782068 1758870 31% /
この時の実際のOSはこんな具合にDISKを消費してた。外からの容量より中からの容量の方が大きい(549M vs 782M)。
どんな風に圧縮してる? 想像するに、DISKはブロっクデバイスなんで、512Byte単位で読み書きされる。だから512byteがずっとZEROになってたら、それはZEROで埋まっているブロックだよって事で容易に圧縮出来るはず。
ちょっと実験で、そのようなデータを生成してみる。
qm$ dd if=/dev/zero of=z bs=1000k count=1000 qm$ dd if=/dev/zero of=zz bs=1000k count=750 qm$ df -k Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/wd0a 2674670 2532964 7974 100% /
ずっとZEROが続くファイルを2ヶ生成してみた。ゲストOS上からは、ほとんどDISK FULL状態。
この時、外から見た仮想DISKのサイズは、こんな状態。当然、肥大化してる。先と同じように圧縮したものも掲載。なお圧縮には約1分半ほどかかった。
sakae@deb:~/sim/QEMU$ ls -lh v.tmp virtual.img -rw-r--r-- 1 sakae sakae 2.2G Oct 4 06:21 virtual.img -rw-r--r-- 1 sakae sakae 469M Oct 4 06:37 v.tmp
初回の圧縮時は549Mだったのが、今度は更に圧縮されて469Mになった。これはひとえにゲストOS内でデータを整理(ZEROで埋めて)したおかげで、圧縮のチャンスが生れたって事だろう。
watch
ddbの説明書を見ていると、こんな楽しいコマンドが出てた。但し、i386ではエラーになるとな。
watch addr [,size] Set a watchpoint for the region starting at addr. Execution stops and control returns to ddb when an attempt is made to modify a watched region. The size argument defaults to 4. If you specify a wrong space address, the request is rejected with an error message. Warning: attempts to watch wired kernel memory may cause an unrecoverable error on some systems (e.g., i386).
悔しいので、ユーザーランドで観測プログラムを仕立ててgdbで試してみる。
int fix = 0x2982; int yet; int main(){ int i; for (i =0; i < 100; i++) { if ( i % 10 == 0 ) yet = i; } return 0; }
グローバル変数のfixとyetは趣味で、nmの確認に使う。
vbox$ nm a.out : 200010cc D fix 000016d0 T main 00000000 F t.c 20001100 B yet
これとnm(1)の説明を対比させる。オブジェクトのアドレスと種別が表示されてる。
B bss or tbss segment symbol D data or tdata segment symbol F file name T text segment symbol
gdb a.outしてプログラムをロードしてみる。
(gdb) p/x fix $1 = 0x2982 (gdb) p yet $2 = 0
グローバル変数はこの時点で読み込まれている。値の設定が無いやつは規約によりZEROにされてる。次はwatchコマンドでyetを監視する。
(gdb) r Starting program: /tmp/a.out Breakpoint 1, main () at t.c:7 7 for (i =0; i < 100; i++) { (gdb) watch yet Watchpoint 2: yet (gdb) c Continuing. Watchpoint 2: yet Old value = 0 New value = 10 main () at t.c:10 10 } (gdb) c Continuing. Watchpoint 2: yet Old value = 10 New value = 20 main () at t.c:10 10 } (gdb) info locals i = 20
変化が有るとbreakする。このコマンド便利なんだけどなあ。その代わりめっぽう遅い(はず)。
kernel is app
上でwatchに拘った。勿論ddbの問題回避の方法を模索する目的なんだけど、更に深遠な理由がある。前回だったか、dumvdevの疑問に取り組んだ。この値がどうやって設定されてるか追跡してみたかったからだ。
なんたってkernelはアプリケーションだ。ならばgdbを使って普通のアプリをデバッグする方法が使えるはず。すでにカーネルをgdbの遡上に載せられるように用意してるからね。
bootコマンドに -S をつけておくと、普通のアプリのようにカーネルの起動処理を追跡出来るようになる。カーネルを起動。そうしておいてgdbを起動。
これでbootの手前でSTOPする。やおらmainにBPを於て継続する。既に起動してるからここは継続を選ぶんだ。
Reading symbols from bsd.gdb... 0x0000fff0 in ?? () (gdb) b main Breakpoint 1 at 0xd02603a0: file /usr/src/sys/kern/init_main.c, line 169. (gdb) c Continuing. Breakpoint 1, main (framep=<error reading variable: Cannot access memory at address 0x8>) at /usr/src/sys/kern/init_main.c:169 169 { (gdb) p dumpdev $1 = -1 (gdb) watch dumpdev Hardware watchpoint 2: dumpdev (gdb) c Continuing.
で、現在のdumpdevを確認。そして watchの登場ですよ。
Hardware watchpoint 2: dumpdev Old value = -1 New value = 1 setroot (bootdv=<optimized out>, part=0, exitflags=16384) at /usr/src/sys/kern/subr_disk.c:1587 1587 swdevt[0].sw_dev = nswapdev; (gdb) bt #0 setroot (bootdv=<optimized out>, part=0, exitflags=16384) at /usr/src/sys/kern/subr_disk.c:1587 #1 0xd02b10de in diskconf () at /usr/src/sys/arch/i386/i386/autoconf.c:255 #2 0xd02607f3 in main (framep=0x0) at /usr/src/sys/kern/init_main.c:461 (gdb) p dumpdev $2 = 1 (gdb) p nswapdev $3 = 1
やーいヒットしたぞ。diskconfの中のsetrootって関数で初期化されてるんだね。
ここまで分れば 後は cscopeを使ってスイスイだな。普通にcscopeで起動しちゃうと、その場でDBの作り直しを始めちゃって大分待たされる。cscope -d で起動してDBの作り直しを抑制するのが吉。
emacsのcscopeバインディング
C-c s s (カーソルの置かれた)シンボルを検索 C-c s d (カーソルの置かれた)シンボルの定義を検索 C-c s c (カーソルの置かれた)関数を呼んでいる関数群を検索 C-c s C (カーソルの置かれた)関数から呼び出している関数群を検索 C-c s t テキスト文字列を検索 C-c s e egrepパターンで検索 C-c s f ファイル検索 C-c s i #include しているファイルを検索 C-c s u マークをポップ(元のファイルに戻る)
よく使うのは、上の2つぐらいかな。 そうそう、DBを作り直さないようにしないとな。
(setq cscope-do-not-update-database t)
でもcscopeで飛んでいって戻ってこれないからなあ。たまに使うのがよろし。