From owner-svn-src-all@FreeBSD.ORG Tue Jul 15 17:37:19 2014 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [8.8.178.115]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 318D2E0B; Tue, 15 Jul 2014 17:37:19 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 1D028203B; Tue, 15 Jul 2014 17:37:19 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.8/8.14.8) with ESMTP id s6FHbJgE000907; Tue, 15 Jul 2014 17:37:19 GMT (envelope-from neel@svn.freebsd.org) Received: (from neel@localhost) by svn.freebsd.org (8.14.8/8.14.8/Submit) id s6FHbI5D000902; Tue, 15 Jul 2014 17:37:18 GMT (envelope-from neel@svn.freebsd.org) Message-Id: <201407151737.s6FHbI5D000902@svn.freebsd.org> From: Neel Natu Date: Tue, 15 Jul 2014 17:37:18 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r268701 - in head/sys/amd64: include vmm vmm/intel X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 15 Jul 2014 17:37:19 -0000 Author: neel Date: Tue Jul 15 17:37:17 2014 New Revision: 268701 URL: http://svnweb.freebsd.org/changeset/base/268701 Log: Add support for operand size and address size override prefixes in bhyve's instruction emulation [1]. Fix bug in emulation of opcode 0x8A where the destination is a legacy high byte register and the guest vcpu is in 32-bit mode. Prior to this change instead of modifying %ah, %bh, %ch or %dh the emulation would end up modifying %spl, %bpl, %sil or %dil instead. Add support for moffsets by treating it as a 2, 4 or 8 byte immediate value during instruction decoding. Fix bug in verify_gla() where the linear address computed after decoding the instruction was not being truncated to the effective address size [2]. Tested by: Leon Dang [1] Reported by: Peter Grehan [2] Sponsored by: Nahanni Systems Modified: head/sys/amd64/include/vmm.h head/sys/amd64/include/vmm_instruction_emul.h head/sys/amd64/vmm/intel/vmx.c head/sys/amd64/vmm/vmm.c head/sys/amd64/vmm/vmm_instruction_emul.c Modified: head/sys/amd64/include/vmm.h ============================================================================== --- head/sys/amd64/include/vmm.h Tue Jul 15 17:26:43 2014 (r268700) +++ head/sys/amd64/include/vmm.h Tue Jul 15 17:37:17 2014 (r268701) @@ -322,11 +322,11 @@ struct seg_desc { uint32_t limit; uint32_t access; }; -#define SEG_DESC_TYPE(desc) ((desc)->access & 0x001f) -#define SEG_DESC_PRESENT(desc) ((desc)->access & 0x0080) -#define SEG_DESC_DEF32(desc) ((desc)->access & 0x4000) -#define SEG_DESC_GRANULARITY(desc) ((desc)->access & 0x8000) -#define SEG_DESC_UNUSABLE(desc) ((desc)->access & 0x10000) +#define SEG_DESC_TYPE(access) ((access) & 0x001f) +#define SEG_DESC_PRESENT(access) (((access) & 0x0080) ? 1 : 0) +#define SEG_DESC_DEF32(access) (((access) & 0x4000) ? 1 : 0) +#define SEG_DESC_GRANULARITY(access) (((access) & 0x8000) ? 1 : 0) +#define SEG_DESC_UNUSABLE(access) (((access) & 0x10000) ? 1 : 0) enum vm_cpu_mode { CPU_MODE_REAL, @@ -366,11 +366,14 @@ struct vie { uint8_t num_valid; /* size of the instruction */ uint8_t num_processed; + uint8_t addrsize:4, opsize:4; /* address and operand sizes */ uint8_t rex_w:1, /* REX prefix */ rex_r:1, rex_x:1, rex_b:1, - rex_present:1; + rex_present:1, + opsize_override:1, /* Operand size override */ + addrsize_override:1; /* Address size override */ uint8_t mod:2, /* ModRM byte */ reg:4, @@ -450,6 +453,7 @@ struct vm_exit { struct { uint64_t gpa; uint64_t gla; + int cs_d; /* CS.D */ struct vm_guest_paging paging; struct vie vie; } inst_emul; Modified: head/sys/amd64/include/vmm_instruction_emul.h ============================================================================== --- head/sys/amd64/include/vmm_instruction_emul.h Tue Jul 15 17:26:43 2014 (r268700) +++ head/sys/amd64/include/vmm_instruction_emul.h Tue Jul 15 17:37:17 2014 (r268701) @@ -108,7 +108,7 @@ void vie_init(struct vie *vie); */ #define VIE_INVALID_GLA (1UL << 63) /* a non-canonical address */ int vmm_decode_instruction(struct vm *vm, int cpuid, uint64_t gla, - enum vm_cpu_mode cpu_mode, struct vie *vie); + enum vm_cpu_mode cpu_mode, int csd, struct vie *vie); #endif /* _KERNEL */ #endif /* _VMM_INSTRUCTION_EMUL_H_ */ Modified: head/sys/amd64/vmm/intel/vmx.c ============================================================================== --- head/sys/amd64/vmm/intel/vmx.c Tue Jul 15 17:26:43 2014 (r268700) +++ head/sys/amd64/vmm/intel/vmx.c Tue Jul 15 17:37:17 2014 (r268701) @@ -1793,10 +1793,25 @@ vmx_paging_info(struct vm_guest_paging * static void vmexit_inst_emul(struct vm_exit *vmexit, uint64_t gpa, uint64_t gla) { + struct vm_guest_paging *paging; + uint32_t csar; + + paging = &vmexit->u.inst_emul.paging; + vmexit->exitcode = VM_EXITCODE_INST_EMUL; vmexit->u.inst_emul.gpa = gpa; vmexit->u.inst_emul.gla = gla; - vmx_paging_info(&vmexit->u.inst_emul.paging); + vmx_paging_info(paging); + switch (paging->cpu_mode) { + case CPU_MODE_PROTECTED: + case CPU_MODE_COMPATIBILITY: + csar = vmcs_read(VMCS_GUEST_CS_ACCESS_RIGHTS); + vmexit->u.inst_emul.cs_d = SEG_DESC_DEF32(csar); + break; + default: + vmexit->u.inst_emul.cs_d = 0; + break; + } } static int Modified: head/sys/amd64/vmm/vmm.c ============================================================================== --- head/sys/amd64/vmm/vmm.c Tue Jul 15 17:26:43 2014 (r268700) +++ head/sys/amd64/vmm/vmm.c Tue Jul 15 17:37:17 2014 (r268701) @@ -1190,15 +1190,18 @@ vm_handle_inst_emul(struct vm *vm, int v struct vm_guest_paging *paging; mem_region_read_t mread; mem_region_write_t mwrite; - int error; + enum vm_cpu_mode cpu_mode; + int cs_d, error; vcpu = &vm->vcpu[vcpuid]; vme = &vcpu->exitinfo; gla = vme->u.inst_emul.gla; gpa = vme->u.inst_emul.gpa; + cs_d = vme->u.inst_emul.cs_d; vie = &vme->u.inst_emul.vie; paging = &vme->u.inst_emul.paging; + cpu_mode = paging->cpu_mode; vie_init(vie); @@ -1212,7 +1215,7 @@ vm_handle_inst_emul(struct vm *vm, int v else if (error != 0) panic("%s: vmm_fetch_instruction error %d", __func__, error); - if (vmm_decode_instruction(vm, vcpuid, gla, paging->cpu_mode, vie) != 0) + if (vmm_decode_instruction(vm, vcpuid, gla, cpu_mode, cs_d, vie) != 0) return (EFAULT); /* return to userland unless this is an in-kernel emulated device */ Modified: head/sys/amd64/vmm/vmm_instruction_emul.c ============================================================================== --- head/sys/amd64/vmm/vmm_instruction_emul.c Tue Jul 15 17:26:43 2014 (r268700) +++ head/sys/amd64/vmm/vmm_instruction_emul.c Tue Jul 15 17:37:17 2014 (r268701) @@ -69,8 +69,9 @@ enum { }; /* struct vie_op.op_flags */ -#define VIE_OP_F_IMM (1 << 0) /* immediate operand present */ -#define VIE_OP_F_IMM8 (1 << 1) /* 8-bit immediate operand */ +#define VIE_OP_F_IMM (1 << 0) /* 16/32-bit immediate operand */ +#define VIE_OP_F_IMM8 (1 << 1) /* 8-bit immediate operand */ +#define VIE_OP_F_MOFFSET (1 << 2) /* 16/32/64-bit immediate moffset */ static const struct vie_op two_byte_opcodes[256] = { [0xB6] = { @@ -181,18 +182,15 @@ vie_read_register(void *vm, int vcpuid, return (error); } -static int -vie_read_bytereg(void *vm, int vcpuid, struct vie *vie, uint8_t *rval) +static void +vie_calc_bytereg(struct vie *vie, enum vm_reg_name *reg, int *lhbr) { - uint64_t val; - int error, rshift; - enum vm_reg_name reg; - - rshift = 0; - reg = gpr_map[vie->reg]; + *lhbr = 0; + *reg = gpr_map[vie->reg]; /* - * 64-bit mode imposes limitations on accessing legacy byte registers. + * 64-bit mode imposes limitations on accessing legacy high byte + * registers (lhbr). * * The legacy high-byte registers cannot be addressed if the REX * prefix is present. In this case the values 4, 5, 6 and 7 of the @@ -204,17 +202,56 @@ vie_read_bytereg(void *vm, int vcpuid, s */ if (!vie->rex_present) { if (vie->reg & 0x4) { - /* - * Obtain the value of %ah by reading %rax and shifting - * right by 8 bits (same for %bh, %ch and %dh). - */ - rshift = 8; - reg = gpr_map[vie->reg & 0x3]; + *lhbr = 1; + *reg = gpr_map[vie->reg & 0x3]; } } +} +static int +vie_read_bytereg(void *vm, int vcpuid, struct vie *vie, uint8_t *rval) +{ + uint64_t val; + int error, lhbr; + enum vm_reg_name reg; + + vie_calc_bytereg(vie, ®, &lhbr); error = vm_get_register(vm, vcpuid, reg, &val); - *rval = val >> rshift; + + /* + * To obtain the value of a legacy high byte register shift the + * base register right by 8 bits (%ah = %rax >> 8). + */ + if (lhbr) + *rval = val >> 8; + else + *rval = val; + return (error); +} + +static int +vie_write_bytereg(void *vm, int vcpuid, struct vie *vie, uint8_t byte) +{ + uint64_t origval, val, mask; + int error, lhbr; + enum vm_reg_name reg; + + vie_calc_bytereg(vie, ®, &lhbr); + error = vm_get_register(vm, vcpuid, reg, &origval); + if (error == 0) { + val = byte; + mask = 0xff; + if (lhbr) { + /* + * Shift left by 8 to store 'byte' in a legacy high + * byte register. + */ + val <<= 8; + mask <<= 8; + } + val |= origval & ~mask; + error = vm_set_register(vm, vcpuid, reg, val); + } return (error); } @@ -247,17 +284,6 @@ vie_update_register(void *vm, int vcpuid return (error); } -/* - * The following simplifying assumptions are made during emulation: - * - * - guest is in 64-bit mode - * - default address size is 64-bits - * - default operand size is 32-bits - * - * - operand size override is not supported - * - * - address size override is not supported - */ static int emulate_mov(void *vm, int vcpuid, uint64_t gpa, struct vie *vie, mem_region_read_t memread, mem_region_write_t memwrite, void *arg) @@ -267,7 +293,7 @@ emulate_mov(void *vm, int vcpuid, uint64 uint8_t byte; uint64_t val; - size = 4; + size = vie->opsize; error = EINVAL; switch (vie->op.op_byte) { @@ -277,7 +303,7 @@ emulate_mov(void *vm, int vcpuid, uint64 * 88/r: mov r/m8, r8 * REX + 88/r: mov r/m8, r8 (%ah, %ch, %dh, %bh not available) */ - size = 1; + size = 1; /* override for byte operation */ error = vie_read_bytereg(vm, vcpuid, vie, &byte); if (error == 0) error = memwrite(vm, vcpuid, gpa, byte, size, arg); @@ -285,11 +311,10 @@ emulate_mov(void *vm, int vcpuid, uint64 case 0x89: /* * MOV from reg (ModRM:reg) to mem (ModRM:r/m) + * 89/r: mov r/m16, r16 * 89/r: mov r/m32, r32 * REX.W + 89/r mov r/m64, r64 */ - if (vie->rex_w) - size = 8; reg = gpr_map[vie->reg]; error = vie_read_register(vm, vcpuid, reg, &val); if (error == 0) { @@ -298,18 +323,23 @@ emulate_mov(void *vm, int vcpuid, uint64 } break; case 0x8A: + /* + * MOV byte from mem (ModRM:r/m) to reg (ModRM:reg) + * 8A/r: mov r8, r/m8 + * REX + 8A/r: mov r8, r/m8 + */ + size = 1; /* override for byte operation */ + error = memread(vm, vcpuid, gpa, &val, size, arg); + if (error == 0) + error = vie_write_bytereg(vm, vcpuid, vie, val); + break; case 0x8B: /* * MOV from mem (ModRM:r/m) to reg (ModRM:reg) - * 8A/r: mov r/m8, r8 - * REX + 8A/r: mov r/m8, r8 + * 8B/r: mov r16, r/m16 * 8B/r: mov r32, r/m32 * REX.W 8B/r: mov r64, r/m64 */ - if (vie->op.op_byte == 0x8A) - size = 1; - else if (vie->rex_w) - size = 8; error = memread(vm, vcpuid, gpa, &val, size, arg); if (error == 0) { reg = gpr_map[vie->reg]; @@ -322,23 +352,17 @@ emulate_mov(void *vm, int vcpuid, uint64 * C6/0 mov r/m8, imm8 * REX + C6/0 mov r/m8, imm8 */ - size = 1; + size = 1; /* override for byte operation */ error = memwrite(vm, vcpuid, gpa, vie->immediate, size, arg); break; case 0xC7: /* - * MOV from imm32 to mem (ModRM:r/m) + * MOV from imm16/imm32 to mem (ModRM:r/m) + * C7/0 mov r/m16, imm16 * C7/0 mov r/m32, imm32 * REX.W + C7/0 mov r/m64, imm32 (sign-extended to 64-bits) */ - val = vie->immediate; /* already sign-extended */ - - if (vie->rex_w) - size = 8; - - if (size != 8) - val &= size2mask[size]; - + val = vie->immediate & size2mask[size]; error = memwrite(vm, vcpuid, gpa, val, size, arg); break; default: @@ -348,17 +372,6 @@ emulate_mov(void *vm, int vcpuid, uint64 return (error); } -/* - * The following simplifying assumptions are made during emulation: - * - * - guest is in 64-bit mode - * - default address size is 64-bits - * - default operand size is 32-bits - * - * - operand size override is not supported - * - * - address size override is not supported - */ static int emulate_movx(void *vm, int vcpuid, uint64_t gpa, struct vie *vie, mem_region_read_t memread, mem_region_write_t memwrite, @@ -368,7 +381,7 @@ emulate_movx(void *vm, int vcpuid, uint6 enum vm_reg_name reg; uint64_t val; - size = 4; + size = vie->opsize; error = EINVAL; switch (vie->op.op_byte) { @@ -377,8 +390,9 @@ emulate_movx(void *vm, int vcpuid, uint6 * MOV and zero extend byte from mem (ModRM:r/m) to * reg (ModRM:reg). * - * 0F B6/r movzx r/m8, r32 - * REX.W + 0F B6/r movzx r/m8, r64 + * 0F B6/r movzx r16, r/m8 + * 0F B6/r movzx r32, r/m8 + * REX.W + 0F B6/r movzx r64, r/m8 */ /* get the first operand */ @@ -389,8 +403,8 @@ emulate_movx(void *vm, int vcpuid, uint6 /* get the second operand */ reg = gpr_map[vie->reg]; - if (vie->rex_w) - size = 8; + /* zero-extend byte */ + val = (uint8_t)val; /* write the result */ error = vie_update_register(vm, vcpuid, reg, val, size); @@ -400,8 +414,9 @@ emulate_movx(void *vm, int vcpuid, uint6 * MOV and sign extend byte from mem (ModRM:r/m) to * reg (ModRM:reg). * - * 0F BE/r movsx r/m8, r32 - * REX.W + 0F BE/r movsx r/m8, r64 + * 0F BE/r movsx r16, r/m8 + * 0F BE/r movsx r32, r/m8 + * REX.W + 0F BE/r movsx r64, r/m8 */ /* get the first operand */ @@ -412,9 +427,6 @@ emulate_movx(void *vm, int vcpuid, uint6 /* get the second operand */ reg = gpr_map[vie->reg]; - if (vie->rex_w) - size = 8; - /* sign extend byte */ val = (int8_t)val; @@ -435,7 +447,7 @@ emulate_and(void *vm, int vcpuid, uint64 enum vm_reg_name reg; uint64_t val1, val2; - size = 4; + size = vie->opsize; error = EINVAL; switch (vie->op.op_byte) { @@ -444,11 +456,10 @@ emulate_and(void *vm, int vcpuid, uint64 * AND reg (ModRM:reg) and mem (ModRM:r/m) and store the * result in reg. * + * 23/r and r16, r/m16 * 23/r and r32, r/m32 * REX.W + 23/r and r64, r/m64 */ - if (vie->rex_w) - size = 8; /* get the first operand */ reg = gpr_map[vie->reg]; @@ -470,8 +481,9 @@ emulate_and(void *vm, int vcpuid, uint64 * AND mem (ModRM:r/m) with immediate and store the * result in mem. * - * 81/ and r/m32, imm32 - * REX.W + 81/ and r/m64, imm32 sign-extended to 64 + * 81 /4 and r/m16, imm16 + * 81 /4 and r/m32, imm32 + * REX.W + 81 /4 and r/m64, imm32 sign-extended to 64 * * Currently, only the AND operation of the 0x81 opcode * is implemented (ModRM:reg = b100). @@ -479,9 +491,6 @@ emulate_and(void *vm, int vcpuid, uint64 if ((vie->reg & 7) != 4) break; - if (vie->rex_w) - size = 8; - /* get the first operand */ error = memread(vm, vcpuid, gpa, &val1, size, arg); if (error) @@ -507,7 +516,7 @@ emulate_or(void *vm, int vcpuid, uint64_ int error, size; uint64_t val1; - size = 4; + size = vie->opsize; error = EINVAL; switch (vie->op.op_byte) { @@ -516,8 +525,9 @@ emulate_or(void *vm, int vcpuid, uint64_ * OR mem (ModRM:r/m) with immediate and store the * result in mem. * - * 83/ OR r/m32, imm8 sign-extended to 32 - * REX.W + 83/ OR r/m64, imm8 sign-extended to 64 + * 83 /1 OR r/m16, imm8 sign-extended to 16 + * 83 /1 OR r/m32, imm8 sign-extended to 32 + * REX.W + 83/1 OR r/m64, imm8 sign-extended to 64 * * Currently, only the OR operation of the 0x83 opcode * is implemented (ModRM:reg = b001). @@ -525,9 +535,6 @@ emulate_or(void *vm, int vcpuid, uint64_ if ((vie->reg & 7) != 1) break; - if (vie->rex_w) - size = 8; - /* get the first operand */ error = memread(vm, vcpuid, gpa, &val1, size, arg); if (error) @@ -651,7 +658,7 @@ vie_calculate_gla(enum vm_cpu_mode cpu_m * then the descriptor is unusable and attempting to use * it results in a #GP(0). */ - if (SEG_DESC_UNUSABLE(desc)) + if (SEG_DESC_UNUSABLE(desc->access)) return (-1); /* @@ -660,13 +667,13 @@ vie_calculate_gla(enum vm_cpu_mode cpu_m * descriptor that is not present. If this was the case then * it would have been checked before the VM-exit. */ - KASSERT(SEG_DESC_PRESENT(desc), ("segment %d not present: %#x", - seg, desc->access)); + KASSERT(SEG_DESC_PRESENT(desc->access), + ("segment %d not present: %#x", seg, desc->access)); /* * The descriptor type must indicate a code/data segment. */ - type = SEG_DESC_TYPE(desc); + type = SEG_DESC_TYPE(desc->access); KASSERT(type >= 16 && type <= 31, ("segment %d has invalid " "descriptor type %#x", seg, type)); @@ -695,7 +702,8 @@ vie_calculate_gla(enum vm_cpu_mode cpu_m if ((type & 0xC) == 0x4) { /* expand-down data segment */ low_limit = desc->limit + 1; - high_limit = SEG_DESC_DEF32(desc) ? 0xffffffff : 0xffff; + high_limit = SEG_DESC_DEF32(desc->access) ? + 0xffffffff : 0xffff; } else { /* code segment or expand-up data segment */ low_limit = 0; @@ -1022,24 +1030,65 @@ vie_advance(struct vie *vie) } static int -decode_rex(struct vie *vie) +decode_prefixes(struct vie *vie, enum vm_cpu_mode cpu_mode, int cs_d) { uint8_t x; - if (vie_peek(vie, &x)) - return (-1); + while (1) { + if (vie_peek(vie, &x)) + return (-1); - if (x >= 0x40 && x <= 0x4F) { - vie->rex_present = 1; + if (x == 0x66) + vie->opsize_override = 1; + else if (x == 0x67) + vie->addrsize_override = 1; + else + break; + vie_advance(vie); + } + + /* + * From section 2.2.1, "REX Prefixes", Intel SDM Vol 2: + * - Only one REX prefix is allowed per instruction. + * - The REX prefix must immediately precede the opcode byte or the + * escape opcode byte. + * - If an instruction has a mandatory prefix (0x66, 0xF2 or 0xF3) + * the mandatory prefix must come before the REX prefix. + */ + if (cpu_mode == CPU_MODE_64BIT && x >= 0x40 && x <= 0x4F) { + vie->rex_present = 1; vie->rex_w = x & 0x8 ? 1 : 0; vie->rex_r = x & 0x4 ? 1 : 0; vie->rex_x = x & 0x2 ? 1 : 0; vie->rex_b = x & 0x1 ? 1 : 0; - vie_advance(vie); } + /* + * Section "Operand-Size And Address-Size Attributes", Intel SDM, Vol 1 + */ + if (cpu_mode == CPU_MODE_64BIT) { + /* + * Default address size is 64-bits and default operand size + * is 32-bits. + */ + vie->addrsize = vie->addrsize_override ? 4 : 8; + if (vie->rex_w) + vie->opsize = 8; + else if (vie->opsize_override) + vie->opsize = 2; + else + vie->opsize = 4; + } else if (cs_d) { + /* Default address and operand sizes are 32-bits */ + vie->addrsize = vie->addrsize_override ? 2 : 4; + vie->opsize = vie->opsize_override ? 2 : 4; + } else { + /* Default address and operand sizes are 16-bits */ + vie->addrsize = vie->addrsize_override ? 4 : 2; + vie->opsize = vie->opsize_override ? 4 : 2; + } return (0); } @@ -1086,6 +1135,9 @@ decode_modrm(struct vie *vie, enum vm_cp { uint8_t x; + if (cpu_mode == CPU_MODE_REAL) + return (-1); + if (vie_peek(vie, &x)) return (-1); @@ -1262,22 +1314,44 @@ decode_immediate(struct vie *vie) int i, n; uint8_t x; union { - char buf[4]; + char buf[8]; int8_t signed8; + int16_t signed16; int32_t signed32; + int64_t signed64; } u; /* Figure out immediate operand size (if any) */ - if (vie->op.op_flags & VIE_OP_F_IMM) - vie->imm_bytes = 4; - else if (vie->op.op_flags & VIE_OP_F_IMM8) + if (vie->op.op_flags & VIE_OP_F_MOFFSET) { + /* + * Section 2.2.1.4, "Direct Memory-Offset MOVs", Intel SDM: + * The memory offset size follows the address-size of the + * instruction. Although this is treated as an immediate + * value during instruction decoding it is interpreted as + * a segment offset by the instruction emulation. + */ + vie->imm_bytes = vie->addrsize; + } else if (vie->op.op_flags & VIE_OP_F_IMM) { + /* + * Section 2.2.1.5 "Immediates", Intel SDM: + * In 64-bit mode the typical size of immediate operands + * remains 32-bits. When the operand size if 64-bits, the + * processor sign-extends all immediates to 64-bits prior + * to their use. + */ + if (vie->opsize == 4 || vie->opsize == 8) + vie->imm_bytes = 4; + else + vie->imm_bytes = 2; + } else if (vie->op.op_flags & VIE_OP_F_IMM8) { vie->imm_bytes = 1; + } if ((n = vie->imm_bytes) == 0) return (0); - if (n != 1 && n != 4) - panic("decode_immediate: invalid imm_bytes %d", n); + KASSERT(n == 1 || n == 2 || n == 4 || n == 8, + ("%s: invalid number of immediate bytes: %d", __func__, n)); for (i = 0; i < n; i++) { if (vie_peek(vie, &x)) @@ -1286,11 +1360,25 @@ decode_immediate(struct vie *vie) u.buf[i] = x; vie_advance(vie); } - + + /* sign-extend the immediate value before use */ if (n == 1) - vie->immediate = u.signed8; /* sign-extended */ + vie->immediate = u.signed8; + else if (n == 2) + vie->immediate = u.signed16; + else if (n == 4) + vie->immediate = u.signed32; else - vie->immediate = u.signed32; /* sign-extended */ + vie->immediate = u.signed64; + + + if (vie->op.op_flags & VIE_OP_F_MOFFSET) { + /* + * If the immediate value is going to be interpreted as a + * segment offset then undo the sign-extension above. + */ + vie->immediate &= size2mask[n]; + } return (0); } @@ -1316,7 +1404,7 @@ static int verify_gla(struct vm *vm, int cpuid, uint64_t gla, struct vie *vie) { int error; - uint64_t base, idx; + uint64_t base, idx, gla2; /* Skip 'gla' verification */ if (gla == VIE_INVALID_GLA) @@ -1349,11 +1437,14 @@ verify_gla(struct vm *vm, int cpuid, uin } } - if (base + vie->scale * idx + vie->displacement != gla) { + /* XXX assuming that the base address of the segment is 0 */ + gla2 = base + vie->scale * idx + vie->displacement; + gla2 &= size2mask[vie->addrsize]; + if (gla != gla2) { printf("verify_gla mismatch: " "base(0x%0lx), scale(%d), index(0x%0lx), " - "disp(0x%0lx), gla(0x%0lx)\n", - base, vie->scale, idx, vie->displacement, gla); + "disp(0x%0lx), gla(0x%0lx), gla2(0x%0lx)\n", + base, vie->scale, idx, vie->displacement, gla, gla2); return (-1); } @@ -1362,13 +1453,11 @@ verify_gla(struct vm *vm, int cpuid, uin int vmm_decode_instruction(struct vm *vm, int cpuid, uint64_t gla, - enum vm_cpu_mode cpu_mode, struct vie *vie) + enum vm_cpu_mode cpu_mode, int cs_d, struct vie *vie) { - if (cpu_mode == CPU_MODE_64BIT) { - if (decode_rex(vie)) - return (-1); - } + if (decode_prefixes(vie, cpu_mode, cs_d)) + return (-1); if (decode_opcode(vie)) return (-1);