| /* |
| * Copyright 2015 Google Inc. |
| * |
| * This file is part of Akaros. |
| * |
| * Akaros is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, version 2 of the License. |
| * |
| * Akaros is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * Lesser GNU General Public License for more details. |
| * |
| * See COPYING.LESSER for details on the GNU Lesser General Public License. |
| * See COPYING for details on the GNU General Public License. |
| * |
| * |
| * A few notes: |
| * - We only emulate memory operations, so we don't need to worry about decoding |
| * the target addresses or whatnot. We could, just for sanity reasons, though |
| * the registers (via modrm/sib) are in the guest virtual address space. We |
| * operate in the guest physical space. Having the GPA from the fault makes |
| * this all easier. |
| * - Just like with fetch_insn(), since we use the GPA, we assume that the |
| * target of our memory access is also contiguous physically. The guest could |
| * have two virtual pages, one mapped to something that triggers an EPT fault |
| * and the other doesn't. The upper part of that access will go to the |
| * adjacent physical page (e.g. a virtio region), and not to the actual |
| * destination that the guest had mapped. Buyer beware. I'm less concerned |
| * about this than I am with instructions. |
| * - To emulate instructions that set rflags, like add and cmp, I just execute |
| * the instruction with inline asm and pop rflags. Let the hardware do it. |
| * - The inline asm often uses %2,%1 and not %1,%2 for the args to e.g. cmp. |
| * The good book is in Intel syntax. Code AT&T. It's easier to read the args |
| * as if they are in the book, and just switch the ASM args like that. |
| * - add and cmp (80 /0) have a rex, and the Good Book says to add a |
| * sign-extended imm8 to r/m8. Extended to what? I skipped that, and treated |
| * it like a regular imm8. The rex should apply for register selection still. |
| */ |
| |
| #include <parlib/stdio.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <parlib/arch/arch.h> |
| #include <parlib/ros_debug.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/uio.h> |
| #include <stdint.h> |
| #include <err.h> |
| #include <sys/mman.h> |
| #include <vmm/vmm.h> |
| #include <vmm/virtio.h> |
| #include <vmm/virtio_mmio.h> |
| #include <vmm/virtio_ids.h> |
| #include <vmm/virtio_config.h> |
| #include <ros/arch/mmu.h> |
| #include <ros/arch/trapframe.h> |
| |
| int debug_decode = 0; |
| #define DPRINTF(fmt, ...) \ |
| do { \ |
| if (debug_decode) { \ |
| fprintf(stderr, "decode: " fmt, ## __VA_ARGS__); \ |
| } \ |
| } \ |
| while (0) |
| |
| enum x86_register { |
| X86_RAX = 0, |
| X86_RCX = 1, |
| X86_RDX = 2, |
| X86_RBX = 3, |
| X86_RSP = 4, |
| X86_RBP = 5, |
| X86_RSI = 6, |
| X86_RDI = 7, |
| X86_R8 = 8, |
| X86_R9 = 9, |
| X86_R10 = 10, |
| X86_R11 = 11, |
| X86_R12 = 12, |
| X86_R13 = 13, |
| X86_R14 = 14, |
| X86_R15 = 15, |
| }; |
| |
| static const char * const reg_names[] = { |
| "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", |
| "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" |
| }; |
| |
| static const char *regname(uint8_t reg) |
| { |
| return reg_names[reg]; |
| } |
| |
| /* Helper: points to the reg in the VMTF */ |
| static uint64_t *reg_to_vmtf_reg(struct vm_trapframe *vm_tf, int reg) |
| { |
| switch (reg) { |
| case 0: |
| return &vm_tf->tf_rax; |
| case 1: |
| return &vm_tf->tf_rcx; |
| case 2: |
| return &vm_tf->tf_rdx; |
| case 3: |
| return &vm_tf->tf_rbx; |
| case 4: |
| return &vm_tf->tf_rsp; |
| case 5: |
| return &vm_tf->tf_rbp; |
| case 6: |
| return &vm_tf->tf_rsi; |
| case 7: |
| return &vm_tf->tf_rdi; |
| case 8: |
| return &vm_tf->tf_r8; |
| case 9: |
| return &vm_tf->tf_r9; |
| case 10: |
| return &vm_tf->tf_r10; |
| case 11: |
| return &vm_tf->tf_r11; |
| case 12: |
| return &vm_tf->tf_r12; |
| case 13: |
| return &vm_tf->tf_r13; |
| case 14: |
| return &vm_tf->tf_r14; |
| case 15: |
| return &vm_tf->tf_r15; |
| } |
| panic("Unknown reg %d\n", reg); |
| } |
| |
| struct x86_decode { |
| uint8_t prefix_sz; |
| uint8_t opcode_sz; |
| uint8_t modrm_sib_sz; |
| uint8_t imm_sz; |
| uint8_t operand_bytes; |
| uint8_t address_bytes; |
| bool has_modrm; |
| bool is_store; |
| bool rex_r; |
| bool rex_x; |
| bool rex_b; |
| }; |
| |
| /* We decode instructions in response to memory faults, so most every |
| * instruction will have modrm. */ |
| #define X86_DECODE_64_DEFAULT { \ |
| .prefix_sz = 0, \ |
| .opcode_sz = 1, \ |
| .modrm_sib_sz = 0, \ |
| .imm_sz = 0, \ |
| .operand_bytes = 4, \ |
| .address_bytes = 8, \ |
| .has_modrm = true, \ |
| .is_store = false, \ |
| .rex_r = false, \ |
| .rex_x = false, \ |
| .rex_b = false, \ |
| } |
| |
| static void print_decoded_insn(uint8_t *insn, struct x86_decode *d); |
| static void run_decode_hokey_tests(void); |
| |
| static int decode_prefix(uint8_t *insn, struct x86_decode *d) |
| { |
| uint8_t *p = insn; |
| |
| for (;; p++) { |
| /* Operand-size override prefix */ |
| if (p[0] == 0x66) { |
| /* Ignore 0x66 if REX.W changed us to 8 bytes (64). |
| * Though we should only see 0x66 before REX.W. |
| * |
| * If this was handling 32 bit code but with cs.d clear |
| * (default 16), 66 should set us to 4 bytes. */ |
| if (d->operand_bytes == 4) |
| d->operand_bytes = 2; |
| continue; |
| } |
| /* Address-size override prefix */ |
| if (p[0] == 0x67) { |
| d->address_bytes = 4; |
| continue; |
| } |
| /* REX.* */ |
| if ((p[0] & 0xf0) == 0x40) { |
| if (p[0] & 0x08) |
| d->operand_bytes = 8; |
| if (p[0] & 0x04) |
| d->rex_r = true; |
| if (p[0] & 0x02) |
| d->rex_x = true; |
| if (p[0] & 0x01) |
| d->rex_b = true; |
| continue; |
| } |
| break; |
| } |
| d->prefix_sz = p - insn; |
| return 0; |
| } |
| |
| static uint8_t *get_modrm(uint8_t *insn, struct x86_decode *d) |
| { |
| if (!d->has_modrm) |
| return NULL; |
| return insn + d->prefix_sz + d->opcode_sz; |
| } |
| |
| static uint8_t modrm_sib_bytes_16(int mod, int rm, struct x86_decode *d) |
| { |
| uint8_t ret = 1; /* counting the mod/rm byte itself */ |
| |
| switch (mod) { |
| case 0: |
| if (rm == 6) |
| ret += 2; /* disp16 */ |
| break; |
| case 1: |
| ret += 1; /* disp8 */ |
| if (rm == 4) |
| ret += 1; /* SIB */ |
| break; |
| case 2: |
| ret += 2; /* disp16 */ |
| break; |
| case 3: |
| break; |
| } |
| return ret; |
| } |
| |
| static uint8_t modrm_sib_bytes_32(int mod, int rm, struct x86_decode *d) |
| { |
| uint8_t ret = 1; /* counting the mod/rm byte itself */ |
| |
| switch (mod) { |
| case 0: |
| if (rm == 4) |
| ret += 1; /* SIB */ |
| else if (rm == 5) |
| ret += 4; /* disp32 */ |
| break; |
| case 1: |
| ret += 1; /* disp8 */ |
| if (rm == 4) |
| ret += 1; /* SIB */ |
| break; |
| case 2: |
| ret += 4; /* disp32 */ |
| if (rm == 4) /* SIB */ |
| ret += 1; |
| break; |
| case 3: |
| break; |
| } |
| return ret; |
| } |
| |
| /* Returns the number of bytes in the instruction due to things encoded by |
| * mod/rm, such as displacements (disp32) or the SIB byte, including the mod/rm |
| * byte itself. */ |
| static uint8_t modrm_sib_bytes(uint8_t *insn, struct x86_decode *d) |
| { |
| uint8_t *modrm = get_modrm(insn, d); |
| int mod, rm; |
| |
| if (!modrm) |
| return 0; |
| mod = *modrm >> 6; |
| rm = *modrm & 0x7; |
| |
| switch (d->address_bytes) { |
| case 2: |
| /* We should never see this, but was easy enough to code */ |
| fprintf(stderr, "decode: %s had %u address bytes!\n", __func__, |
| d->address_bytes); |
| return modrm_sib_bytes_16(mod, rm, d); |
| case 4: |
| case 8: |
| return modrm_sib_bytes_32(mod, rm, d); |
| default: |
| panic("decode: %s had %u address bytes!\n", __func__, |
| d->address_bytes); |
| return 0; |
| } |
| } |
| |
| static uint8_t modrm_get_reg_16(uint8_t reg, struct x86_decode *d) |
| { |
| return reg; |
| } |
| |
| static uint8_t modrm_get_reg_32(uint8_t reg, struct x86_decode *d) |
| { |
| return reg + (d->rex_r ? 8 : 0); |
| } |
| |
| static uint64_t get_imm(uint8_t *insn, struct x86_decode *d) |
| { |
| uint8_t *imm = insn + d->prefix_sz + d->opcode_sz + d->modrm_sib_sz; |
| uint64_t ret = 0; |
| |
| switch (d->imm_sz) { |
| case 8: |
| ret |= (uint64_t)imm[7] << (8 * 7); |
| ret |= (uint64_t)imm[6] << (8 * 6); |
| ret |= (uint64_t)imm[5] << (8 * 5); |
| ret |= (uint64_t)imm[4] << (8 * 4); |
| /* fall-through */ |
| case 4: |
| ret |= (uint64_t)imm[3] << (8 * 3); |
| ret |= (uint64_t)imm[2] << (8 * 2); |
| /* fall-through */ |
| case 2: |
| ret |= (uint64_t)imm[1] << (8 * 1); |
| /* fall-through */ |
| case 1: |
| ret |= (uint64_t)imm[0] << (8 * 0); |
| break; |
| default: |
| panic("Bad IMM size %u", d->imm_sz); |
| } |
| return ret; |
| } |
| |
| /* This is the three-bit (or more with REX) register used by opcodes that have |
| * /r. The first argument for opcodes is in the modrm part, e.g. [eax]+disp8. |
| * We don't need to parse that, since we know the faulting GPA. */ |
| static uint8_t modrm_get_reg(uint8_t *insn, struct x86_decode *d) |
| { |
| uint8_t *modrm = get_modrm(insn, d); |
| uint8_t reg; |
| |
| if (!modrm) { |
| fprintf(stderr, "%s called with no modrm!\n, insn: %p\n", |
| __func__, insn); |
| hexdump(stderr, insn, 15); |
| panic("Continuing could corrupt registers"); |
| } |
| reg = (*modrm >> 3) & 7; |
| |
| switch (d->address_bytes) { |
| case 2: |
| fprintf(stderr, "decode: %s had %u address bytes!\n", __func__, |
| d->address_bytes); |
| return modrm_get_reg_16(reg, d); |
| case 4: |
| case 8: |
| return modrm_get_reg_32(reg, d); |
| default: |
| panic("decode: %s had %u address bytes!\n", __func__, |
| d->address_bytes); |
| } |
| } |
| |
| /* Decodes the actual opcode, storing things we care about in d. |
| * -1 on error (for opcodes we haven't coded up), 0 success. |
| * |
| * Sets operand_bytes, various sizes, is_store, etc. */ |
| static int decode_opcode(uint8_t *insn, struct x86_decode *d) |
| { |
| uint8_t *opcode = insn + d->prefix_sz; |
| |
| /* If we don't set anything, we're using the defaults: 1 byte opcode, |
| * has_modrm, operand_bytes determined by the prefix/64-bit mode, etc */ |
| switch (opcode[0]) { |
| case 0x80: |
| switch (modrm_get_reg(insn, d)) { |
| case 0: // add |
| case 7: // cmp |
| break; |
| default: |
| goto unknown; |
| } |
| d->imm_sz = 1; |
| d->operand_bytes = 1; |
| break; |
| case 0x81: |
| switch (modrm_get_reg(insn, d)) { |
| case 0: // add |
| case 7: // cmp |
| break; |
| default: |
| goto unknown; |
| } |
| d->imm_sz = d->address_bytes == 2 ? 2 : 4; |
| break; |
| case 0x3a: // cmp /r |
| d->operand_bytes = 1; |
| break; |
| case 0x88: // mov |
| case 0x8a: // mov |
| d->operand_bytes = 1; |
| /* Instructions that could be loads or stores differ in bit 2. |
| * e.g. 88 (store, bit 2 unset) vs 8a (load, bit 2 set). */ |
| d->is_store = !(opcode[0] & 2); |
| break; |
| case 0x89: // mov |
| case 0x8b: // mov |
| d->is_store = !(opcode[0] & 2); |
| break; |
| case 0x0f: |
| d->opcode_sz = 2; |
| switch (opcode[1]) { |
| case 0xb7: // movzw |
| d->operand_bytes = 2; |
| break; |
| case 0xb6: // movzb |
| d->operand_bytes = 1; |
| break; |
| default: |
| goto unknown; |
| } |
| break; |
| default: |
| unknown: |
| fprintf(stderr, "unknown decode %02x %02x %02x@ %p\n", |
| opcode[0], opcode[1], opcode[2], opcode); |
| return -1; |
| } |
| |
| d->modrm_sib_sz = modrm_sib_bytes(insn, d); |
| return 0; |
| } |
| |
| static void set_rflags_status_bits(uint64_t *rfl, uint64_t new) |
| { |
| *rfl &= ~FL_STATUS; |
| new &= FL_STATUS; |
| *rfl |= new; |
| } |
| |
| static int add_8081(struct guest_thread *gth, uint8_t *insn, |
| struct x86_decode *d, emu_mem_access access, |
| uint64_t gpa) |
| { |
| struct vm_trapframe *vm_tf = gth_to_vmtf(gth); |
| int ret; |
| uint64_t scratch = 0; |
| int8_t imm8, scr8; |
| int16_t imm16, scr16; |
| int32_t imm32, scr32; |
| int64_t imm64, scr64; |
| unsigned long rflags; |
| |
| ret = access(gth, gpa, &scratch, d->operand_bytes, false); |
| if (ret < 0) |
| return ret; |
| switch (d->operand_bytes) { |
| case 1: |
| imm8 = get_imm(insn, d); |
| /* scratch is input and output, but you can't cast it in an |
| * output operand */ |
| scr8 = scratch; |
| asm volatile ("addb %2,%1; pushfq; popq %0" |
| : "=r"(rflags), |
| "+r"(scr8) |
| : "r"(imm8) |
| : "cc"); |
| scratch = scr8; |
| break; |
| case 2: |
| imm16 = get_imm(insn, d); |
| scr16 = scratch; |
| asm volatile ("addw %2,%1; pushfq; popq %0" |
| : "=r"(rflags), |
| "+r"(scr16) |
| : "r"(imm16) |
| : "cc"); |
| scratch = scr16; |
| break; |
| case 4: |
| imm32 = get_imm(insn, d); |
| scr32 = scratch; |
| asm volatile ("addl %2,%1; pushfq; popq %0" |
| : "=r"(rflags), |
| "+r"(scr32) |
| : "r"(imm32) |
| : "cc"); |
| scratch = scr32; |
| break; |
| case 8: |
| imm32 = get_imm(insn, d); |
| scr64 = scratch; |
| asm volatile ("addq %2,%1; pushfq; popq %0" |
| : "=r"(rflags), |
| "+r"(scr64) |
| : "r"((int64_t)imm32) |
| : "cc"); |
| scratch = scr64; |
| break; |
| } |
| set_rflags_status_bits(&vm_tf->tf_rflags, rflags); |
| return access(gth, gpa, &scratch, d->operand_bytes, true); |
| } |
| |
| static int cmp_8081(struct guest_thread *gth, uint8_t *insn, |
| struct x86_decode *d, emu_mem_access access, |
| uint64_t gpa) |
| { |
| struct vm_trapframe *vm_tf = gth_to_vmtf(gth); |
| int ret; |
| uint64_t scratch = 0; |
| int8_t imm8; |
| int16_t imm16; |
| int32_t imm32; |
| int64_t imm64; |
| unsigned long rflags; |
| |
| ret = access(gth, gpa, &scratch, d->operand_bytes, false); |
| if (ret < 0) |
| return ret; |
| switch (d->operand_bytes) { |
| case 1: |
| imm8 = get_imm(insn, d); |
| asm volatile ("cmpb %2,%1; pushfq; popq %0" |
| : "=r"(rflags) |
| : "r"((int8_t)scratch), |
| "r"(imm8) |
| : "cc"); |
| break; |
| case 2: |
| imm16 = get_imm(insn, d); |
| asm volatile ("cmpw %2,%1; pushfq; popq %0" |
| : "=r"(rflags) |
| : "r"((int16_t)scratch), |
| "r"(imm16) |
| : "cc"); |
| break; |
| case 4: |
| imm32 = get_imm(insn, d); |
| asm volatile ("cmpl %2,%1; pushfq; popq %0" |
| : "=r"(rflags) |
| : "r"((int32_t)scratch), |
| "r"(imm32) |
| : "cc"); |
| break; |
| case 8: |
| imm32 = get_imm(insn, d); |
| asm volatile ("cmpq %2,%1; pushfq; popq %0" |
| : "=r"(rflags) |
| : "r"((int64_t)scratch), |
| "r"((int64_t)imm32) |
| : "cc"); |
| break; |
| } |
| set_rflags_status_bits(&vm_tf->tf_rflags, rflags); |
| return 0; |
| } |
| |
| static int cmp_3a(struct guest_thread *gth, uint8_t *insn, |
| struct x86_decode *d, emu_mem_access access, |
| uint64_t gpa) |
| { |
| struct vm_trapframe *vm_tf = gth_to_vmtf(gth); |
| int ret; |
| uint64_t scratch = 0; |
| int8_t reg8; |
| unsigned long rflags; |
| int mod_reg = modrm_get_reg(insn, d); |
| |
| assert(d->operand_bytes == 1); |
| ret = access(gth, gpa, &scratch, 1, false); |
| if (ret < 0) |
| return ret; |
| reg8 = (int8_t)*reg_to_vmtf_reg(vm_tf, mod_reg); |
| asm volatile ("cmpb %2,%1; pushfq; popq %0" |
| : "=r"(rflags) |
| : "r"(reg8), |
| "r"((int8_t)scratch) |
| : "cc"); |
| set_rflags_status_bits(&vm_tf->tf_rflags, rflags); |
| return 0; |
| } |
| |
| static int execute_op(struct guest_thread *gth, uint8_t *insn, |
| struct x86_decode *d, emu_mem_access access, |
| uint64_t gpa) |
| { |
| struct vm_trapframe *vm_tf = gth_to_vmtf(gth); |
| uint8_t *opcode = insn + d->prefix_sz; |
| int ret, mod_reg; |
| |
| switch (opcode[0]) { |
| case 0x80: |
| case 0x81: |
| switch (modrm_get_reg(insn, d)) { |
| case 0: // add |
| return add_8081(gth, insn, d, access, gpa); |
| case 7: // cmp |
| return cmp_8081(gth, insn, d, access, gpa); |
| } |
| goto unknown; |
| case 0x3a: // cmp |
| return cmp_3a(gth, insn, d, access, gpa); |
| case 0x89: // mov |
| case 0x8b: // mov |
| case 0x8a: // mov |
| case 0x88: // mov |
| mod_reg = modrm_get_reg(insn, d); |
| ret = access(gth, gpa, reg_to_vmtf_reg(vm_tf, mod_reg), |
| d->operand_bytes, d->is_store); |
| /* We set a register's value. For 32-bit operands, we need to |
| * zero the upper 32 bits. */ |
| if (!ret && !d->is_store && d->operand_bytes == 4) |
| *reg_to_vmtf_reg(vm_tf, mod_reg) &= 0xffffffff; |
| return ret; |
| case 0x0f: |
| switch (opcode[1]) { |
| case 0xb7: // movzw |
| case 0xb6: // movzb |
| mod_reg = modrm_get_reg(insn, d); |
| *reg_to_vmtf_reg(vm_tf, mod_reg) = 0; |
| return access(gth, gpa, reg_to_vmtf_reg(vm_tf, mod_reg), |
| d->operand_bytes, d->is_store); |
| } |
| goto unknown; |
| default: |
| unknown: |
| fprintf(stderr, "unknown execute %02x %02x %02x@ %p\n", |
| opcode[0], opcode[1], opcode[2], opcode); |
| return -1; |
| } |
| } |
| |
| static int decode_inst_size(uint8_t *insn, struct x86_decode *d) |
| { |
| return d->prefix_sz + d->opcode_sz + d->modrm_sib_sz + |
| + d->imm_sz; |
| } |
| |
| /* Emulates a memory operation that faulted/vmexited. Despite the file name, |
| * this is x86-specific, so we only have at most one address involved. We have |
| * at least one address involved, since it is a memory operation. |
| * |
| * The main thing our caller provides is a function pointer for accessing |
| * memory. The address is gpa, the register (which doesn't have to be a real |
| * register in a VMTF) involved for the source/destination (based on 'store'). |
| */ |
| int emulate_mem_insn(struct guest_thread *gth, uint8_t *insn, |
| emu_mem_access access, int *advance) |
| { |
| struct x86_decode d[1] = {X86_DECODE_64_DEFAULT}; |
| uintptr_t gpa; |
| |
| // Duh, which way did he go George? Which way did he go? |
| // First hit on Google gets you there! |
| // This is the guest physical address of the access. |
| // This is nice, because if we ever go with more complete |
| // instruction decode, knowing this gpa reduces our work: |
| // we don't have to find the source address in registers, |
| // only the register holding or receiving the value. |
| gpa = gth_to_vmtf(gth)->tf_guest_pa; |
| if (decode_prefix(insn, d) < 0) |
| return -1; |
| if (decode_opcode(insn, d) < 0) |
| return -1; |
| if (execute_op(gth, insn, d, access, gpa) < 0) |
| return -1; |
| *advance = decode_inst_size(insn, d); |
| |
| if (debug_decode) { |
| /* This is racy - multiple decoded threads on different cores |
| * will clutter the output. */ |
| fprintf(stderr, "gpa %p", gpa); |
| print_decoded_insn(insn, d); |
| } |
| return 0; |
| } |
| |
| /* Debugging */ |
| |
| static void print_decoded_insn(uint8_t *insn, struct x86_decode *d) |
| { |
| uint8_t inst_sz = decode_inst_size(insn, d); |
| |
| fprintf(stderr, |
| "oprnd_bs %d, addr_bs %d, reg %2d, imm 0x%08llx, inst_size %2d:", |
| d->operand_bytes, d->address_bytes, |
| get_modrm(insn, d) ? modrm_get_reg(insn, d) : -1, |
| d->imm_sz ? get_imm(insn, d) : 0xdeadbeef, inst_sz); |
| for (int i = 0; i < inst_sz; i++) |
| fprintf(stderr, " %02x", insn[i]); |
| fprintf(stderr, "\n"); |
| } |
| |
| static int tst_mem_access(struct guest_thread *gth, uintptr_t gpa, |
| unsigned long *regp, size_t size, bool store) |
| { |
| if (store) { |
| switch (size) { |
| case 1: |
| *(uint8_t*)gpa = *(uint8_t*)regp; |
| break; |
| case 2: |
| *(uint16_t*)gpa = *(uint16_t*)regp; |
| break; |
| case 4: |
| *(uint32_t*)gpa = *(uint32_t*)regp; |
| break; |
| case 8: |
| *(uint64_t*)gpa = *(uint64_t*)regp; |
| break; |
| } |
| } else { |
| switch (size) { |
| case 1: |
| *(uint8_t*)regp = *(uint8_t*)gpa; |
| break; |
| case 2: |
| *(uint16_t*)regp = *(uint16_t*)gpa; |
| break; |
| case 4: |
| *(uint32_t*)regp = *(uint32_t*)gpa; |
| break; |
| case 8: |
| *(uint64_t*)regp = *(uint64_t*)gpa; |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| /* Far from exhaustive, and you need to call it manually. It actually caught a |
| * bug though. */ |
| static void run_decode_hokey_tests(void) |
| { |
| struct guest_thread gth[1] = {0}; |
| struct vm_trapframe *vm_tf = gth_to_vmtf(gth); |
| uint8_t insn[VMM_MAX_INSN_SZ]; |
| uint8_t ram[16]; |
| int inst_size, ret; |
| |
| vm_tf->tf_guest_pa = (uint64_t)ram; |
| |
| // movw [rax],ecx (rax ignored, we use the GPA) |
| memcpy(insn, "\x89\x08", 2); |
| memset(ram, 0, sizeof(ram)); |
| vm_tf->tf_rcx = 0x1234; |
| ret = emulate_mem_insn(gth, insn, tst_mem_access, &inst_size); |
| assert(!ret); |
| assert(inst_size = 2); |
| assert(*(uint16_t*)ram == 0x1234); |
| |
| // add8 /0 [rbx], -1 |
| memcpy(insn, "\x80\x03\xff", 3); |
| memset(ram, 0, sizeof(ram)); |
| ram[0] = 1; |
| ret = emulate_mem_insn(gth, insn, tst_mem_access, &inst_size); |
| assert(!ret); |
| assert(inst_size == 3); |
| assert(ram[0] == 0); |
| assert(vm_tf->tf_rflags & FL_ZF); |
| assert(!(vm_tf->tf_rflags & FL_SF)); |
| |
| // REX.W add /0, r/m64/imm32: 84: SIB+disp32 /0. (-1 sign extend) |
| memcpy(insn, "\x48\x81\x84\x00\x00\x00\x00\x00\xff\xff\xff\xff", 12); |
| memset(ram, 0, sizeof(ram)); |
| ram[0] = 2; |
| ret = emulate_mem_insn(gth, insn, tst_mem_access, &inst_size); |
| assert(!ret); |
| assert(inst_size == 12); |
| assert(*(uint64_t*)ram == 1); |
| assert(!(vm_tf->tf_rflags & FL_ZF)); |
| assert(!(vm_tf->tf_rflags & FL_SF)); |
| |
| // REX.R movzw, 14: SIB, reg rdx -> r10 |
| memcpy(insn, "\x44\x0f\xb7\x14\x00", 5); |
| memset(ram, 0, sizeof(ram)); |
| ram[0] = 0x12; |
| vm_tf->tf_r10 = 0xffffffffffffffff; |
| ret = emulate_mem_insn(gth, insn, tst_mem_access, &inst_size); |
| assert(!ret); |
| assert(inst_size == 5); |
| assert(vm_tf->tf_r10 == 0x12); |
| |
| fprintf(stderr, "Hokey decode tests passed\n"); |
| return; |
| |
| /* Helpful debuggers for the debugger */ |
| fprintf(stderr, "ram %x %x %x %x %x %x %x %x\n", |
| ram[0], ram[1], ram[2], ram[3], ram[4], ram[5], ram[6], ram[7]); |
| } |