kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }