blob: 18919b4da497eb229926f79f68df897fb584ce20 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string.h>
#include <hypervisor/decode.h>
#include <zircon/syscalls/hypervisor.h>
#include <zircon/syscalls/port.h>
static const uint8_t kRexRMask = 1u << 2;
static const uint8_t kRexWMask = 1u << 3;
static const uint8_t kModRMRegMask = 0b00111000;
static bool is_h66_prefix(uint8_t prefix) {
return prefix == 0x66;
}
static bool is_rex_prefix(uint8_t prefix) {
return (prefix >> 4) == 0b0100;
}
static bool has_sib_byte(uint8_t mod_rm) {
return (mod_rm >> 6) != 0b11 && (mod_rm & 0b111) == 0b100;
}
static uint8_t displacement_size(uint8_t mod_rm) {
switch (mod_rm >> 6) {
case 0b01:
return 1;
case 0b10:
return 4;
default:
return (mod_rm & ~kModRMRegMask) == 0b00000101 ? 4 : 0;
}
}
static uint8_t mem_size(bool h66, bool rex_w) {
if (rex_w) {
return 8;
} else if (!h66) {
return 4;
} else {
return 2;
}
}
static uint8_t register_id(uint8_t mod_rm, bool rex_r) {
return static_cast<uint8_t>(((mod_rm >> 3) & 0b111) + (rex_r ? 0b1000 : 0));
}
static uint64_t* select_register(zx_vcpu_state_t* vcpu_state, uint8_t register_id) {
// From Intel Volume 2, Section 2.1.
switch (register_id) {
// From Intel Volume 2, Section 2.1.5.
case 0:
return &vcpu_state->rax;
case 1:
return &vcpu_state->rcx;
case 2:
return &vcpu_state->rdx;
case 3:
return &vcpu_state->rbx;
case 4:
return &vcpu_state->rsp;
case 5:
return &vcpu_state->rbp;
case 6:
return &vcpu_state->rsi;
case 7:
return &vcpu_state->rdi;
case 8:
return &vcpu_state->r8;
case 9:
return &vcpu_state->r9;
case 10:
return &vcpu_state->r10;
case 11:
return &vcpu_state->r11;
case 12:
return &vcpu_state->r12;
case 13:
return &vcpu_state->r13;
case 14:
return &vcpu_state->r14;
case 15:
return &vcpu_state->r15;
default:
return NULL;
}
}
zx_status_t deconstruct_instruction(const uint8_t* inst_buf, uint32_t inst_len,
uint16_t* opcode, uint8_t* mod_rm) {
if (inst_len == 0)
return ZX_ERR_NOT_SUPPORTED;
switch (inst_buf[0]) {
case 0x0f:
if (inst_len < 3)
return ZX_ERR_NOT_SUPPORTED;
*opcode = *(uint16_t*)inst_buf;
*mod_rm = inst_buf[2];
break;
default:
if (inst_len < 2)
return ZX_ERR_OUT_OF_RANGE;
*opcode = inst_buf[0];
*mod_rm = inst_buf[1];
break;
}
return ZX_OK;
}
zx_status_t inst_decode(const uint8_t* inst_buf, uint32_t inst_len, zx_vcpu_state_t* vcpu_state,
instruction_t* inst) {
if (inst_len == 0)
return ZX_ERR_BAD_STATE;
if (inst_len > X86_MAX_INST_LEN)
return ZX_ERR_OUT_OF_RANGE;
// Parse 66H prefix.
bool h66 = is_h66_prefix(inst_buf[0]);
if (h66) {
if (inst_len == 1)
return ZX_ERR_BAD_STATE;
inst_buf++;
inst_len--;
}
// Parse REX prefix.
//
// From Intel Volume 2, Appendix 2.2.1: Only one REX prefix is allowed per
// instruction. If used, the REX prefix byte must immediately precede the
// opcode byte or the escape opcode byte (0FH).
bool rex_r = false;
bool rex_w = false;
if (is_rex_prefix(inst_buf[0])) {
rex_r = inst_buf[0] & kRexRMask;
rex_w = inst_buf[0] & kRexWMask;
inst_buf++;
inst_len--;
}
// Technically this is valid, but no sane compiler should emit it.
if (h66 && rex_w)
return ZX_ERR_NOT_SUPPORTED;
uint16_t opcode;
uint8_t mod_rm;
zx_status_t status = deconstruct_instruction(inst_buf, inst_len, &opcode, &mod_rm);
if (status != ZX_OK)
return status;
if (has_sib_byte(mod_rm))
return ZX_ERR_NOT_SUPPORTED;
const uint8_t disp_size = displacement_size(mod_rm);
switch (opcode) {
// Move r to r/m.
case 0x89:
if (inst_len != disp_size + 2u)
return ZX_ERR_OUT_OF_RANGE;
inst->type = INST_MOV_WRITE;
inst->mem = mem_size(h66, rex_w);
inst->imm = 0;
inst->reg = select_register(vcpu_state, register_id(mod_rm, rex_r));
inst->flags = NULL;
return inst->reg == NULL ? ZX_ERR_NOT_SUPPORTED : ZX_OK;
// Move r/m to r.
case 0x8b:
if (inst_len != disp_size + 2u)
return ZX_ERR_OUT_OF_RANGE;
inst->type = INST_MOV_READ;
inst->mem = mem_size(h66, rex_w);
inst->imm = 0;
inst->reg = select_register(vcpu_state, register_id(mod_rm, rex_r));
inst->flags = NULL;
return inst->reg == NULL ? ZX_ERR_NOT_SUPPORTED : ZX_OK;
// Move imm to r/m.
case 0xc7: {
const uint8_t imm_size = h66 ? 2 : 4;
if (inst_len != disp_size + imm_size + 2u)
return ZX_ERR_OUT_OF_RANGE;
if ((mod_rm & kModRMRegMask) != 0)
return ZX_ERR_INVALID_ARGS;
inst->type = INST_MOV_WRITE;
inst->mem = mem_size(h66, rex_w);
inst->imm = 0;
inst->reg = NULL;
inst->flags = NULL;
memcpy(&inst->imm, inst_buf + disp_size + 2, imm_size);
return ZX_OK;
}
// Move (8-bit) with zero-extend r/m to r.
case 0xb60f:
if (h66)
return ZX_ERR_BAD_STATE;
if (inst_len != disp_size + 3u)
return ZX_ERR_OUT_OF_RANGE;
inst->type = INST_MOV_READ;
inst->mem = 1;
inst->imm = 0;
inst->reg = select_register(vcpu_state, register_id(mod_rm, rex_r));
inst->flags = NULL;
return inst->reg == NULL ? ZX_ERR_NOT_SUPPORTED : ZX_OK;
// Move (16-bit) with zero-extend r/m to r.
case 0xb70f:
if (h66)
return ZX_ERR_BAD_STATE;
if (inst_len != disp_size + 3u)
return ZX_ERR_OUT_OF_RANGE;
inst->type = INST_MOV_READ;
inst->mem = 2;
inst->imm = 0;
inst->reg = select_register(vcpu_state, register_id(mod_rm, rex_r));
inst->flags = NULL;
return inst->reg == NULL ? ZX_ERR_NOT_SUPPORTED : ZX_OK;
// Logical compare (8-bit) imm with r/m.
case 0xf6:
if (h66)
return ZX_ERR_BAD_STATE;
if (inst_len != disp_size + 3u)
return ZX_ERR_OUT_OF_RANGE;
if ((mod_rm & kModRMRegMask) != 0)
return ZX_ERR_INVALID_ARGS;
inst->type = INST_TEST;
inst->mem = 1;
inst->imm = 0;
inst->reg = NULL;
inst->flags = &vcpu_state->flags;
memcpy(&inst->imm, inst_buf + disp_size + 2, 1);
return ZX_OK;
default:
return ZX_ERR_NOT_SUPPORTED;
}
}