On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <[email protected]> wrote:
>
> Add a focused nested SVM selftest for DecodeAssists. Verify that KVM
> exposes the feature to L1 and synthesizes EXITINFO for representative
> MOV CR/DR, CLTS, INTn, INVLPG, and INVLPGA intercepts.
>
> Exercise instruction bytes for hardware and synthesized #NPF/#PF exits.
> Cover a synthesized #NPF that follows a hardware #NPF in the same emulated
> instruction, an unreadable instruction tail that leaves only the cached
> opcode, and the absence of bytes for an instruction-fetch #PF. Modify the
> MOVSB opcode while its MMIO read is pending to verify that the emulator's
> cached bytes are preserved.
>
> Inject a userspace #PF while an MMIO read is pending. Verify that after
> MMIO completion, L1 receives instruction bytes from the current RIP,
> not the previous instruction's emulator cache.
>
> The synthesized OUTSB #NPF and userspace-injected #PF cases run by
> default. The forced-emulation #PF and instruction-intercept cases
> require kvm.force_emulation_prefix=1.
>
> Signed-off-by: Tina Zhang <[email protected]>
> ---
> ...
> +static const struct instruction_intercept_test instruction_intercept_tests[]
> = {
> + {
> + .name = "MOV-from-CR4",
> + .code = l2_fep_mov_from_cr4_code,
> + .intercept_cr = BIT(INTERCEPT_CR4_READ),
> + .exit_code = SVM_EXIT_READ_CR4,
> + .exit_info_1 = BIT_ULL(63) | 10,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "MOV-to-DR7",
> + .code = l2_fep_mov_to_dr7_code,
> + .intercept_dr = BIT(INTERCEPT_DR7_WRITE),
> + .exit_code = SVM_EXIT_WRITE_DR7,
> + .exit_info_1 = 3,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "CLTS",
> + .code = l2_fep_clts_code,
> + .intercept_cr = BIT(INTERCEPT_CR0_WRITE),
> + .exit_code = SVM_EXIT_WRITE_CR0,
> + .exit_info_1_mask = BIT_ULL(63),
Should this be:
.exit_info_1 = BIT_ULL(63),
.exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "INTn",
> + .code = l2_fep_int_code,
> + .intercept = BIT_ULL(INTERCEPT_INTn),
> + .exit_code = SVM_EXIT_SWINT,
> + .exit_info_1 = TEST_INT_VECTOR,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "INVLPG",
> + .code = l2_fep_invlpg_code,
> + .intercept = BIT_ULL(INTERCEPT_INVLPG),
> + .exit_code = SVM_EXIT_INVLPG,
> + .exit_info_1 = (u64)&npf_target,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "INVLPGA",
> + .code = l2_fep_invlpga_code,
> + .intercept = BIT_ULL(INTERCEPT_INVLPGA),
> + .exit_code = SVM_EXIT_INVLPGA,
> + .exit_info_1_mask = ~0ULL,
> + .check_rax = true,
> + .rax = (u64)&npf_target,
> + },
> +};
If you omit the FEP from the assembly in
instruction_intercept_tests[], KVM will copy VMCS02's EXITINFO1 to
VMCS12. Assuming L1 itself is not nested, this can be used to validate
KVM emulator behavior against hardware. (Maybe this can be used to see
if I'm right about CR0_SEL_WRITE, which is currently untested.)
> ...
> +static void run_intercept_test(struct svm_test_data *svm,
> + const struct instruction_intercept_test *test)
> +{
> + struct vmcb *vmcb = svm->vmcb;
> + struct vmcb_control_area *control = &vmcb->control;
> + u64 expected_exit_info_1 = test->exit_info_1 & test->exit_info_1_mask;
> +
> + control->intercept |= test->intercept;
> + control->intercept_cr |= test->intercept_cr;
> + control->intercept_dr |= test->intercept_dr;
> +
> + control->exit_info_1 = ~0ULL;
Perhaps you should poison insn_len and insn_bytes here as well?