Debugging the FreeBSD kernel via QEMU

うろうろしてたら、時代の先端を行く記事が見つかった。

blockchain

なんたって、ビットコインですからねぇ。みんなひと山あてようと狙ってる。良きにつけ悪きにつけ、避けて通れないのでしょうな。

つらつら見て行くと、なんだかPython語で書かれたコードも引けるようになってた。

そして、こちらは、rubyで解説されてたぞ。

Bitcoinの仕組み

Bitcoinウォレットを実装する(ruby)

Bitcoinでは、バイナリ列を人が読み書きできる形式に変換する必要がある時は、Base58が
用いられる。Base58は、Base64と似ているが、書体によっては紛らわしい複数の文字や、
スラッシュが取り除かれているという点でBase64と異なる。

BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

郷に入っては郷に従えの見本だな。奇しくも前回、綺麗フォントうんぬんより(綺麗なおねーさんは大好きです)、Oと0みたいに 紛らわしい字体を明確に視認出来るのが欲しいとかいた。こちらは、そんなのは放棄しましたと、潔い仕様だな。

なんでもこれらの文字だけだと、ダブルクリック一発で、コピペが確実に出来るっていう、現代風な効果もあるらしい。

技術者向けビットコイン講座 第2回 ビットコインアドレスと秘密鍵のフォーマット

こちらにも詳しい説明が有ったぞ。

改ざんされにくいブロックチェーン、理由はデータ構造にあり

仮想通貨だけじゃない、ブロックチェーンの新機能

適用範囲広がるブロックチェーン、5つの応用事例

女房に付き合わされて行った書店にも、ビットコイン本がわんさかと置いてあった。投資と言うより投機と言うより博打だな。まあ、国もカジノを公認しようとしてるけど、これらで本当に儲けるのは誰かって事を、冷静に考えてみたらよい。

そう言えば、お馬が駆けて、それに賭ける人が気を揉む季節がやってきたな。こういうの啓蟄と言わないで、何と言うんだろう? 散財症候群? 自爆寄付者?

fetch old FreeBSD

今までxv6を見てきたけど、本物のOSも動く所を生体解剖してみたくなった。過去の記憶を辿ると、

Debugging the OpenBSD kernel via QEMU

なのに出会って、やった事が有った。(すぐに撤退しちゃったけど)撤退の理由は、糞石の事を よく知らなかった。OpenBSDの資料が少なく、取っ掛かりが余り無かったんだな。

ならば、手元にあるユニマガの記事をムック本にした『FreeBSDのブートプロセスをみる』に倣って やってみればよかろう。

この本で取り上げられているのは、FreeBSD 5.1に付属するkernel。出来たら同じ版のやつが よいな。そんなの手に入るのか? 検索したらFreeBSDの博物館で古い資料が手に入る事が分かった。

FreeBSD Old-release

ちょっと時間がかかったけど、無事にISOが手に入った。リナだとこうはいかないだろうね。 hostOSとして、FreeBSD 11.1 を使う事にした。qemuはpkgから入れた2.9.0という代物だ。

ping,ssh NG but ...

インストールは簡単と思っていたら、swapをけちったために失敗。昔は柔軟性が無かったのね。 そして、インストール中に kernel deveropperを指定すると、perl軍団を引き連れて、kernelのソースがインストールされた。ここまでは、まあ順調。そして起動。

[fb11: 5.1]$ qemu-system-i386 -m 256 -net nic -net user,hostfwd=tcp::2022-:22 disk

最近のqemuは、 -redirectってのはお勧めされないようで、上記のように設定したよ。

早速、guest内から導通試験をやってみる。

$ ping host-ip
PING host-ip: 56 data byte
 :
10 packet transmitted, 0 packets received, 100% packet loss

pingがhostに届かない。昔はどうだったかな?記憶の彼方である。じゃ、sshぐらいはいけるだろうか?

$ ssh host-ip
no hex alg
$

見た事も無いエラーに見舞われた。ホスト側からはどうかな? ダメ元と思ってやってみる。

[fb11: ~]$ ssh -p2022 localhost
Unable to negotiate with 127.0.0.1 port 2022: no matching host key type found. Their offer: ssh-dss

なんだかなあのエラーである。でも通信はかろうじて出来ているみたい。こういう時は、もう少し詳しく状況報告をお願いしてみる。

