rv64_vm_unit_test.c (10847B)
1 /* RV64 emulator white-box unit tests. 2 * 3 * These exercise INTERNAL units that have no public API surface — the rv64 4 * decoder (ArchDecodeOps), the guest address space (EmuAddrSpace), and the 5 * Linux syscall handler (mmap/mprotect/munmap) — so this binary links the 6 * library objects directly (mk/test.mk), unlike rv64_smoke_test.c which drives 7 * the emulator end-to-end through the public kit_emu_* API and links the 8 * archive. 9 */ 10 11 #include <kit/compile.h> 12 #include <kit/core.h> 13 #include <stdarg.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 #include <unistd.h> 18 19 #include "arch/arch.h" 20 #include "arch/riscv/isa.h" 21 #include "core/core.h" 22 #include "emu/emu.h" 23 #include "lib/kit_unit.h" 24 25 /* Shared test context replaces the per-file heap/diag/counter globals; 26 * EXPECT aliases CU_EXPECT so the call sites are unchanged. */ 27 static KitUnit g_u; 28 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) 29 30 static KitCompiler* new_compiler(void) { 31 KitTargetSpec t = kit_unit_target(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF); 32 KitCompiler* c = NULL; 33 if (kit_unit_compiler_new(&g_u, t, &c) != KIT_OK || !c) { 34 fprintf(stderr, "compiler_new failed\n"); 35 exit(2); 36 } 37 return c; 38 } 39 40 static KitCompiler* new_host_compiler(void) { 41 KitArchKind arch; 42 KitOSKind os; 43 KitObjFmt obj; 44 KitTargetSpec t; 45 KitCompiler* c = NULL; 46 #if defined(__x86_64__) || defined(_M_X64) 47 arch = KIT_ARCH_X86_64; 48 #elif defined(__aarch64__) || defined(_M_ARM64) 49 arch = KIT_ARCH_ARM_64; 50 #elif defined(__riscv) && __riscv_xlen == 64 51 arch = KIT_ARCH_RV64; 52 #else 53 return NULL; 54 #endif 55 #if defined(__APPLE__) 56 os = KIT_OS_MACOS; 57 obj = KIT_OBJ_MACHO; 58 #elif defined(__linux__) 59 os = KIT_OS_LINUX; 60 obj = KIT_OBJ_ELF; 61 #else 62 return NULL; 63 #endif 64 t = kit_unit_target(arch, os, obj); 65 if (kit_unit_compiler_new(&g_u, t, &c) != KIT_OK || !c) { 66 fprintf(stderr, "host compiler_new failed\n"); 67 exit(2); 68 } 69 return c; 70 } 71 72 static void put32(unsigned char* b, size_t off, u32 v) { 73 b[off + 0] = (unsigned char)v; 74 b[off + 1] = (unsigned char)(v >> 8); 75 b[off + 2] = (unsigned char)(v >> 16); 76 b[off + 3] = (unsigned char)(v >> 24); 77 } 78 79 /* ---- The rv64 decoder: ADDI/ECALL decode + terminator stop. ---- */ 80 static void decoder_smoke(void) { 81 KitCompiler* c = new_compiler(); 82 const ArchImpl* arch = arch_lookup(KIT_ARCH_RV64); 83 KitDecodedInsn insts[8]; 84 KitStatus st; 85 u32 n; 86 unsigned char buf[16]; 87 put32(buf, 0, rv_addi(RV_A0, RV_ZERO, 42)); 88 put32(buf, 4, rv_addi(RV_A7, RV_ZERO, 94)); 89 put32(buf, 8, rv_ecall()); 90 put32(buf, 12, rv_add(RV_T0, RV_A0, RV_A1)); 91 EXPECT(arch && arch->decode && arch->decode->decode_block, 92 "rv64 ArchDecodeOps unavailable"); 93 if (!arch || !arch->decode || !arch->decode->decode_block) { 94 kit_compiler_free(c); 95 return; 96 } 97 st = arch->decode->decode_block((Compiler*)c, buf, sizeof(buf), 0x10000, 98 insts, 8, &n); 99 EXPECT(st == KIT_OK, "decode_block returned %d", (int)st); 100 EXPECT(n >= 3u, "decode block returned %u insts", n); 101 EXPECT(insts[0].opcode == RV64_DEC_ADDI, "first insn must be ADDI, got %u", 102 insts[0].opcode); 103 EXPECT(insts[0].operands[0].reg == RV_A0, "rd should be a0"); 104 EXPECT(insts[0].operands[2].imm == 42, "imm should be 42"); 105 EXPECT(insts[1].opcode == RV64_DEC_ADDI, "second insn must be ADDI"); 106 EXPECT(insts[1].operands[2].imm == 94, "imm should be 94"); 107 EXPECT(insts[2].opcode == RV64_DEC_ECALL, "third insn must be ECALL, got %u", 108 insts[2].opcode); 109 EXPECT(insts[2].flags & KIT_DECODE_TERMINATOR, 110 "ECALL must be marked terminator"); 111 /* The block stops at ECALL; the ADD at offset 12 should not have 112 * been decoded. */ 113 EXPECT(n == 3u, "decoder must stop at the terminator (got n=%u)", n); 114 kit_compiler_free(c); 115 } 116 117 /* ---- The guest address space: map/protect/unmap/find_gap + fault kinds. ---- 118 */ 119 static void vm_unit_smoke(void) { 120 KitCompiler* c = new_host_compiler(); 121 EmuAddrSpace as; 122 u8* p; 123 const EmuMemFault* fault; 124 KitStatus st; 125 if (!c) return; 126 memset(&as, 0, sizeof(as)); 127 st = emu_addr_space_init(&as, (Compiler*)c, 0x1000u); 128 EXPECT(st == KIT_OK, "vm: init returned %d", (int)st); 129 if (st != KIT_OK) { 130 kit_compiler_free(c); 131 return; 132 } 133 134 st = emu_addr_space_map(&as, 0x10000u, 0x3000u, EMU_MEM_READ | EMU_MEM_WRITE, 135 EMU_MAP_ANON); 136 EXPECT(st == KIT_OK, "vm: initial anon map returned %d", (int)st); 137 EXPECT(as.nmaps == 1u, "vm: expected one map, got %u", as.nmaps); 138 139 st = emu_addr_space_map(&as, 0x11000u, 0x1000u, EMU_MEM_READ, EMU_MAP_ANON); 140 EXPECT(st == KIT_INVALID, "vm: overlapping map must be rejected"); 141 142 p = emu_addr_space_ptr(&as, 0x10ff8u, 16u, EMU_MEM_WRITE); 143 EXPECT(p != NULL, "vm: cross-page access inside one map should succeed"); 144 EXPECT(as.maps[0].dirty_pages[0] && as.maps[0].dirty_pages[1], 145 "vm: cross-page write should dirty both pages"); 146 147 st = emu_addr_space_map(&as, 0x20000u, 0x1000u, 0, EMU_MAP_GUARD); 148 EXPECT(st == KIT_OK, "vm: guard map returned %d", (int)st); 149 p = emu_addr_space_ptr(&as, 0x20000u, 1u, EMU_MEM_READ); 150 fault = emu_addr_space_last_fault(&as); 151 EXPECT(p == NULL && fault && fault->kind == EMU_FAULT_PROT, 152 "vm: guard read should be a protection fault"); 153 154 st = emu_addr_space_protect(&as, 0x11000u, 0x1000u, EMU_MEM_READ); 155 EXPECT(st == KIT_OK, "vm: middle-page protect returned %d", (int)st); 156 EXPECT(as.nmaps == 4u, "vm: protect should split maps, got %u", as.nmaps); 157 p = emu_addr_space_ptr(&as, 0x11000u, 1u, EMU_MEM_WRITE); 158 fault = emu_addr_space_last_fault(&as); 159 EXPECT(p == NULL && fault && fault->kind == EMU_FAULT_PROT, 160 "vm: write to read-only split page should fault"); 161 p = emu_addr_space_ptr(&as, 0x11000u, 1u, EMU_MEM_READ); 162 EXPECT(p != NULL, "vm: read from read-only split page should succeed"); 163 164 st = emu_addr_space_unmap(&as, 0x11000u, 0x1000u); 165 EXPECT(st == KIT_OK, "vm: middle-page unmap returned %d", (int)st); 166 p = emu_addr_space_ptr(&as, 0x11000u, 1u, EMU_MEM_READ); 167 fault = emu_addr_space_last_fault(&as); 168 EXPECT(p == NULL && fault && fault->kind == EMU_FAULT_UNMAPPED, 169 "vm: read from unmapped hole should fault as unmapped"); 170 171 { 172 u64 gap = 0; 173 st = emu_addr_space_find_gap(&as, 0x1000u, 0x1000u, 0x10000u, 0x30000u, 174 &gap); 175 EXPECT(st == KIT_OK && gap == 0x11000u, 176 "vm: find_gap should return the unmapped hole, got st=%d gap=0x%llx", 177 (int)st, (unsigned long long)gap); 178 } 179 180 emu_addr_space_destroy(&as); 181 kit_compiler_free(c); 182 } 183 184 /* ---- The Linux syscall handler: mmap -> mprotect -> munmap. ---- */ 185 static void linux_vm_syscall_smoke(void) { 186 KitCompiler* c = new_host_compiler(); 187 EmuProcess process; 188 EmuThread thread; 189 EmuCPUState* cpu; 190 const KitOsImpl* os; 191 const ArchImpl* arch; 192 u64 addr; 193 u8* p; 194 const EmuMemFault* fault; 195 if (!c) return; 196 memset(&process, 0, sizeof(process)); 197 memset(&thread, 0, sizeof(thread)); 198 os = os_lookup(KIT_OS_LINUX); 199 arch = arch_lookup(KIT_ARCH_RV64); 200 EXPECT(os && os->emu_default_syscall, "linux vm syscall: OS hook missing"); 201 EXPECT(arch && arch->emu, "linux vm syscall: arch hook missing"); 202 if (!os || !os->emu_default_syscall || !arch || !arch->emu) { 203 kit_compiler_free(c); 204 return; 205 } 206 process.compiler = (Compiler*)c; 207 process.os = os; 208 process.arch = arch; 209 process.guest_target.arch = KIT_ARCH_RV64; 210 process.guest_target.os = KIT_OS_LINUX; 211 process.bindings.syscall = os->emu_default_syscall; 212 if (os->emu_init_process_private) { 213 EXPECT(os->emu_init_process_private((Compiler*)c, &process) == KIT_OK, 214 "linux vm syscall: process private init failed"); 215 } 216 thread.process = &process; 217 if (os->emu_init_thread_private) { 218 EXPECT( 219 os->emu_init_thread_private((Compiler*)c, &process, &thread) == KIT_OK, 220 "linux vm syscall: thread private init failed"); 221 } 222 EXPECT(emu_addr_space_init(&process.image.addr_space, (Compiler*)c, 223 0x1000u) == KIT_OK, 224 "linux vm syscall: address-space init failed"); 225 cpu = arch->emu->cpu_new((Compiler*)c, 0, 0); 226 EXPECT(cpu != NULL, "linux vm syscall: cpu alloc failed"); 227 if (!cpu) { 228 if (os->emu_destroy_thread_private) 229 os->emu_destroy_thread_private((Compiler*)c, &thread); 230 if (os->emu_destroy_process_private) 231 os->emu_destroy_process_private((Compiler*)c, &process); 232 emu_addr_space_destroy(&process.image.addr_space); 233 kit_compiler_free(c); 234 return; 235 } 236 thread.cpu = cpu; 237 emu_cpu_set_thread(cpu, &thread); 238 emu_cpu_attach_addr_space(cpu, &process.image.addr_space); 239 240 arch->emu->set_gpr(&thread, RV_A0, 0); 241 arch->emu->set_gpr(&thread, RV_A1, 0x1000u); 242 arch->emu->set_gpr(&thread, RV_A2, 3u); 243 arch->emu->set_gpr(&thread, RV_A3, 0x22u); 244 arch->emu->set_gpr(&thread, RV_A4, ~(u64)0); 245 arch->emu->set_gpr(&thread, RV_A5, 0); 246 arch->emu->set_gpr(&thread, RV_A7, 222u); 247 emu_syscall(&thread); 248 addr = arch->emu->get_gpr(&thread, RV_A0); 249 EXPECT((i64)addr > 0, "linux vm syscall: mmap returned 0x%llx", 250 (unsigned long long)addr); 251 p = emu_addr_space_ptr(&process.image.addr_space, addr, 8u, EMU_MEM_WRITE); 252 EXPECT(p != NULL, "linux vm syscall: mmap result should be writable"); 253 254 arch->emu->set_gpr(&thread, RV_A0, addr); 255 arch->emu->set_gpr(&thread, RV_A1, 0x1000u); 256 arch->emu->set_gpr(&thread, RV_A2, 1u); 257 arch->emu->set_gpr(&thread, RV_A7, 226u); 258 emu_syscall(&thread); 259 EXPECT(arch->emu->get_gpr(&thread, RV_A0) == 0, 260 "linux vm syscall: mprotect failed"); 261 p = emu_addr_space_ptr(&process.image.addr_space, addr, 8u, EMU_MEM_WRITE); 262 fault = emu_addr_space_last_fault(&process.image.addr_space); 263 EXPECT(p == NULL && fault && fault->kind == EMU_FAULT_PROT, 264 "linux vm syscall: mprotect should deny writes"); 265 266 arch->emu->set_gpr(&thread, RV_A0, addr); 267 arch->emu->set_gpr(&thread, RV_A1, 0x1000u); 268 arch->emu->set_gpr(&thread, RV_A7, 215u); 269 emu_syscall(&thread); 270 EXPECT(arch->emu->get_gpr(&thread, RV_A0) == 0, 271 "linux vm syscall: munmap failed"); 272 p = emu_addr_space_ptr(&process.image.addr_space, addr, 1u, EMU_MEM_READ); 273 fault = emu_addr_space_last_fault(&process.image.addr_space); 274 EXPECT(p == NULL && fault && fault->kind == EMU_FAULT_UNMAPPED, 275 "linux vm syscall: munmap should remove the mapping"); 276 277 emu_cpu_free(cpu); 278 if (os->emu_destroy_thread_private) 279 os->emu_destroy_thread_private((Compiler*)c, &thread); 280 if (os->emu_destroy_process_private) 281 os->emu_destroy_process_private((Compiler*)c, &process); 282 emu_addr_space_destroy(&process.image.addr_space); 283 kit_compiler_free(c); 284 } 285 286 int main(void) { 287 kit_unit_init(&g_u); 288 decoder_smoke(); 289 vm_unit_smoke(); 290 linux_vm_syscall_smoke(); 291 kit_unit_summary(&g_u, "emu_rv64_unit_test"); 292 return kit_unit_status(&g_u); 293 }