この記事はカーネル/VM Advent Calendarのネタとして書きました。
さて、今回はタイトルの通りJavaで書くブートローダです。すでに過去のエントリで書いてたりしますが、使いまわします(おい。まぁ、ブートローダと言ってもまだディスクの読み込みもできてないので、ブートセクタで動くプログラム程度の物です。でもJavaで書きます。あえてJavaで書きます。やり方は
という流れです。まずJavaでプログラムを書きます。まずBIOS.javaです。とりあえずメソッドだけを定義しておきます。putCharacterメソッドはこの段階では何もしません。これはコンパイラにこんなメソッドがあるんだよ!と伝えるためだけのクラスです。
public class BIOS{ public static void putCharacter(char c){} }
次にプログラム本体です。先程作ったBIOS.putCharacterを呼び出しています。ただ、これを素直にコンパイルして、実行しても何も起こりません。
public class Hello{ public static void main(String[] args){ BIOS.putCharacter('H'); } }
そこで次の段階に入ります。今度は先ほどのファイルをコンパイルしてできたHello.classを解析します。クラスファイルの内容まで説明するのも大変なので、バイトコードだけ載せておきます。Hello.classのmainの中身はこうなっています。全部16進数です。
10 48 B8 00 02 B1
これをx86バイナリに変換していきます。以下にでてくるバイトコードの説明を書いておきます。
- 0x10 bipush
- この命令の次にある値をスタックに乗せる。今回の場合0x48をスタックに乗せます
- B8 invokestatic
- B1 return
- 今回は無視、ブートローダのメインプログラムでリターンされても困る。変わりに最後に無限ループを入れておく。
以上が今回扱うバイトコードの命令です。次に上記のバイトコードがどのx86の命令に対応させるかを書きます。
- 0x10 bipush
- x86にあるPUSH命令をそのまま使える。オペコードは0x6A
- B8 invokestatiic
- B1 return
- 無視
プログラムの最後に無限ループ用のJMP命令を入れておく。無限ループさせるには0xEB 0xFEと続ければ良い。最後に今回はフロッピブートを想定しているので、511バイト目と512バイト目におまじないが必要。0x55 0xAAを書き込んでおきましょう。
変換プログラムはこんな感じ
import parser.ClassParser; import parser.struct.StaticMethod; import java.io.FileOutputStream; import java.io.BufferedOutputStream; public class Class2x86{ private byte[] memory; private int index; public Class2x86(){ memory = new byte[512]; index = 0; memory[510] = (byte)0x55; memory[511] = (byte)0xAA; } public static void main(String[] args) throws Exception{ Class2x86 translator = new Class2x86(); translator.translate(args[0], args[1]); } public void translate(String className, String x86Name) throws Exception{ ClassParser parser = new ClassParser(); parser.parse(className); byte[] code = parser.getMethodCode("main"); for(int i = 0; i < code.length; i++){ int opcode = code[i] & 0xFF; if(opcode == 0x10){ put((byte)0x6A); put(code[i + 1]); i++; }else if(opcode == 0xB8){ int pindex = (code[i + 1] & 0xFF) << 8; pindex |= (code[i + 2] & 0xFF); StaticMethod method = parser.getStaticMethod(pindex); String name = String.format("%s.%s", method.getClassName(), method.getMethodName()); if("BIOS.put".equals(name)){ put((byte)0x58); put((byte)0xB4); put((byte)0x0E); put((byte)0xB7); put((byte)0x00); put((byte)0xB3); put((byte)0x15); put((byte)0xCD); put((byte)0x10); i+=2; } } } put((byte)0xEB); put((byte)0xFE); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(x86Name)); bos.write(memory); bos.close(); } public void put(byte data){ memory[index] = data; index++; } }
あとは、このプログラムに先程コンパイルしてできたHello.classを読み込ませるのみ。qemuやらbochsやらで変換後のプログラムを読み込ませるとHが表示されます。めでたしめでたし。次回のカーネル/VM Advent Calendarは期待のmasami256さんです。期待して待ちましょう