Binary Translation型エミュレータにおいてシフト命令を実装した。Binary Translation型エミュレータでは、ゲストマシンの命令(今回はRISC-V)からホストマシンの命令(今回はx86)に直接変換することで高速実行を可能にするシミュレータだ。
現在実装しているのはシフト命令だ。RISC-Vで取り扱う必要のあるシフト命令は以下の6種類だ。
SLL
: 左論理シフト命令(シフト量はレジスタ指定)SRL
: 右論理シフト命令(シフト量はレジスタ指定)SRA
: 右算術シフト命令(シフト量はレジスタ指定)SLLI
: 左論理シフト命令(シフト量は即値指定)SRLI
: 右論理シフト命令(シフト量は即値指定)SRAI
: 右算術シフト命令(シフト量は即値指定)
x86のシフト命令について調査
そしてこれらをx86にどのように変換すればよいかをチェックするために以下のようなC言語のプログラムを用意した。
uint64_t sll_64(uint64_t a, uint64_t b) { return a << b; } uint64_t srl_64(uint64_t a, uint64_t b) { return a >> b; } int64_t sra_64(int64_t a, uint64_t b) { return a >> b; }
0000000000000068 <sll_64>: ... 7e: 89 d1 mov %edx,%ecx 80: 48 d3 e0 shl %cl,%rax 0000000000000085 <srl_64>: ... 9b: 89 d1 mov %edx,%ecx 9d: 48 d3 e8 shr %cl,%rax 00000000000000a2 <sra_64>: ... b8: 89 d1 mov %edx,%ecx ba: 48 d3 f8 sar %cl,%rax
シフト命令はそれぞれSHL
, SHR
, SAR
命令が定義されている。そしてシフト量はCL
つまりECX
の下位8ビットで指定するようだ。一応これらの命令がどのような構成になっているのか前回導入したCapstoneで確認してみよう。
cstool -d x64 "48 d3 e0"
0 48 d3 e0 shl rax, cl Prefix:0x00 0x00 0x00 0x00 Opcode:0xd3 0x00 0x00 0x00 rex: 0x48 addr_size: 8 modrm: 0xe0 disp: 0x0 sib: 0x0 op_count: 2 operands[0].type: REG = rax operands[0].size: 8 operands[0].access: READ | WRITE operands[1].type: REG = cl operands[1].size: 1 operands[1].access: READ Registers read: cl rax Registers modified: rflags rax EFLAGS: MOD_CF MOD_SF MOD_ZF MOD_PF MOD_OF UNDEF_AF
なるほど。構成としては、まずはREXを64ビットモードに設定して64ビット演算をするように設定して、Opcodeは0xd3を設定する。0xd3はシフトとかローテートとかが定義されているものだ。
Mod/RMにおいて、regフィールドを4/5/6に設定することで算術・論理・右左シフトを設定できるようになる。このルールに従って命令を生成すればよい。
一方で即値指定の場合はどうなるのか。
uint64_t slli_64(uint64_t a) { return a << 17; } uint64_t srli_64(uint64_t a) { return a >> 17; } int64_t srai_64(int64_t a) { return a >> 17; }
00000000000000bf <slli_64>: ... cb: 48 c1 e0 11 shl $0x11,%rax 00000000000000d1 <srli_64>: ... dd: 48 c1 e8 11 shr $0x11,%rax 00000000000000e3 <srai_64>: ... ef: 48 c1 f8 11 sar $0x11,%rax
今度は0xd3ではなく0xc1を使用しているようだ。こちらも同じようなルールで生成できるらしい。
これらのルールを使ってx86命令を生成するようにコードを組み上げた。
テストの実行と確認
という訳で上記の6種類のRISC-Vシフト命令を実装したので確認する。以下のようなテストパタンを作成した。
_start: addi x1, x0, 11 lui x10, 0xdeadb addi x10, x10, 0x3ef srl x15, x10, x1 sll x16, x10, x1 sra x17, x10, x1 srli x18, x10, 11 slli x19, x10, 11 srai x20, x10, 11 slli x21, x10, 32 srai x22, x21, 32 srli x23, x21, 32 ret
レジスタ指定と即値指定の両方のシフト命令を確認するほか。32ビットのデータを32ビット左シフトして最上位ビットを1に設定し、もう一度右シフトすることで、算術シフトと論理シフトで結果が異なるの確認する。実行後のレジスタをダンプすると以下のようになった。
x00 = 0000000000000000 x01 = 000000000000000b x02 = 0000000000000000 x03 = 0000000000000000 x04 = 0000000000000000 x05 = 0000000000000000 x06 = 0000000000000000 x07 = 0000000000000000 x08 = 0000000000000000 x09 = 0000000000000000 x10 = 00000000deadb3ef x11 = 0000000000000000 x12 = 0000000000000000 x13 = 0000000000000000 x14 = 0000000000000000 x15 = 00000000001bd5b6 x16 = 000006f56d9f7800 x17 = 00000000001bd5b6 x18 = 00000000001bd5b6 x19 = 000006f56d9f7800 x20 = 00000000001bd5b6 x21 = deadb3ef00000000 x22 = ffffffffdeadb3ef x23 = 00000000deadb3ef x24 = 0000000000000000 x25 = 0000000000000000 x26 = 0000000000000000 x27 = 0000000000000000 x28 = 0000000000000000 x29 = 0000000000000000 x30 = 0000000000000000 x31 = 0000000000000000
想定通りの結果が確認できた。問題無いようだ。