xv6

Table of Contents

xv6

前回は、ちょっとアセンブラが出てきた。引き続きやってみよう。ハロワでは つまらないので、教育的なOSにする。そう、いにしえのOSである、uv6(on PDP11)をx86(i386)に移植した、xv6って奴ね。

https://github.com/mit-pdos/xv6-public.git (for x86)

https://github.com/mit-pdos/xv6-riscv.git (for riscv)

探ると2種出てくる。x86は、各種のしきたりが面倒で、教師も生徒も疲弊して しまうので、素直な石RISC-Vに鞍替えしたよとな。実際、x86版のgit logを見 ると、

Date:   Mon Aug 10 14:28:02 2020 -0400

    Be more explicit that we are not maintaining the x86 version anymore

Date:   Tue Sep 24 14:45:16 2019 -0400

    Mention we switched to xv6 on RISC-V

こんな記述が見られた。理由は大人の事情で書かれていないけど、そういう事 だ。

はじめてのOSコードリーディング for uv6 (book)

xv6: シンプルで Unix 風な教育用オペレーティングシステム (Russ Cox)

何がOS実装の理解を難しくしているのかとGNU assemblyの準備 (good)

xv6実装の詳解(boot処理編: segmentationとpagingを中心に)

xv6 on OpenBSD

上記は万人に受け入れられるリナで動作させるのが常識なんだろうけど、それ では面白みがないので、あえてOpenBSDでやってみる。裏の理由は、リナ側に Diskの肥やしになりそうな、QEMUを持ち込みたくないから。予想される困難は、 コンパイラー系が、gccじゃなくてClang系になっている事。

ob$ gmake
gcc -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer  -fno-pic -O -nostdinc -I. -c bootmain.c
gmake: gcc: No such file or directory
gmake: *** [Makefile:104: bootblock] Error 127

CC = $(TOOLPREFIX)gcc となっている所をccに変更。

ld -m  -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o
ld: error: unknown emulation: -N
gmake: *** [Makefile:106: bootblock] Error 1

次なるは、リンカーのエラーだ。それも -Nってスイッチを知らないと。man ldすると、

--omagic, -N
        Set the text and data sections to be readable and writable, do
        not page align sections, link against static libraries.

ちゃんと列挙されてるんだけどな。リナでコンパイルしてみっかな。

ld -m    elf_i386 -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o
ld: warning: bootasm.o:missing .note.GNU-stack section implies executable stackld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker                                                                    l
d: warning: bootblock.o has a LOAD segment with RWX permissions

該当部分は、こうなってた。決定的に違うのは、-m elf_i386 っての。 OpenBSDでは、このスイッチの引数が欠落してるので、次の-Nを喰ってしまい、 消化不良になったんだろう。

-m value
        Set target emulation.

やっぱり、引数として、どんなマシンを対象にするんやってのが必要とな。該 当するMakefileには、

# FreeBSD ld wants ``elf_i386_fbsd''
LDFLAGS += -m $(shell $(LD) -V | grep elf_i386 2>/dev/null | head -n 1)

こんな注意書きと共に、抽出方法が説明されてた。我がOpenBSDは辺鄙なOS だから勿論ない。ググるで、OpenBSD ld なんてキーワード検索すると、 ld.bfd(1)なんてのも有るよと出てきた。(1)って、ユーザー用のコマンドじゃ ん。manも見ずに、いきなりコマンドを叩いてみる。

ob$ ld.bfd -V
GNU ld version 2.17
  Supported emulations:
   elf_i386_obsd

叩くのは、まず自分の頭からだな。ググるに頼らず、OpenBSDの事はOpenBSDに、 まず聞いてみるのが先だろう。聞くのは簡単。

ob$ ld    ; TAB
ld        ld.lld    ldapctl   ldattach  ldd       ldpd
ld.bfd    ldap      ldapd     ldconfig  ldpctl

ldで始まるコマンドを列挙させれば良い。あるいは、

ob$ ls /usr/bin/ld*
/usr/bin/ld*        /usr/bin/ld.lld*    /usr/bin/ldd*
/usr/bin/ld.bfd*    /usr/bin/ldap*

先へ進む。次なるエラーは、なんじゃらホイ。冒険物語だな。

