Several improvements and fixes:

* Change the Assist API. Rather than passing callbacks in each call, the
   callbacks are now registered beforehand. Then change the I/O Assist to
   fetch MMIO data via the Mem callback. This allows a guest to perform an
   I/O string operation on a memory that is itself an MMIO.

 * Introduce two new functions internal to libnvmm, read_guest_memory and
   write_guest_memory. They can handle mapped memory, MMIO memory and
   cross-page transactions.

 * Allow nvmm_gva_to_gpa and nvmm_gpa_to_hva to take non-page-aligned
   addresses. This simplifies a lot of things.

 * Support the MOVS instruction, and add a test for it. This instruction
   is special, in that it takes two implicit memory operands. In
   particular, it means that the two buffers can both be in MMIO memory,
   and we handle this case.

 * Fix gross copy-pasto in nvmm_hva_unmap. Also fix a few things here and
   there.
This commit is contained in:
maxv 2018-12-27 07:22:31 +00:00
parent 450723dc75
commit 38b2a665bf
6 changed files with 552 additions and 304 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: libnvmm.3,v 1.5 2018/12/15 13:39:43 maxv Exp $
.\" $NetBSD: libnvmm.3,v 1.6 2018/12/27 07:22:31 maxv Exp $
.\"
.\" Copyright (c) 2018 The NetBSD Foundation, Inc.
.\" All rights reserved.
@ -27,7 +27,7 @@
.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd December 14, 2018
.Dd December 26, 2018
.Dt LIBNVMM 3
.Os
.Sh NAME
@ -78,12 +78,14 @@
.Ft int
.Fn nvmm_gpa_to_hva "struct nvmm_machine *mach" "gpaddr_t gpa" \
"uintptr_t *hva"
.Ft void
.Fn nvmm_callbacks_register "const struct nvmm_callbacks *cbs"
.Ft int
.Fn nvmm_assist_io "struct nvmm_machine *mach" "nvmm_cpuid_t cpuid" \
"struct nvmm_exit *exit" "void (*cb)(struct nvmm_io *)"
"struct nvmm_exit *exit"
.Ft int
.Fn nvmm_assist_mem "struct nvmm_machine *mach" "nvmm_cpuid_t cpuid" \
"struct nvmm_exit *exit" "void (*cb)(struct nvmm_mem *)"
"struct nvmm_exit *exit"
.Sh DESCRIPTION
.Nm
provides a library for VMM software to handle hardware-accelerated virtual
@ -228,6 +230,11 @@ into a host virtual address returned in
.Fa gpa
must be page-aligned.
.Pp
.Fn nvmm_callbacks_register
registers in
.Nm
the callbacks descriptor passed as argument.
.Pp
.Fn nvmm_assist_io
emulates the I/O operation described in
.Fa exit
@ -397,8 +404,8 @@ it is necessary for VMM software to emulate the associated I/O operation.
provides an easy way for VMM software to perform that.
.Pp
.Fn nvmm_assist_io
will call the
.Fa cb
will call the registered
.Fa io
callback function and give it a
.Cd nvmm_io
structure as argument.
@ -444,8 +451,8 @@ provides an easy way for VMM software to perform that, similar to the I/O
Assist.
.Pp
.Fn nvmm_assist_mem
will call the
.Fa cb
will call the registered
.Fa mem
callback function and give it a
.Cd nvmm_mem
structure as argument.

View File

