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 なんてのも。