A guest/Makefile => guest/Makefile +30 -0
@@ 0,0 1,30 @@
+CC ?= cc
+LD ?= ld
+OBJCOPY ?= objcopy
+CFLAGS = -std=c11 -O2 -Wall -Wextra -Werror -ffreestanding -fno-pie -fno-stack-protector -fno-builtin -mno-red-zone -mgeneral-regs-only
+
+.PHONY: all check clean
+
+all: build/disk
+
+build:
+ mkdir -p build
+
+build/boot.o: boot.S | build
+ $(CC) -c -m64 -fno-pie $< -o $@
+
+build/guest.o: guest.c | build
+ $(CC) $(CFLAGS) -c $< -o $@
+
+build/guest: build/boot.o build/guest.o layout.ld
+ $(LD) -T layout.ld build/boot.o build/guest.o -o $@
+
+build/disk: build/guest
+ $(OBJCOPY) -O binary $< $@
+ python3 -c 'from pathlib import Path; p = Path("$@"); b = p.read_bytes(); assert len(b) <= 61440; p.write_bytes(b.ljust(61952, b"\0"))'
+
+check: build/disk
+ python3 verify.py ../_build/default/bin/main.exe build/disk
+
+clean:
+ rm -rf build
A guest/boot.S => guest/boot.S +173 -0
@@ 0,0 1,173 @@
+.section .boot,"ax"
+.code16
+.global boot
+boot:
+ cli
+ xor %ax, %ax
+ mov %ax, %ds
+ mov %ax, %es
+ mov %ax, %ss
+ mov $0x7c00, %sp
+ mov $packet, %si
+ mov $0x42, %ah
+ int $0x13
+ jc stopped
+ in $0x92, %al
+ or $2, %al
+ out %al, $0x92
+ lgdt descriptor
+ mov %cr0, %eax
+ or $1, %eax
+ mov %eax, %cr0
+ ljmpl $8, $protected
+stopped:
+ hlt
+ jmp stopped
+.balign 8
+packet:
+ .byte 16, 0
+ .word 120
+ .word 0, 0x1000
+ .quad 1
+.global gdt
+gdt:
+ .quad 0
+ .quad 0x00cf9a000000ffff
+ .quad 0x00cf92000000ffff
+ .quad 0x00af9a000000ffff
+ .quad 0x00affa000000ffff
+ .quad 0x00cff2000000ffff
+ .quad 0, 0
+descriptor:
+ .word 63
+ .long gdt
+.org 510
+.word 0xaa55
+
+.section .text
+.code32
+protected:
+ mov $0x10, %ax
+ mov %ax, %ds
+ mov %ax, %es
+ mov %ax, %ss
+ mov $0x90000, %esp
+ cld
+ mov $0x70000, %edi
+ xor %eax, %eax
+ mov $3072, %ecx
+ rep stosl
+ movl $0x71003, 0x70000
+ movl $0x72003, 0x71000
+ mov $0x72000, %edi
+ mov $0x83, %eax
+ mov $512, %ecx
+1:
+ mov %eax, (%edi)
+ add $0x200000, %eax
+ add $8, %edi
+ loop 1b
+ mov $0x20, %eax
+ mov %eax, %cr4
+ mov $0x70000, %eax
+ mov %eax, %cr3
+ mov $0xc0000080, %ecx
+ rdmsr
+ or $0x900, %eax
+ wrmsr
+ mov %cr0, %eax
+ or $0x80010000, %eax
+ mov %eax, %cr0
+ ljmp $0x18, $longmode
+
+.code64
+longmode:
+ mov $0x10, %ax
+ mov %ax, %ds
+ mov %ax, %es
+ mov %ax, %ss
+ mov $0x90000, %rsp
+ lea __bss_start(%rip), %rdi
+ lea __bss_end(%rip), %rcx
+ sub %rdi, %rcx
+ xor %eax, %eax
+ rep stosb
+ call guest
+2:
+ hlt
+ jmp 2b
+
+.global probe
+probe:
+ mov %rsp, recovery_stack(%rip)
+ lea 5f(%rip), %rax
+ mov %rax, recovery_address(%rip)
+ test %edi, %edi
+ jz 3f
+ cmp $1, %edi
+ je 4f
+ call *%rsi
+ jmp 5f
+3:
+ mov (%rsi), %rax
+ jmp 5f
+4:
+ movq $0xc3, (%rsi)
+5:
+ ret
+
+.global probe_user
+probe_user:
+ mov %rsp, recovery_stack(%rip)
+ lea 9f(%rip), %rax
+ mov %rax, recovery_address(%rip)
+ pushq $0x2b
+ pushq $0x86000
+ pushq $2
+ pushq $0x23
+ lea 6f(%rip), %rax
+ push %rax
+ iretq
+6:
+ test %edi, %edi
+ jz 7f
+ cmp $1, %edi
+ je 8f
+ call *%rsi
+ int $0x80
+7:
+ mov (%rsi), %rax
+ int $0x80
+8:
+ movq $0xc3, (%rsi)
+ int $0x80
+9:
+ ret
+
+.global completed
+completed:
+ pushq $0
+ jmp recover
+
+.global pagefault
+pagefault:
+ movq $14, observed_vector(%rip)
+ jmp recover
+.global protection
+protection:
+ movq $13, observed_vector(%rip)
+recover:
+ push %rax
+ mov 8(%rsp), %rax
+ mov %rax, observed_error(%rip)
+ mov recovery_address(%rip), %rax
+ mov %rax, 16(%rsp)
+ mov recovery_stack(%rip), %rax
+ mov %rax, 40(%rsp)
+ movq $0x18, 24(%rsp)
+ movq $0x10, 48(%rsp)
+ pop %rax
+ add $8, %rsp
+ iretq
+
+.section .note.GNU-stack,"",@progbits
A guest/guest.c => guest/guest.c +223 -0
@@ 0,0 1,223 @@
+#include <stdint.h>
+#include <stddef.h>
+
+static uint64_t root[512] __attribute__((aligned(4096)));
+static uint64_t directory[512] __attribute__((aligned(4096)));
+static uint64_t middle[512] __attribute__((aligned(4096)));
+static uint64_t table[512] __attribute__((aligned(4096)));
+static uint8_t payload[4096] __attribute__((aligned(4096)));
+
+volatile uint64_t recovery_stack;
+volatile uint64_t recovery_address;
+volatile uint64_t observed_vector;
+volatile uint64_t observed_error;
+
+extern void probe(int access, uint64_t address);
+extern void pagefault(void);
+extern void protection(void);
+extern void completed(void);
+extern void probe_user(int access, uint64_t address);
+extern uint64_t gdt[];
+
+struct gate {
+ uint16_t low;
+ uint16_t selector;
+ uint8_t ist;
+ uint8_t flags;
+ uint16_t middle;
+ uint32_t high;
+ uint32_t reserved;
+} __attribute__((packed));
+
+static struct gate idt[256];
+static int phys_bits;
+static int nx;
+static int gb_pages;
+static int user_mode;
+static uint64_t root_bits;
+
+static struct {
+ uint32_t reserved;
+ uint64_t rsp[3];
+ uint64_t reserved1;
+ uint64_t ist[7];
+ uint64_t reserved2;
+ uint16_t reserved3;
+ uint16_t iomap;
+} __attribute__((packed)) tss;
+
+static void put(char c) {
+ __asm__ volatile("outb %0, $0xe9" : : "a"(c));
+}
+
+static void text(const char *s) {
+ while (*s) put(*s++);
+}
+
+static void hex(uint64_t n) {
+ text("0x");
+ for (int shift = 60; shift >= 0; shift -= 4) put("0123456789abcdef"[(n >> shift) & 15]);
+}
+
+static void decimal(int n) {
+ if (n >= 10) decimal(n / 10);
+ put('0' + n % 10);
+}
+
+static uint64_t cr0(void) {
+ uint64_t value;
+ __asm__ volatile("mov %%cr0, %0" : "=r"(value));
+ return value;
+}
+
+static uint64_t cr4(void) {
+ uint64_t value;
+ __asm__ volatile("mov %%cr4, %0" : "=r"(value));
+ return value;
+}
+
+static void set_cr0(uint64_t value) {
+ __asm__ volatile("mov %0, %%cr0" : : "r"(value) : "memory");
+}
+
+static void set_cr4(uint64_t value) {
+ __asm__ volatile("mov %0, %%cr4" : : "r"(value) : "memory");
+}
+
+static uint64_t efer(void) {
+ uint32_t low, high;
+ __asm__ volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(0xc0000080));
+ return (uint64_t)high << 32 | low;
+}
+
+static void set_efer(uint64_t value) {
+ __asm__ volatile("wrmsr" : : "a"((uint32_t)value), "d"((uint32_t)(value >> 32)), "c"(0xc0000080));
+}
+
+static void set_ac(int enabled) {
+ uint64_t flags;
+ __asm__ volatile("pushfq; pop %0" : "=r"(flags));
+ flags = enabled ? flags | (1u << 18) : flags & ~(1u << 18);
+ __asm__ volatile("push %0; popfq" : : "r"(flags) : "cc", "memory");
+}
+
+static void flush(void) {
+ __asm__ volatile("mov %0, %%cr3" : : "r"((uint64_t)root | root_bits) : "memory");
+}
+
+static void gate(int vector, void (*handler)(void)) {
+ uint64_t address = (uint64_t)handler;
+ idt[vector] = (struct gate){address, 0x18, 0, 0x8e, address >> 16, address >> 32, 0};
+}
+
+static void reset(void) {
+ __asm__ volatile("mov %0, %%cr3" : : "r"(0x70000ull) : "memory");
+ set_cr4(0x20);
+ set_cr0(cr0() | (1u << 16));
+ set_efer(efer() | (1u << 11));
+ set_ac(0);
+ user_mode = 0;
+ root_bits = 0;
+ for (int i = 0; i < 512; i++) root[i] = directory[i] = middle[i] = table[i] = 0;
+ root[0] = (uint64_t)directory | 7;
+ directory[0] = (uint64_t)middle | 7;
+ middle[0] = 0x83;
+ middle[2] = (uint64_t)table | 7;
+ table[0] = (uint64_t)payload | 3;
+ payload[0] = 0xc3;
+ flush();
+}
+
+static void page(uint64_t *frame) {
+ text("page ");
+ hex((uint64_t)frame);
+ put(' ');
+ const uint8_t *bytes = (const uint8_t *)frame;
+ for (int i = 0; i < 4096; i++) {
+ put("0123456789abcdef"[bytes[i] >> 4]);
+ put("0123456789abcdef"[bytes[i] & 15]);
+ }
+ put('\n');
+}
+
+static void run(const char *name, int access, uint64_t address) {
+ uint64_t flags, current_cr2, current_cr3;
+ flush();
+ __asm__ volatile("pushfq; pop %0" : "=r"(flags));
+ __asm__ volatile("mov %%cr2, %0" : "=r"(current_cr2));
+ __asm__ volatile("mov %%cr3, %0" : "=r"(current_cr3));
+ text("begin "); text(name); put(' '); decimal(access); put(' '); hex(address);
+ text(user_mode ? " user\n" : " supervisor\n");
+ text("context "); hex(cr0()); put(' '); hex(current_cr2); put(' '); hex(current_cr3);
+ put(' '); hex(cr4()); put(' '); hex(efer()); put(' '); hex(flags); text(" 0x0 ");
+ decimal(phys_bits); put(' '); decimal(nx); put(' '); decimal(gb_pages); put('\n');
+ page(root); page(directory); page(middle); page(table);
+ observed_vector = 0;
+ observed_error = 0;
+ flush();
+ if (user_mode) probe_user(access, address);
+ else probe(access, address);
+ text("observed "); decimal(observed_vector); put(' '); decimal(observed_error); put('\n');
+ text("end\n");
+}
+
+static void run_user(const char *name, int access) {
+ middle[0] |= 4;
+ user_mode = 1;
+ run(name, access, 0x400000);
+}
+
+void guest(void) {
+ uint32_t a, b, c, d;
+ __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(0x80000008), "c"(0));
+ phys_bits = a & 255;
+ __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(0x80000001), "c"(0));
+ nx = (d >> 20) & 1;
+ gb_pages = (d >> 26) & 1;
+ gate(13, protection);
+ gate(14, pagefault);
+ gate(0x80, completed);
+ idt[0x80].flags = 0xee;
+ tss.rsp[0] = 0x88000;
+ tss.iomap = sizeof(tss);
+ uint64_t base = (uint64_t)&tss;
+ gdt[6] = (sizeof(tss) - 1) | ((base & 0xffffff) << 16) | (0x89ull << 40) | ((base >> 24) << 56);
+ gdt[7] = base >> 32;
+ __asm__ volatile("ltr %0" : : "r"((uint16_t)0x30));
+ struct { uint16_t limit; uint64_t base; } __attribute__((packed)) idtr = {sizeof(idt) - 1, (uint64_t)idt};
+ __asm__ volatile("lidt %0" : : "m"(idtr));
+ reset(); run("read", 0, 0x400000);
+ reset(); run("write", 1, 0x400000);
+ reset(); run("execute", 2, 0x400000);
+ reset(); middle[2] &= ~2ull; run("parent", 1, 0x400000);
+ reset(); middle[2] &= ~2ull; set_cr0(cr0() & ~(1u << 16)); run("wp", 1, 0x400000);
+ reset(); table[0] |= 1ull << 63; run("nx", 2, 0x400000);
+ reset(); middle[2] |= 1ull << 63; run("ancestor", 2, 0x400000);
+ reset(); table[0] = 0; run("absent", 0, 0x400000);
+ reset(); table[0] = 0; run("absentwrite", 1, 0x400000);
+ reset(); table[0] = 0; run("absentfetch", 2, 0x400000);
+ reset(); table[0] |= 1ull << phys_bits; run("reserved", 0, 0x400000);
+ reset(); table[0] |= 1ull << 63; set_efer(efer() & ~(1u << 11)); run("nxe", 0, 0x400000);
+ reset(); middle[2] = 0x2083; run("alignment", 0, 0x400000);
+ reset(); middle[2] = 0x1083; run("huge", 0, 0x401000);
+ reset(); directory[1] = 0x83; run("giga", 0, 0x40001000);
+ reset(); table[0] |= 0x80; run("pat", 0, 0x400000);
+ reset(); root[1] = 0x83; run("pml", 0, 0x8000000000);
+ reset(); run("canonical", 0, 0x800000000000);
+ reset(); table[0] |= 4; set_cr4(cr4() | (1u << 20)); run("smep", 2, 0x400000);
+ reset(); table[0] |= 4; set_cr4(cr4() | (1u << 21)); run("smap", 0, 0x400000);
+ reset(); table[0] |= 4; set_cr4(cr4() | (1u << 21)); run("smapwrite", 1, 0x400000);
+ reset(); table[0] |= 4; set_cr4(cr4() | (1u << 21)); set_ac(1); run("ac", 1, 0x400000);
+ reset(); middle[2] &= ~2ull; table[0] = 0; run("precedence", 1, 0x400000);
+ reset(); run_user("userdenied", 0);
+ reset(); table[0] |= 4; run_user("userread", 0);
+ reset(); table[0] |= 4; run_user("userwrite", 1);
+ reset(); table[0] |= 4; run_user("userfetch", 2);
+ reset(); table[0] |= 4; middle[2] &= ~2ull; set_cr0(cr0() & ~(1u << 16)); run_user("userwp", 1);
+ reset(); table[0] |= (1ull << 63) | 4; run_user("usernx", 2);
+ reset(); root_bits = 0xabc; run("rootbits", 0, 0x400000);
+ reset(); set_cr4(cr4() | (1u << 17)); root_bits = 0xabc; run("pcid", 0, 0x400000);
+ text("done\n");
+ __asm__ volatile("outl %0, $0xf4" : : "a"(0x10));
+ for (;;) __asm__ volatile("hlt");
+}
A guest/layout.ld => guest/layout.ld +13 -0
@@ 0,0 1,13 @@
+ENTRY(boot)
+SECTIONS {
+ .boot 0x7c00 : AT(0) { *(.boot) }
+ .text 0x10000 : AT(512) { *(.text*) }
+ .rodata : AT(LOADADDR(.text) + SIZEOF(.text)) { *(.rodata*) }
+ .data : AT(LOADADDR(.rodata) + SIZEOF(.rodata)) { *(.data*) }
+ .bss (NOLOAD) : ALIGN(4096) {
+ __bss_start = .;
+ *(.bss*) *(COMMON)
+ __bss_end = .;
+ }
+ /DISCARD/ : { *(.eh_frame*) *(.note*) *(.comment*) }
+}
A guest/verify.py => guest/verify.py +66 -0
@@ 0,0 1,66 @@
+import json
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+
+
+scope = Path(sys.argv[1]).resolve()
+disk = Path(sys.argv[2]).resolve()
+with tempfile.TemporaryDirectory(prefix="scopeguest") as directory:
+ root = Path(directory)
+ transcript = root / "transcript"
+ command = [
+ "qemu-system-x86_64", "-machine", "pc,accel=tcg", "-cpu", "max",
+ "-m", "32M", "-display", "none", "-serial", "none", "-monitor", "none",
+ "-no-reboot", "-drive", f"format=raw,file={disk}",
+ "-debugcon", f"file:{transcript}", "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",
+ ]
+ try:
+ result = subprocess.run(command, capture_output=True, text=True, timeout=45)
+ except subprocess.TimeoutExpired:
+ print(transcript.read_text()[-2000:] if transcript.exists() else "no guest output", file=sys.stderr)
+ raise
+ assert result.returncode == 33, (result.returncode, result.stderr, transcript.read_text()[-2000:])
+ lines = transcript.read_text().splitlines()
+ assert lines[-1] == "done", "guest did not finish"
+ current = None
+ checks = 0
+ discrepancies = 0
+ for line in lines:
+ if line.startswith("begin "):
+ _, name, access, address, privilege = line.split()
+ current = []
+ elif line.startswith("context ") or line.startswith("page "):
+ assert current is not None
+ current.append(line)
+ elif line.startswith("observed "):
+ _, vector, error = line.split()
+ observed = int(vector), int(error)
+ elif line == "end":
+ assert current is not None
+ source = root / "source"
+ capture = root / "capture"
+ source.write_text("\n".join(current) + "\n")
+ subprocess.run([str(scope), "import", str(source), str(capture), "--source", f"qemu guest case {name}"],
+ check=True, capture_output=True, text=True)
+ result = subprocess.run([str(scope), "explain", str(capture), address, "--access",
+ ["read", "write", "execute"][int(access)], "--privilege", privilege, "--json"],
+ capture_output=True, text=True, timeout=10)
+ assert result.returncode in (0, 1), (name, result.stderr)
+ prediction = json.loads(result.stdout)["result"]
+ if prediction["status"] == "mapped":
+ predicted = 0, 0
+ else:
+ predicted = prediction["fault"]["vector"], prediction["fault"]["error_code"]
+ if observed != predicted:
+ known = name in {"reserved", "nxe", "alignment", "pml"} and observed == (14, predicted[1] & ~1) and predicted == (14, 9)
+ assert known, (name, "observed", observed, "predicted", predicted, prediction)
+ discrepancies += 1
+ print(f"{name}: known tcg discrepancy, observed=0x{observed[1]:x} architectural=0x{predicted[1]:x}")
+ else:
+ print(f"{name}: vector={observed[0]} error=0x{observed[1]:x}")
+ checks += 1
+ current = None
+ assert checks == 31, checks
+ print(f"{checks - discrepancies} exact guest comparisons, {discrepancies} reserved-bit tcg discrepancies")