41
1 Java x86 エミュレータ を作る 2010/08/21 d-kami

d-kami x86-1

Embed Size (px)

Citation preview

Page 1: d-kami x86-1

1

Java でx86 エミュレータ

を作る

2010/08/21d-kami

Page 2: d-kami x86-1

2

自己紹介

■自己紹介 本名 : 上川大介 所属 : 筑波大学某研究室 Hatena: d-kami Twitter: d_kami

Page 3: d-kami x86-1

3

x86 エミュレータをなぜ作ろうと思ったのか? (1/2)

■なぜ x86 エミュレータを作ろうと思ったのか? (1/2)

作れそうだったから

Page 4: d-kami x86-1

4

x86 エミュレータをなぜ作ろうと思ったのか? (2/2)

■x86 エミュレータをなぜ作ろうと思ったのか? (2/2)

アセンブリ言語で作った小さなプログラムなら簡単にエミュレートできるのではないか?

それじゃ、作ってみよう!

Page 5: d-kami x86-1

5

持っていた知識

■持っていた知識 メモリにプログラムが載っている プログラムカウンタがある レジスタがある (EAX 、 EBX 、 ECX 、 EDX...) フラグレジスタ EFLAGS の一部 コントロールレジスタ CR0 の一部 A20 ゲート、 GDT 、 IDT

Page 6: d-kami x86-1

6

x86 エミュレータの開発環境

■x86 エミュレータの開発環境

    Java

Page 7: d-kami x86-1

7

目標

■目標

   Live CD 版の  Fedora を動かす

Page 8: d-kami x86-1

8

本題

■本題■この発表の内容

とりあえずアセンブリ言語でプログラムを作る セグメントレジスタやフラグレジスタの簡単な説明 命令の書式 BIOS ファンクションの一部 ところどころに Java の簡単なソースコードがでてくる

Page 9: d-kami x86-1

9

進め方

■進め方 私が勉強した通りにスライドは進んで行きます わからなくなるまで進み、わからなくなったら調べる これがいいやり方かどうかはわかりません アセンブリ言語として NASM を利用しているため Intel の構文が

でてきます そのため MOV EAX, ECX は ECX の値を EAX に代入する

Page 10: d-kami x86-1

10

とりあえず作業開始

■とりあえず作業開始public class VM{ // 汎用レジスタの一部 long eax; long ebx; long ecx; long edx; // プログラムカウンタ long eip; // メモリ byte[] memory;}

Page 11: d-kami x86-1

11

基本的な作業

■基本的な作業1 アセンブリ言語でプログラムを作ってアセンブル2 アセンブルしてできたバイナリを memory に読み込む3 eip を初期化する4 memory の eip 番目の値を取得して、その値を命令と見て実行 →例えば取得した値が 0x05 だったら足し算を行う5 実行した命令の長さ分だけ eip を増やし、 4 へ

Page 12: d-kami x86-1

12

アセンブリ言語で書いてみる

■アセンブリ言語で書いてみる

MOV AX, 1 ADD AX, 1

■まずはこれだけをアセンブル■test.asm で保存して nasm test.asm でバイナリファイルを作る

Page 13: d-kami x86-1

13

アセンブル後、どうなるのか

■アセンブル後、どうなるのか nasm test.asm -l test.list と入れると test.list に以下の内容が

出力される

1 00000000 B80100 MOV AX, 1 2 00000003 050100 ADD AX, 1

Page 14: d-kami x86-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;}

Page 15: d-kami x86-1

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

Page 16: d-kami x86-1

16

セグメントレジスタ

■セグメントレジスタ CS や DS などの 16bit のレジスタ リアルモードの場合、セグメントレジスタの値を 16 倍したもの

とプログラムカウンタやアドレスを足す 他にもデータセグメントの DS やスタックセグメントの SS があ

る。

Page 17: d-kami x86-1

17

フラグレジスタ

■フラグレジスタ 条件分岐などで使うフラグの集合

➔ 演算で変化するフラグ- Carry Flag- Parity Flag- Adjust Flag- Zero Flag- Sign Flag- Overflow Flag

➔ 特定の命令で変化するフラグ- Direction Flag- Intteerupt Enable Flag

Page 18: d-kami x86-1

18

Java で実装 (1/3)

■Java で実装 (1/3) まずセグメントレジスタから

➔ コードセグメントを使ってる場合、オペコードの取得がこうなる

int opecode = memory[cs * 16 + eip] & 0xFF;➔ データセグメントの場合

