Emulate the kernel privilege boundary

aa9a023771f764a2324d431eea82b96b2c722a506ead5d2640b9cfc4658d82db
Alexis Sellier committed ago 1 parent 92cdb0c5
Makefile +7 -2
26 26
install: bin/emulator
27 27
	@echo "copy  bin/emulator => $(PREFIX)/emulator"
28 28
	@mkdir -p $(PREFIX)
29 29
	@cp bin/emulator $(PREFIX)/emulator
30 30
31 +
test: bin/emulator
32 +
	@mkdir -p build
33 +
	@printf '\163\045\100\361\163\000\120\020' > build/machine.rv64
34 +
	@bin/emulator -machine -run build/machine.rv64
35 +
31 36
fmt:
32 37
	git ls-files "*.c" "*.h" | xargs clang-format -i
33 38
34 39
clean:
35 -
	@rm -f bin/emulator
40 +
	@rm -rf bin/emulator build
36 41
37 -
.PHONY: default clean install
42 +
.PHONY: default clean install test
38 43
.SUFFIXES:
39 44
.DELETE_ON_ERROR:
40 45
.SILENT:
README +5 -0
43 43
44 44
OPTIONS
45 45
46 46
    -run                    Run headless (no TUI).
47 47
    -debug                  Load debug info for source-level diagnostics.
48 +
    -machine                Execute privileged RV64 machine images. Disables
49 +
                            Linux-compatible host ecalls and the JIT.
48 50
    -no-jit                 Disable the JIT compiler; interpret only.
49 51
    -memory-size=KB         Physical memory size in KB (default 128 MB).
50 52
    -data-size=KB           Data memory size in KB.
51 53
    -stack-size=KB          Stack size in KB (default 256 KB).
52 54
    -no-guard-stack         Disable stack guard zones (enabled by default, 16 bytes).
84 86
                   x86-64 machine code. Blocks are compiled on first
85 87
                   encounter and cached (16 MB code cache, up to 256K
86 88
                   blocks). Falls back to the interpreter for system
87 89
                   calls and faults.
88 90
91 +
  Machine mode implements the M-mode CSRs used by the Radiant kernel,
92 +
  U-mode `ecall` traps, `mret`, and `wfi`. The emulated boot hart is hart 0.
93 +
89 94
  The TUI debugger supports single-stepping, reverse execution (via
90 95
  snapshots), register and stack inspection, and memory watchpoints.
91 96
92 97
LICENSE
93 98
emulator.c +124 -1
112 112
    instr_t *program;     /* Program instructions. */
113 113
    bool     running;     /* Execution status. */
114 114
    bool     faulted;     /* There was a fault in execution. */
115 115
    bool     ebreak;      /* Program terminated via EBREAK. */
116 116
    reg_t    modified;    /* Index of the last modified register. */
117 +
    u64      mstatus;
118 +
    u64      mie;
119 +
    u64      mtvec;
120 +
    u64      mscratch;
121 +
    u64      mepc;
122 +
    u64      mcause;
123 +
    u64      mtval;
124 +
    u64      mip;
125 +
    u64      mhartid;
126 +
    u8       privilege;   /* RISC-V privilege level: U=0, M=3. */