@ -1,4 +1,4 @@
/* $NetBSD: libnvmm.c,v 1.5 2018/12/15 13:39:43 maxv Exp $ */
/* $NetBSD: libnvmm.c,v 1.6 2018/12/27 07:22:31 maxv Exp $ */
/*
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@ -43,6 +43,8 @@
#include "nvmm.h"
struct nvmm_callbacks __callbacks;
typedef struct __area {
LIST_ENTRY(__area) list;
gpaddr_t gpa;
@ -53,7 +55,6 @@ typedef struct __area {
typedef LIST_HEAD(, __area) area_list_t;
static int nvmm_fd = -1;
static size_t nvmm_page_size = 0;
/* -------------------------------------------------------------------------- */
@ -146,7 +147,6 @@ nvmm_init(void)
nvmm_fd = open("/dev/nvmm", O_RDWR);
if (nvmm_fd == -1)
return -1;
nvmm_page_size = sysconf(_SC_PAGESIZE);
return 0;
}
@ -454,7 +454,7 @@ nvmm_hva_map(struct nvmm_machine *mach, uintptr_t hva, size_t size)
int
nvmm_hva_unmap(struct nvmm_machine *mach, uintptr_t hva, size_t size)
{
struct nvmm_ioc_hva_map args;
struct nvmm_ioc_hva_unmap args;
int ret;
if (nvmm_init() == -1) {
@ -465,7 +465,7 @@ nvmm_hva_unmap(struct nvmm_machine *mach, uintptr_t hva, size_t size)
args.hva = hva;
args.size = size;
ret = ioctl(nvmm_fd, NVMM_IOC_HVA_MAP, &args);
ret = ioctl(nvmm_fd, NVMM_IOC_HVA_UNMAP, &args);
if (ret == -1)
return -1;
@ -482,11 +482,6 @@ nvmm_gpa_to_hva(struct nvmm_machine *mach, gpaddr_t gpa, uintptr_t *hva)
area_list_t *areas = mach->areas;
area_t *ent;
if (gpa % nvmm_page_size != 0) {
errno = EINVAL;
return -1;
}
LIST_FOREACH(ent, areas, list) {
if (gpa >= ent->gpa && gpa < ent->gpa + ent->size) {
*hva = ent->hva + (gpa - ent->gpa);
@ -501,3 +496,13 @@ nvmm_gpa_to_hva(struct nvmm_machine *mach, gpaddr_t gpa, uintptr_t *hva)
/*
* nvmm_assist_io(): architecture-specific.
*/
/*
* nvmm_assist_mem(): architecture-specific.
*/
void
nvmm_callbacks_register(const struct nvmm_callbacks *cbs)
{
memcpy(&__callbacks, cbs, sizeof(__callbacks));
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: libnvmm_x86.c,v 1.5 2018/12/15 13:09:02 maxv Exp $ */
/* $NetBSD: libnvmm_x86.c,v 1.6 2018/12/27 07:22:31 maxv Exp $ */
/*
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@ -47,6 +47,46 @@
#include <x86/specialreg.h>
extern struct nvmm_callbacks __callbacks;
/* -------------------------------------------------------------------------- */
/*
* Undocumented debugging function. Helpful.
*/
int
nvmm_vcpu_dump(struct nvmm_machine *mach, nvmm_cpuid_t cpuid)
{
struct nvmm_x64_state state;
size_t i;
int ret;
const char *segnames[] = {
"CS", "DS", "ES", "FS", "GS", "SS", "GDT", "IDT", "LDT", "TR"
};
ret = nvmm_vcpu_getstate(mach, cpuid, &state, NVMM_X64_STATE_ALL);
if (ret == -1)
return -1;
printf("+ VCPU id=%d\n", (int)cpuid);
printf("| -> RIP=%p\n", (void *)state.gprs[NVMM_X64_GPR_RIP]);
printf("| -> RSP=%p\n", (void *)state.gprs[NVMM_X64_GPR_RSP]);
printf("| -> RAX=%p\n", (void *)state.gprs[NVMM_X64_GPR_RAX]);
printf("| -> RBX=%p\n", (void *)state.gprs[NVMM_X64_GPR_RBX]);
printf("| -> RCX=%p\n", (void *)state.gprs[NVMM_X64_GPR_RCX]);
for (i = 0; i < NVMM_X64_NSEG; i++) {
printf("| -> %s: sel=0x%lx base=%p, limit=%p, P=%d\n",
segnames[i],
state.segs[i].selector,
(void *)state.segs[i].base,
(void *)state.segs[i].limit,
state.segs[i].attrib.p);
}
return 0;
}
/* -------------------------------------------------------------------------- */
#define PTE32_L1_SHIFT 12
@ -330,6 +370,7 @@ x86_gva_to_gpa(struct nvmm_machine *mach, struct nvmm_x64_state *state,
{
bool is_pae, is_lng, has_pse;
uint64_t cr3;
size_t off;
int ret;
if ((state->crs[NVMM_X64_CR_CR0] & CR0_PG) == 0) {
@ -339,6 +380,9 @@ x86_gva_to_gpa(struct nvmm_machine *mach, struct nvmm_x64_state *state,
return 0;
}
off = (gva & PAGE_MASK);
gva &= ~PAGE_MASK;
is_pae = (state->crs[NVMM_X64_CR_CR4] & CR4_PAE) != 0;
is_lng = (state->msrs[NVMM_X64_MSR_EFER] & EFER_LME) != 0;
has_pse = (state->crs[NVMM_X64_CR_CR4] & CR4_PSE) != 0;
@ -362,6 +406,8 @@ x86_gva_to_gpa(struct nvmm_machine *mach, struct nvmm_x64_state *state,
errno = EFAULT;
}
*gpa = *gpa + off;
return ret;
}
@ -372,11 +418,6 @@ nvmm_gva_to_gpa(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
struct nvmm_x64_state state;
int ret;
if (gva & PAGE_MASK) {
errno = EINVAL;
return -1;
}
ret = nvmm_vcpu_getstate(mach, cpuid, &state,
NVMM_X64_STATE_CRS | NVMM_X64_STATE_MSRS);
if (ret == -1)
@ -413,12 +454,6 @@ is_long_mode(struct nvmm_x64_state *state)
return (state->msrs[NVMM_X64_MSR_EFER] & EFER_LME) != 0;
}
static inline bool
is_illegal(struct nvmm_io *io, nvmm_prot_t prot)
{
return (io->in && !(prot & NVMM_PROT_WRITE));
}
static int
segment_apply(struct nvmm_x64_state_seg *seg, gvaddr_t *gva, size_t size)
{
@ -449,21 +484,150 @@ error:
return -1;
}
static uint64_t
mask_from_adsize(size_t adsize)
{
switch (adsize) {
case 8:
return 0xFFFFFFFFFFFFFFFF;
case 4:
return 0x00000000FFFFFFFF;
case 2:
default: /* impossible */
return 0x000000000000FFFF;
}
}
static uint64_t
rep_dec_apply(struct nvmm_x64_state *state, size_t adsize)
{
uint64_t mask, cnt;
mask = mask_from_adsize(adsize);
cnt = state->gprs[NVMM_X64_GPR_RCX] & mask;
cnt -= 1;
cnt &= mask;
state->gprs[NVMM_X64_GPR_RCX] &= ~mask;
state->gprs[NVMM_X64_GPR_RCX] |= cnt;
return cnt;
}
static int
read_guest_memory(struct nvmm_machine *mach, struct nvmm_x64_state *state,
gvaddr_t gva, uint8_t *data, size_t size)
{
struct nvmm_mem mem;
nvmm_prot_t prot;
gpaddr_t gpa;
uintptr_t hva;
bool is_mmio;
int ret, remain;
ret = x86_gva_to_gpa(mach, state, gva, &gpa, &prot);
if (__predict_false(ret == -1)) {
return -1;
}
if (__predict_false(!(prot & NVMM_PROT_READ))) {
errno = EFAULT;
return -1;
}
if ((gva & PAGE_MASK) + size > PAGE_SIZE) {
remain = ((gva & PAGE_MASK) + size - PAGE_SIZE);
} else {
remain = 0;
}
size -= remain;
ret = nvmm_gpa_to_hva(mach, gpa, &hva);
is_mmio = (ret == -1);
if (is_mmio) {
mem.gva = gva;
mem.gpa = gpa;
mem.write = false;
mem.size = size;
(*__callbacks.mem)(&mem);
memcpy(data, mem.data, size);
} else {
memcpy(data, (uint8_t *)hva, size);
}
if (remain > 0) {
ret = read_guest_memory(mach, state, gva + size,
data + size, remain);
} else {
ret = 0;
}
return ret;
}
static int
write_guest_memory(struct nvmm_machine *mach, struct nvmm_x64_state *state,
gvaddr_t gva, uint8_t *data, size_t size)
{
struct nvmm_mem mem;
nvmm_prot_t prot;
gpaddr_t gpa;
uintptr_t hva;
bool is_mmio;
int ret, remain;
ret = x86_gva_to_gpa(mach, state, gva, &gpa, &prot);
if (__predict_false(ret == -1)) {
return -1;
}
if (__predict_false(!(prot & NVMM_PROT_WRITE))) {
errno = EFAULT;
return -1;
}
if ((gva & PAGE_MASK) + size > PAGE_SIZE) {
remain = ((gva & PAGE_MASK) + size - PAGE_SIZE);
} else {
remain = 0;
}
size -= remain;
ret = nvmm_gpa_to_hva(mach, gpa, &hva);
is_mmio = (ret == -1);
if (is_mmio) {
mem.gva = gva;
mem.gpa = gpa;
mem.write = true;
memcpy(mem.data, data, size);
mem.size = size;
(*__callbacks.mem)(&mem);
} else {
memcpy((uint8_t *)hva, data, size);
}
if (remain > 0) {
ret = write_guest_memory(mach, state, gva + size,
data + size, remain);
} else {
ret = 0;
}
return ret;
}
/* -------------------------------------------------------------------------- */
int
nvmm_assist_io(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
struct nvmm_exit *exit, void (*cb)(struct nvmm_io *))
struct nvmm_exit *exit)
{
struct nvmm_x64_state state;
struct nvmm_io io;
nvmm_prot_t prot;
size_t remain, done;
uintptr_t hva;
gvaddr_t gva, off;
gpaddr_t gpa;
uint8_t tmp[8];
uint8_t *ptr, *ptr2;
uint64_t cnt;
gvaddr_t gva;
int reg = 0; /* GCC */
bool cross;
int ret;
if (__predict_false(exit->reason != NVMM_EXIT_IO)) {
@ -481,29 +645,18 @@ nvmm_assist_io(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
if (ret == -1)
return -1;
cross = false;
if (!exit->u.io.str) {
ptr = (uint8_t *)&state.gprs[NVMM_X64_GPR_RAX];
} else {
/*
* Determine GVA.
*/
if (exit->u.io.str) {
if (io.in) {
reg = NVMM_X64_GPR_RDI;
} else {
reg = NVMM_X64_GPR_RSI;
}
switch (exit->u.io.address_size) {
case 8:
gva = state.gprs[reg];
break;
case 4:
gva = (state.gprs[reg] & 0x00000000FFFFFFFF);
break;
case 2:
default: /* impossible */
gva = (state.gprs[reg] & 0x000000000000FFFF);
break;
}
gva = state.gprs[reg];
gva &= mask_from_adsize(exit->u.io.address_size);
if (!is_long_mode(&state)) {
ret = segment_apply(&state.segs[exit->u.io.seg], &gva,
@ -511,70 +664,30 @@ nvmm_assist_io(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
if (ret == -1)
return -1;
}
off = (gva & PAGE_MASK);
gva &= ~PAGE_MASK;
ret = x86_gva_to_gpa(mach, &state, gva, &gpa, &prot);
if (ret == -1)
return -1;
if (__predict_false(is_illegal(&io, prot))) {
errno = EFAULT;
return -1;
}
ret = nvmm_gpa_to_hva(mach, gpa, &hva);
if (ret == -1)
return -1;
ptr = (uint8_t *)hva + off;
/*
* Special case. If the buffer is in between two pages, we
* need to retrieve data from the next page.
*/
if (__predict_false(off + io.size > PAGE_SIZE)) {
cross = true;
remain = off + io.size - PAGE_SIZE;
done = PAGE_SIZE - off;
memcpy(tmp, ptr, done);
ret = x86_gva_to_gpa(mach, &state, gva + PAGE_SIZE,
&gpa, &prot);
if (ret == -1)
return -1;
if (__predict_false(is_illegal(&io, prot))) {
errno = EFAULT;
return -1;
}
ret = nvmm_gpa_to_hva(mach, gpa, &hva);
if (ret == -1)
return -1;
memcpy(&tmp[done], (uint8_t *)hva, remain);
ptr2 = &tmp[done];
}
}
if (io.in) {
/* nothing to do */
} else {
memcpy(io.data, ptr, io.size);
}
(*cb)(&io);
if (io.in) {
if (!exit->u.io.str)
state.gprs[NVMM_X64_GPR_RAX] = 0;
if (__predict_false(cross)) {
memcpy(ptr, io.data, done);
memcpy(ptr2, &io.data[done], remain);
if (!io.in) {
if (!exit->u.io.str) {
memcpy(io.data, &state.gprs[NVMM_X64_GPR_RAX], io.size);
} else {
memcpy(ptr, io.data, io.size);
ret = read_guest_memory(mach, &state, gva, io.data,
io.size);
if (ret == -1)
return -1;
}
}
(*__callbacks.io)(&io);
if (io.in) {
if (!exit->u.io.str) {
memcpy(&state.gprs[NVMM_X64_GPR_RAX], io.data, io.size);
} else {
ret = write_guest_memory(mach, &state, gva, io.data,
io.size);
if (ret == -1)
return -1;
}
} else {
/* nothing to do */
}
if (exit->u.io.str) {
@ -586,8 +699,8 @@ nvmm_assist_io(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
}
if (exit->u.io.rep) {
state.gprs[NVMM_X64_GPR_RCX] -= 1;
if (state.gprs[NVMM_X64_GPR_RCX] == 0) {
cnt = rep_dec_apply(&state, exit->u.io.address_size);
if (cnt == 0) {
state.gprs[NVMM_X64_GPR_RIP] = exit->u.io.npc;
}
} else {
@ -609,6 +722,7 @@ static void x86_emul_xor(struct nvmm_mem *, void (*)(struct nvmm_mem *), uint64_
static void x86_emul_mov(struct nvmm_mem *, void (*)(struct nvmm_mem *), uint64_t *);
static void x86_emul_stos(struct nvmm_mem *, void (*)(struct nvmm_mem *), uint64_t *);
static void x86_emul_lods(struct nvmm_mem *, void (*)(struct nvmm_mem *), uint64_t *);
static void x86_emul_movs(struct nvmm_mem *, void (*)(struct nvmm_mem *), uint64_t *);
enum x86_legpref {
/* Group 1 */
@ -721,6 +835,7 @@ struct x86_store {
uint64_t dmo;
} u;
struct x86_disp disp;
int hardseg;
};
struct x86_instr {
@ -760,6 +875,7 @@ struct x86_opcode {
bool regtorm;
bool dmo;
bool todmo;
bool movs;
bool stos;
bool lods;
bool szoverride;
@ -1033,6 +1149,28 @@ static const struct x86_opcode primary_opcode_table[] = {
.emul = x86_emul_mov
},
/*
* MOVS
*/
{
/* Yb, Xb */
.byte = 0xA4,
.movs = true,
.szoverride = false,
.defsize = OPSIZE_BYTE,
.allsize = -1,
.emul = x86_emul_movs
},
{
/* Yv, Xv */
.byte = 0xA5,
.movs = true,
.szoverride = true,
.defsize = -1,
.allsize = OPSIZE_WORD|OPSIZE_DOUB|OPSIZE_QUAD,
.emul = x86_emul_movs
},
/*
* STOS
*/
@ -1380,6 +1518,33 @@ resolve_special_register(struct x86_instr *instr, uint8_t enc, size_t regsize)
return &gpr_map__special[instr->rexpref.present][enc][regsize-1];
}
/*
* Special node, for MOVS. Fake two displacements of zero on the source and
* destination registers.
*/
static int
node_movs(struct x86_decode_fsm *fsm, struct x86_instr *instr)
{
size_t adrsize;
adrsize = instr->address_size;
/* DS:RSI */
instr->src.type = STORE_REG;
instr->src.u.reg = &gpr_map__special[1][2][adrsize-1];
instr->src.disp.type = DISP_0;
/* ES:RDI, force ES */
instr->dst.type = STORE_REG;
instr->dst.u.reg = &gpr_map__special[1][3][adrsize-1];
instr->dst.disp.type = DISP_0;
instr->dst.hardseg = NVMM_X64_SEG_ES;
fsm_advance(fsm, 0, NULL);
return 0;
}
/*
* Special node, for STOS and LODS. Fake a displacement of zero on the
* destination register.
@ -1409,7 +1574,7 @@ node_stlo(struct x86_decode_fsm *fsm, struct x86_instr *instr)
if (opcode->stos) {
/* ES:RDI, force ES */
stlo->u.reg = &gpr_map__special[1][3][adrsize-1];
instr->legpref[LEG_OVR_ES] = true;
stlo->hardseg = NVMM_X64_SEG_ES;
} else {
/* DS:RSI */
stlo->u.reg = &gpr_map__special[1][2][adrsize-1];
@ -1855,6 +2020,8 @@ node_primary_opcode(struct x86_decode_fsm *fsm, struct x86_instr *instr)
fsm_advance(fsm, 1, node_dmo);
} else if (opcode->stos || opcode->lods) {
fsm_advance(fsm, 1, node_stlo);
} else if (opcode->movs) {
fsm_advance(fsm, 1, node_movs);
} else {
return -1;
}
@ -2167,6 +2334,24 @@ x86_emul_lods(struct nvmm_mem *mem, void (*cb)(struct nvmm_mem *),
}
}
static void
x86_emul_movs(struct nvmm_mem *mem, void (*cb)(struct nvmm_mem *),
uint64_t *gprs)
{
/*
* Special instruction: double memory operand. Don't call the cb,
* because the storage has already been performed earlier.
*/
if (gprs[NVMM_X64_GPR_RFLAGS] & PSL_D) {
gprs[NVMM_X64_GPR_RSI] -= mem->size;
gprs[NVMM_X64_GPR_RDI] -= mem->size;
} else {
gprs[NVMM_X64_GPR_RSI] += mem->size;
gprs[NVMM_X64_GPR_RDI] += mem->size;
}
}
/* -------------------------------------------------------------------------- */
static inline uint64_t
@ -2185,18 +2370,15 @@ gpr_read_address(struct x86_instr *instr, struct nvmm_x64_state *state, int gpr)
}
static int
store_to_mem(struct nvmm_machine *mach, struct nvmm_x64_state *state,
struct x86_instr *instr, struct x86_store *store, struct nvmm_mem *mem)
store_to_gva(struct nvmm_x64_state *state, struct x86_instr *instr,
struct x86_store *store, gvaddr_t *gvap, size_t size)
{
struct x86_sib *sib;
nvmm_prot_t prot;
gvaddr_t gva, off;
gvaddr_t gva = 0;
uint64_t reg;
int ret, seg;
uint32_t *p;
gva = 0;
if (store->type == STORE_SIB) {
sib = &store->u.sib;
if (sib->bas != NULL)
@ -2216,44 +2398,55 @@ store_to_mem(struct nvmm_machine *mach, struct nvmm_x64_state *state,
gva += *p;
}
mem->gva = gva;
if (!is_long_mode(state)) {
if (instr->legpref[LEG_OVR_CS]) {
seg = NVMM_X64_SEG_CS;
} else if (instr->legpref[LEG_OVR_SS]) {
seg = NVMM_X64_SEG_SS;
} else if (instr->legpref[LEG_OVR_ES]) {
seg = NVMM_X64_SEG_ES;
} else if (instr->legpref[LEG_OVR_FS]) {
seg = NVMM_X64_SEG_FS;
} else if (instr->legpref[LEG_OVR_GS]) {
seg = NVMM_X64_SEG_GS;
if (store->hardseg != 0) {
seg = store->hardseg;
} else {
seg = NVMM_X64_SEG_DS;
if (instr->legpref[LEG_OVR_CS]) {
seg = NVMM_X64_SEG_CS;
} else if (instr->legpref[LEG_OVR_SS]) {
seg = NVMM_X64_SEG_SS;
} else if (instr->legpref[LEG_OVR_ES]) {
seg = NVMM_X64_SEG_ES;
} else if (instr->legpref[LEG_OVR_FS]) {
seg = NVMM_X64_SEG_FS;
} else if (instr->legpref[LEG_OVR_GS]) {
seg = NVMM_X64_SEG_GS;
} else {
seg = NVMM_X64_SEG_DS;
}
}
ret = segment_apply(&state->segs[seg], &mem->gva, mem->size);
ret = segment_apply(&state->segs[seg], &gva, size);
if (ret == -1)
return -1;
}
*gvap = gva;
return 0;
}
static int
store_to_mem(struct nvmm_machine *mach, struct nvmm_x64_state *state,
struct x86_instr *instr, struct x86_store *store, struct nvmm_mem *mem)
{
nvmm_prot_t prot;
int ret;
ret = store_to_gva(state, instr, store, &mem->gva, mem->size);
if (ret == -1)
return -1;
if ((mem->gva & PAGE_MASK) + mem->size > PAGE_SIZE) {
/* Don't allow a cross-page MMIO. */
errno = EINVAL;
return -1;
}
off = (mem->gva & PAGE_MASK);
mem->gva &= ~PAGE_MASK;
ret = x86_gva_to_gpa(mach, state, mem->gva, &mem->gpa, &prot);
if (ret == -1)
return -1;
mem->gva += off;
mem->gpa += off;
return 0;
}
@ -2261,12 +2454,8 @@ static int
fetch_instruction(struct nvmm_machine *mach, struct nvmm_x64_state *state,
struct nvmm_exit *exit)
{
size_t fetchsize, remain, done;
gvaddr_t gva, off;
nvmm_prot_t prot;
gpaddr_t gpa;
uintptr_t hva;
uint8_t *ptr;
size_t fetchsize;
gvaddr_t gva;
int ret;
fetchsize = sizeof(exit->u.mem.inst_bytes);
@ -2279,50 +2468,46 @@ fetch_instruction(struct nvmm_machine *mach, struct nvmm_x64_state *state,
return -1;
}
off = (gva & PAGE_MASK);
gva &= ~PAGE_MASK;
ret = x86_gva_to_gpa(mach, state, gva, &gpa, &prot);
if (ret == -1)
return -1;
if (__predict_false((prot & NVMM_PROT_EXEC) == 0)) {
errno = EFAULT;
return -1;
}
ret = nvmm_gpa_to_hva(mach, gpa, &hva);
ret = read_guest_memory(mach, state, gva, exit->u.mem.inst_bytes,
fetchsize);
if (ret == -1)
return -1;
ptr = (uint8_t *)hva + off;
exit->u.mem.inst_len = fetchsize;
/*
* Special case. If the buffer is in between two pages, we
* need to retrieve data from the next page.
*/
if (__predict_false(off + fetchsize > PAGE_SIZE)) {
remain = off + fetchsize - PAGE_SIZE;
done = PAGE_SIZE - off;
return 0;
}
memcpy(exit->u.mem.inst_bytes, ptr, done);
static int
assist_mem_double(struct nvmm_machine *mach, struct nvmm_x64_state *state,
struct x86_instr *instr)
{
struct nvmm_mem mem;
uint8_t data[8];
gvaddr_t gva;
size_t size;
int ret;
ret = x86_gva_to_gpa(mach, state, gva + PAGE_SIZE,
&gpa, &prot);
if (ret == -1)
return -1;
if (__predict_false((prot & NVMM_PROT_EXEC) == 0)) {
errno = EFAULT;
return -1;
}
ret = nvmm_gpa_to_hva(mach, gpa, &hva);
if (ret == -1)
return -1;
size = instr->operand_size;
memcpy(&exit->u.mem.inst_bytes[done], (uint8_t *)hva, remain);
} else {
memcpy(exit->u.mem.inst_bytes, ptr, fetchsize);
exit->u.mem.inst_len = fetchsize;
}
/* Source. */
ret = store_to_gva(state, instr, &instr->src, &gva, size);
if (ret == -1)
return -1;
ret = read_guest_memory(mach, state, gva, data, size);
if (ret == -1)
return -1;
/* Destination. */
ret = store_to_gva(state, instr, &instr->dst, &gva, size);
if (ret == -1)
return -1;
ret = write_guest_memory(mach, state, gva, data, size);
if (ret == -1)
return -1;
mem.size = size;
(*instr->emul)(&mem, NULL, state->gprs);
return 0;
}
@ -2333,14 +2518,126 @@ fetch_instruction(struct nvmm_machine *mach, struct nvmm_x64_state *state,
return -1; \
} while (0);
static int
assist_mem_single(struct nvmm_machine *mach, struct nvmm_x64_state *state,
struct x86_instr *instr)
{
struct nvmm_mem mem;
uint64_t val;
int ret;
memset(&mem, 0, sizeof(mem));
switch (instr->src.type) {
case STORE_REG:
if (instr->src.disp.type != DISP_NONE) {
/* Indirect access. */
mem.write = false;
mem.size = instr->operand_size;
ret = store_to_mem(mach, state, instr, &instr->src,
&mem);
if (ret == -1)
return -1;
} else {
/* Direct access. */
mem.write = true;
mem.size = instr->operand_size;
val = state->gprs[instr->src.u.reg->num];
val = __SHIFTOUT(val, instr->src.u.reg->mask);
memcpy(mem.data, &val, mem.size);
}
break;
case STORE_IMM:
mem.write = true;
mem.size = instr->src.u.imm.size;
memcpy(mem.data, instr->src.u.imm.data, mem.size);
break;
case STORE_SIB:
mem.write = false;
mem.size = instr->operand_size;
ret = store_to_mem(mach, state, instr, &instr->src, &mem);
if (ret == -1)
return -1;
break;
case STORE_DMO:
mem.write = false;
mem.size = instr->operand_size;
ret = store_to_mem(mach, state, instr, &instr->src, &mem);
if (ret == -1)
return -1;
break;
default:
return -1;
}
switch (instr->dst.type) {
case STORE_REG:
if (instr->dst.disp.type != DISP_NONE) {
if (__predict_false(!mem.write)) {
DISASSEMBLER_BUG();
}
mem.size = instr->operand_size;
ret = store_to_mem(mach, state, instr, &instr->dst,
&mem);
if (ret == -1)
return -1;
} else {
/* nothing */
}
break;
case STORE_IMM:
/* The dst can't be an immediate. */
DISASSEMBLER_BUG();
case STORE_SIB:
if (__predict_false(!mem.write)) {
DISASSEMBLER_BUG();
}
mem.size = instr->operand_size;
ret = store_to_mem(mach, state, instr, &instr->dst, &mem);
if (ret == -1)
return -1;
break;
case STORE_DMO:
if (__predict_false(!mem.write)) {
DISASSEMBLER_BUG();
}
mem.size = instr->operand_size;
ret = store_to_mem(mach, state, instr, &instr->dst, &mem);
if (ret == -1)
return -1;
break;
default:
return -1;
}
(*instr->emul)(&mem, __callbacks.mem, state->gprs);
if (!mem.write) {
/* instr->dst.type == STORE_REG */
memcpy(&val, mem.data, sizeof(uint64_t));
val = __SHIFTIN(val, instr->dst.u.reg->mask);
state->gprs[instr->dst.u.reg->num] &= ~instr->dst.u.reg->mask;
state->gprs[instr->dst.u.reg->num] |= val;
}
return 0;
}
int
nvmm_assist_mem(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
struct nvmm_exit *exit, void (*cb)(struct nvmm_mem *))
struct nvmm_exit *exit)
{
struct nvmm_x64_state state;
struct x86_instr instr;
struct nvmm_mem mem;
uint64_t val;
uint64_t cnt;
int ret;
if (__predict_false(exit->reason != NVMM_EXIT_MEMORY)) {
@ -2371,120 +2668,24 @@ nvmm_assist_mem(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
return -1;
}
if (instr.legpref[LEG_REPN]) {
if (__predict_false(instr.legpref[LEG_REPN])) {
errno = ENODEV;
return -1;
}
memset(&mem, 0, sizeof(mem));
switch (instr.src.type) {
case STORE_REG:
if (instr.src.disp.type != DISP_NONE) {
/* Indirect access. */
mem.write = false;
mem.size = instr.operand_size;
ret = store_to_mem(mach, &state, &instr, &instr.src,
&mem);
if (ret == -1)
return -1;
} else {
/* Direct access. */
mem.write = true;
mem.size = instr.operand_size;
val = state.gprs[instr.src.u.reg->num];
val = __SHIFTOUT(val, instr.src.u.reg->mask);
memcpy(mem.data, &val, mem.size);
}
break;
case STORE_IMM:
mem.write = true;
mem.size = instr.src.u.imm.size;
memcpy(mem.data, instr.src.u.imm.data, mem.size);
break;
case STORE_SIB:
mem.write = false;
mem.size = instr.operand_size;
ret = store_to_mem(mach, &state, &instr, &instr.src,
&mem);
if (ret == -1)
return -1;
break;
case STORE_DMO:
mem.write = false;
mem.size = instr.operand_size;
ret = store_to_mem(mach, &state, &instr, &instr.src,
&mem);
if (ret == -1)
return -1;
break;
default:
return -1;
if (instr.opcode->movs) {
ret = assist_mem_double(mach, &state, &instr);
} else {
ret = assist_mem_single(mach, &state, &instr);
}
switch (instr.dst.type) {
case STORE_REG:
if (instr.dst.disp.type != DISP_NONE) {
if (__predict_false(!mem.write)) {
DISASSEMBLER_BUG();
}
mem.size = instr.operand_size;
ret = store_to_mem(mach, &state, &instr, &instr.dst,
&mem);
if (ret == -1)
return -1;
} else {
/* nothing */
}
break;
case STORE_IMM:
/* The dst can't be an immediate. */
DISASSEMBLER_BUG();
case STORE_SIB:
if (__predict_false(!mem.write)) {
DISASSEMBLER_BUG();
}
mem.size = instr.operand_size;
ret = store_to_mem(mach, &state, &instr, &instr.dst,
&mem);
if (ret == -1)
return -1;
break;
case STORE_DMO:
if (__predict_false(!mem.write)) {
DISASSEMBLER_BUG();
}
mem.size = instr.operand_size;
ret = store_to_mem(mach, &state, &instr, &instr.dst,
&mem);
if (ret == -1)
return -1;
break;
default:
if (ret == -1) {
errno = ENODEV;
return -1;
}
(*instr.emul)(&mem, cb, state.gprs);
if (!mem.write) {
/* instr.dst.type == STORE_REG */
memcpy(&val, mem.data, sizeof(uint64_t));
val = __SHIFTIN(val, instr.dst.u.reg->mask);
state.gprs[instr.dst.u.reg->num] &= ~instr.dst.u.reg->mask;
state.gprs[instr.dst.u.reg->num] |= val;
}
if (instr.legpref[LEG_REP]) {
state.gprs[NVMM_X64_GPR_RCX] -= 1;
if (state.gprs[NVMM_X64_GPR_RCX] == 0) {
cnt = rep_dec_apply(&state, instr.address_size);
if (cnt == 0) {
state.gprs[NVMM_X64_GPR_RIP] += instr.len;
}
} else {

View File

@ -1,4 +1,4 @@
/* $NetBSD: nvmm.h,v 1.3 2018/12/15 13:39:43 maxv Exp $ */
/* $NetBSD: nvmm.h,v 1.4 2018/12/27 07:22:31 maxv Exp $ */
/*
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@ -61,6 +61,11 @@ struct nvmm_mem {
uint8_t data[8];
};
struct nvmm_callbacks {
void (*io)(struct nvmm_io *);
void (*mem)(struct nvmm_mem *);
};
#define NVMM_PROT_READ 0x01
#define NVMM_PROT_WRITE 0x02
#define NVMM_PROT_EXEC 0x04
@ -90,9 +95,10 @@ int nvmm_gva_to_gpa(struct nvmm_machine *, nvmm_cpuid_t, gvaddr_t, gpaddr_t *,
nvmm_prot_t *);
int nvmm_gpa_to_hva(struct nvmm_machine *, gpaddr_t, uintptr_t *);
int nvmm_assist_io(struct nvmm_machine *, nvmm_cpuid_t, struct nvmm_exit *,
void (*)(struct nvmm_io *));
int nvmm_assist_mem(struct nvmm_machine *, nvmm_cpuid_t, struct nvmm_exit *,
void (*)(struct nvmm_mem *));
int nvmm_assist_io(struct nvmm_machine *, nvmm_cpuid_t, struct nvmm_exit *);
int nvmm_assist_mem(struct nvmm_machine *, nvmm_cpuid_t, struct nvmm_exit *);
void nvmm_callbacks_register(const struct nvmm_callbacks *);
int nvmm_vcpu_dump(struct nvmm_machine *, nvmm_cpuid_t);
#endif /* _LIBNVMM_H_ */

View File

@ -200,12 +200,13 @@ mem_callback(struct nvmm_mem *mem)
memcpy(mem->data, mmiobuf + off, mem->size);
}
}
static int
handle_memory(struct nvmm_machine *mach, struct nvmm_exit *exit)
{
int ret;
ret = nvmm_assist_mem(mach, 0, exit, mem_callback);
ret = nvmm_assist_mem(mach, 0, exit);
if (ret == -1) {
err(errno, "nvmm_assist_mem");
}
@ -255,7 +256,7 @@ struct test {
};
static void
run_test(struct nvmm_machine *mach, struct test *test)
run_test(struct nvmm_machine *mach, const struct test *test)
{
uint64_t *res;
size_t size;
@ -288,8 +289,9 @@ extern uint8_t test5_begin, test5_end;
extern uint8_t test6_begin, test6_end;
extern uint8_t test7_begin, test7_end;
extern uint8_t test8_begin, test8_end;
extern uint8_t test9_begin, test9_end;
struct test tests[] = {
static const struct test tests[] = {
{ "test1 - MOV", &test1_begin, &test1_end, 0x3004 },
{ "test2 - OR", &test2_begin, &test2_end, 0x14FF },
{ "test3 - AND", &test3_begin, &test3_end, 0x1FC0 },
@ -298,9 +300,15 @@ struct test tests[] = {
{ "test6 - DMO", &test6_begin, &test6_end, 0xFFAB },
{ "test7 - STOS", &test7_begin, &test7_end, 0x00123456 },
{ "test8 - LODS", &test8_begin, &test8_end, 0x12345678 },
{ "test9 - MOVS", &test9_begin, &test9_end, 0x12345678 },
{ NULL, NULL, NULL, -1 }
};
static const struct nvmm_callbacks callbacks = {
.io = NULL,
.mem = mem_callback
};
/*
* 0x1000: MMIO address, unmapped
* 0x2000: Instructions, mapped
@ -318,6 +326,7 @@ int main(int argc, char *argv[])
err(errno, "nvmm_machine_create");
if (nvmm_vcpu_create(&mach, 0) == -1)
err(errno, "nvmm_vcpu_create");
nvmm_callbacks_register(&callbacks);
map_pages(&mach);
for (i = 0; tests[i].name != NULL; i++) {

View File

@ -35,6 +35,7 @@
.globl test6_begin, test6_end
.globl test7_begin, test7_end
.globl test8_begin, test8_end
.globl test9_begin, test9_end
.text
.code64
@ -157,3 +158,22 @@ test8_begin:
TEST_END
test8_end:
.align 64
test9_begin:
movq $0x1000,%rax
movq $0x12345678,8(%rax)
movq $0x1008,%rsi
movq $0x1000,%rdi
movq $4,%rcx
rep movsb
movq $2,%rcx
rep movsw
TEST_END
test9_end: