From: Andrew Cooper Date: Thu, 6 Nov 2014 14:16:42 +0000 (+0000) Subject: Software interrupt emulation testing X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=5665da74537fbed919c5b2a531869ee766d0666f;p=people%2Froyger%2Fxen-test-framework.git Software interrupt emulation testing Test documentation is at the head of main.c Additionally * Don't warn on unused parameters - there are legitimate reasons for a parameter to be unused. * Make xtf/extable.h safe for inclusion in assembly files. Signed-off-by: Andrew Cooper --- diff --git a/build/common.mk b/build/common.mk index fd2cd86..4d878dd 100644 --- a/build/common.mk +++ b/build/common.mk @@ -18,6 +18,7 @@ COMMON_CFLAGS := $(COMMON_FLAGS) -Wall -Wextra -Werror -std=gnu99 -Wstrict-proto COMMON_CFLAGS += -fno-common -fno-asynchronous-unwind-tables -fno-strict-aliasing COMMON_CFLAGS += -fno-stack-protector -ffreestanding COMMON_CFLAGS += -mno-red-zone -mno-sse +COMMON_CFLAGS += -Wno-unused-parameter COMMON_AFLAGS-x86_32 := -m32 COMMON_AFLAGS-x86_64 := -m64 diff --git a/docs/all-tests.dox b/docs/all-tests.dox index 13e527f..2f8c853 100644 --- a/docs/all-tests.dox +++ b/docs/all-tests.dox @@ -12,4 +12,9 @@ than useful test. @subpage test-selftest - A set of sanity tests of the framework environment and functionality. + + +@section index-functional Functional tests + +@subpage test-swint-emulation - Software interrupt emulation for HVM guests. */ diff --git a/include/xtf/types.h b/include/xtf/types.h index 4e4b153..e622f6d 100644 --- a/include/xtf/types.h +++ b/include/xtf/types.h @@ -1,12 +1,16 @@ #ifndef XTF_TYPES_H #define XTF_TYPES_H +#ifndef __ASSEMBLY__ + #include #include #include #include #include +#endif /* !__ASSEMBLY__ */ + #endif /* XTF_TYPES_H */ /* diff --git a/tests/swint-emulation/Makefile b/tests/swint-emulation/Makefile new file mode 100644 index 0000000..6a14774 --- /dev/null +++ b/tests/swint-emulation/Makefile @@ -0,0 +1,10 @@ +ROOT := $(abspath $(CURDIR)/../..) + +include $(ROOT)/build/common.mk + +NAME := swint-emulation +TEST-ENVS := hvm64 hvm32 + +obj-perenv += main.o lowlevel.o + +include $(ROOT)/build/gen.mk diff --git a/tests/swint-emulation/lowlevel.S b/tests/swint-emulation/lowlevel.S new file mode 100644 index 0000000..564f162 --- /dev/null +++ b/tests/swint-emulation/lowlevel.S @@ -0,0 +1,67 @@ +#include +#include +#include + + /* Macro to generate a single test function. */ + /* See lowlevel.h for a description of nomenclature. */ +.macro GEN_SINGLE insn, type + + /* Function label. e.g. stub_int3_red(). */ +ENTRY(stub_\insn\()_\type) + + /* Avoid function label and fault label possibly aliasing. */ + nop + + /* Possibly insert a Xen Forced Emulation prefix. */ + .if \type == force || \type == forcered + _ASM_XEN_FEP + .endif + + /* Label where a fault should occur. e.g. label_int3_red_fault. */ +GLOBAL(label_\insn\()_\type\()_fault) + + /* + * Possibly insert a redundant prefix. + * + * Uses 'address size override' which has no effect on any of the + * target instructions, but in a form which GAS won't complain about. + */ + .if \type == red || \type == forcered + .byte 0x67 + .endif + + /* Target instruction to test. */ + .if \insn == int3 + int3 + .elseif \insn == int_0x3 + int $0x3 + .elseif \insn == icebp + .byte 0xf1 + .elseif \insn == int_0x1 + int $0x1 + .endif + + /* Label where a trap should occur. e.g. label_int3_red_trap. */ +GLOBAL(label_\insn\()_\type\()_trap) + + /* Function return. */ + ret + + /* Fixup from fault label to trap label. */ + _ASM_EXTABLE(label_\insn\()_\type\()_fault, + label_\insn\()_\type\()_trap) +.endm + + /* For a single instruction, generate each test variant. */ +.macro GEN_SEQUENCE insn + GEN_SINGLE \insn reg + GEN_SINGLE \insn red + GEN_SINGLE \insn force + GEN_SINGLE \insn forcered +.endm + + /* Generate test sequences for each instruction. */ +GEN_SEQUENCE int3 +GEN_SEQUENCE int_0x3 +GEN_SEQUENCE icebp +GEN_SEQUENCE int_0x1 diff --git a/tests/swint-emulation/lowlevel.h b/tests/swint-emulation/lowlevel.h new file mode 100644 index 0000000..94ed1e4 --- /dev/null +++ b/tests/swint-emulation/lowlevel.h @@ -0,0 +1,81 @@ +/** + * @file tests/swint-emulation/lowlevel.h + * + * Declarations of stubs and labels in generated in lowlevel.S + * + * Nomaclature: + * - `stub_$X_$Y()` + * - Stub function executing instruction `$X` with prefix `$Y`. + * - `label_$X_$Y_{trap, fault}:` + * - Labels for where `$X` is expected to trap or fault. + * + * Instructions `$X`: + * - int3 + * - Breakpoint (`0xcc`) + * - int_0x3 + * - `int $3` (`0xcd 0x03`) + * - icebp + * - Ice Breakpoint (`0xf1`) + * - int_0x1 + * - `int $1` (`0xcd 0x01`) + * + * Prefixes `$Y`: + * - reg + * - Regular - no prefix + * - red + * - Redundant - address size override prefix (`0x67`) + * - force + * - Forced Emulation prefix - see @ref _ASM_XEN_FEP + * - forcered + * - Forced Emulation and redundant prefixes + */ +#ifndef __LOWLEVEL_H__ +#define __LOWLEVEL_H__ + +void stub_int3_reg(void); +void stub_int3_red(void); +void stub_int3_force(void); +void stub_int3_forcered(void); +extern unsigned long label_int3_reg_trap[], label_int3_reg_fault[]; +extern unsigned long label_int3_red_trap[], label_int3_red_fault[]; +extern unsigned long label_int3_force_trap[], label_int3_force_fault[]; +extern unsigned long label_int3_forcered_trap[], label_int3_forcered_fault[]; + +void stub_int_0x3_reg(void); +void stub_int_0x3_red(void); +void stub_int_0x3_force(void); +void stub_int_0x3_forcered(void); +extern unsigned long label_int_0x3_reg_trap[], label_int_0x3_reg_fault[]; +extern unsigned long label_int_0x3_red_trap[], label_int_0x3_red_fault[]; +extern unsigned long label_int_0x3_force_trap[], label_int_0x3_force_fault[]; +extern unsigned long label_int_0x3_forcered_trap[], label_int_0x3_forcered_fault[]; + +void stub_icebp_reg(void); +void stub_icebp_red(void); +void stub_icebp_force(void); +void stub_icebp_forcered(void); +extern unsigned long label_icebp_reg_trap[], label_icebp_reg_fault[]; +extern unsigned long label_icebp_red_trap[], label_icebp_red_fault[]; +extern unsigned long label_icebp_force_trap[], label_icebp_force_fault[]; +extern unsigned long label_icebp_forcered_trap[], label_icebp_forcered_fault[]; + +void stub_int_0x1_reg(void); +void stub_int_0x1_red(void); +void stub_int_0x1_force(void); +void stub_int_0x1_forcered(void); +extern unsigned long label_int_0x1_reg_trap[], label_int_0x1_reg_fault[]; +extern unsigned long label_int_0x1_red_trap[], label_int_0x1_red_fault[]; +extern unsigned long label_int_0x1_force_trap[], label_int_0x1_force_fault[]; +extern unsigned long label_int_0x1_forcered_trap[], label_int_0x1_forcered_fault[]; + +#endif /* __LOWLEVEL_H__ */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/tests/swint-emulation/main.c b/tests/swint-emulation/main.c new file mode 100644 index 0000000..1156f54 --- /dev/null +++ b/tests/swint-emulation/main.c @@ -0,0 +1,344 @@ +/** + * @file tests/swint-emulation/main.c + * @ref test-swint-emulation - Emulation of software interrupts. + * + * @page test-swint-emulation Software Interrupt Emulation + * + * Tests for the exception frame generated from emulated software interrupt + * instructions, in situations which will results in traps and faults. + * + * This test requires a debug Xen, booted with `"hvm_fep"` to allow the test + * to directly invoke the x86 instruction emulator. + * + * Each of the following instructions are tested: + * - `int3` (`0xcc`) + * - `int $3` (`0xcd 0x03`) + * - `icebp` (`0xf1`) + * - `int $1` (`0xcd 0x01`) + * + * with and without a redundant prefix (address size override specifically, as + * it has no effect on instructs like these). Each combination is executed + * both normally, and via the instruction emulator, to confirm that hardware + * and the instruction emulator are behaving identically. + * + * Injection of software interrupts by Xen is complicated by differing + * available of hardware support. Intel VT-x and AMD SVM (both with and + * without NextRIP support) all provide different levels of hardware support, + * which affects how much Xen must emulate in software to cause the guest to + * observe architectural behaviour. + * + * Each instruction/prefix combination is executed in the following + * conditions: + * - cpl0, all permissions ok - expect traps + * - cpl0, descriptors not present - expect #%NP faults + * - cpl3, all permissions ok - expect traps + * - cpl3, descriptors dpl0 - expect #%GP faults (except `icebp`) + * - cpl3, descriptors not present - expect #%NP faults + * + * In all cases, the exception frame is verified to be correct. + */ +#include + +#include +#include + +#include "lowlevel.h" + +/** Single stub's worth of information. */ +struct single +{ + const char *type; + void (*fn)(void); + void *trap, *fault; +}; + +/** A collection of subs for an instruction. */ +struct sequence +{ + const char *name; + struct single tests[4]; +}; + +/** Sequence for `int3`. */ +struct sequence int3 = +{ "int3", + { + {"regular", stub_int3_reg, + label_int3_reg_trap, label_int3_reg_fault}, + + {"redundant", stub_int3_red, + label_int3_red_trap, label_int3_red_fault}, + + {"forced", stub_int3_force, + label_int3_force_trap, label_int3_force_fault}, + + {"forced redundant", stub_int3_forcered, + label_int3_forcered_trap, label_int3_forcered_fault}, + }, +}; + +/** Sequence for `int $3`. */ +struct sequence int_0x3 = +{ "int $3", + { + {"regular", stub_int_0x3_reg, + label_int_0x3_reg_trap, label_int_0x3_reg_fault}, + + {"redundant", stub_int_0x3_red, + label_int_0x3_red_trap, label_int_0x3_red_fault}, + + {"forced", stub_int_0x3_force, + label_int_0x3_force_trap, label_int_0x3_force_fault}, + + {"forced redundant", stub_int_0x3_forcered, + label_int_0x3_forcered_trap, label_int_0x3_forcered_fault}, + }, +}; + +/** Sequence for `icebp`. */ +struct sequence icebp = +{ "icebp", + { + {"regular", stub_icebp_reg, + label_icebp_reg_trap, label_icebp_reg_fault}, + + {"redundant", stub_icebp_red, + label_icebp_red_trap, label_icebp_red_fault}, + + {"forced", stub_icebp_force, + label_icebp_force_trap, label_icebp_force_fault}, + + {"forced redundant", stub_icebp_forcered, + label_icebp_forcered_trap, label_icebp_forcered_fault}, + }, +}; + +/** Sequence for `int $1`. */ +struct sequence int_0x1 = +{ "int $1", + { + {"regular", stub_int_0x1_reg, + label_int_0x1_reg_trap, label_int_0x1_reg_fault}, + + {"redundant", stub_int_0x1_red, + label_int_0x1_red_trap, label_int_0x1_red_fault}, + + {"forced", stub_int_0x1_force, + label_int_0x1_force_trap, label_int_0x1_force_fault}, + + {"forced redundant", stub_int_0x1_forcered, + label_int_0x1_forcered_trap, label_int_0x1_forcered_fault}, + }, +}; + +/** Whether to run the stub in user or supervisor mode. */ +static bool user = false; + +struct expectation { + const char *prefix; + const void *ip; + unsigned int ev, ec; +} /** Expected %%eip, vector and error code from the stub under test. */ + expectation; + +/** Latch details of the stub under test. */ +void expect(const void *prefix, const void *ip, + unsigned int ev, unsigned int ec) +{ + expectation = (struct expectation){prefix, ip, ev, ec}; + xtf_exlog_reset(); +} + +/** Check the exception long against the expected details. */ +void check(void) +{ + unsigned int entries = xtf_exlog_entries(); + + if ( entries != 1 ) + { + xtf_failure("Fail %s: Expected 1 exception (vec %u at %p), got %u\n", + expectation.prefix, expectation.ev, + expectation.ip, entries); + xtf_exlog_dump_log(); + return; + } + + exlog_entry_t *entry = xtf_exlog_entry(0); + if ( !entry ) + { + xtf_failure("Fail %s: Unable to retrieve exception log\n", + expectation.prefix); + return; + } + + if ( (_p(entry->ip) != expectation.ip) || + (entry->ev != expectation.ev) || + (entry->ec != expectation.ec) ) + { + xtf_failure("Fail %s:\n" + " Expected vec %2u[%04x] at %p\n" + " got vec %2u[%04x] at %p\n", + expectation.prefix, + expectation.ev, expectation.ec, expectation.ip, + entry->ev, entry->ec, _p(entry->ip)); + return; + } +} + +/** Print expected information in the case of an unexpected exception. */ +bool unhandled_exception(struct cpu_regs *regs) +{ + printk("Unhandled Exception at %p\n", _p(regs)); + check(); + + return false; +} + +/** Test a single sequence of related instructions. */ +void test_seq(struct sequence *seq, unsigned int vector, unsigned int error) +{ + unsigned int i; + + printk(" Testing %s\n", seq->name); + + for ( i = 0; i < ARRAY_SIZE(seq->tests); ++i ) + { + struct single *s = &seq->tests[i]; + + expect(s->type, + error == 0 ? s->trap : s->fault, + vector, error); + + user ? exec_user(s->fn) : s->fn(); + + check(); + + /* Avoid 'force' and 'forcered' stubs if FEP isn't available. */ + if ( i == 1 && !xtf_has_fep ) + break; + } +} + +/** test_seq() wrapper, for caller clarity. */ +static void test_trap(struct sequence *seq, unsigned int vector) +{ + test_seq(seq, vector, 0); +} + +/** test_seq() wrapper, for caller clarity. */ +static void test_fault(struct sequence *seq, + unsigned int vector, unsigned int error) +{ + test_seq(seq, vector, error); +} + +/** Modify the present flag on the IDT entries under test. */ +static void set_idt_entries_present(bool present) +{ + idt[X86_EXC_DB].p = present; + idt[X86_EXC_BP].p = present; +} + +/** Modify the descriptor privilege level on the IDT entries under test. */ +static void set_idt_entries_dpl(unsigned int dpl) +{ + idt[X86_EXC_DB].dpl = dpl; + idt[X86_EXC_BP].dpl = dpl; +} + +/** Tests run in user mode. */ +void cpl3_tests(void) +{ + user = true; + + printk("Test cpl3: all perms ok\n"); + { + test_trap(&int3, X86_EXC_BP); + test_trap(&int_0x3, X86_EXC_BP); + test_trap(&icebp, X86_EXC_DB); + test_trap(&int_0x1, X86_EXC_DB); + } + + printk("Test cpl3: p=0\n"); + { + set_idt_entries_present(false); + + test_fault(&int3, X86_EXC_NP, EXC_EC_SYM(BP)); + test_fault(&int_0x3, X86_EXC_NP, EXC_EC_SYM(BP)); + test_fault(&icebp, X86_EXC_NP, EXC_EC_SYM(DB, EXT)); + test_fault(&int_0x1, X86_EXC_NP, EXC_EC_SYM(DB)); + + set_idt_entries_present(true); + } + + printk("Test cpl3: dpl=0\n"); + { + set_idt_entries_dpl(0); + + test_fault(&int3, X86_EXC_GP, EXC_EC_SYM(BP)); + test_fault(&int_0x3, X86_EXC_GP, EXC_EC_SYM(BP)); + /* icebp count as external, so no dpl check. */ + test_trap (&icebp, X86_EXC_DB); + test_fault(&int_0x1, X86_EXC_GP, EXC_EC_SYM(DB)); + + set_idt_entries_dpl(3); + } + + user = false; +} + +/** Tests run in supervisor mode. */ +void cpl0_tests(void) +{ + printk("Test cpl0: all perms ok\n"); + { + test_trap(&int3, X86_EXC_BP); + test_trap(&int_0x3, X86_EXC_BP); + test_trap(&icebp, X86_EXC_DB); + test_trap(&int_0x1, X86_EXC_DB); + } + + printk("Test cpl0: p=0\n"); + { + set_idt_entries_present(false); + + test_fault(&int3, X86_EXC_NP, EXC_EC_SYM(BP)); + test_fault(&int_0x3, X86_EXC_NP, EXC_EC_SYM(BP)); + test_fault(&icebp, X86_EXC_NP, EXC_EC_SYM(DB, EXT)); + test_fault(&int_0x1, X86_EXC_NP, EXC_EC_SYM(DB)); + + set_idt_entries_present(true); + } +} + +void test_main(void) +{ + printk("Trap emulation\n"); + + if ( !xtf_has_fep ) + xtf_warning("Warning: FEP support not detected - some tests will be skipped\n"); + + /* Setup. Hook unhandled exceptions for debugging purposes. */ + xtf_unhandled_exception_hook = unhandled_exception; + set_idt_entries_present(true); + set_idt_entries_dpl(3); + + xtf_exlog_start(); + + cpl0_tests(); + cpl3_tests(); + + xtf_exlog_stop(); + + xtf_success(); +} + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */