RustでBinary Translation型エミュレータを作っている。Binary Translation型エミュレータの特徴は、ゲストのバイナリ形式(RISC-Vなど)をホストのバイナリ形式(x86など)に実行時に変換するタイプのエミュレータである。今回はホストとしてはx86、ゲストとしてはRISC-Vを使用しており、RISC-Vの命令を等価なx86の命令に変換して実行することで、インタプリタ型のエミュレータ型と異なり高速実行を可能としている。
算術演算命令や分岐命令は簡単な実装が完了したので、次はメモリアクセス命令を実装してみる。まずはロード命令だ。ロード命令は基本的な流れとして、
- 汎用レジスタからベースとなるアドレスを取得する。
- ベースとなるアドレスに対して命令に付属されるオフセット値を加算しアドレスとする。
- 汎用レジスタからストアするデータを取得する。
- データを計算したアドレスに対してストアする。
私の実装したBinary Translationコンパイラの出力結果を見ながら、どのようにストア命令を実行しているのかをまとめる。この考え方はQEMUでも同じような方式で動いている。
まず、ゲストマシンはRISC-Vなので、以下のようなロード命令を考える。
SD rs2,imm(rs1) // 64ビットストア命令 SW rs2,imm(rs1) // 32ビットストア命令 SH rs2,imm(rs1) // 16ビットストア命令 SB rs2,imm(rs1) // 8ビットストア命令
それぞれの命令がどのようにx86にコンパイルされるのかを見ていこう。
SD x11,0x10(x10) // 64ビットストア命令 0x4001c961b8: 48 8b 85 50 00 00 00 movq 0x50(%rbp), %rax 0x4001c961bf: 48 01 d0 addq %rdx, %rax 0x4001c961c2: 48 8b 8d 58 00 00 00 movq 0x58(%rbp), %rcx 0x4001c961c9: 48 89 88 10 00 00 00 movq %rcx, 0x10(%rax)
最初のmovq
が汎用レジスタからの値の取得に相当する。%rbp
に対して0x50バイト加算した場所がx10
の場所であり、そこから値を取得して%rax
に格納する。次のaddq
は、汎用レジスタの値に対してRISC-Vのバイナリが格納されているメモリ領域を加算し、物理メモリ上でロードすべき場所を計算する。
x86命令における3行目のmovq
によりストアするデータを%rcx
にロードする。ここではx11なのでオフセットが0x58に設定されている。ロードした%rcx
の値を次のmovq
で実際にメモリにストアする。今回はx10
+ 0x10なので、movq %rcx, 0x10(%rax)
となっている。
同じように、すべてのサイズに対してストア命令をコーディングする。
sw x12, 24(x10) 0x4001c961d0: 48 8b 85 50 00 00 00 movq 0x50(%rbp), %rax 0x4001c961d7: 48 01 d0 addq %rdx, %rax 0x4001c961da: 48 8b 8d 60 00 00 00 movq 0x60(%rbp), %rcx 0x4001c961e1: 89 88 18 00 00 00 movl %ecx, 0x18(%rax) sh x13, 34(x10) 0x4001c961fe: 48 8b 85 50 00 00 00 movq 0x50(%rbp), %rax 0x4001c96205: 48 01 d0 addq %rdx, %rax 0x4001c96208: 48 8b 8d 68 00 00 00 movq 0x68(%rbp), %rcx 0x4001c9620f: 66 89 88 22 00 00 00 movw %cx, 0x22(%rax) sb x14, 41(x10) 0x4001c9622e: 48 8b 85 50 00 00 00 movq 0x50(%rbp), %rax 0x4001c96235: 48 01 d0 addq %rdx, %rax 0x4001c96238: 48 8b 8d 70 00 00 00 movq 0x70(%rbp), %rcx 0x4001c9623f: 88 88 29 00 00 00 movb %cl, 0x29(%rax)
以下のようなサンプルプログラムをコンパイルし、Binary Translation型エミュレータで実行した結果を示すと以下のようになった。
.section .text _start: lui x10, %hi(data_region) addi x10, x10, %lo(data_region) ld x11, 0(x10) lw x12, 4(x10) lh x13, 6(x10) lb x14, 7(x10) lw x16, 0(x10) lh x17, 0(x10) lb x18, 0(x10) lwu x20, 4(x10) lhu x21, 6(x10) lbu x22, 7(x10) lwu x20, 0(x10) lhu x21, 0(x10) lbu x22, 0(x10) sd zero, 16(x10) sd zero, 24(x10) sd zero, 32(x10) sd zero, 40(x10) sd x11, 16(x10) sw x12, 24(x10) sw x12, 28(x10) sh x13, 34(x10) sh x13, 38(x10) sb x14, 41(x10) sb x14, 43(x10) sb x14, 45(x10) sb x14, 47(x10) ld x25, 16(x10) ld x26, 24(x10) ld x27, 32(x10) ld x28, 40(x10) ret # .section .data data_region: .word 0xdeadbeef .word 0x01234567 .rept 100 nop .endr
x00 = 0000000000000000 x01 = 0000000000000000 x02 = 0000000000000000 x03 = 0000000000000000 x04 = 0000000000000000 x05 = 0000000000000000 x06 = 0000000000000000 x07 = 0000000000000000 x08 = 0000000000000000 x09 = 0000000000000000 x10 = 0000000000000084 x11 = 01234567deadbeef x12 = 0000000001234567 x13 = 0000000000000123 x14 = 0000000000000001 x15 = 0000000000000000 x16 = ffffffffdeadbeef x17 = ffffffffffffbeef x18 = ffffffffffffffef x19 = 0000000000000000 x20 = 00000000deadbeef x21 = 000000000000beef x22 = 00000000000000ef x23 = 0000000000000000 x24 = 0000000000000000 x25 = 01234567deadbeef x26 = 0123456701234567 x27 = 0123000001230000 x28 = 0100010001000100 x29 = 0000000000000000 x30 = 0000000000000000 x31 = 0000000000000000
想定通りの結果だ。所望のメモリアドレスにストアされた値を、ロード命令でロードできていることを確認した。