Emulate the "TEST r/m{16,32,64}, imm{16,32,32}" instructions (opcode F7H).

This adds emulation for:
	test r/m16, imm16
	test r/m32, imm32
	test r/m64, imm32 sign-extended to 64

OpenBSD guests compiled with clang 8.0.0 use TEST directly against a
Local APIC register instead of separate read via MOV followed by a
TEST against the register.

PR:		238794
Submitted by:	jhb
Reported by:	Jason Tubnor jason@tubnor.net
Tested by:	Jason Tubnor jason@tubnor.net
Reviewed by:	markj, Patrick Mooney patrick.mooney@joyent.com
MFC after:	3 days
Differential Revision:	https://reviews.freebsd.org/D20755
This commit is contained in:
Rodney W. Grimes 2019-06-26 21:19:43 +00:00
parent 5baf985da7
commit e4da41f932

View file

@ -78,6 +78,7 @@ enum {
VIE_OP_TYPE_BITTEST,
VIE_OP_TYPE_TWOB_GRP15,
VIE_OP_TYPE_ADD,
VIE_OP_TYPE_TEST,
VIE_OP_TYPE_LAST
};
@ -221,6 +222,12 @@ static const struct vie_op one_byte_opcodes[256] = {
.op_byte = 0x8F,
.op_type = VIE_OP_TYPE_POP,
},
[0xF7] = {
/* XXX Group 3 extended opcode - not just TEST */
.op_byte = 0xF7,
.op_type = VIE_OP_TYPE_TEST,
.op_flags = VIE_OP_F_IMM,
},
[0xFF] = {
/* XXX Group 5 extended opcode - not just PUSH */
.op_byte = 0xFF,
@ -450,6 +457,41 @@ getaddflags(int opsize, uint64_t x, uint64_t y)
return (getaddflags64(x, y));
}
/*
* Return the status flags that would result from doing (x & y).
*/
#define GETANDFLAGS(sz) \
static u_long \
getandflags##sz(uint##sz##_t x, uint##sz##_t y) \
{ \
u_long rflags; \
\
__asm __volatile("and %2,%1; pushfq; popq %0" : \
"=r" (rflags), "+r" (x) : "m" (y)); \
return (rflags); \
} struct __hack
GETANDFLAGS(8);
GETANDFLAGS(16);
GETANDFLAGS(32);
GETANDFLAGS(64);
static u_long
getandflags(int opsize, uint64_t x, uint64_t y)
{
KASSERT(opsize == 1 || opsize == 2 || opsize == 4 || opsize == 8,
("getandflags: invalid operand size %d", opsize));
if (opsize == 1)
return (getandflags8(x, y));
else if (opsize == 2)
return (getandflags16(x, y));
else if (opsize == 4)
return (getandflags32(x, y));
else
return (getandflags64(x, y));
}
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)
@ -1218,6 +1260,55 @@ emulate_cmp(void *vm, int vcpuid, uint64_t gpa, struct vie *vie,
return (error);
}
static int
emulate_test(void *vm, int vcpuid, uint64_t gpa, struct vie *vie,
mem_region_read_t memread, mem_region_write_t memwrite, void *arg)
{
int error, size;
uint64_t op1, rflags, rflags2;
size = vie->opsize;
error = EINVAL;
switch (vie->op.op_byte) {
case 0xF7:
/*
* F7 /0 test r/m16, imm16
* F7 /0 test r/m32, imm32
* REX.W + F7 /0 test r/m64, imm32 sign-extended to 64
*
* Test mem (ModRM:r/m) with immediate and set status
* flags according to the results. The comparison is
* performed by anding the immediate from the first
* operand and then setting the status flags.
*/
if ((vie->reg & 7) != 0)
return (EINVAL);
error = memread(vm, vcpuid, gpa, &op1, size, arg);
if (error)
return (error);
rflags2 = getandflags(size, op1, vie->immediate);
break;
default:
return (EINVAL);
}
error = vie_read_register(vm, vcpuid, VM_REG_GUEST_RFLAGS, &rflags);
if (error)
return (error);
/*
* OF and CF are cleared; the SF, ZF and PF flags are set according
* to the result; AF is undefined.
*/
rflags &= ~RFLAGS_STATUS_BITS;
rflags |= rflags2 & (PSL_PF | PSL_Z | PSL_N);
error = vie_update_register(vm, vcpuid, VM_REG_GUEST_RFLAGS, rflags, 8);
return (error);
}
static int
emulate_add(void *vm, int vcpuid, uint64_t gpa, struct vie *vie,
mem_region_read_t memread, mem_region_write_t memwrite, void *arg)
@ -1643,6 +1734,10 @@ vmm_emulate_instruction(void *vm, int vcpuid, uint64_t gpa, struct vie *vie,
error = emulate_add(vm, vcpuid, gpa, vie, memread,
memwrite, memarg);
break;
case VIE_OP_TYPE_TEST:
error = emulate_test(vm, vcpuid, gpa, vie,
memread, memwrite, memarg);
break;
default:
error = EINVAL;
break;