リアルモードでも32bitのレジスタを使うことができる
この記事は自作OS Advent Calendar 2018の21日目の記事です。
自作OSのアドカレなので、まずは作っているCPUエミュの話をしましょう。
CPUエミュを作っている話
そもそもなぜCPUエミュを作っているのかというと、 Golangで書かれたファミコンのエミュのコードを読んでめっちゃおもしろいなって思ったのが、 CPUエミュを作ってみようと思い始めたきっかけでした。
それで一番馴染みがあるアーキテクチャであるx86を対象にGolangでx86のCPUエミュ「cibo」を作りはじめました。 x86/x64を対象にしたソフトウェアエミュレータは、Type1ハイパーバイザーがあるのであまり需要がない気はしますが、 はじめて作るCPUエミュは親しみのあるアーキテクチャの方がいいでしょう。 ciboという名はBLAMEの生電社の主任科学者シボさんから取っています。 「久しぶりだな頭取!」って言って霧亥を助けに来るシーン、かっこいいですよね。 github.com
ひとまずQEMUとBochsで使われているBIOSである、SeaBIOSを動作させることを目標に開発しています。 今のところ、NASMでアセンブルされた簡単なバイナリを走らせることができるくらいの進捗です。早くVGAあたりをエミュレートしてカラーの画面を出したいですね
$ cibo --debug ./samples/asm/simple_call EIP = 0x7C00, Opcode = 0xB8 EIP = 0x7C05, Opcode = 0xBB EIP = 0x7C0A, Opcode = 0xE8 EIP = 0x7C14, Opcode = 0x89 EIP = 0x7C16, Opcode = 0xB8 EIP = 0x7C1B, Opcode = 0xC3 EIP = 0x7C0F, Opcode = 0xE9 No mapping area: 0x0 ==================== registers ==================== 01: EAX = 0x1011 02: ECX = 0x0 03: EDX = 0x0 04: EBX = 0xF1 05: ESP = 0x7C04 06: EBP = 0x0 07: ESI = 0x0 08: EDI = 0x0 09: EIP = 0x0 10: CS = 0x0 11: DS = 0x0 12: SS = 0x0 13: ES = 0x0 14: FS = 0x0 15: GS = 0x0 16: EFLAGS = 0x2 (00000000000000000000000000000010) 17: MM0 = 0x0 18: MM1 = 0x0 19: MM2 = 0x0 20: MM3 = 0x0 21: MM4 = 0x0 22: MM5 = 0x0 23: MM6 = 0x0 24: MM7 = 0x0
また、Unicornのようにコードからも操作できるようにしています。
package main import ( "github.com/tkmru/cibo/core" ) func main() { beginAddress := 0x7c00 emu := cibo.NewEmulator(32, beginAddress, 29, false, true) cpu := emu.CPU reg := &cpu.X86registers emu.RAM = []byte{0xb8, 0x01, 0x00, 0x00, 0x00, 0x3d, 0x02, 0x00, 0x00, 0x00, 0x75, 0x05, 0xe9, 0xef, 0x83, 0xff, 0xff, 0xb8, 0x02, 0x00, 0x00, 0x00, 0x3d, 0x02, 0x00, 0x00, 0x00, 0x74, 0xef} /* mov eax, 0x1 cmp eax, 0x2 jnz not_equal equal: jmp 0 not_equal: mov eax, 0x2 cmp eax, 0x2 jz equal */ reg.Init() emu.Run() }
テストはmulti-architecture assembler frameworkであるKeystoneのGolangバインディングと標準パッケージのtestingを使って書いています。 Golangを使ってCPUエミュのテストを書くにはこれがいいと思います。実際のテストコードはこれです。
うっかり自作CPUエミュの話が長くなってしまいましたが、自作OSのアドカレの記事なのでまあよしとしましょう。 そろそろ本題に入ります。
BIOSとは
特に説明をせずBIOSについて言及していましたが、ここで説明していきます。 BIOSというのは、Basic Input/Output Systemの略でPCの電源を入れた直後の初期化や HDDから最初のプログラムローダをメモリに読み出す役割を持つプログラムです。
BIOSはリアルモードという16bitで動作するモードで動き、 周辺機器に関する処理を行ったあと、MBR(Master Boot Record)やPBR(PartiPartition Boot Record)といったブートセクタを読み出し、 32bit/64bitで動くOSに動作を移します。
このようなBIOSですが、16bitでしか動作しないので、32bit/64bitで動作するUEFIに取って代わられようとしています。 Intel CPUでは 2020年までに CSM (BIOS 互換機能) を撤廃して UEFI に完全移行する方針のようで、 近いうちにBIOSが搭載されたデバイスは見かけなくなってしまうようです。
なぜ、そのうちなくなるであろうBIOSを自作CPUエミュで動かそうとしているのかというと、いきなりx64のエミュを実装するのは大変かなと思ったからです。ところで、seabiosを自作CPUエミュで動かそうと思うと、seabios内で使われている命令すべてを自作エミュ上で動作するようにしておく必要があります。そのため、どんな命令が使われているのか知るためにIDAでseabiosのバイナリをディスアセンブルしてみたところ、リアルモードでも32bitのレジスタは読み書きされていることに気づきました。
リアルモードで32bitのレジスタ???
下の画像を見ると32bitのレジスタを使っている命令は先頭に0x66
というopcodeがついているのが分かると思います。
0x66
というopcodeはOperand-size override prefix
という役割を持っていて、オペランドのサイズを上書きできる接頭辞です。
つまり、リアルモードではopcode 0x66
を使うことで32bitのレジスタを読み書きできるのです!!
これを知って、私の好きなopcodeは0x66
になりました。
16bitで動作している環境で32bitのレジスタが使えるというのはとてもおもしろいですね。
わたしはこれを自分で発見してとても驚いたのですが、
Wikipediaに書かれていたので自明なことだったようです....