117 127
};
118 128
119 129
/* Snapshot of CPU and memory state for reversing execution. */
120 130
struct snapshot {
121 131
    struct cpu cpu;                 /* Copy of CPU state. */
238 248
    bool watch_backtrace;
239 249
    u32  watch_backtrace_depth;
240 250
    bool validate_memory;
241 251
    bool count_instructions;
242 252
    bool jit_disabled;
253 +
    bool machine_mode;
243 254
};
244 255
245 256
/* Global emulator options. */
246 257
static struct emulator_options g_opts = {
247 258
    .stack_guard              = true,
263 274
    .watch_backtrace          = false,
264 275
    .watch_backtrace_depth    = 8,
265 276
    .validate_memory          = true,
266 277
    .count_instructions       = false,
267 278
    .jit_disabled             = false,
279 +
    .machine_mode             = false,
268 280
};
269 281
270 282
static void dump_watch_context(struct cpu *, u32 addr, u32 size, u32 value);
271 283
272 284
/* Return true if the given address range overlaps the watched region. */
499 511
500 512
/* Print usage information and return 1. */
501 513
static int usage(const char *prog) {
502 514
    fprintf(
503 515
        stderr,
504 -
        "usage: %s [-run] [-no-guard-stack]"
516 +
        "usage: %s [-run] [-machine] [-no-guard-stack]"
505 517
        " [-stack-size=KB] [-no-validate] [-debug]"
506 518
        " [-trace|-trace-headless] [-trace-depth=n] [-trace-instructions]"
507 519
        " [-max-steps=n] [-memory-size=KB] [-data-size=KB]"
508 520
        " [-watch=addr] [-watch-size=bytes] [-watch-arm-pc=addr]"
509 521
        " [-watch-zero-only] [-watch-skip=n]"
681 693
        if (strcmp(arg, "-run") == 0) {
682 694
            headless = true;
683 695
            argi++;
684 696
            continue;
685 697
        }
698 +
        if (strcmp(arg, "-machine") == 0) {
699 +
            g_opts.machine_mode = true;
700 +
            g_opts.jit_disabled = true;
701 +
            argi++;
702 +
            continue;
703 +
        }
686 704
687 705
        if (strncmp(arg, "-stack-size=", 12) == 0) {
688 706
            if (!parse_stack_size_value(arg + 12))
689 707
                return false;
690 708
            argi++;
1190 1208
1191 1209
/* Reset CPU state (keeping program loaded). */
1192 1210
static void cpu_reset(struct cpu *cpu) {
1193 1211
    trace_reset();
1194 1212
    memset(cpu->regs, 0, sizeof(cpu->regs));
1213 +
    cpu->mstatus   = 0;
1214 +
    cpu->mie       = 0;
1215 +
    cpu->mtvec     = 0;
1216 +
    cpu->mscratch  = 0;
1217 +
    cpu->mepc      = 0;
1218 +
    cpu->mcause    = 0;
1219 +
    cpu->mtval     = 0;
1220 +
    cpu->mip       = 0;
1221 +
    cpu->mhartid   = 0;
1222 +
    cpu->privilege = 3;
1195 1223
1196 1224
    /* Set SP to the top of the usable stack, aligned to 16 bytes
1197 1225
     * as required by the RISC-V ABI. */
1198 1226
    cpu->regs[SP]    = stack_usable_top() & ~0xF;
1199 1227
    cpu->pc          = program_base;
1204 1232
    last_executed_pc = 0;
1205 1233
}
1206 1234
1207 1235
/* Initialize CPU and memory to a clean state. */
1208 1236
static void cpu_init(struct cpu *cpu) {
1237 +
    memset(cpu, 0, sizeof(*cpu));
1209 1238
    memset(memory, 0, g_opts.memory_size);
1210 1239
    cpu->program     = (instr_t *)memory;
1211 1240
    cpu->programsize = 0;
1212 1241
    trace_reset();
1242 +
    cpu->privilege = 3;
1243 +
    cpu->mhartid   = 0;
1213 1244
    guest_fd_table_init();
1214 1245
    cpu_reset(cpu);
1215 1246
}
1216 1247
1217 1248
/* Open a file via the openat syscall (56). */
1623 1654
1624 1655
    cpu->programsize = program_bytes / sizeof(instr_t);
1625 1656
    cpu->pc          = program_base;
1626 1657
}
1627 1658
1659 +
static u64 *cpu_csr(struct cpu *cpu, u32 csr) {
1660 +
    switch (csr) {
1661 +
    case 0x300: return &cpu->mstatus;
1662 +
    case 0x304: return &cpu->mie;
1663 +
    case 0x305: return &cpu->mtvec;
1664 +
    case 0x340: return &cpu->mscratch;
1665 +
    case 0x341: return &cpu->mepc;
1666 +
    case 0x342: return &cpu->mcause;
1667 +
    case 0x343: return &cpu->mtval;
1668 +
    case 0x344: return &cpu->mip;
1669 +
    case 0xF14: return &cpu->mhartid;
1670 +
    default: return NULL;
1671 +
    }
1672 +
}
1673 +
1674 +
static void cpu_machine_trap(
1675 +
    struct cpu *cpu, u32 executed_pc, u64 cause, u64 value, u32 *pc_next
1676 +
) {
1677 +
    u64 mie = (cpu->mstatus >> 3) & 1;
1678 +
    cpu->mstatus &= ~((u64)3 << 11);
1679 +
    cpu->mstatus |= (u64)cpu->privilege << 11;
1680 +
    cpu->mstatus = (cpu->mstatus & ~((u64)1 << 7)) | (mie << 7);
1681 +
    cpu->mstatus &= ~((u64)1 << 3);
1682 +
    cpu->mepc      = executed_pc;
1683 +
    cpu->mcause    = cause;
1684 +
    cpu->mtval     = value;
1685 +
    cpu->privilege = 3;
1686 +
    *pc_next       = (u32)(cpu->mtvec & ~(u64)3);
1687 +
}
1688 +
1628 1689
/* Execute a single instruction. */
1629 1690
static void cpu_execute(struct cpu *cpu, enum display display, bool headless) {
1630 1691
    if (cpu_out_of_bounds(cpu)) {
1631 1692
        cpu->running = false;
1632 1693
        emit_fault_diagnostics(cpu, cpu->pc);
2165 2226
    }
2166 2227
2167 2228
    case OP_SYSTEM: {
2168 2229
        u32 funct12 = ins.i.imm_11_0;
2169 2230
2231 +
        if (g_opts.machine_mode) {
2232 +
            u32 funct3 = (ins.raw >> 12) & 7;
2233 +
            if (funct3 != 0) {
2234 +
                u32 csr_num = (ins.raw >> 20) & 0xFFF;
2235 +
                u32 rd      = (ins.raw >> 7) & 0x1F;
2236 +
                u32 rs1     = (ins.raw >> 15) & 0x1F;
2237 +
                u64 src     = funct3 >= 5 ? rs1 : cpu->regs[rs1];
2238 +
                bool writes = funct3 == 1 || funct3 == 5
2239 +
                    || ((funct3 == 2 || funct3 == 3
2240 +
                         || funct3 == 6 || funct3 == 7) && src != 0);
2241 +
                u64 *csr = cpu_csr(cpu, csr_num);
2242 +
                if (funct3 == 4 || !csr || cpu->privilege < ((csr_num >> 8) & 3)
2243 +
                    || (writes && ((csr_num >> 10) & 3) == 3)) {
2244 +
                    cpu_machine_trap(cpu, executed_pc, 2, ins.raw, &pc_next);
2245 +
                    break;
2246 +
                }
2247 +
                u64 old = *csr;
2248 +
                switch (funct3) {
2249 +
                case 1: case 5: *csr = src; break;
2250 +
                case 2: case 6: if (src != 0) *csr = old | src; break;
2251 +
                case 3: case 7: if (src != 0) *csr = old & ~src; break;
2252 +
                default: break;
2253 +
                }
2254 +
                if (writes && csr_num == 0x300) {
2255 +
                    u64 mpp = (*csr >> 11) & 3;
2256 +
                    if (mpp != 0 && mpp != 3)
2257 +
                        *csr &= ~((u64)3 << 11);
2258 +
                } else if (writes && (csr_num == 0x305 || csr_num == 0x341)) {
2259 +
                    *csr &= ~(u64)3;
2260 +
                }
2261 +
                if (rd != 0)
2262 +
                    cpu->regs[rd] = old;
2263 +
                break;
2264 +
            }
2265 +
            if (funct12 == 0) {
2266 +
                cpu_machine_trap(
2267 +
                    cpu, executed_pc, cpu->privilege == 0 ? 8 : 11, 0, &pc_next
2268 +
                );
2269 +
            } else if (funct12 == 1) {
2270 +
                cpu_machine_trap(cpu, executed_pc, 3, executed_pc, &pc_next);
2271 +
            } else if (funct12 == 0x105) {
2272 +
                cpu->running = false;
2273 +
            } else if (funct12 == 0x302) {
2274 +
                if (cpu->privilege != 3) {
2275 +
                    cpu_machine_trap(cpu, executed_pc, 2, ins.raw, &pc_next);
2276 +
                    break;
2277 +
                }
2278 +
                u8 next_priv = (cpu->mstatus >> 11) & 3;
2279 +
                u64 mpie     = (cpu->mstatus >> 7) & 1;
2280 +
                if (next_priv != 0 && next_priv != 3)
2281 +
                    next_priv = 0;
2282 +
                cpu->mstatus = (cpu->mstatus & ~((u64)1 << 3)) | (mpie << 3);
2283 +
                cpu->mstatus |= (u64)1 << 7;
2284 +
                cpu->mstatus &= ~((u64)3 << 11);
2285 +
                cpu->privilege = next_priv;
2286 +
                pc_next        = (u32)(cpu->mepc & ~(u64)3);
2287 +
            } else {
2288 +
                cpu_machine_trap(cpu, executed_pc, 2, ins.raw, &pc_next);
2289 +
            }
2290 +
            break;
2291 +
        }
2292 +
2170 2293
        if (funct12 == 0) {
2171 2294
            u32 syscall_num = (u32)cpu->regs[A7];
2172 2295
2173 2296
            switch (syscall_num) {
2174 2297
            case 64: { /* write. */