1
Java でx86 エミュレータ
を作る
2010/08/21d-kami
2
自己紹介
■自己紹介 本名 : 上川大介 所属 : 筑波大学某研究室 Hatena: d-kami Twitter: d_kami
3
x86 エミュレータをなぜ作ろうと思ったのか? (1/2)
■なぜ x86 エミュレータを作ろうと思ったのか? (1/2)
作れそうだったから
4
x86 エミュレータをなぜ作ろうと思ったのか? (2/2)
■x86 エミュレータをなぜ作ろうと思ったのか? (2/2)
アセンブリ言語で作った小さなプログラムなら簡単にエミュレートできるのではないか?
それじゃ、作ってみよう!
5
持っていた知識
■持っていた知識 メモリにプログラムが載っている プログラムカウンタがある レジスタがある (EAX 、 EBX 、 ECX 、 EDX...) フラグレジスタ EFLAGS の一部 コントロールレジスタ CR0 の一部 A20 ゲート、 GDT 、 IDT
6
x86 エミュレータの開発環境
■x86 エミュレータの開発環境
Java
7
目標
■目標
Live CD 版の Fedora を動かす
8
本題
■本題■この発表の内容
とりあえずアセンブリ言語でプログラムを作る セグメントレジスタやフラグレジスタの簡単な説明 命令の書式 BIOS ファンクションの一部 ところどころに Java の簡単なソースコードがでてくる
9
進め方
■進め方 私が勉強した通りにスライドは進んで行きます わからなくなるまで進み、わからなくなったら調べる これがいいやり方かどうかはわかりません アセンブリ言語として NASM を利用しているため Intel の構文が
でてきます そのため MOV EAX, ECX は ECX の値を EAX に代入する
10
とりあえず作業開始
■とりあえず作業開始public class VM{ // 汎用レジスタの一部 long eax; long ebx; long ecx; long edx; // プログラムカウンタ long eip; // メモリ byte[] memory;}
11
基本的な作業
■基本的な作業1 アセンブリ言語でプログラムを作ってアセンブル2 アセンブルしてできたバイナリを memory に読み込む3 eip を初期化する4 memory の eip 番目の値を取得して、その値を命令と見て実行 →例えば取得した値が 0x05 だったら足し算を行う5 実行した命令の長さ分だけ eip を増やし、 4 へ
12
アセンブリ言語で書いてみる
■アセンブリ言語で書いてみる
MOV AX, 1 ADD AX, 1
■まずはこれだけをアセンブル■test.asm で保存して nasm test.asm でバイナリファイルを作る
13
アセンブル後、どうなるのか
■アセンブル後、どうなるのか nasm test.asm -l test.list と入れると test.list に以下の内容が
出力される
1 00000000 B80100 MOV AX, 1 2 00000003 050100 ADD AX, 1
14
命令を実装する ( 超簡易版 )
■命令を実装する ( 超簡易版 )
int opcode = memory[eip] & 0xFF;
if(opcode == 0xB8){ eax = getMemoryValue16(eip + 1); eip += 3;}else if(opecode == 0x05){ eax += getMemoryValue16(eip + 1); eip += 3;}
15
レジスタの内容確認プログラム
■レジスタの内容確認プログラムpublic void dump(){ System.out.println(“EAX = ” + eax); System.out.println(“EBX = ” + ebx); System.out.println(“ECX = ” + ecx); System.out.println(“EDX = ” + edx); System.out.println(“EIP = “ + eip);}
出力結果EAX = 2EBX = 0ECX = 0EDX = 0EIP = 6
16
セグメントレジスタ
■セグメントレジスタ CS や DS などの 16bit のレジスタ リアルモードの場合、セグメントレジスタの値を 16 倍したもの
とプログラムカウンタやアドレスを足す 他にもデータセグメントの DS やスタックセグメントの SS があ
る。
17
フラグレジスタ
■フラグレジスタ 条件分岐などで使うフラグの集合
➔ 演算で変化するフラグ- Carry Flag- Parity Flag- Adjust Flag- Zero Flag- Sign Flag- Overflow Flag
➔ 特定の命令で変化するフラグ- Direction Flag- Intteerupt Enable Flag
18
Java で実装 (1/3)
■Java で実装 (1/3) まずセグメントレジスタから
➔ コードセグメントを使ってる場合、オペコードの取得がこうなる
int opecode = memory[cs * 16 + eip] & 0xFF;➔ データセグメントの場合
ds * 16 + address;
19
Java で実装 (2/3)
■Java で実装 (2/3) Eflags を表すクラス
public class EFlags{ private int eflags;
public void setZero(boolean){ ... }
public boolean isZero(){ int czero = (eflags >> 0x06) & 0x01; return czero == 0x01; }}
Zero Flag の設定boolean flag = (result == 0);eflags.setZero(flag);
20
Java で実装 (3/3)
■Java で実装 (3/3) 利用例
public class JE implements Instruction{ public void execute(VM vm){ if(vm.getEFlags().isZero()){ int diff = vm.getSignedCode8(1); vm.addEIP(diff); }
vm.addEIP(2); }}
21
命令の書式
■命令の書式
Prefix Opecode ModR/M SIB Displacement
Immediate
22
Prefix(1/3)
■Prefix(1/3)
Prefix には4つのグループがある 4つのグループから1つずつ追加することができる 1つの Prefix につき1 byte
Prefix Opecode ModR/M SIB Displacement
Immediate
23
Prefix(2/3)
■Prefix(2/3) グループ1 ロック、およびリピート
➔ 0xF0 LOCK➔ 0xF2 REPNE/REPNZ➔ 0xF3 REP または REPE/REPZ
グループ2 セグメント・オーバーライド・プリフィクス➔ 0x2E CS セグメント・オーバーライド➔ 0x36 SS セグメント・オーバーライド➔ 0x3E DS セグメント・オーバーライド➔ 0x26 ES セグメント・オーバーライド➔ 0x64 FS セグメント・オーバーライド➔ 0x65 GS セグメント・オーバーライド
24
Prefix(3/3)
■Prefix(3/3) グループ 3
➔ 0x66 オペランド・サイズ・オーバーライド・プリフィックス➔ オペランドを 16bit 又は 32bit に切り替えられる
グループ 4➔ 0x67 アドレス・サイズ・オーバーライド・プリフィックス➔ アドレスを 16bit 又は 32bit に切り替えられる
25
Opecode
■Opecode
1〜3 byte で表される命令の番号 ModR/M のうちの 3bit が拡張オペコードフィールドとして扱わ
れる場合がある
Prefix Opecode ModR/M SIB Displacement
Immediate
26
ModR/M(1/2)
■ModR/M(1/2)
Mod フィールド、 Register/Opecode フィールド、 R/Mフィールドに分かれている
Register/Opecode は命令によってレジスタのインデックスになったり、拡張オペコードフィールドになる
Prefix Opecode ModR/M SIB Displacement
Immediate
27
ModR/M(2/2)
■ModR/M(2/2)
Mod が 2bit 、 Register/Opecode が 3bit 、 R/M が 3bit Mod と R/M を合わせて使い、 5bitぶん 32 通りの表現が可能 32 通りのなか、8個のレジスタと24通りのアドレスの指定方法がある
Mod Register/Opecode R/M
28
SIB(1/2)
■SIB(1/2)
Scale Index Base の略 ModR/M でアドレスを指定する場合、2つのレジスタの足し
算、またはレジスタと値の足し算のみだった しかし、 SIB を使うと [EAX * 8 + 32 + EBP] といった掛け算
を混ぜたり、オペランドを増やすことができる
Prefix Opecode ModR/M SIB Displacement
Immediate
29
SIB(2/2)
■SIB(2/2)
2の Scale乗 × Base + Index(8 * EAX + ECX) ModR/M の Mod によってさらに値を足すことができる
Scale Index Base
30
Displacement と Immediate
■Displacement と Immediate
Displacement は1、2、4 byte の値。 ModR/M や SIB のアドレスの計算に数値を入れたいに使う
Immediate は命令によって、レジスタやメモリではなく、直接値を指定する場合に使う (ADD EAX, 1)
Prefix Opecode ModR/M SIB Displacement
Immediate
31
Java で実装 その2 (1/4)
■Java で実装 その2 (1/4)public void execute() throws NotImplementException{ int code = getCode8(0); Instruction instruction = instMap.get(code);
if(instruction == null){ throw new NotImplementException(code); }
instruction.execute(this);}
32
Java で実装 その2 (2/4)
■Java で実装 その2 (2/4)public class InstructionMap{ private Instruction[] instructions;
public InstructionMap(){ instructions = new Instruction[256]; }
public void init(){ instructions[0x01] = new AddRMXRX(); instructions[0x03] = new AddRXRMX(); instructions[0x04] = new AddALImm8(); instructions[0x05] = new AddAXImmX(); instructions[0x06] = new Push(ES); ... }}
33
Java で実装 その2 (3/4)
■Java で実装 その2 (3/4)public class OperandPrefix implements Instruction{ public void execute(VM vm){ int code = vm.getCode8(1);
vm.setOperandPrefix(true); vm.addEIP(1);
Instruction instruction = vm.getInstruction(code); instruction.execute(vm);
vm.setOperandPrefix(false); }}
34
Java で実装 その2 (4/4)
■Java で実装 その2 (4/4)public class VMUtil{ public static int getMod(int modrm){ return ((modrm & 0xC0) >> 6) & 0x03; }
public static int getRegisterIndex(int modrm){ return ((modrm & 0x38) >> 3) & 0x07; }
public static int getOpecode(int modrm){ return getRegisterIndex(modrm); }
public static int getRM(int modrm){ return (modrm & 0x07); }
35
ここからが本番
■OS をブートしたい ブートセクタを 0x7C00 に置く フロッピーブートだと先頭 512byte CD だと少し大変
➔ El Torito に従ってブートセクタを読み込む➔ Live CD 版のブートを目指してるのでこちらの対応必須
36
BIOS ファンクション
■BIOS ファンクション エミュレータでブートローダを実行してると、でてくる
➔ メモリ上の 0xCD の次の byte とレジスタの内容で使われる機能が変化- 文字表示やグラフィックモードの確認・設定- ディスクの確認・設定、読み込み
37
INT 0x10
■INT 0x10 グラフィック関係の機能が集まっている AH が 0x0E のとき文字表示、 AH=0x00 でグラフィックモード設
定 ただし、通常のグラフィックモード設定より VESA が使えるかど
うかを調べることが普通だと思うので、 VESA が使えるかどうか調べる AX = 0x4F03 と設定の AX = 0x4F02
38
INT 0x13
■INT 0x13 ディスクの読み書き、設定など AH が 0x02 で読み込み、 0x03 で書き込み ただし、拡張 INT 0x13 というものがあり、大抵のブートローダ
は機能の有無をチェックするため、対応する必要あり AH が 0x41 で存在のチェック、 0x42 で読み込み、 0x43 で書き
込み
39
あとは命令をひたすら実装
■あとは命令をひたすら実装1 まず実行2 実装してない命令が来るまで繰り返す3 実装してない命令が来たら Intel のマニュアルを見て実装4 2 へ戻る
これの繰り返しでうまくいったらいいなぁ
40
現状
■現状
boot: Could not find kernel image
41
残念な途中経過ですが頑張っています
残念な途中経過ですが 頑張っています
Top Related