ld.bfd -m    elf_i386_obsd -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o
objdump -S bootblock.o > bootblock.asm
objcopy -S -O binary -j .text bootblock.o bootblock
./sign.pl bootblock
boot block too large: 598 bytes (max 510)
gmake: *** [Makefile:109: bootblock] Error 1

Uum… このエラーは致命的だな。作成されたブート・ブロック昔言葉で言う MBRのサイズが510バイト以内になってるかの監査に引掛ったって事だ。

objdumpとかobjcopyにGNUの変種が登録されていないか、聞いてみる。それに は、locateコマンドが最適。一度、doas sh /etc/weekly すると、locate用の DBが作成される。

ob$ locate objdump | grep bin
/usr/bin/llvm-objdump
/usr/bin/objdump
/usr/src/gnu/usr.bin/binutils-2.17/binutils/doc/objdump.1
 :

locate自身でも検索ファイルの展開指定が可能だけど、エスケーブがドウタラ と面倒なので、上記の様に2段階の検索にしてる。

で、boot blockが肥大化しちゃうのは、Clangなコンパイラーの影響ではなか ろうかと見当を付ける。普通のgccが欲しいな。portsを探ってみると、lang/gcc に8系と11系の2種類が存在してた。古い方が馴染が有るだろうって、根拠が無 い推測、いや、xv6がリリースされた頃には、11系なんて無かったはず。 ちなみに、Debian/WSL2のgcc12系では、変な所でコンパイルに失敗すると言う、 互換性の無視を体験したからね。まったく、GNUの連中ときたら、、(以下自粛)

で、8系を入れようとしたら、既に鎮座してた。多分Rあたりを入れた時に紛れ こんだものだろう。ちょっとMakefileを修正。

ob$ gmake qemu-nox
 :
objdump -S _zombie > zombie.asm
BFD: Dwarf Error: mangled line number section.
BFD: Dwarf Error: mangled line number section.
BFD: Dwarf Error: mangled line number section.
objdump -t _zombie | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > zombie.sym
./mkfs fs.img README _cat _echo _forktest _grep _init _kill _ln _ls _mkdir _rm _sh _stressfs _usertests _wc _zombie
nmeta 59 (boot, super, log blocks 30 inode blocks 26, bitmap blocks 1) blocks 941 total 1000
assertion "fbn < MAXFILE" failed: file "mkfs.c", line 270, function "iappend"
gmake: *** [Makefile:186: fs.img] Abort trap (core dumped)
gmake: *** Deleting file 'fs.img'

なんだ、こりゃ? 多分、ファイルシステム内に、コマンド類が収まりきれな いで、監視機構が文句を垂れたんだろう。思いっりすがすがしいファイルシス テムを作成してみる。

ob$ ./mkfs fs.img README _cat _echo
nmeta 59 (boot, super, log blocks 30 inode blocks 26, bitmap blocks 1) blocks 941 total 1000
balloc: first 143 blocks have been allocated
balloc: write bitmap block at sector 58

思った通りだ。必要としないバイナリーを、取りあえず1個削除する。じゃ駄 目だったので3個削除。

参考までに、バイナリーのサイズを比較してみる。

ob$ ls -l _*
-rwxr-xr-x  1 sakae  wheel  19904 Feb 23 07:05 _cat*
-rwxr-xr-x  1 sakae  wheel  12307 Feb 23 07:05 _forktest*
-rwxr-xr-x  1 sakae  wheel  22724 Feb 23 07:05 _grep*
-rwxr-xr-x  1 sakae  wheel  19326 Feb 23 07:05 _init*

Debian側はスリムだなあ。readelfぐらいを利用して、肥満分析でもするか。 いや、先を急ごう。

sakae@atom:~/xv6-public$ ls -l _*
-rwxr-xr-x 1 sakae sakae 15180 Feb 23 06:56 _cat*
-rwxr-xr-x 1 sakae sakae  8644 Feb 23 06:56 _forktest*
-rwxr-xr-x 1 sakae sakae 17968 Feb 23 06:56 _grep*
-rwxr-xr-x 1 sakae sakae 14684 Feb 23 06:56 _init*

起動するか。

