intelの流儀
何気にTVを見てたら、お楽しみゲレンデグッズなんてのをやってた。
雪合戦用の雪玉を製造する雪玉鋳造機。雪を型に入れて圧縮すると雪玉の出来上がり。これって 雛形と同じものが出来るんで、雪玉クラスだな。インスタンスはどうする?
赤組って事で、紅で着色。白組は、えーと、雪は白いからそのままでいいんで、、、こういうの オブジェクト嗜好では何と言う専門用語でしたっけね? 習ったような気がしたけど、縁が無い んで忘れた。
おいらの頃の雪玉って言ったら、中にcoreがあるのがデフォルトだったな。coreは勿論、小石。 無ければ、小学校の石炭庫からくすねてきた、石炭だったりします。当たると痛かったな。
雪だるまセットなんてのも紹介してた。型と、目、眉毛、鼻、口、髭とかがセットになってた。 レポーターは、美魔女を作って喜んでいたけど、やっぱり、雪達磨の顔は木炭がデフォだと思うぞ。 おいら、古いんですかね?
後、面白そうだったのは、セパレートになった、スノボー。上達すると、雪の上でくるくると 回転出来たり二人でダンスしながら滑れるよと紹介してた。
これ、遡ること35年ぐらい前だったかなあ。1メーターのスキーを付けて、よくやったものだ。 いわゆる、フリースタイルスキーを緩く楽しもうってやつ。フィギアスキーって呼んでたなあ。 分かり易く言うと、フィギアスケートのスキー版ね。片足を上げて一本足で滑ったり、 滑りながら足をクロスさせたり、回転させたり。若かったんで体力も有り余ってたし、体は 柔軟だったりしたんで、いろいろな事が出来て雪遊び面白かったよ。
今のおいらの雪の関わりと言ったら、、、、毎日の雪かたづけ、、で、腰が痛いですだ。
古いFreeBSDでもXv6
この間からXv6をやってるんだけど、13年もののNotePCに入っているFreeBSD6.4でも動くか 検証してみる。このPC何たって、Windows Meが入ってたやつなんだけど、いまだにちゃんと 動いてる感心なやつだ。
qemuは無事にコンパイル出来たよ。qemuって言ったって数あるアーキのうちのqemu-system-i386を リンクしたやつだけどね。Xv6も無事にコンパイル出来て、さてgdbから動かそうとすると FreeBSD9.1と同様に、gdbが落ちる。
こんな時は、portsからgdb6.6を入れるんだなとすでに学習してるんで、用意を始めたんだ。そしたら、 あろう事がgccが古くて気に喰わないらしく、先にgccの新しいのをコンパイルすると言い出す始末。 gccのコンパイルなんて果てしなくDISKを酷使するんで老体にはきついわ。portsから入れるのは 諦めGNUのサイトへ入って、微妙に古いgdb6.4を落としてきて野良ビルドしましたよ。
そしたら、gdbからXv6が動くようになった。やれやれ。
気をよくしたおいらは、ひょっとしたらOpenBSDでも動くかなと思って試してみたけど、qemuが 固まってしまってだめだった。微妙なのね。
で、このまま引き下がるのも、あれなので、まずはqemuがちゃんと動いているか確認してみれ。 何か適当な題材は無いか?
リアルモード
qemuって(仮想)パソコンだよな。だったらBIOSぐらいは動く事を確認しておきたいぞ。調べて みると、BIOSってのは、リアルモードでしか動かない代物らしい。
BIOSで動く適当な題材が無いかと思っていたら、 起動するまでの長い道のり IPL編(2) 文字表示の巻が 良い試験になりそう。この作者さんは、Windowsでやっておられるようなので、Unix用にMakefileを 作ったよ。
real: fd.img qemu -m 64 -fda fd.img # qemu -nographic -m 64 -fda fd.img -S -gdb tcp::26000 x86.o: real.s as -o x86.o real.s objdump -S x86.o > x86.asm ipl.bin: x86.o objcopy -S -O binary x86.o ipl.bin fd.img: ipl.bin dd if=/dev/zero of=fd.img count=2880 dd if=ipl.bin of=fd.img conv=notrunc
ソースは、載ってたものを少々改変して、real.sってした。
# generate real mode code .code16 begin: # deny interrupt cli # setup registers cld xorw %ax, %ax movw %ax, %ss movw %ax, %es movw %ax, %fs # setup stack pointer movw $0x7c00, %ax movw %ax, %sp # setup code segment ljmp $0x7c0, $set_cs set_cs: movw %cs, %ax movw %ax, %ds # show message movw $BOOT_MSG, %si call print # infinite loop end: jmp end print: xorw %bx, %bx movb $0x0e, %ah print_char: lodsb orb %al, %al jz print_end int $0x10 jmp print_char print_end: ret BOOT_MSG: .string "Hello World!\r\n" # boot signature. . = 510 .short 0xaa55
510バイト目にマークを入れておかないと、壊れたブートブロックって言われて、起動しない。 起動順は、HDD FD CDROM になってるのね。エラーメッセージを眺めていて知ったよ。
上記は、Xが上がった所でしか実行出来ないけど(int 0x10のため)、 AT(BIOS)なんて所を見ると、シリアルポート へも出せるようだから、そしたら端末だけでも動作確認出来るかな。
プロテクトモード
インテルの説明書では、保護モードって言うそうだ。リアルモードは実モードって言ってるけど ピンとこないな。
IA32の館の歴史からすると、旧館に相当するのがリアルモード。それを拡張したのがプロテクトモード。 新館のプロテクトモードは、旧館を大幅に拡張して安全性に優れてますよって言う宣伝なんだな。
旧館は、壁が無いんで、酔っ払って暴走(したプログラムが)すると簡単に全員に迷惑を及ぼす ようはCPUの作りになってた。プロテクトモードではしっかり壁を作ったとな。
旧館から新館へはインテルの作法に則って移行できるけど、逆は無理。やっぱり古いやつも 動かしたいって客のために、仮想86モードも作られたそうな。
今回は、インテルの流儀に則り、旧館の窮屈な(16bit)から新館(32Bit)でのんびり出来るように してみよう。題材は 起動するまでの長い道のり プロテクト・モード移行編(2) から頂いてきました。
今回のソースをprot.sに改名して、Makefileをそれ用に用意。
prot: fdp.img qemu -nographic -m 64 -fda fdp.img -S -gdb tcp:localhost:26000 i386.o: prot.s as -o i386.o prot.s objdump -S i386.o > i386.asm ipp.bin: i386.o objcopy -S -O binary i386.o ipp.bin fdp.img: ipp.bin dd if=/dev/zero of=fdp.img count=2880 dd if=ipp.bin of=fdp.img conv=notrunc
新館か信管か
QemuにはMonitorモードが用意されてて、ALT+CTL+2 で、モニターモードに出来るようだ。 元に戻すには、ALT+CTL+1 らしい。 モニターモードでは、info registersで色々なRegがダンプされる。メモリーのダンプは、
xp /16xb 0x1000 memory dump xp /16i 0x1000 show inst
とかだ。help TABすれば使えるコマンドが表示されるよ。(一部、画面をはみ出しちゃうけど。。)
で、今回はプロテクトモードに移る事を、gdbで確認してみたい。OpenBSDはやはりgdbのバージョンが 6.3と古く、remoteの接続を拒否される。しょうがないので、7.4.1を入れた。
OpenBSDは、qemuのパラメータに、 -S -gdb tcp::26000 とすると、IPv6の方で待ってしまって いて、接続がタイムアウトした。しょうがないので、localhostを追加したよ。準備が整ったんで 早速動かしてみる。動かすって言っても、gdbで追うだけだけどね。
ああ、そうそう、こんな.gdbinitを作っておくと、多少楽が出来るよ。 やってる事は、qemuにリモート接続し、ブレークポイントを設定し、止まった時にeipの差すメモリーアドレスを 逆アセンプリしてねって設定。それからcontnueしてBIOSからbootするって言う手順。
target remote localhost:26000 b *0x7c00 disp/i $eip c
リアルモードからプロテクトモードへ移行するテストプログラムを走らせる。
$ make as -o i386.o prot.s objdump -S i386.o > i386.asm objcopy -S -O binary i386.o ipp.bin dd if=/dev/zero of=fdp.img count=2880 2880+0 records in 2880+0 records out 1474560 bytes transferred in 0.091 secs (16066946 bytes/sec) dd if=ipp.bin of=fdp.img conv=notrunc 1+0 records in 1+0 records out 512 bytes transferred in 0.000 secs (2994152 bytes/sec) qemu -nographic -m 64 -fda fdp.img -S -gdb tcp:localhost:26000 QEMU 1.1.0 monitor - type 'help' for more information (qemu) QEMU 1.1.0 monitor - type 'help' for more information (qemu)
別端末からgdbを起動。ふーん、こんな状態で起動してくんのね。
$ gdb -q 0x0000fff0 in ?? () Breakpoint 1 at 0x7c00 Breakpoint 1, 0x00007c00 in ?? () 1: x/i $eip => 0x7c00: cli (gdb) i r eax 0xaa55 43605 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0x6f50 0x6f50 ebp 0x0 0x0 esi 0x0 0 edi 0x0 0 eip 0x7c00 0x7c00 eflags 0x202 [ IF ] cs 0x0 0 ss 0x0 0 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
ちょいとステップ実行して、追ってみます。
(gdb) si 0x00007c01 in ?? () 1: x/i $eip => 0x7c01: lgdtl (%esi) (gdb) 0x00007c06 in ?? () 1: x/i $eip => 0x7c06: mov %cr0,%eax (gdb) 0x00007c09 in ?? () 1: x/i $eip => 0x7c09: or $0x1,%ax (gdb) 0x00007c0d in ?? () 1: x/i $eip => 0x7c0d: mov %eax,%cr0 (gdb) 0x00007c10 in ?? () 1: x/i $eip => 0x7c10: jmp 0x7c12 (gdb) 0x00007c12 in ?? () 1: x/i $eip => 0x7c12: ljmp $0xb866,$0x80017 (gdb) 0x0000e05b in ?? () 1: x/i $eip => 0xe05b: add %al,(%eax)
gdt用のポインターを専用レジスターにセットして、cr0レジスタにプロテクトモードビットを セットしてるんだな。それから、インテルの作法にのっとり、パイプラインをフラッシュしてから、 ロングジャンプで、CSにgtd用のオフセットをセットしつつ、次の番地に行く。はずなんだけど、 とんでもない所へ飛んでるぞ。新館へ行かなくて信管炸裂だな。爆弾持ちかい。インテルの糞石は。
まあ、悪態ついていても進歩が無いので、ちろっと、レジスタの値を眺めてから、いろいろ想像 してみよう。
(gdb) i r eax 0x0 0 ecx 0x0 0 edx 0x633 1587 ebx 0x0 0 esp 0x0 0x0 ebp 0x0 0x0 esi 0x0 0 edi 0x0 0 eip 0xe05b 0xe05b eflags 0x2 [ ] cs 0xf000 61440 ss 0x0 0 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
CSの値がとんでもない事になってる。なして?こんな事になるの。gdtのポインターが正しくセット 出来なかったか、ロングジャンプで明後日の方へ飛んで行ったかのどちらかだな。
ロングジャンプって事は、言い換えると絶対番地へのジャンプなのかな。アセンブルリストを作って あるので、ぼーと眺めてみる。
i386.o: file format elf32-i386 Disassembly of section .text: 00000000 <begin>: 0: fa cli 1: 0f 01 16 lgdtl (%esi) 4: 30 00 xor %al,(%eax) 6: 0f 20 c0 mov %cr0,%eax 9: 66 83 c8 01 or $0x1,%ax d: 0f 22 c0 mov %eax,%cr0 10: eb 00 jmp 12 <reset_pipeline> 00000012 <reset_pipeline>: 12: ea 17 00 08 00 66 b8 ljmp $0xb866,$0x80017 00000017 <set_cs>: 17: 66 b8 10 00 mov $0x10,%ax 1b: 8e d8 mov %eax,%ds 1d: 8e c0 mov %eax,%es :
一番左側の列は、ひょっとしてアセンブラ君が認識してるアドレスかな。そうすると、この プログラム自体が0番地から始まってると仮定してるんだな。そいつを、おいらの都合で0x7c00から ロードして実行したら、、、、そりゃ、めちゃくちゃな事になるわな。
こういう時のCS頼みって事で、冒頭で、CSに0x7c0をセットしてみた(4Bitずらした値をセット するのが、リアルモードの流儀)。けど、実行すると、そこでループしちゃう。きっとインテルの 流儀に反した事を、おいらはやっちまったんだな。すごすごと引き下がりましたよ。
次なる手を考えねば。そうだ、ソースレベルで、0x7c00から始まるよーんって宣言しとけば、いいんでないかい。 えっと、どうやって宣言するんだ。ちょっと連想を働かしたら、ソースの冒頭で、
. = 0x7c00
するだけでいいみたい。実際にやってみると
$ make as -o i386.o prot.s prot.s: Assembler messages: prot.s:79: Error: attempt to .org/.space backwards? (-31314) prot.s:12: Error: relocation out of range prot.s:21: Error: relocation out of range prot.s:47: Error: relocation out of range prot.s:81: Fatal error: cannot write to output file *** Error code 1
エラーを丁寧に想像するに、アドレスの逆戻りはダメなのね。( . = 510 の所 )そこを 克服して走らせると、
$ make as -o i386.o prot.s objdump -S i386.o > i386.asm objcopy -S -O binary i386.o ipp.bin dd if=/dev/zero of=fdp.img count=2880 2880+0 records in 2880+0 records out 1474560 bytes transferred in 0.097 secs (15121831 bytes/sec) dd if=ipp.bin of=fdp.img conv=notrunc 63+0 records in 63+0 records out
どうもおかしいね。1ブロック書き込むはずなのに、63ブロックも書き込んでるよ。調べてみたら、 0番地からのデータも付属してた。とほほ。
次なる手は、gdtとかの在り処を示すデータ、ljumpの飛び先にオフセットを加える事だな。 そうすれば、きっとインテルの糞石を出し抜けるだろう。以下のように修正してみた。
# prot.s stack_begin = 0x1000 offset = 0x7c00 .code16 begin: cli lgdt gdtr + offset movl %cr0, %eax orl $0x01, %eax movl %eax, %cr0 jmp reset_pipeline reset_pipeline: ljmp $0x08, $set_cs + offset // Same as above for i486 bug ?? // .byte 0x66 // size prefix // .byte 0xea // far jump // .long set_cs + offset // jump address // .word $0x08 // gdt_kernel_cs set_cs: .code32 movw $0x10, %ax movw %ax, %ds movw %ax, %es movw %ax, %ss movl $stack_begin, %esp movl %cr0, %eax nop hlt .align 8 gdtr: gdtr_limit: .word gdt_end - gdt - 1 gdtr_base: .long gdt + offset .align 8 gdt: gdt_null: .word 0x0000 .word 0x0000 .byte 0x00 .byte 0x00 .byte 0x00 .byte 0x00 gdt_kernel_cs: .word 0xffff #Byte 1,0 .word 0x0000 #Byte 3,2 .byte 0x00 #Byte 4 .byte 0x98 #Byte 5 .byte 0xdf #Byte 6 .byte 0x00 #Byte 7 gdt_kernel_ds: .word 0xffff .word 0x0000 .byte 0x00 .byte 0x92 .byte 0xdf .byte 0x00 gdt_end: . = 510 .short 0xaa55
さあ、これでどうだ。
0x00007c21 in ?? () 1: x/i $eip => 0x7c21: mov $0x1000,%esp (gdb) 0x00007c26 in ?? () 1: x/i $eip => 0x7c26: mov %cr0,%eax (gdb) 0x00007c29 in ?? () 1: x/i $eip => 0x7c29: nop (gdb) 0x00007c2a in ?? () 1: x/i $eip => 0x7c2a: hlt (gdb) i r eax 0x11 17 ecx 0x0 0 edx 0x0 0 ebx 0x0 0 esp 0x1000 0x1000 ebp 0x0 0x0 esi 0x0 0 edi 0x0 0 eip 0x7c2a 0x7c2a eflags 0x6 [ PF ] cs 0x8 8 ss 0x10 16 ds 0x10 16 es 0x10 16 fs 0x0 0 gs 0x0 0
うん、成功。やっと新館へ入れた。これでやっとMIPSとかの普通の石に近づいたな。それにしても インテルは無理してんなあ。8086の時のCSの意味は、アドレスの上位データだったはずなのに、i386 とかになると、テーブルのオフセットって言う意味に変えちゃったんだもんなあ。
なお、ソースの途中にコメントしといた所は、486のハードバグのため(正しくジャンプしない)の 回避コードらしいです。歴史を背負ってしぶとく生きる石だ事。
ついでに、はちゃめちゃなビットのアサインぶりは、 セグメント・ディスクリプタの構造 の所で、確認出来るよ。
他の道
昔の石から現代風な石に変身させるのに、おいらは随分苦労したけど、皆さんはどうしてるかと 思って探ってみたよ。そしたら、
プロテクトモードモニタを作る なんて素敵なものが見つかった。
そして、 HariboteOS なんてのも。