[fb11: ~]$ ssh -v -p2022 localhost
OpenSSH_7.2p2, OpenSSL 1.0.2k-freebsd  26 Jan 2017
  :
debug1: match: OpenSSH_3.6.1p1 FreeBSD-20030423 pat OpenSSH_3.* compat 0x01000000
debug1: Authenticating to localhost:2022 as 'sakae'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: diffie-hellman-group-exchange-sha1
debug1: kex: host key algorithm: (no match)
Unable to negotiate with 127.0.0.1 port 2022: no matching host key type found. Their offer: ssh-dss

ふむ、基本的なやり取りは出来ているみたいだけど、古すぎるsshに対しては、交渉決裂を宣言されてしまった。しまったなと寒いギャグ。ドッグイヤーな世界ですから、2003年製のOpenSSHなんて、危なくて使ってられませんって事だな。

System shutdown time has arrived
Writing entropy file:.
Shutting down daemon processes:.
Saving firewall state tables:.

それから、guestを落とす時に火壁がどうのこうのと言って来るんだけど、pingが壁で跳ね返されているのかなあ。自分では壁を作っていないんだけどな。後、最後にqemuがcoreを吐いちゃうんだけど、何だろう? 毎回core出来るの気にくわないから、ulimit -c 0 を入れて、凌いでる。

さてこれからどうする? debug用のkernelを作る前に、GUIの端末を止めにしたいな。GUIの 端末だと、入力応答が悪い。これって、入力をポーリングしてるんじゃなかろうか? qemuを 立ち上げているとロードアベレージが1に張り付いていて、気分が悪いぞ。

serial terminal

リナだとカーネルの起動パラメータでシリアル端末を指定出来たんだけど、FreeBSDではどうやるのだろう? リサーチしてみた。

25.6. Setting Up the Serial Console

言わずと知れたハンドブックにあった。

FreeBSD 10.x を libvirt なホストに、シリアルコンソール経由でインストールする

そして、こちらはもっと実用的に解説されてた。リナをhostにされてたけど、guestへの設定なんで、一緒の事でしょ。

# echo '-h' > /boot.config
# echo 'boot_serial="YES"' > /boot/loader.conf
# echo 'console="comconsole"' >> /boot/loader.conf
# vi /etc/ttys
  :
# Serial terminals
# The 'dialup' keyword identifies dialin lines to login, fingerd etc.
ttyd0   "/usr/libexec/getty std.9600"   xterm   on secure
ttyd1   "/usr/libexec/getty std.9600"   dialup  off secure
  :

ttyd0をxtermとして(あるいはvt100として)振る舞うように設定しろとな。そして、boot時の 待ち時間を無くすため、autoboot_delay=1の設定も追加しておくと良いらしい。

この設定を施して起動すると、確かに起動メッセージがCUIな端末(勿論host側だけど)に出て 来るんだけど、途中で固まった。何度やっても同じ。 qemuの起動パラメータは、下記を使ってるけど有ってるよな。

qemu-system-i386 -m 256 -nographic disk

こういう時は、hostを変えてみる事だな。HostOSとして、Debian 9.3を選ぼう。(それしか入っていない)qemuは2.8.1になってた。引っ越しは簡単で、guestOSが入ったdisk(442M)をコピーするだけ。簡単でいいわ。

debianでやったら、無事に動いた。

deb9:5.1$ ./boot
Console: serial port
BIOS drive A: is disk0
BIOS drive C: is disk1
BIOS 639kB/260992kB available memory

