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作業は容易にコピペ出来無い。そこで、 過去の記録を列挙してみた。

OpenBSD with qemu

OpenBSD観光 by debian

OpenBSD 探検 most old

リナだと -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で飛んでいって戻ってこれないからなあ。たまに使うのがよろし。


This year's Index

Home