Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Java で
x86 エミュレータ
     を作る
       2010/08/21
           d-kami
                1
自己紹介


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




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

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


  作れそうだったから


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

■x86   エミュレータをなぜ作ろうと思った
     のか? (2/2)
    アセンブリ言語で作った小さなプログラムなら簡単にエミュレー
      トできるのではないか?
    それじゃ、作ってみよう!




                                    4
持っていた知識


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




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

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




     Java


                             6
目標


■目標




    Live CD 版の
   Fedora を動かす

                  7
本題


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




                                     8
進め方


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




                                            9
とりあえず作業開始


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


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




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


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


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




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


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


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




                                                     13
命令を実装する ( 超簡易版 )


■命令を実装する           ( 超簡易版 )
int opcode = memory[eip] & 0xFF;

if(opcode == 0xB8){
   eax = getMemoryValue16(eip + 1);
   eip += 3;
}else if(opecode == 0x05){
   eax += getMemoryValue16(eip + 1);
   eip += 3;
}

                                         14
レジスタの内容確認プログラム


■レジスタの内容確認プログラム

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 = 2
EBX = 0
ECX = 0
EDX = 0
EIP = 6
                                                 15
セグメントレジスタ


■セグメントレジスタ
   CS や DS などの 16bit のレジスタ

    リアルモードの場合、セグメントレジスタの値を 16 倍したもの
     とプログラムカウンタやアドレスを足す

    他にもデータセグメントの DS やスタックセグメントの SS があ
     る。




                                      16
フラグレジスタ


■フラグレジスタ
   条件分岐などで使うフラグの集合
     ➔ 演算で変化するフラグ

        - Carry Flag
        - Parity Flag
        - Adjust Flag
        - Zero Flag
        - Sign Flag
        - Overflow Flag
     ➔ 特定の命令で変化するフラグ

        - Direction Flag
        - Intteerupt Enable Flag




                                         17
Java で実装 (1/3)

■Java で実装        (1/3)
   まずセグメントレジスタから
     ➔ コードセグメントを使ってる場合、オペコードの取得がこう


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


       ds * 16 + address;




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




                                                             20
命令の書式


■命令の書式



Prefix   Opecode   ModR/M   SIB   Displacemen Immediate
                                  t




                                                      21
Prefix(1/3)

■Prefix(1/3)



     Prefix   Opecode   ModR/M   SIB   Displacemen Immediate
                                       t


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




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




                                           23
Prefix(3/3)

■Prefix(3/3)
 
     グループ 3
      ➔ 0x66 オペランド・サイズ・オーバーライド・プリフィックス

      ➔ オペランドを 16bit 又は 32bit に切り替えられる




 
     グループ 4
      ➔ 0x67 アドレス・サイズ・オーバーライド・プリフィックス


      ➔ アドレスを 16bit 又は 32bit に切り替えられる




                                        24
Opecode

■Opecode



    Prefix   Opecode   ModR/M   SIB   Displacemen Immediate
                                      t



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




                                                          25
ModR/M(1/2)

■ModR/M(1/2)



     Prefix   Opecode   ModR/M   SIB   Displacemen Immediate
                                       t



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



                                                           26
ModR/M(2/2)

■ModR/M(2/2)


        Mod         Register/Opecode       R/M




    Mod が 2bit 、 Register/Opecode が 3bit 、 R/M が 3bit
    Mod と R/M を合わせて使い、 5bit ぶん 32 通りの表現が可能
    32 通りのなか、8個のレジスタと24通りのアドレスの指定方
      法がある



                                                         27
SIB(1/2)

■SIB(1/2)



     Prefix   Opecode   ModR/M   SIB   Displacemen Immediate
                                       t



     Scale Index Base の略
     ModR/M でアドレスを指定する場合、2つのレジスタの足し
       算、またはレジスタと値の足し算のみだった
 
      しかし、 SIB を使うと [EAX * 8 + 32 + EBP] といった掛け算
       を混ぜたり、オペランドを増やすことができる


                                                           28
SIB(2/2)

■SIB(2/2)



       Scale         Index             Base



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




                                                29
Displacement と Immediate

■Displacement と             Immediate

     Prefix   Opecode   ModR/M   SIB   Displaceme Immediate
                                       nt



     Displacement は1、2、4 byte の値。 ModR/M や SIB のア
        ドレスの計算に数値を入れたいに使う
     Immediate は命令によって、レジスタやメモリではなく、直接
        値を指定する場合に使う (ADD EAX, 1)



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




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



                                                                 33
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);
  }
                                                           34
ここからが本番


■OS をブートしたい
 
     ブートセクタを 0x7C00 に置く
 
     フロッピーブートだと先頭 512byte
    CD だと少し大変
      ➔ El Torito に従ってブートセクタを読み込む

      ➔ Live CD 版のブートを目指してるのでこちらの対応必須




                                        35
BIOS ファンクション

