blob: 27c049acb1d1d2a17b069e48e665fbd638dd9b47 [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 "src/virtualization/bin/vmm/arch/x64/decode.h"
#include <string.h>
#include <zircon/syscalls/hypervisor.h>
#include <zircon/syscalls/port.h>
static constexpr uint8_t kRexRMask = 1u << 2;
static constexpr uint8_t kRexWMask = 1u << 3;
static constexpr uint8_t kModRMRegMask = 0b00111000;
// The Operand Size (w) Bit.
static constexpr uint8_t kWMask = 1u;
static constexpr uint8_t kSibBaseMask = 0b00000111;
static constexpr uint8_t kSibBaseNone = 0b101;
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, uint8_t sib) {
switch (mod_rm >> 6) {
case 0b00:
if (has_sib_byte(mod_rm) && (sib & kSibBaseMask) == kSibBaseNone) {
return 4;
} else {
return 0;
}
case 0b01:
return 1;
case 0b10:
return 4;
default:
return (mod_rm & ~kModRMRegMask) == 0b00000101 ? 4 : 0;
}
}
static uint8_t operand_size(bool h66, bool rex_w, bool w, uint8_t default_operand_size) {
if (!w) {
return 1;
} else if (rex_w) {
return 8;
}
if (!h66) {
return default_operand_size;
} else {
return default_operand_size == 2 ? 4 : 2;
}
}
static uint8_t immediate_size(bool h66, bool w, uint8_t default_operand_size) {
if (!w) {
return 1;
} else if (!h66) {
return default_operand_size;
} else {
return default_operand_size == 2 ? 4 : 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));
}
// From Intel Volume 2, Appendix B.1.4.1
//
// Registers 4-7 (typically referring to SP,BP,SI,DI) instead refer to the
// high byte registers (AH,CH,DH,BH) when using 1 byte registers and no rex
// prefix is provided.
static inline bool is_high_byte(uint8_t size, bool rex) { return size == 1 && !rex; }
static uint64_t* select_register(zx_vcpu_state_t* vcpu_state, uint8_t register_id, uint8_t size,
bool rex) {
// 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:
if (is_high_byte(size, rex)) {
return nullptr;
}
return &vcpu_state->rsp;
case 5:
if (is_high_byte(size, rex)) {
return nullptr;
}
return &vcpu_state->rbp;
case 6:
if (is_high_byte(size, rex)) {
return nullptr;
}
return &vcpu_state->rsi;
case 7:
if (is_high_byte(size, rex)) {
return nullptr;
}
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;
}
}
static zx_status_t deconstruct_instruction(const uint8_t* inst_buf, uint32_t inst_len,
uint16_t* opcode, uint8_t* mod_rm, uint8_t* sib) {
if (inst_len == 0) {
return ZX_ERR_NOT_SUPPORTED;
}
switch (inst_buf[0]) {
case 0x0f:
if (inst_len < 3) {
return ZX_ERR_NOT_SUPPORTED;
}
// Use memcpy instead of casting, otherwise we may cause an unaligned
// access, resulting in undefined behaviour.
memcpy(opcode, inst_buf, sizeof(uint16_t));
*mod_rm = inst_buf[2];
if (!has_sib_byte(*mod_rm)) {
*sib = 0;
} else if (inst_len < 4) {
return ZX_ERR_NOT_SUPPORTED;
} else {
*sib = inst_buf[3];
}
break;
default:
if (inst_len < 2) {
return ZX_ERR_OUT_OF_RANGE;
}
*opcode = inst_buf[0];
*mod_rm = inst_buf[1];
if (!has_sib_byte(*mod_rm)) {
*sib = 0;
} else if (inst_len < 3) {
return ZX_ERR_NOT_SUPPORTED;
} else {
*sib = inst_buf[2];
}
break;
}
return ZX_OK;
}
// Decode an instruction used in a memory access to determine the register used
// as a source or destination. There's no need to decode memory operands because
// the faulting address is already known.
zx_status_t inst_decode(const uint8_t* inst_buf, uint32_t inst_len, uint8_t default_operand_size,
zx_vcpu_state_t* vcpu_state, Instruction* inst) {
if (inst_len == 0) {
return ZX_ERR_BAD_STATE;
}
if (inst_len > X86_MAX_INST_LEN) {
return ZX_ERR_OUT_OF_RANGE;
}
if (default_operand_size != 2 && default_operand_size != 4) {
return ZX_ERR_NOT_SUPPORTED;
}
if (vcpu_state == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
// 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 = false;
bool rex_r = false;
bool rex_w = false;
if (is_rex_prefix(inst_buf[0])) {
rex = true;
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;
uint8_t sib;
zx_status_t status = deconstruct_instruction(inst_buf, inst_len, &opcode, &mod_rm, &sib);
if (status != ZX_OK) {
return status;
}
const uint8_t sib_size = has_sib_byte(mod_rm) ? 1 : 0;
const uint8_t disp_size = displacement_size(mod_rm, sib);
switch (opcode) {
// Move r to r/m.
// 1000 100w : mod reg r/m
case 0x88:
case 0x89: {
if (inst_len != sib_size + disp_size + 2u) {
return ZX_ERR_OUT_OF_RANGE;
}
const bool w = opcode & kWMask;
inst->type = INST_MOV_WRITE;
inst->access_size = operand_size(h66, rex_w, w, default_operand_size);
inst->imm = 0;
inst->reg = select_register(vcpu_state, register_id(mod_rm, rex_r), inst->access_size, rex);
inst->flags = NULL;
return inst->reg == NULL ? ZX_ERR_NOT_SUPPORTED : ZX_OK;
}
// Move r/m to r.
// 1000 101w : mod reg r/m
case 0x8a:
case 0x8b: {
if (inst_len != sib_size + disp_size + 2u) {
return ZX_ERR_OUT_OF_RANGE;
}
const bool w = opcode & kWMask;
inst->type = INST_MOV_READ;
inst->access_size = operand_size(h66, rex_w, w, default_operand_size);
inst->imm = 0;
inst->reg = select_register(vcpu_state, register_id(mod_rm, rex_r), inst->access_size, rex);
inst->flags = NULL;
return inst->reg == NULL ? ZX_ERR_NOT_SUPPORTED : ZX_OK;
}
// Move imm to r/m.
// 1100 011w : mod 000 r/m : immediate data
case 0xc6:
case 0xc7: {
const bool w = opcode & kWMask;
const uint8_t imm_size = immediate_size(h66, w, default_operand_size);
if (inst_len != sib_size + 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->access_size = operand_size(h66, rex_w, w, default_operand_size);
inst->imm = 0;
inst->reg = NULL;
inst->flags = NULL;
memcpy(&inst->imm, inst_buf + sib_size + disp_size + 2, imm_size);
return ZX_OK;
}
// Move (16-bit) with zero-extend r/m to r.
case 0xb70f:
if (h66) {
return ZX_ERR_BAD_STATE;
}
// Move (8-bit) with zero-extend r/m to r.
case 0xb60f: {
if (inst_len != sib_size + disp_size + 3u) {
return ZX_ERR_OUT_OF_RANGE;
}
const bool w = opcode & (kWMask << 8);
// We'll be operating with different sized operands due to the zero-
// extend. The 'w' bit determines if we're reading 8 or 16 bits out of
// memory while the h66/rex_w bits and default operand size are used to
// select the destination register size.
const uint8_t access_size = w ? 2 : 1;
const uint8_t reg_size = operand_size(h66, rex_w, true, default_operand_size);
inst->type = INST_MOV_READ;
inst->access_size = access_size;
inst->imm = 0;
inst->reg = select_register(vcpu_state, register_id(mod_rm, rex_r), reg_size, rex);
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 != sib_size + disp_size + 3u) {
return ZX_ERR_OUT_OF_RANGE;
}
if ((mod_rm & kModRMRegMask) != 0) {
return ZX_ERR_INVALID_ARGS;
}
inst->type = INST_TEST;
inst->access_size = 1;
inst->imm = 0;
inst->reg = NULL;
inst->flags = &vcpu_state->rflags;
memcpy(&inst->imm, inst_buf + sib_size + disp_size + 2, 1);
return ZX_OK;
default:
return ZX_ERR_NOT_SUPPORTED;
}
}