FreeBSD/i386 bootstrap loader, Revision 1.1
(root@wv1u.btc.adaptec.com, Thu Jun  5 00:52:26 GMT 2003)
Loading /boot/defaults/loader.conf
/boot/kernel/kernel text=0x40b4e8 data=0x7735c+0x59ab8 syms=[0x4+0x54f90+0x4+0x6446a]
Loading /boot/defaults/loader.conf


                                                          ,        ,
                                                         /(        )`
                                                         \\ \\___ / |
                                                         /- _  `-/  '
                                                        (/\\/ \\ \\ /\\
    1. Boot FreeBSD [default]                           / /   | `    \\
    2. Boot FreeBSD with ACPI disabled                  O O   ) /    |
    3. Boot FreeBSD in Safe Mode                        `-^--'`<     '
    4. Boot FreeBSD in single user mode                (_.)  _  )   /
    5. Boot FreeBSD with verbose logging                `.___/`    /
    6. Escape to loader prompt                            `-----' /
    7. Reboot                                <----.     __ / __   \\
                                             <----|====O)))==) \\) /====
                                             <----'    `--' `.__,' \\
                                                          |        |
                                                           \\       /       /\\
    Select option, [Enter] for default                ______( (_  / \\______/
    or [Space] to pause timer  0                    ,'  ,-----'   |
         :
login: root
Password:
Mar  6 16:00:49 f51 login: ROOT LOGIN (root) ON ttyd0
Last login: Tue Mar  6 15:08:36 on ttyd0
main-loop: WARNING: I/O thread spun for 1000 iterations
Copyright (c) 1992-2003 The FreeBSD Project.
Copyright (c) 1979, 1980, 1983, 1986, 1988, 1989, 1991, 1992, 1993, 1994
        The Regents of the University of California. All rights reserved.

FreeBSD 5.1-RELEASE (SAKAE) #0: Mon Mar  5 01:12:40 JST 2018

Welcome to FreeBSD!

f51#

我らがダエモン君もちゃんと拝めたし(女房は羽生君の写真集を買って拝んでる。おばさん丸出しだ)、良かった良かった。

そして、不思議とロードアベレージも下がったし、qemuがcoreを吐く事も無くなった。きちんと手入れがされているんだろう。

build kernel

さて、debug用のkernelを作るか。どうもqemuの配下のguestOSでは、コンパイルに時間がかかりそうなので、豪華にvboxにFreeBSD 5.1を入れ、そこでコンパイルしよう。

ユニマガお勧めのconfigしたよ。って、cd /sys/i386/confして、GENERICをSAKAEにcpしてから、ちょいと編集しただけだけど。

deb9:conf$ diff -u GENERIC SAKAE
--- GENERIC     2003-06-01 00:18:41.000000000 +0900
+++ SAKAE       2018-03-05 01:09:44.000000000 +0900
@@ -22,12 +22,13 @@
 cpu            I486_CPU
 cpu            I586_CPU
 cpu            I686_CPU
-ident          GENERIC
+ident          SAKAE

 #To statically compile in device wiring instead of /boot/device.hints
-#hints         "GENERIC.hints"         #Default places to look for devices.
+hints          "GENERIC.hints"         #Default places to look for devices.

-#makeoptions   DEBUG=-g                #Build kernel with gdb(1) debug symbols
+makeoptions    DEBUG=-g                #Build kernel with gdb(1) debug symbols
+makeoptions    NO_MODULES=yes

 options        SCHED_4BSD              #4BSD scheduler
 options        INET                    #InterNETworking
@@ -59,7 +60,7 @@
                                        # output.  Adds ~215k to driver.

 # Debugging for use in -current
-#options       DDB                     #Enable the kernel debugger
+options        DDB                     #Enable the kernel debugger
 #options       INVARIANTS              #Enable calls of extra sanity checking
 options        INVARIANT_SUPPORT       #Extra sanity checks of internal structures, required by INVARIANTS
 #options       WITNESS                 #Enable checks to detect deadlocks and cycles

コンパイルはあっと言う間に終わった。サイズが小さいのか母艦が速いのかは定かではない。 後は、sys以下をまとめて、debianに持ってくる。と、言っても、ここではsshもscpも例のごとく使えない。

そこで登場するのが、hostのdebianに、移動式のftpサーバーを立てる事です。

alias ftpd='python3 -m pyftpdlib -w'

sys/のtgzを取っておいて、それをvbox側からftpでput。

今、出来上がりを見たら、随分と余計な機能(NFSとかINET6とか)が付いている。本当ならこういう機能を削り、不要なデバイスも削除すべきだな。

get kernel

今度は、guestOSの中で、kernelだけをgetしてあげる。(可動式ftpサーバーは、sys/i386/compile/SAKAEの所で起動の事)

$ ftp host-ip 2121
Name: anonymous
Password: hack
ftp> bin
ftp> get kernel
ftp> quit

後は、そのkernelを/boot/kernelの下に配置。(元々有った/boot/kernelは事前に、/boot/kernel.orgなりに、改名しておく)

try

guestOSを起動するため、こんなスクリプトを用意。

deb9:5.1$ cat with-gdb
#! /bin/sh
echo 'cd sys/i386/compile/SAKAE;  gdb -q kernel.debug'
echo 'target remote localhost:1234'
qemu-system-i386 -m 256 -nographic disk -S -gdb tcp::1234

起動すると、2行の案内が出てくるので、別端末を開いて、一行ずつコピペするだけ。

deb9:5.1$ cd sys/i386/compile/SAKAE;  gdb -q kernel.debug

warning: A handler for the OS ABI "FreeBSD/ELF" is not built into this configuration
of GDB.  Attempting to continue with the default i386 settings.

Reading symbols from kernel.debug...done.
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in ?? ()
(gdb) b init386
Breakpoint 1 at 0xc04848f0: file ../../../i386/i386/machdep.c, line 1922.
(gdb) c
Continuing.

Breakpoint 1, init386 (first=-1066692608) at ../../../i386/i386/machdep.c:1922
1922    {
(gdb) bt
#0  init386 (first=-1066692608) at ../../../i386/i386/machdep.c:1922
#1  0xc0137d3d in begin () at {standard input}:165
(gdb) l
1917    }
1918
1919    void
1920    init386(first)
1921            int first;
1922    {
1923            struct gate_descriptor *gdp;
1924            int gsel_tss, metadata_missing, off, x;
1925    #ifndef SMP
1926            /* table descriptors - used to load tables by microp */

init386にBPを貼ってから、cすると、暫くして制御がgdbに帰ってくる。後は煮るなり焼くなりすればよい。これで当初のxv6ツアーと同じ感覚で遊べるな。

syncing disks, buffers remaining...
done
Uptime: 2m27s

The operating system has halted.
Please press any key to reboot.
Ctl-a x   ;; <================== Soon key in
QEMU: Terminated

ゲストOSをshutdownしたら、最後のメッセージが出た所で、Ctl-a x してqemuを素早く抜ける事。放置するとホスト側の負荷が上がる。それもこれもゲストOS側でACPIがサポートされて いない為、電源断が自動で出来ないのよ。まあ、これぐらいは我慢しよう。

最初から、/boot/loader.conf ファイルに hint.acpi.0.disabled="1" と書いておくと、qemuも忖度してくれるかな? そんな事ありえません! と、あーべちゃんの答弁を真似ておこう。

f51# readelf -l /boot/kernel/kernel

Elf file type is EXEC (Executable file)
Entry point 0xc0137cb0
There are 5 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0xc0100034 0xc0100034 0x000a0 0x000a0 R E 0x4
  INTERP         0x0000d4 0xc01000d4 0xc01000d4 0x0000d 0x0000d R   0x1
      [Requesting program interpreter: /red/herring]
  LOAD           0x000000 0xc0100000 0xc0100000 0x40b4e8 0x40b4e8 R E 0x1000
  LOAD           0x40b500 0xc050c500 0xc050c500 0x7735c 0xd0e14 RW  0x1000
  DYNAMIC        0x4827f4 0xc05837f4 0xc05837f4 0x00068 0x00068 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .hash .dynsym .dynstr .text .rodata .rodata.str1.1 set_sysctl_set set_modmetadata_set set_sysinit_set set_db_cmd_set set_db_show_cmd_set set_sysuninit_set set_kbddriver_set set_cons_set set_videodriver_set set_scterm_set set_scrndr_set set_vga_set
   03     .data .got .dynamic .bss
   04     .dynamic

折角なので、カーネルのプログラムセグメントを出してみた。

次にやる事は、小さいカーネルにする事だな。ダイエット前に現状の把握

f51# nm /boot/kernel/kernel | wc
   21728   65184  663481

色々な機能が入っていると、名前も沢山あるだろうと思って。

deb9:SAKAE$ ls -lh
total 35M
-r--r--r-- 1 sakae sakae 5.3M Mar  5 01:12 kernel
-r-xr-xr-x 1 sakae sakae  29M Mar  5 01:12 kernel.debug

そして、ずばり大きさを物理的に確認。

small kernel

ロシアンルーレットか海賊危機一発の時間です。失敗すると起動不能に陥るので、その時のために回復手段を確認しておく。

カーネルが起動しない

boot kernel.org すればいいんだな。転ばぬ先の杖だわい。

次に設定ファイルをコメント化してくんだけど、使えるのは純正のviのみ。マクロが欲しい今日この頃。冒頭にカーソルを移動して入力モードに切り替えてから#を入力。コマンドモードに戻ってカーソルを次の行に移動の繰り返し。あんちょこ本を参照すると、マクロキーは、vとかgがお勧めって書いてあった。

ちょいと気になったんで、vimではどうやるか調べてみた。qv.....q ってやると、....のキーストロークがvキーにバインディングされるとな。マクロの実行は、この場合 @v とするとか。 バインディングはa-zまでの26個が使えるそうな。vimを抜けるに、:q とする積りが、コロンを 抜かして、レコーディングに何度も突入したな。これで、vim嫌いが一人誕生ってわけ。

:map v 0i#^[j
          ^^-------- Ctl-v してからESCを押す。(ESCの代わりにCtl-[でも可)

例のムック本では、VMwareを2つ用意し、同一のOSを入れてから、片方でコンパイル。kernelをもう一方へ転送して、(仮想)シリアルケーブルで結んでから探検しろとなってた。

オイラーは無謀にもHostのFreeBSD11.1上で、ゲストのFreeBSD5.1をコンパイル出来るか、馬鹿やってみる。

[fb11: conf]$ vi SAKAE
[fb11: conf]$ config SAKAE
ERROR: version of config(8) does not match kernel!
config version = 600014, version required = 500012

Make sure that /usr/src/usr.sbin/config is in sync
with your /usr/src/sys and install a new config binary
before trying this again.

If running the new config fails check your config
file against the GENERIC or LINT config files for
changes in config syntax, or option/device naming
conventions

しっかり出鼻をくじかれた。configにもversionが有って、その時々のソースに対応させてるのね。じゃ、コンパイルなんて夢のまた夢だろうね。片やclangだし、guestの方は昔堅気のgccだもの。どんなエラーを出すか拝見してみましょ。毒食わば皿までです。

[fb11: SAKAE]$ make
cc -c -O -pipe -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual -fformat-extensions -std=c99 -g -nostdinc -I- -I. -I../../.. -I../../../dev -I../../../contrib/dev/acpica -I../../../contrib/ipfilter -D_KERNEL -include opt_global.h -mcmodel=kernel -mno-red-zone -mfpmath=387 -mno-sse -mno-sse2 -mno-mmx -mno-3dnow -msoft-float -fno-asynchronous-unwind-tables -ffreestanding ../../../amd64/amd64/genassym.c
cc: error: unknown argument: '-fformat-extensions'
cc: error: '-I-' not supported, please use -iquote instead
*** Error code 1

Stop.
make: stopped in /tmp/sys/i386/compile/SAKAE

i386用を作っているのにamd64と思っているぞ。これはもう絶望的。

[fb11: SAKAE]$ CC=gcc make
gcc -c -O -pipe -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual -fformat-extensions -std=c99 -g -nostdinc -I- -I. -I../../.. -I../../../dev -I../../../contrib/dev/acpica -I../../../contrib/ipfilter -D_KERNEL -include opt_global.h -mcmodel=kernel -mno-red-zone -mfpmath=387 -mno-sse -mno-sse2 -mno-mmx -mno-3dnow -msoft-float -fno-asynchronous-unwind-tables -ffreestanding ../../../amd64/amd64/genassym.c
gcc: error: unrecognized command line option '-fformat-extensions'; did you mean '-fno-ms-extensions'?
*** Error code 1

Stop.
make: stopped in /tmp/sys/i386/compile/SAKAE

gccも、これまた然りで叱られた。

そろりそろりとカーネルをダイエットしてみた。まだ頑張れそうだけど、適当な所で止めておく。論理的容量が小さいとgdbの負担が減るだろう。

f51# nm /boot/kernel/kernel | wc
   13059   39177  401593

そして、こちらは物理的容量。

deb9:tmp$ ls -l kernel*
-rw-r--r-- 1 sakae sakae  2934566 Mar 10 13:31 kernel
-rw-r--r-- 1 sakae sakae 17602282 Mar 10 13:31 kernel.debug

boot0の待ち時間が長かったので、小さくしてみた。およそ18カウントが1秒に相当するらしい。

f51# boot0cfg -t 4 ad0

マルチブートで複数のOSを選ばなければ、直ぐにboot2へ行って、ダエモン君と対面したいですからね。所で、何処を書き換えているのだろう? きっとMBRの某所に違いない。