■BIOS ファンクション
   エミュレータでブートローダを実行してると、でてくる
     ➔ メモリ上の 0xCD の次の byte とレジスタの内容で使われる機


        能が変化
        - 文字表示やグラフィックモードの確認・設定
        - ディスクの確認・設定、読み込み




                                        36
INT 0x10

■INT   0x10
    グラフィック関係の機能が集まっている
    AH が 0x0E のとき文字表示、 AH=0x00 でグラフィックモード設
      定
    ただし、通常のグラフィックモード設定より VESA が使えるかど
      うかを調べることが普通だと思うので、 VESA が使えるかどう
      か調べる AX = 0x4F03 と設定の AX = 0x4F02




                                          37
INT 0x13

■INT    0x13
    ディスクの読み書き、設定など
    AH が 0x02 で読み込み、 0x03 で書き込み
    ただし、拡張 INT 0x13 というものがあり、大抵のブートローダ
      は機能の有無をチェックするため、対応する必要あり
    AH が 0x41 で存在のチェック、 0x42 で読み込み、 0x43 で書き
      込み




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


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

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




                                       39
現状


■現状




boot: Could not find
  kernel image

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




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

                    41

More Related Content

d-kami x86-1

  • 1. Java で x86 エミュレータ を作る 2010/08/21 d-kami 1
  • 2. 自己紹介 ■自己紹介  本名 : 上川大介  所属 : 筑波大学某研究室  Hatena: d-kami  Twitter: d_kami 2
  • 3. x86 エミュレータをなぜ作ろうと思ったのか? (1/2) ■なぜ x86 エミュレータを作ろうと思った のか? (1/2) 作れそうだったから 3
  • 4. x86 エミュレータをなぜ作ろうと思ったのか? (2/2) ■x86 エミュレータをなぜ作ろうと思った のか? (2/2)  アセンブリ言語で作った小さなプログラムなら簡単にエミュレー トできるのではないか?  それじゃ、作ってみよう! 4
  • 5. 持っていた知識 ■持っていた知識  メモリにプログラムが載っている  プログラムカウンタがある  レジスタがある (EAX 、 EBX 、 ECX 、 EDX...)  フラグレジスタ EFLAGS の一部  コントロールレジスタ CR0 の一部  A20 ゲート、 GDT 、 IDT 5
  • 6. x86 エミュレータの開発環境 ■x86 エミュレータの開発環境     Java 6
  • 7. 目標 ■目標    Live CD 版の   Fedora を動かす 7
  • 8. 本題 ■本題 ■この発表の内容  とりあえずアセンブリ言語でプログラムを作る  セグメントレジスタやフラグレジスタの簡単な説明  命令の書式  BIOS ファンクションの一部  ところどころに Java の簡単なソースコードがでてくる 8
  • 9. 進め方 ■進め方  私が勉強した通りにスライドは進んで行きます  わからなくなるまで進み、わからなくなったら調べる  これがいいやり方かどうかはわかりません  アセンブリ言語として NASM を利用しているため Intel の構文が でてきます  そのため MOV EAX, ECX は ECX の値を EAX に代入する 9
  • 10. とりあえず作業開始 ■とりあえず作業開始 public class VM{ // 汎用レジスタの一部 long eax; long ebx; long ecx; long edx; // プログラムカウンタ long eip; // メモリ byte[] memory; } 10
  • 11. 基本的な作業 ■基本的な作業 1 アセンブリ言語でプログラムを作ってアセンブル 2 アセンブルしてできたバイナリを memory に読み込む 3 eip を初期化する 4 memory の eip 番目の値を取得して、その値を命令と見て実行  →例えば取得した値が 0x05 だったら足し算を行う 5 実行した命令の長さ分だけ eip を増やし、 4 へ 11
  • 12. アセンブリ言語で書いてみる ■アセンブリ言語で書いてみる MOV AX, 1 ADD AX, 1 ■まずはこれだけをアセンブル ■test.asm で保存して nasm test.asm でバイナリファイルを作る 12
  • 13. アセンブル後、どうなるのか ■アセンブル後、どうなるのか  nasm test.asm -l test.list と入れると test.list に以下の内容が 出力される 1 00000000 B80100 MOV AX, 1 2 00000003 050100 ADD AX, 1 13
  • 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; } 14
  • 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 = 2 EBX = 0 ECX = 0 EDX = 0 EIP = 6 15
  • 16. セグメントレジスタ ■セグメントレジスタ  CS や DS などの 16bit のレジスタ  リアルモードの場合、セグメントレジスタの値を 16 倍したもの とプログラムカウンタやアドレスを足す  他にもデータセグメントの DS やスタックセグメントの SS があ る。 16
  • 17. フラグレジスタ ■フラグレジスタ  条件分岐などで使うフラグの集合 ➔ 演算で変化するフラグ - Carry Flag - Parity Flag - Adjust Flag - Zero Flag - Sign Flag - Overflow Flag ➔ 特定の命令で変化するフラグ - Direction Flag - Intteerupt Enable Flag 17
  • 18. Java で実装 (1/3) ■Java で実装 (1/3)  まずセグメントレジスタから ➔ コードセグメントを使ってる場合、オペコードの取得がこう なる int opecode = memory[cs * 16 + eip] & 0xFF; ➔ データセグメントの場合 ds * 16 + address; 18
  • 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); 19
  • 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); } } 20
  • 21. 命令の書式 ■命令の書式 Prefix Opecode ModR/M SIB Displacemen Immediate t 21
  • 22. Prefix(1/3) ■Prefix(1/3) Prefix Opecode ModR/M SIB Displacemen Immediate t  Prefix には4つのグループがある  4つのグループから1つずつ追加することができる  1つの Prefix につき1 byte 22
  • 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 セグメント・オーバーライド 23
  • 24. Prefix(3/3) ■Prefix(3/3)  グループ 3 ➔ 0x66 オペランド・サイズ・オーバーライド・プリフィックス ➔ オペランドを 16bit 又は 32bit に切り替えられる  グループ 4 ➔ 0x67 アドレス・サイズ・オーバーライド・プリフィックス ➔ アドレスを 16bit 又は 32bit に切り替えられる 24
  • 25. Opecode ■Opecode Prefix Opecode ModR/M SIB Displacemen Immediate t  1〜3 byte で表される命令の番号  ModR/M のうちの 3bit が拡張オペコードフィールドとして扱わ れる場合がある 25
  • 26. ModR/M(1/2) ■ModR/M(1/2) Prefix Opecode ModR/M SIB Displacemen Immediate t  Mod フィールド、 Register/Opecode フィールド、 R/M フィールドに分かれている  Register/Opecode は命令によってレジスタのインデックスに なったり、拡張オペコードフィールドになる 26
  • 27. ModR/M(2/2) ■ModR/M(2/2) Mod Register/Opecode R/M  Mod が 2bit 、 Register/Opecode が 3bit 、 R/M が 3bit  Mod と R/M を合わせて使い、 5bit ぶん 32 通りの表現が可能  32 通りのなか、8個のレジスタと24通りのアドレスの指定方 法がある 27
  • 28. SIB(1/2) ■SIB(1/2) Prefix Opecode ModR/M SIB Displacemen Immediate t  Scale Index Base の略  ModR/M でアドレスを指定する場合、2つのレジスタの足し 算、またはレジスタと値の足し算のみだった  しかし、 SIB を使うと [EAX * 8 + 32 + EBP] といった掛け算 を混ぜたり、オペランドを増やすことができる 28
  • 29. SIB(2/2) ■SIB(2/2) Scale Index Base  2の Scale 乗 × Base + Index(8 * EAX + ECX)  ModR/M の Mod によってさらに値を足すことができる 29
  • 30. Displacement と Immediate ■Displacement と Immediate Prefix Opecode ModR/M SIB Displaceme Immediate nt  Displacement は1、2、4 byte の値。 ModR/M や SIB のア ドレスの計算に数値を入れたいに使う  Immediate は命令によって、レジスタやメモリではなく、直接 値を指定する場合に使う (ADD EAX, 1) 30
  • 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); } 31
  • 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); ... } } 32
  • 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); } } 33
  • 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); } 34
  • 35. ここからが本番 ■OS をブートしたい  ブートセクタを 0x7C00 に置く  フロッピーブートだと先頭 512byte  CD だと少し大変 ➔ El Torito に従ってブートセクタを読み込む ➔ Live CD 版のブートを目指してるのでこちらの対応必須 35
  • 36. BIOS ファンクション ■BIOS ファンクション  エミュレータでブートローダを実行してると、でてくる ➔ メモリ上の 0xCD の次の byte とレジスタの内容で使われる機 能が変化 - 文字表示やグラフィックモードの確認・設定 - ディスクの確認・設定、読み込み 36
  • 37. INT 0x10 ■INT 0x10  グラフィック関係の機能が集まっている  AH が 0x0E のとき文字表示、 AH=0x00 でグラフィックモード設 定  ただし、通常のグラフィックモード設定より VESA が使えるかど うかを調べることが普通だと思うので、 VESA が使えるかどう か調べる AX = 0x4F03 と設定の AX = 0x4F02 37
  • 38. INT 0x13 ■INT 0x13  ディスクの読み書き、設定など  AH が 0x02 で読み込み、 0x03 で書き込み  ただし、拡張 INT 0x13 というものがあり、大抵のブートローダ は機能の有無をチェックするため、対応する必要あり  AH が 0x41 で存在のチェック、 0x42 で読み込み、 0x43 で書き 込み 38
  • 39. あとは命令をひたすら実装 ■あとは命令をひたすら実装 1 まず実行 2 実装してない命令が来るまで繰り返す 3 実装してない命令が来たら Intel のマニュアルを見て実装 4 2 へ戻る  これの繰り返しでうまくいったらいいなぁ 39
  • 40. 現状 ■現状 boot: Could not find   kernel image 40