From: Sean Christopherson <seanjc@google.com>
To: Aaron Lewis <aaronlewis@google.com>
Cc: kvm@vger.kernel.org, pbonzini@redhat.com, jmattson@google.com
Subject: Re: [kvm-unit-tests PATCH v2 3/4] x86: Add a test framework for nested_vmx_reflect_vmexit() testing
Date: Wed, 12 Jan 2022 20:48:31 +0000 [thread overview]
Message-ID: <Yd8+n+/2GCZtIhaB@google.com> (raw)
In-Reply-To: <20211214011823.3277011-4-aaronlewis@google.com>
On Tue, Dec 14, 2021, Aaron Lewis wrote:
> Set up a test framework that verifies an exception occurring in L2 is
> forwarded to the right place (L0?, L1?, L2?). To add a test to this
> framework just add the exception and callbacks to the
> vmx_exception_tests array.
The bulk of this patch belongs in patch 04. It's nearly impossible to properly
review the guts of vmx_exception_test() without seeing how the hooks are actually
used, and it also adds a test without any testcases, which is odd.
The introduction of test_set_guest_restartable() and test_set_guest_finished()
can and should go in a separate patch. It would be nice if a few existing tests
were converted to use test_set_guest_finished(), but certainly not necessary,
and that might cause too much scope screep.
Exposing exception_mnemonic() should also go in a separate patch. E.g. if someone
else is working on a test that wants to use exception_mnemonic(), then the two
in-flight series can share a single patch. That's not very likely to happen in
KUT, but it's good practice in general.
> This framework tests two things:
> 1) It tests that an exception is handled by L2.
> 2) It tests that an exception is handled by L1.
> To test that this happens, each exception is triggered twice; once with
> just an L2 exception handler registered, and again with both an L2
> exception handler registered and L1's exception bitmap set. The
> expectation is that the first exception will be handled by L2 and the
> second by L1.
>
> To implement this support was added to vmx.c to allow more than one
> L2 test be run in a single test. Previously there was a hard limit of
> only being allowed to set the L2 guest code once in a given test. That
> is no longer a limitation with the addition of
> test_set_guest_restartable().
>
> Support was also added to allow the test to complete without running
> through the entirety of the L2 guest code. Calling the function
> test_set_guest_finished() marks the guest code as completed, allowing
> it to end without running to the end.
>
> Signed-off-by: Aaron Lewis <aaronlewis@google.com>
> ---
...
> diff --git a/x86/unittests.cfg b/x86/unittests.cfg
> index 9fcdcae..0353b69 100644
> --- a/x86/unittests.cfg
> +++ b/x86/unittests.cfg
> @@ -368,6 +368,13 @@ arch = x86_64
> groups = vmx nested_exception
> check = /sys/module/kvm_intel/parameters/allow_smaller_maxphyaddr=Y
>
> +[vmx_exception_test]
> +file = vmx.flat
> +extra_params = -cpu max,+vmx -append vmx_exception_test
> +arch = x86_64
> +groups = vmx nested_exception
> +timeout = 10
Why add a new test case instead of folding this into "vmx"? It's quite speedy.
The "vmx" bucket definitely needs some cleanup, but I don't thinking adding a bunch
of one-off tests is the way forward.
> +
> [debug]
> file = debug.flat
> arch = x86_64
> diff --git a/x86/vmx.c b/x86/vmx.c
> index f4fbb94..9908746 100644
> --- a/x86/vmx.c
> +++ b/x86/vmx.c
> @@ -1895,6 +1895,23 @@ void test_set_guest(test_guest_func func)
> v2_guest_main = func;
> }
>
> +/*
> + * Set the target of the first enter_guest call and reset the RIP so 'func'
> + * will start from the beginning. This can be called multiple times per test.
> + */
> +void test_set_guest_restartable(test_guest_func func)
Hmm, "restartable" is somewhat confusing as it implies that other guests aren't
restartable, and sometimes people refer to resuming a guest after a VM-Exit as
"restarting" the guest. Maybe test_override_guest()?
> +{
> + assert(current->v2);
> + v2_guest_main = func;
These two lines can be shared with the existing test_set_guest(). It's kinda silly
since it's just two lines, but it is helpful to show the relationship between the
two helpers. E.g.
static void __test_set_guest(test_guest_func func)
{
assert(current->v2);
v2_guest_main = func;
}
/*
* Set the target of the first enter_guest call. Can only be called once per
* test. Must be called before first enter_guest call.
*/
void test_set_guest(test_guest_func func)
{
TEST_ASSERT_MSG(!v2_guest_main, "Already set guest func.");
__test_set_guest(func);
}
void test_override_guest(test_guest_func func)
{
__test_set_guest(func);
init_vmcs_guest();
}
> + init_vmcs_guest();
> + guest_finished = 0;
This seems unnecessary, can't the test simply not set this flag? Ah, after
running and debugging, the issue is that vmx_l2_ac_test() doesn't do a vmcall()
and so that test runs to completion. As annoying as I find guest_entry to be, I
do think it's better to leave this alone and add the vmcall() to vmx_l2_ac_test().
> +}
> +
> +void test_set_guest_finished(void)
> +{
> + guest_finished = 1;
> +}
> +
> static void check_for_guest_termination(union exit_reason exit_reason)
> {
> if (is_hypercall(exit_reason)) {
> diff --git a/x86/vmx.h b/x86/vmx.h
> index 4423986..5321a7e 100644
> --- a/x86/vmx.h
> +++ b/x86/vmx.h
> @@ -1055,7 +1055,9 @@ void hypercall(u32 hypercall_no);
> typedef void (*test_guest_func)(void);
> typedef void (*test_teardown_func)(void *data);
> void test_set_guest(test_guest_func func);
> +void test_set_guest_restartable(test_guest_func func);
> void test_add_teardown(test_teardown_func func, void *data);
> void test_skip(const char *msg);
> +void test_set_guest_finished(void);
>
> #endif
> diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
> index 3d57ed6..018db2f 100644
> --- a/x86/vmx_tests.c
> +++ b/x86/vmx_tests.c
> @@ -10701,6 +10701,93 @@ static void vmx_pf_vpid_test(void)
> __vmx_pf_vpid_test(invalidate_tlb_new_vpid, 1);
> }
>
> +struct vmx_exception_test {
> + u8 vector;
> + void (*guest_code)(void);
> + void (*init_test)(void);
> + void (*uninit_test)(void);
The init/uninit helpers are unnecessary, the #DB test can instead set EFLAGS.TF
from L2 and then rely on test_override_guest() to restore vmcs.GUEST_RFLAGS. The
#AC test can do the same thing (stuff state without restoring). It's a little
gross, but it yields much cleaner code in the test loop and we're already relying
on test_override_guest() to restore guest state (RIP).
> +};
> +
> +struct vmx_exception_test vmx_exception_tests[] = {
> +};
> +
> +static u8 vmx_exception_test_vector;
> +
> +static void vmx_exception_handler(struct ex_regs *regs)
> +{
> + report(regs->vector == vmx_exception_test_vector,
> + "Handling %s in L2's exception handler",
> + exception_mnemonic(vmx_exception_test_vector));
> + vmcall();
> +}
> +
> +static void handle_exception_in_l2(u8 vector)
> +{
> + handler old_handler = handle_exception(vector, vmx_exception_handler);
> +
> + vmx_exception_test_vector = vector;
> +
> + enter_guest();
> + report(vmcs_read(EXI_REASON) == VMX_VMCALL,
> + "%s handled by L2", exception_mnemonic(vector));
> +
> + test_set_guest_finished();
Just call test_set_guest_finished() after all tests run.
> + handle_exception(vector, old_handler);
> +}
> +
> +static void handle_exception_in_l1(u32 vector)
> +{
> + handler old_handler = handle_exception(vector, vmx_exception_handler);
> + u32 old_eb = vmcs_read(EXC_BITMAP);
> +
> + vmx_exception_test_vector = 0xff;
No need to install the handler or set the vector, just let L2 expode on the
unexpected exception.
> +
> + vmcs_write(EXC_BITMAP, old_eb | (1u << vector));
> +
> + enter_guest();
> +
> + report((vmcs_read(EXI_REASON) == VMX_EXC_NMI) &&
> + ((vmcs_read(EXI_INTR_INFO) & 0xff) == vector),
> + "%s handled by L1", exception_mnemonic(vector));
> +
> + test_set_guest_finished();
> +
> + vmcs_write(EXC_BITMAP, old_eb);
> + handle_exception(vector, old_handler);
> +}
> +
> +static void vmx_exception_test(void)
> +{
> + struct vmx_exception_test *t;
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(vmx_exception_tests); i++) {
> + t = &vmx_exception_tests[i];
> +
> + TEST_ASSERT(t->guest_code);
Eh, I wouldn't bother, especially once (un)init_test go bye bye.
This is what I ended up with (pulling in code from the next patch):
static void vmx_l2_gp_test(void)
{
*(volatile u64 *)NONCANONICAL = 0;
}
static void vmx_l2_ud_test(void)
{
asm volatile ("ud2");
}
static void vmx_l2_de_test(void)
{
asm volatile (
"xor %%eax, %%eax\n\t"
"xor %%ebx, %%ebx\n\t"
"xor %%edx, %%edx\n\t"
"idiv %%ebx\n\t"
::: "eax", "ebx", "edx");
}
static void vmx_l2_bp_test(void)
{
asm volatile ("int3");
}
static void vmx_l2_db_test(void)
{
write_rflags(read_rflags() | X86_EFLAGS_TF);
}
static uint64_t usermode_callback(void)
{
/* Trigger an #AC by writing 8 bytes to a 4-byte aligned address. */
asm volatile(
"sub $0x10, %rsp\n\t"
"movq $0, 0x4(%rsp)\n\t"
"add $0x10, %rsp\n\t");
return 0;
}
static void vmx_l2_ac_test(void)
{
bool raised_vector = false;
write_cr0(read_cr0() | X86_CR0_AM);
write_rflags(read_rflags() | X86_EFLAGS_AC);
run_in_user(usermode_callback, AC_VECTOR, 0, 0, 0, 0, &raised_vector);
report(raised_vector, "#AC vector raised from usermode in L2");
vmcall();
}
struct vmx_exception_test {
u8 vector;
void (*guest_code)(void);
};
struct vmx_exception_test vmx_exception_tests[] = {
{ GP_VECTOR, vmx_l2_gp_test },
{ UD_VECTOR, vmx_l2_ud_test },
{ DE_VECTOR, vmx_l2_de_test },
{ DB_VECTOR, vmx_l2_db_test },
{ BP_VECTOR, vmx_l2_bp_test },
{ AC_VECTOR, vmx_l2_ac_test },
};
static u8 vmx_exception_test_vector;
static void vmx_exception_handler(struct ex_regs *regs)
{
report(regs->vector == vmx_exception_test_vector,
"Handling %s in L2's exception handler",
exception_mnemonic(vmx_exception_test_vector));
vmcall();
}
static void handle_exception_in_l2(u8 vector)
{
handler old_handler = handle_exception(vector, vmx_exception_handler);
vmx_exception_test_vector = vector;
enter_guest();
report(vmcs_read(EXI_REASON) == VMX_VMCALL,
"%s handled by L2", exception_mnemonic(vector));
handle_exception(vector, old_handler);
}
static void handle_exception_in_l1(u32 vector)
{
u32 old_eb = vmcs_read(EXC_BITMAP);
vmcs_write(EXC_BITMAP, old_eb | (1u << vector));
enter_guest();
report((vmcs_read(EXI_REASON) == VMX_EXC_NMI) &&
((vmcs_read(EXI_INTR_INFO) & 0xff) == vector),
"%s handled by L1", exception_mnemonic(vector));
vmcs_write(EXC_BITMAP, old_eb);
}
static void vmx_exception_test(void)
{
struct vmx_exception_test *t;
int i;
for (i = 0; i < ARRAY_SIZE(vmx_exception_tests); i++) {
t = &vmx_exception_tests[i];
/*
* Override the guest code before each run even though it's the
* same code, the VMCS guest state needs to be reinitialized.
*/
test_override_guest(t->guest_code);
handle_exception_in_l2(t->vector);
test_override_guest(t->guest_code);
handle_exception_in_l1(t->vector);
}
test_set_guest_finished();
}
next prev parent reply other threads:[~2022-01-12 20:48 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-12-14 1:18 [kvm-unit-tests PATCH v2 0/4] Add additional testing for routing L2 exceptions Aaron Lewis
2021-12-14 1:18 ` [kvm-unit-tests PATCH v2 1/4] x86: Fix a #GP from occurring in usermode library's exception handlers Aaron Lewis
2021-12-14 1:18 ` [kvm-unit-tests PATCH v2 2/4] x86: Align L2's stacks Aaron Lewis
2022-01-12 19:38 ` Sean Christopherson
2021-12-14 1:18 ` [kvm-unit-tests PATCH v2 3/4] x86: Add a test framework for nested_vmx_reflect_vmexit() testing Aaron Lewis
2022-01-12 20:48 ` Sean Christopherson [this message]
2022-01-19 16:57 ` Aaron Lewis
2022-01-20 0:46 ` Sean Christopherson
2021-12-14 1:18 ` [kvm-unit-tests PATCH v2 4/4] x86: Add test coverage " Aaron Lewis
2021-12-14 12:19 ` [kvm-unit-tests PATCH v2 0/4] Add additional testing for routing L2 exceptions Paolo Bonzini
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Yd8+n+/2GCZtIhaB@google.com \
--to=seanjc@google.com \
--cc=aaronlewis@google.com \
--cc=jmattson@google.com \
--cc=kvm@vger.kernel.org \
--cc=pbonzini@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).