ob$ gmake qemu-nox
which: qemu: Command not found.
qemu-system-i386 -nographic -drive file=fs.img,index=1,media=disk,format=raw -d
qemu-system-i386: cannot set up guest memory 'pc.ram': Cannot allocate memory
gmake: *** [Makefile:231: qemu-nox] Error 1

デフォの512では駄目とな。

Booting from Hard Disk...
failed to allocate memory for stack: Cannot allocate memory
gmake: *** [Makefile:228: qemu-nox] Abort trap (core dumped)

qemuで動かしているOpenBSDは、512Mなメモリー割付でも、ちゃんと動作する なあ。この違いは何?

boot ?

Makefileのターゲットを見ていたら、メモリーが256で動くものが用意されて た。んで、それをパクってきて、下記のターゲットを追加。

go: xv6memfs.img
        qemu-system-i386 -m 384 -nographic -no-fd-bootchk \
  -net nic -net user,hostfwd=tcp::2022-:22  \
  -drive file=xv6memfs.img,index=0,media=disk,format=raw -smp 2
objdump -t kernelmemfs | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernelmemfm
dd if=/dev/zero of=xv6memfs.img count=10000
10000+0 records in
10000+0 records out
5120000 bytes transferred in 0.061 secs (82958047 bytes/sec)
dd if=bootblock of=xv6memfs.img conv=notrunc
1+0 records in
1+0 records out
512 bytes transferred in 0.000 secs (13728750 bytes/sec)
dd if=kernelmemfs of=xv6memfs.img seek=1 conv=notrunc
1480+1 records in
1480+1 records out
757813 bytes transferred in 0.009 secs (84109139 bytes/sec)
qemu-system-i386 -m 384 -nographic -no-fd-bootchk -s \
  -net nic -net user,hostfwd=tcp::2022-:22  \
  -drive file=xv6memfs.img,index=0,media=disk,format=raw -smp 2

こんな風に準備が進められて、qemuが起動した。

SeaBIOS (version rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org)

iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+16FD11A0+16F311A0 CA00

Booting from Hard Disk...
failed to allocate memory for stack: Cannot allocate memory
gmake: *** [Makefile:292: go] Abort trap (core dumped)

これは、暴走してるな。 gmake go した後、gdbを起動してステップ実行してみる。

ob$ gdb -q kernel
  :
The target architecture is assumed to be i8086
[f000:fff0]    0xffff0: ljmp   $0x3630,$0xf000e05b
0x0000fff0 in ?? ()
(gdb) si
[f000:e05b]    0xfe05b: cmpw   $0xffc8,%cs:(%esi)
[f000:e062]    0xfe062: jne    0xd241d09e
[f000:e066]    0xfe066: xor    %edx,%edx
[f000:e068]    0xfe068: mov    %edx,%ss
[f000:e06a]    0xfe06a: mov    $0x7000,%sp
  :
[f000:ec39]    0xfec39: or     $0x1,%cx
[f000:ec3d]    0xfec3d: mov    %ecx,%cr0
[f000:ec40]    0xfec40: ljmpw  $0xf,$0xec48
The target architecture is assumed to be i386
=> 0xfec48:     mov    $0x10,%ecx
=> 0xfec4d:     mov    %ecx,%ds
=> 0xfec4f:     mov    %ecx,%es
=> 0xfec51:     mov    %ecx,%ss
=> 0xfec53:     mov    %ecx,%fs
=> 0xfec55:     mov    %ecx,%gs
=> 0xfec57:     jmp    *%edx
=> 0xefc3f:     push   %ebx
=> 0xefc40:     sub    $0x20,%esp
=> 0xefc43:     call   0xec41c
=> 0xec41c:     push   $0xf51a0
=> 0xec421:     push   $0xf049f
=> 0xec426:     call   0xec409
=> 0xec40d:     mov    0x4(%esp),%edx
=> 0xec411:     mov    $0xf5128,%eax
=> 0xec416:     call   0xebb83
=> 0xebb83:     push   %ebp
=> 0xebb84:     push   %edi
=> 0xebb85:     push   %esi
=> 0xebb86:     push   %ebx
=> 0xebb87:     sub    $0x10,%esp
0x000ebb87 in ?? ()
;; ここで暴走した。そしてqemuはコアを吐いた。