ds * 16 + address;

Page 19: d-kami x86-1

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);

Page 20: d-kami x86-1

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); }}

Page 21: d-kami x86-1

21

命令の書式

■命令の書式

Prefix Opecode ModR/M SIB Displacement

Immediate

Page 22: d-kami x86-1

22

Prefix(1/3)

■Prefix(1/3)

Prefix には4つのグループがある 4つのグループから1つずつ追加することができる 1つの Prefix につき1 byte

Prefix Opecode ModR/M SIB Displacement

Immediate

Page 23: d-kami x86-1

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 セグメント・オーバーライド

Page 24: d-kami x86-1

24

Prefix(3/3)

■Prefix(3/3) グループ 3

➔ 0x66 オペランド・サイズ・オーバーライド・プリフィックス➔ オペランドを 16bit 又は 32bit に切り替えられる

グループ 4➔ 0x67 アドレス・サイズ・オーバーライド・プリフィックス➔ アドレスを 16bit 又は 32bit に切り替えられる

Page 25: d-kami x86-1

25

Opecode

■Opecode

1〜3 byte で表される命令の番号 ModR/M のうちの 3bit が拡張オペコードフィールドとして扱わ

れる場合がある

Prefix Opecode ModR/M SIB Displacement

Immediate

Page 26: d-kami x86-1

26

ModR/M(1/2)

■ModR/M(1/2)

Mod フィールド、 Register/Opecode フィールド、 R/Mフィールドに分かれている

Register/Opecode は命令によってレジスタのインデックスになったり、拡張オペコードフィールドになる

Prefix Opecode ModR/M SIB Displacement

Immediate

Page 27: d-kami x86-1

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

Page 28: d-kami x86-1

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

Page 29: d-kami x86-1

29

SIB(2/2)

■SIB(2/2)

2の Scale乗 × Base + Index(8 * EAX + ECX) ModR/M の Mod によってさらに値を足すことができる

Scale Index Base

Page 30: d-kami x86-1

30

Displacement と Immediate

■Displacement と Immediate

Displacement は1、2、4 byte の値。 ModR/M や SIB のアドレスの計算に数値を入れたいに使う

Immediate は命令によって、レジスタやメモリではなく、直接値を指定する場合に使う (ADD EAX, 1)

Prefix Opecode ModR/M SIB Displacement

Immediate

Page 31: d-kami x86-1

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);}

Page 32: d-kami x86-1

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); ... }}

Page 33: d-kami x86-1

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); }}

Page 34: d-kami x86-1

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); }

Page 35: d-kami x86-1

35

ここからが本番

■OS をブートしたい ブートセクタを 0x7C00 に置く フロッピーブートだと先頭 512byte CD だと少し大変

➔ El Torito に従ってブートセクタを読み込む➔ Live CD 版のブートを目指してるのでこちらの対応必須

Page 36: d-kami x86-1

36

BIOS ファンクション

■BIOS ファンクション エミュレータでブートローダを実行してると、でてくる

➔ メモリ上の 0xCD の次の byte とレジスタの内容で使われる機能が変化- 文字表示やグラフィックモードの確認・設定- ディスクの確認・設定、読み込み

Page 37: d-kami x86-1

37

INT 0x10

■INT 0x10 グラフィック関係の機能が集まっている AH が 0x0E のとき文字表示、 AH=0x00 でグラフィックモード設

定 ただし、通常のグラフィックモード設定より VESA が使えるかど

うかを調べることが普通だと思うので、 VESA が使えるかどうか調べる AX = 0x4F03 と設定の AX = 0x4F02

Page 38: d-kami x86-1

38

INT 0x13

■INT 0x13 ディスクの読み書き、設定など AH が 0x02 で読み込み、 0x03 で書き込み ただし、拡張 INT 0x13 というものがあり、大抵のブートローダ

は機能の有無をチェックするため、対応する必要あり AH が 0x41 で存在のチェック、 0x42 で読み込み、 0x43 で書き

込み

Page 39: d-kami x86-1

39

あとは命令をひたすら実装

■あとは命令をひたすら実装1 まず実行2 実装してない命令が来るまで繰り返す3 実装してない命令が来たら Intel のマニュアルを見て実装4 2 へ戻る

これの繰り返しでうまくいったらいいなぁ

Page 40: d-kami x86-1

40

現状

■現状

boot: Could not find   kernel image

Page 41: d-kami x86-1

41

残念な途中経過ですが頑張っています

残念な途中経過ですが  頑張っています