Remote connection closed

もう、これぐらいにしよう。主目的は、アセンブラのコードを読む事だったか らね。

それはそうと、 xv6 (2018-01-29) とか、2013年の冬にもやってる。我ながが進歩が無いな。

FreeBSDの事情

一応リサーチしておくと、やはりgccが肝になる。gccでしか通過出来ないコー ドが書かれているからね。しかも特定なバージョン(以下)じゃないと駄目と言 う駄目っぷりです。portsにどんなのが提供されてるかと言うと、

sakae@fb:/usr/ports $ ls -d lang/gcc*
lang/gcc/               lang/gcc12/             lang/gcc14-devel/
lang/gcc10/             lang/gcc12-devel/       lang/gcc48/
lang/gcc11/             lang/gcc13/             lang/gcc6-aux/
lang/gcc11-devel/       lang/gcc13-devel/       lang/gcc9/

gcc10以下は過去の遺物をハンドリングする、考古学者用。gcc11以降は、一応 現役って事かな。gcc14は人柱用の地雷原です。数多の犠牲を強いられて、安 全地帯になるんでしょう。そして、gcc48は、Clangが走り始めた時の、記念碑 です。世界遺産入り確定です。きっと、御老体が昔を懐しがって、保守されて るんでしょう。

gcc48を入れるのは忍びないので、clangでコンパイル出来る様にMakefileを改 変して、ユーザーランドを作成してみた(勿論カーネルはgccに依存してるので、 コンパイルは失敗する)。

sakae@fb:~/xv6-public $ gmake fs.img
  :
cc -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer -fno-stack-protector   -c -o grep.o grep.c
ld -m elf_i386_fbsd -N -e main -Ttext 0 -o _grep grep.o ulib.o usys.o printf.o umalloc.o
llvm-objdump -S _grep > grep.asm
llvm-objdump -t _grep | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > grep.sym
  :
./mkfs fs.img README _cat _echo _forktest _grep _init _kill _ln _ls _mkdir _rm _sh _stressfs _usertests _wc _zombie
nmeta 59 (boot, super, log blocks 30 inode blocks 26, bitmap blocks 1) blocks 941 total 1000
balloc: first 613 blocks have been allocated
balloc: write bitmap block at sector 58

バイナリーが肥大化しないので、間引く必要は無かった。サイズは、こんなも んだ。

sakae@fb:~/xv6-public $ ls -l _*
-rwxr-xr-x  1 sakae  wheel  14188 Feb 24 08:22 _cat*
-rwxr-xr-x  1 sakae  wheel   8612 Feb 24 08:22 _forktest*
-rwxr-xr-x  1 sakae  wheel  16552 Feb 24 08:22 _grep*

Linuxの事情

まずは、ArchLinux

[sakae@arch ~]$ pacman -Ss gcc | grep '^[a-z]'
core/gcc 13.2.1-5 [installed]
extra/gcc12 12.3.0-3
extra/riscv64-linux-gnu-gcc 12.2.0-1 (risc-v)

そしてDebinan

sakae@atom:~$ apt-cache search gcc | grep '^gcc-[1-9]'
gcc-11 - GNU C compiler
gcc-12 - GNU C compiler
gcc-12-riscv64-linux-gnu-base

総じて、過去には見向きもしない、行け行けドンドンというお調子者です。

diff Makefile

OpenBSD用です。

ob$ diff Makefile.org Makefile
74c74
< CC = $(TOOLPREFIX)gcc
---
> CC = $(TOOLPREFIX)egcc
76c76
< LD = $(TOOLPREFIX)ld
---
> LD = $(TOOLPREFIX)ld.bfd
160c160
<       gcc -Werror -Wall -o mkfs mkfs.c
---
>       egcc -Werror -Wall -o mkfs mkfs.c
170d169
<       _echo\
175d173
<       _ln\
181d178
<       _usertests\
286a284,289
>
> go: xv6memfs.img
>       qemu-system-i386 -m 384 -nographic -no-fd-bootchk -S $(QEMUGDB) \
>   -net nic -net user,hostfwd=tcp::2022-:22  \
>   -drive file=xv6memfs.img,index=0,media=disk,format=raw -smp 2
>

This year's Index

Home