emulator.c 79.7 KiB raw
1
#include <errno.h>
2
#include <fcntl.h>
3
#include <limits.h>
4
#include <stdint.h>
5
#include <stdio.h>
6
#include <stdlib.h>
7
#include <string.h>
8
#include <sys/ioctl.h>
9
#include <termios.h>
10
#include <unistd.h>
11
12
#include "color.h"
13
#include "io.h"
14
#include "jit.h"
15
#include "riscv.h"
16
#include "riscv/debug.h"
17
#include "types.h"
18
19
#ifndef PATH_MAX
20
#define PATH_MAX 4096
21
#endif
22
23
/* Define BINARY for `bail` and `assert` functions. */
24
#undef BINARY
25
#define BINARY "emulator"
26
27
#undef assert
28
#define assert(condition)                                                      \
29
    ((condition) ? (void)0 : _assert_failed(#condition, __FILE__, __LINE__))
30
31
static inline __attribute__((noreturn)) void _assert_failed(
32
    const char *condition, const char *file, int line
33
) {
34
    fprintf(stderr, "%s:%d: assertion `%s` failed\n", file, line, condition);
35
    abort();
36
}
37
38
/* Maximum physical memory size (384MB). The runtime can choose any size up to
39
 * this value with `-memory-size=...`. */
40
#define MEMORY_SIZE              (384 * 1024 * 1024)
41
/* Default physical memory size (128MB). */
42
#define DEFAULT_MEMORY_SIZE      (128 * 1024 * 1024)
43
/* Program memory size (4MB), reserved at start of memory for program code. */
44
#define PROGRAM_SIZE             (4 * 1024 * 1024)
45
/* Writable data region base. */
46
#define DATA_RW_OFFSET           0xFFFFF0
47
/* Data memory starts at writable data region. */
48
#define DATA_MEMORY_START        DATA_RW_OFFSET
49
/* Default data memory size. */
50
#define DEFAULT_DATA_MEMORY_SIZE (DEFAULT_MEMORY_SIZE - DATA_MEMORY_START)
51
/* Default stack size (256KB), allocated at the end of memory. */
52
#define DEFAULT_STACK_SIZE       (256 * 1024)
53
/* Maximum instructions to show in the TUI. */
54
#define MAX_INSTR_DISPLAY        40
55
/* Stack words to display in the TUI. */
56
#define STACK_DISPLAY_WORDS      32
57
/* Maximum number of CPU state snapshots to store for undo. */
58
#define MAX_SNAPSHOTS            64
59
/* Maximum open files in guest runtime. */
60
#define MAX_OPEN_FILES           32
61
/* Number of history entries to print when reporting faults. */
62
#define FAULT_TRACE_DEPTH        8
63
/* Instruction trace depth for headless tracing. */
64
#define TRACE_HISTORY            64
65
/* Maximum number of steps executed in headless mode before timing out. */
66
#define HEADLESS_MAX_STEPS       ((u64)1000000000000)
67
/* Height of header and footer in rows. */
68
#define HEADER_HEIGHT            2
69
#define FOOTER_HEIGHT            3
70
/* Read-only data offset. */
71
#define DATA_RO_OFFSET           0x10000
72
/* TTY escape codes. */
73
#define TTY_CLEAR                "\033[2J\033[H"
74
#define TTY_GOTO_RC              "\033[%d;%dH"
75
/* Exit code returned on EBREAK. */
76
#define EBREAK_EXIT_CODE         133
77
78
/* Registers displayed in the TUI, in order. */
79
static const reg_t registers_displayed[] = {
80
    SP, FP, RA, A0, A1, A2, A3, A4, A5, A6, A7, T0, T1, T2, T3, T4, T5, T6
81
};
82
83
/* Display mode for immediates and values. */
84
enum display { DISPLAY_HEX, DISPLAY_DEC };
85
86
/* Debug info entry mapping PC to source location. */
87
struct debug_entry {
88
    u32  pc;
89
    u32  offset;
90
    char file[PATH_MAX];
91
};
92
93
/* Debug info table. */
94
struct debug_info {
95
    struct debug_entry *entries;
96
    size_t              count;
97
    size_t              capacity;
98
};
99
100
/* Global debug info. */
101
static struct debug_info g_debug = { 0 };
102
103
/* CPU state. */
104
struct cpu {
105
    u64      regs[REGISTERS];
106
    u32      pc;          /* Program counter. */
107
    u32      programsize; /* Size of loaded program. */
108
    instr_t *program;     /* Program instructions. */
109
    bool     running;     /* Execution status. */
110
    bool     faulted;     /* There was a fault in execution. */
111
    bool     ebreak;      /* Program terminated via EBREAK. */
112
    reg_t    modified;    /* Index of the last modified register. */
113
};
114
115
/* Snapshot of CPU and memory state for reversing execution. */
116
struct snapshot {
117
    struct cpu cpu;                 /* Copy of CPU state. */
118
    u8         memory[MEMORY_SIZE]; /* Copy of memory. */
119
};
120
121
/* Circular buffer for snapshots. */
122
struct snapshot_buffer {
123
    struct snapshot snapshots[MAX_SNAPSHOTS];
124
    int             head; /* Index of most recent snapshot. */
125
    int             count;
126
};
127
128
/* CPU memory. */
129
static u8 memory[MEMORY_SIZE];
130
131
/* Loaded section sizes, used for bounds checking and diagnostics. */
132
static u32 program_base  = 0;
133
static u32 program_bytes = 0;
134
static u32 rodata_bytes  = 0;
135
static u32 data_bytes    = 0;
136
137
/* Snapshot buffer. */
138
static struct snapshot_buffer snapshots;
139
140
/* File descriptor table for guest file operations. */
141
static int guest_fds[MAX_OPEN_FILES];
142
143
/* Initialize the guest file descriptor table. */
144
static void guest_fd_table_init(void) {
145
    for (int i = 0; i < MAX_OPEN_FILES; i++) {
146
        guest_fds[i] = -1;
147
    }
148
}
149
150
/* Add a host file descriptor to the guest table. */
151
static int guest_fd_table_add(int host_fd) {
152
    /* Start at 3 to skip stdin/stdout/stderr. */
153
    for (int i = 3; i < MAX_OPEN_FILES; i++) {
154
        if (guest_fds[i] == -1) {
155
            guest_fds[i] = host_fd;
156
            return i;
157
        }
158
    }
159
    return -1;
160
}
161
162
/* Get the host fd for a guest file descriptor. */
163
static int guest_fd_table_get(int guest_fd) {
164
    if (guest_fd < 0 || guest_fd >= MAX_OPEN_FILES) {
165
        return -1;
166
    }
167
    /* Standard streams map directly. */
168
    if (guest_fd < 3) {
169
        return guest_fd;
170
    }
171
    return guest_fds[guest_fd];
172
}
173
174
/* Remove a file descriptor from the guest table. */
175
static void guest_fd_table_remove(int guest_fd) {
176
    if (guest_fd >= 3 && guest_fd < MAX_OPEN_FILES) {
177
        guest_fds[guest_fd] = -1;
178
    }
179
}
180
181
/* Single entry in the instruction trace ring buffer. */
182
struct trace_entry {
183
    u32     pc;
184
    instr_t instr;
185
    u64     regs[REGISTERS];
186
};
187
188
/* Circular buffer of recent instruction traces. */
189
struct trace_ring {
190
    struct trace_entry entries[TRACE_HISTORY];
191
    int                head;
192
    int                count;
193
};
194
195
/* Headless-mode instruction trace buffer. */
196
static struct trace_ring headless_trace = { .head = -1, .count = 0 };
197
198
/* Terminal dimensions in rows and columns. */
199
struct termsize {
200
    int rows;
201
    int cols;
202
};
203
204
/* Forward declarations. */
205
static void ui_render_instructions(
206
    struct cpu *, int col, int width, int height
207
);
208
static void ui_render_registers(
209
    struct cpu *, enum display, int col, int height
210
);
211
static void ui_render_stack(struct cpu *, enum display, int col, int height);
212
static void ui_render(struct cpu *, enum display);
213
static void cpu_execute(struct cpu *, enum display, bool headless);
214
static void emit_fault_diagnostics(struct cpu *, u32 pc);
215
216
/* Emulator runtime options, populated from CLI flags. */
217
struct emulator_options {
218
    bool stack_guard;
219
    u32  stack_size;
220
    bool debug_enabled;
221
    bool trace_headless;
222
    bool trace_enabled;
223
    bool trace_print_instructions;
224
    u32  trace_depth;
225
    u64  headless_max_steps;
226
    u32  memory_size;
227
    u32  data_memory_size;
228
    bool watch_enabled;
229
    u32  watch_addr;
230
    u32  watch_size;
231
    u32  watch_arm_pc;
232
    bool watch_zero_only;
233
    u32  watch_skip;
234
    bool watch_backtrace;
235
    u32  watch_backtrace_depth;
236
    bool validate_memory;
237
    bool count_instructions;
238
    bool jit_disabled;
239
};
240
241
/* Global emulator options. */
242
static struct emulator_options g_opts = {
243
    .stack_guard              = true,
244
    .stack_size               = DEFAULT_STACK_SIZE,
245
    .debug_enabled            = false,
246
    .trace_headless           = false,
247
    .trace_enabled            = false,
248
    .trace_print_instructions = false,
249
    .trace_depth              = 32,
250
    .headless_max_steps       = HEADLESS_MAX_STEPS,
251
    .memory_size              = DEFAULT_MEMORY_SIZE,
252
    .data_memory_size         = DEFAULT_DATA_MEMORY_SIZE,
253
    .watch_enabled            = false,
254
    .watch_addr               = 0,
255
    .watch_size               = 0,
256
    .watch_arm_pc             = 0,
257
    .watch_zero_only          = false,
258
    .watch_skip               = 0,
259
    .watch_backtrace          = false,
260
    .watch_backtrace_depth    = 8,
261
    .validate_memory          = true,
262
    .count_instructions       = false,
263
    .jit_disabled             = false,
264
};
265
266
static void dump_watch_context(struct cpu *, u32 addr, u32 size, u32 value);
267
268
/* Return true if the given address range overlaps the watched region. */
269
static inline bool watch_hit(u32 addr, u32 size) {
270
    if (!g_opts.watch_enabled)
271
        return false;
272
    u32 start = g_opts.watch_addr;
273
    u32 end   = start + (g_opts.watch_size ? g_opts.watch_size : 1);
274
    return addr < end && (addr + size) > start;
275
}
276
277
/* Check a store against the memory watchpoint and halt on a hit. */
278
static inline void watch_store(struct cpu *cpu, u32 addr, u32 size, u32 value) {
279
    if (!watch_hit(addr, size))
280
        return;
281
    if (g_opts.watch_arm_pc && cpu && cpu->pc < g_opts.watch_arm_pc)
282
        return;
283
    if (g_opts.watch_zero_only && value != 0)
284
        return;
285
    if (g_opts.watch_skip > 0) {
286
        g_opts.watch_skip--;
287
        return;
288
    }
289
    fprintf(
290
        stderr,
291
        "[WATCH] pc=%08x addr=%08x size=%u value=%08x\n",
292
        cpu ? cpu->pc : 0,
293
        addr,
294
        size,
295
        value
296
    );
297
    dump_watch_context(cpu, addr, size, value);
298
    if (cpu) {
299
        cpu->running = false;
300
        cpu->faulted = true;
301
        cpu->ebreak  = true;
302
    }
303
}
304
305
/* Fixed stack guard zone size. */
306
#define STACK_GUARD_BYTES 16
307
308
/* Clamp and align stack size to a valid range. */
309
static inline u32 sanitize_stack_bytes(u32 bytes) {
310
    if (bytes < WORD_SIZE)
311
        bytes = WORD_SIZE;
312
    bytes = (u32)align((i32)bytes, WORD_SIZE);
313
314
    /* Keep at least one word for guard computations. */
315
    if (bytes >= g_opts.memory_size)
316
        bytes = g_opts.memory_size - WORD_SIZE;
317
318
    return bytes;
319
}
320
321
/* Return the active stack guard size, or 0 if guards are disabled. */
322
static inline u32 stack_guard_bytes(void) {
323
    if (!g_opts.stack_guard)
324
        return 0;
325
    return STACK_GUARD_BYTES;
326
}
327
328
/* Return the configured stack size. */
329
static inline u32 stack_size(void) {
330
    return g_opts.stack_size;
331
}
332
333
/* Return the highest addressable word-aligned memory address. */
334
static inline u32 memory_top(void) {
335
    return g_opts.memory_size - WORD_SIZE;
336
}
337
338
/* Return the lowest address in the stack region. */
339
static inline u32 stack_bottom(void) {
340
    return memory_top() - stack_size() + WORD_SIZE;
341
}
342
343
/* Return the highest usable stack address (inside the guard zone). */
344
static inline u32 stack_usable_top(void) {
345
    u32 guard = stack_guard_bytes();
346
    u32 size  = stack_size();
347
    if (guard >= size)
348
        guard = size - WORD_SIZE;
349
    return memory_top() - guard;
350
}
351
352
/* Return the lowest usable stack address (inside the guard zone). */
353
static inline u32 stack_usable_bottom(void) {
354
    u32 guard = stack_guard_bytes();
355
    u32 size  = stack_size();
356
    if (guard >= size)
357
        guard = size - WORD_SIZE;
358
    return stack_bottom() + guard;
359
}
360
361
/* Return true if addr falls within the stack region. */
362
static inline bool stack_contains(u32 addr) {
363
    return addr >= stack_bottom() && addr <= memory_top();
364
}
365
366
/* Return true if the range [start, end] overlaps a stack guard zone. */
367
static inline bool stack_guard_overlaps(u32 guard, u32 start, u32 end) {
368
    if (guard == 0)
369
        return false;
370
371
    u32 low_guard_end    = stack_bottom() + guard - 1;
372
    u32 high_guard_start = memory_top() - guard + 1;
373
374
    return (start <= low_guard_end && end >= stack_bottom()) ||
375
           (end >= high_guard_start && start <= memory_top());
376
}
377
378
/* Return true if addr falls inside a stack guard zone. */
379
static inline bool stack_guard_contains(u32 guard, u32 addr) {
380
    return stack_guard_overlaps(guard, addr, addr);
381
}
382
383
/* Load a 16-bit value from memory in little-endian byte order. */
384
static inline u16 memory_load_u16(u32 addr) {
385
    return (u16)(memory[addr] | (memory[addr + 1] << 8));
386
}
387
388
/* Load a 32-bit value from memory in little-endian byte order. */
389
static inline u32 memory_load_u32(u32 addr) {
390
    return memory[addr] | (memory[addr + 1] << 8) | (memory[addr + 2] << 16) |
391
           (memory[addr + 3] << 24);
392
}
393
394
/* Load a 64-bit value from memory in little-endian byte order. */
395
static inline u64 memory_load_u64(u32 addr) {
396
    return (u64)memory[addr] | ((u64)memory[addr + 1] << 8) |
397
           ((u64)memory[addr + 2] << 16) | ((u64)memory[addr + 3] << 24) |
398
           ((u64)memory[addr + 4] << 32) | ((u64)memory[addr + 5] << 40) |
399
           ((u64)memory[addr + 6] << 48) | ((u64)memory[addr + 7] << 56);
400
}
401
402
/* Store a byte to memory. */
403
static inline void memory_store_u8(u32 addr, u8 value) {
404
    memory[addr] = value;
405
}
406
407
/* Store a 16-bit value to memory in little-endian byte order. */
408
static inline void memory_store_u16(u32 addr, u16 value) {
409
    memory[addr]     = (u8)(value & 0xFF);
410
    memory[addr + 1] = (u8)((value >> 8) & 0xFF);
411
}
412
413
/* Store a 32-bit value to memory in little-endian byte order. */
414
static inline void memory_store_u32(u32 addr, u32 value) {
415
    assert(addr + 3 < g_opts.memory_size);
416
    memory[addr]     = (u8)(value & 0xFF);
417
    memory[addr + 1] = (u8)((value >> 8) & 0xFF);
418
    memory[addr + 2] = (u8)((value >> 16) & 0xFF);
419
    memory[addr + 3] = (u8)((value >> 24) & 0xFF);
420
}
421
422
/* Store a 64-bit value to memory in little-endian byte order. */
423
static inline void memory_store_u64(u32 addr, u64 value) {
424
    assert(addr + 7 < g_opts.memory_size);
425
    memory_store_u32(addr, (u32)(value & 0xFFFFFFFF));
426
    memory_store_u32(addr + 4, (u32)(value >> 32));
427
}
428
429
/* Load a 32-bit word from memory, returning false if out of bounds. */
430
static inline bool load_word_safe(u32 addr, u32 *out) {
431
    if (addr > g_opts.memory_size - WORD_SIZE)
432
        return false;
433
    *out = memory_load_u32(addr);
434
    return true;
435
}
436
437
/* Dump register state and backtrace when a watchpoint fires. */
438
static void dump_watch_context(struct cpu *cpu, u32 addr, u32 size, u32 value) {
439
    if (!g_opts.watch_backtrace || !cpu)
440
        return;
441
442
    (void)addr;
443
    (void)size;
444
    (void)value;
445
446
    fprintf(
447
        stderr,
448
        "         regs: SP=%08x FP=%08x RA=%08x A0=%08x A1=%08x A2=%08x "
449
        "A3=%08x\n",
450
        (u32)cpu->regs[SP],
451
        (u32)cpu->regs[FP],
452
        (u32)cpu->regs[RA],
453
        (u32)cpu->regs[A0],
454
        (u32)cpu->regs[A1],
455
        (u32)cpu->regs[A2],
456
        (u32)cpu->regs[A3]
457
    );
458
459
    u64 fp64 = cpu->regs[FP];
460
    u32 fp   = (fp64 <= (u64)UINT32_MAX) ? (u32)fp64 : 0;
461
    u32 pc   = cpu->pc;
462
463
    fprintf(
464
        stderr, "         backtrace (depth %u):\n", g_opts.watch_backtrace_depth
465
    );
466
467
    for (u32 depth = 0; depth < g_opts.watch_backtrace_depth; depth++) {
468
        bool has_frame = stack_contains(fp) && fp >= (2 * WORD_SIZE);
469
        u32  saved_ra  = 0;
470
        u32  prev_fp   = 0;
471
472
        if (has_frame) {
473
            has_frame = load_word_safe(fp - WORD_SIZE, &saved_ra) &&
474
                        load_word_safe(fp - 2 * WORD_SIZE, &prev_fp) &&
475
                        stack_contains(prev_fp);
476
        }
477
478
        fprintf(
479
            stderr,
480
            "           #%u pc=%08x fp=%08x ra=%08x%s\n",
481
            depth,
482
            pc,
483
            fp,
484
            has_frame ? saved_ra : 0,
485
            has_frame ? "" : " (?)"
486
        );
487
488
        if (!has_frame || prev_fp == fp || prev_fp == 0)
489
            break;
490
491
        pc = saved_ra;
492
        fp = prev_fp;
493
    }
494
}
495
496
/* Print usage information and return 1. */
497
static int usage(const char *prog) {
498
    fprintf(
499
        stderr,
500
        "usage: %s [-run] [-no-guard-stack]"
501
        " [-stack-size=KB] [-no-validate] [-debug]"
502
        " [-trace|-trace-headless] [-trace-depth=n] [-trace-instructions]"
503
        " [-max-steps=n] [-memory-size=KB] [-data-size=KB]"
504
        " [-watch=addr] [-watch-size=bytes] [-watch-arm-pc=addr]"
505
        " [-watch-zero-only] [-watch-skip=n]"
506
        " [-watch-backtrace] [-watch-bt-depth=n]"
507
        " [-count-instructions] [-no-jit]"
508
        " <file.bin> [program args...]\n",
509
        prog
510
    );
511
    return 1;
512
}
513
514
/* Configuration parsed from CLI flags prior to launching the emulator. */
515
struct cli_config {
516
    bool        headless;
517
    const char *program_path;
518
    int         arg_index;
519
};
520
521
/* Parse a string as an unsigned 32-bit integer.  Returns false on error. */
522
static bool parse_u32(const char *str, const char *label, int base, u32 *out) {
523
    char *end         = NULL;
524
    errno             = 0;
525
    unsigned long val = strtoul(str, &end, base);
526
    if (errno != 0 || end == str || *end != '\0') {
527
        fprintf(stderr, "invalid %s '%s'; expected integer\n", label, str);
528
        return false;
529
    }
530
    if (val > UINT32_MAX)
531
        val = UINT32_MAX;
532
    *out = (u32)val;
533
    return true;
534
}
535
536
/* Parse a string as an unsigned 64-bit integer.  Returns false on error. */
537
static bool parse_u64(const char *str, const char *label, u64 *out) {
538
    char *end              = NULL;
539
    errno                  = 0;
540
    unsigned long long val = strtoull(str, &end, 10);
541
    if (errno != 0 || end == str || *end != '\0') {
542
        fprintf(stderr, "invalid %s '%s'; expected integer\n", label, str);
543
        return false;
544
    }
545
    *out = (u64)val;
546
    return true;
547
}
548
549
/* Parse and validate the physical memory size passed to -memory-size=. */
550
static bool parse_memory_size_value(const char *value) {
551
    u64 parsed;
552
    if (!parse_u64(value, "memory size", &parsed))
553
        return false;
554
    u64 bytes = parsed * 1024;
555
    if (bytes <= (u64)(DATA_MEMORY_START + WORD_SIZE)) {
556
        fprintf(
557
            stderr,
558
            "memory size too small; minimum is %u KB\n",
559
            (DATA_MEMORY_START + WORD_SIZE + 1024) / 1024
560
        );
561
        return false;
562
    }
563
    if (bytes > (u64)MEMORY_SIZE) {
564
        fprintf(
565
            stderr,
566
            "memory size too large; maximum is %u KB (recompile emulator "
567
            "to increase)\n",
568
            MEMORY_SIZE / 1024
569
        );
570
        return false;
571
    }
572
    g_opts.memory_size = (u32)bytes;
573
    return true;
574
}
575
576
/* Parse and validate the depth passed to -trace-depth=. */
577
static bool parse_trace_depth_value(const char *value) {
578
    u32 parsed;
579
    if (!parse_u32(value, "trace depth", 10, &parsed))
580
        return false;
581
    if (parsed == 0) {
582
        fprintf(stderr, "trace depth must be greater than zero\n");
583
        return false;
584
    }
585
    if (parsed > TRACE_HISTORY)
586
        parsed = TRACE_HISTORY;
587
    g_opts.trace_depth = parsed;
588
    return true;
589
}
590
591
/* Parse and validate the step limit passed to -max-steps=. */
592
static bool parse_max_steps_value(const char *value) {
593
    u64 parsed;
594
    if (!parse_u64(value, "max steps", &parsed))
595
        return false;
596
    if (parsed == 0) {
597
        fprintf(stderr, "max steps must be greater than zero\n");
598
        return false;
599
    }
600
    g_opts.headless_max_steps = parsed;
601
    return true;
602
}
603
604
/* Parse and validate the stack size passed to -stack-size=. */
605
static bool parse_stack_size_value(const char *value) {
606
    u64 parsed;
607
    if (!parse_u64(value, "stack size", &parsed))
608
        return false;
609
    if (parsed == 0) {
610
        fprintf(stderr, "stack size must be greater than zero\n");
611
        return false;
612
    }
613
    u64 bytes = parsed * 1024;
614
    if (bytes >= MEMORY_SIZE) {
615
        fprintf(
616
            stderr,
617
            "stack size too large; maximum is %u KB\n",
618
            MEMORY_SIZE / 1024
619
        );
620
        return false;
621
    }
622
    g_opts.stack_size = sanitize_stack_bytes((u32)bytes);
623
    return true;
624
}
625
626
/* Parse and validate the data size passed to -data-size=. */
627
static bool parse_data_size_value(const char *value) {
628
    u64 parsed;
629
    if (!parse_u64(value, "data size", &parsed))
630
        return false;
631
    if (parsed == 0) {
632
        fprintf(stderr, "data size must be greater than zero\n");
633
        return false;
634
    }
635
    u64 bytes = parsed * 1024;
636
    if (bytes > (u64)MEMORY_SIZE) {
637
        fprintf(
638
            stderr,
639
            "data size too large; maximum is %u KB\n",
640
            MEMORY_SIZE / 1024
641
        );
642
        return false;
643
    }
644
    g_opts.data_memory_size = (u32)bytes;
645
    return true;
646
}
647
648
/* Validate that the stack fits within available memory. */
649
static bool validate_memory_layout(void) {
650
    if (g_opts.stack_size >= g_opts.memory_size) {
651
        fprintf(
652
            stderr,
653
            "stack size (%u) must be smaller than memory size (%u)\n",
654
            g_opts.stack_size,
655
            g_opts.memory_size
656
        );
657
        return false;
658
    }
659
    return true;
660
}
661
662
/* Parse emulator CLI arguments, returning the selected mode and file path. */
663
static bool parse_cli_args(int argc, char *argv[], struct cli_config *cfg) {
664
    bool headless = false;
665
    int  argi     = 1;
666
667
    while (argi < argc) {
668
        const char *arg = argv[argi];
669
670
        if (strcmp(arg, "--") == 0) {
671
            argi++;
672
            break;
673
        }
674
        if (arg[0] != '-')
675
            break;
676
677
        if (strcmp(arg, "-run") == 0) {
678
            headless = true;
679
            argi++;
680
            continue;
681
        }
682
683
        if (strncmp(arg, "-stack-size=", 12) == 0) {
684
            if (!parse_stack_size_value(arg + 12))
685
                return false;
686
            argi++;
687
            continue;
688
        }
689
        if (strcmp(arg, "-no-guard-stack") == 0) {
690
            g_opts.stack_guard = false;
691
            argi++;
692
            continue;
693
        }
694
        if (strcmp(arg, "-no-validate") == 0) {
695
            g_opts.validate_memory = false;
696
            argi++;
697
            continue;
698
        }
699
        if (strcmp(arg, "-debug") == 0) {
700
            g_opts.debug_enabled = true;
701
            argi++;
702
            continue;
703
        }
704
        if (strcmp(arg, "-trace") == 0 || strcmp(arg, "-trace-headless") == 0) {
705
            g_opts.trace_enabled = true;
706
            argi++;
707
            continue;
708
        }
709
        if (strcmp(arg, "-trace-instructions") == 0) {
710
            g_opts.trace_print_instructions = true;
711
            argi++;
712
            continue;
713
        }
714
        if (strncmp(arg, "-trace-depth=", 13) == 0) {
715
            if (!parse_trace_depth_value(arg + 13))
716
                return false;
717
            argi++;
718
            continue;
719
        }
720
        if (strncmp(arg, "-max-steps=", 11) == 0) {
721
            if (!parse_max_steps_value(arg + 11))
722
                return false;
723
            argi++;
724
            continue;
725
        }
726
        if (strncmp(arg, "-memory-size=", 13) == 0) {
727
            if (!parse_memory_size_value(arg + 13))
728
                return false;
729
            argi++;
730
            continue;
731
        }
732
        if (strncmp(arg, "-data-size=", 11) == 0) {
733
            if (!parse_data_size_value(arg + 11))
734
                return false;
735
            argi++;
736
            continue;
737
        }
738
        if (strncmp(arg, "-watch=", 7) == 0) {
739
            if (!parse_u32(arg + 7, "watch address", 0, &g_opts.watch_addr))
740
                return false;
741
            g_opts.watch_enabled = true;
742
            argi++;
743
            continue;
744
        }
745
        if (strncmp(arg, "-watch-size=", 12) == 0) {
746
            if (!parse_u32(arg + 12, "watch size", 0, &g_opts.watch_size))
747
                return false;
748
            if (g_opts.watch_size == 0) {
749
                fprintf(stderr, "watch size must be greater than zero\n");
750
                return false;
751
            }
752
            argi++;
753
            continue;
754
        }
755
        if (strcmp(arg, "-watch-zero-only") == 0) {
756
            g_opts.watch_zero_only = true;
757
            argi++;
758
            continue;
759
        }
760
        if (strncmp(arg, "-watch-skip=", 12) == 0) {
761
            if (!parse_u32(arg + 12, "watch skip", 0, &g_opts.watch_skip))
762
                return false;
763
            argi++;
764
            continue;
765
        }
766
        if (strcmp(arg, "-watch-disable") == 0) {
767
            g_opts.watch_enabled = false;
768
            argi++;
769
            continue;
770
        }
771
        if (strncmp(arg, "-watch-arm-pc=", 14) == 0) {
772
            if (!parse_u32(arg + 14, "watch arm pc", 0, &g_opts.watch_arm_pc))
773
                return false;
774
            argi++;
775
            continue;
776
        }
777
        if (strcmp(arg, "-watch-backtrace") == 0) {
778
            g_opts.watch_backtrace = true;
779
            argi++;
780
            continue;
781
        }
782
        if (strncmp(arg, "-watch-bt-depth=", 16) == 0) {
783
            u32 depth;
784
            if (!parse_u32(arg + 16, "watch backtrace depth", 0, &depth))
785
                return false;
786
            if (depth == 0) {
787
                fprintf(
788
                    stderr, "watch backtrace depth must be greater than zero\n"
789
                );
790
                return false;
791
            }
792
            g_opts.watch_backtrace       = true;
793
            g_opts.watch_backtrace_depth = depth;
794
            argi++;
795
            continue;
796
        }
797
        if (strcmp(arg, "-count-instructions") == 0) {
798
            g_opts.count_instructions = true;
799
            argi++;
800
            continue;
801
        }
802
        if (strcmp(arg, "-no-jit") == 0) {
803
            g_opts.jit_disabled = true;
804
            argi++;
805
            continue;
806
        }
807
        usage(argv[0]);
808
809
        return false;
810
    }
811
    if (argi >= argc) {
812
        usage(argv[0]);
813
        return false;
814
    }
815
    cfg->program_path = argv[argi++];
816
    cfg->arg_index    = argi;
817
    cfg->headless     = headless;
818
    if (g_opts.watch_enabled && g_opts.watch_size == 0)
819
        g_opts.watch_size = 4;
820
    if (!validate_memory_layout())
821
        return false;
822
823
    g_opts.stack_size = sanitize_stack_bytes(g_opts.stack_size);
824
825
    return true;
826
}
827
828
/* Validate a load or store against memory bounds and stack guards. */
829
static bool validate_memory_access(
830
    struct cpu *cpu,
831
    u64         addr,
832
    u32         size,
833
    reg_t       base_reg,
834
    const char *op,
835
    bool        is_store
836
) {
837
    /* Skip validation for performance if disabled. */
838
    if (!g_opts.validate_memory)
839
        return true;
840
841
    if (size == 0)
842
        size = 1;
843
844
    const char *kind = is_store ? "store" : "load";
845
846
    u64 span_end = addr + (u64)size;
847
    if (addr > (u64)g_opts.memory_size || span_end > (u64)g_opts.memory_size) {
848
        printf(
849
            "Memory %s out of bounds at PC=%08x: addr=%016llx size=%u (%s)\n",
850
            kind,
851
            cpu->pc,
852
            (unsigned long long)addr,
853
            size,
854
            op
855
        );
856
        cpu->running = false;
857
        emit_fault_diagnostics(cpu, cpu->pc);
858
        return false;
859
    }
860
861
    u32 addr32 = (u32)addr;
862
    u32 end    = (u32)(span_end - 1);
863
864
    if (addr32 < DATA_MEMORY_START) {
865
        if (is_store) {
866
            printf(
867
                "Read-only memory store at PC=%08x: addr=%08x size=%u (%s)\n",
868
                cpu->pc,
869
                addr32,
870
                size,
871
                op
872
            );
873
            cpu->running = false;
874
            emit_fault_diagnostics(cpu, cpu->pc);
875
            return false;
876
        }
877
        return true;
878
    }
879
880
    u32  guard    = stack_guard_bytes();
881
    u64  base_val = cpu->regs[base_reg];
882
    bool base_in_stack =
883
        base_val <= (u64)UINT32_MAX && stack_contains((u32)base_val);
884
    bool start_in_stack = stack_contains(addr32);
885
    bool end_in_stack   = stack_contains(end);
886
887
    if (base_in_stack || start_in_stack || end_in_stack) {
888
        u32 bottom = stack_bottom();
889
        if (addr32 < bottom || end > memory_top()) {
890
            printf(
891
                "Stack %s out of bounds at PC=%08x: base=%s (0x%08x) addr=%08x "
892
                "size=%u (%s)\n",
893
                kind,
894
                cpu->pc,
895
                reg_names[base_reg],
896
                (u32)cpu->regs[base_reg],
897
                addr32,
898
                size,
899
                op
900
            );
901
            cpu->running = false;
902
            emit_fault_diagnostics(cpu, cpu->pc);
903
            return false;
904
        }
905
        if (stack_guard_overlaps(guard, addr32, end)) {
906
            printf(
907
                "Stack guard %s violation at PC=%08x: base=%s (0x%08x) "
908
                "addr=%08x "
909
                "size=%u guard=%u (%s)\n",
910
                kind,
911
                cpu->pc,
912
                reg_names[base_reg],
913
                (u32)cpu->regs[base_reg],
914
                addr32,
915
                size,
916
                guard,
917
                op
918
            );
919
            cpu->running = false;
920
            emit_fault_diagnostics(cpu, cpu->pc);
921
            return false;
922
        }
923
    }
924
    return true;
925
}
926
927
/* Validate that a register holds a valid stack address. */
928
static bool validate_stack_register(
929
    struct cpu *cpu, reg_t reg, const char *label, u32 pc, bool optional
930
) {
931
    /* Skip validation for performance if disabled. */
932
    if (!g_opts.validate_memory)
933
        return true;
934
935
    u64 value = cpu->regs[reg];
936
937
    if (optional && value == 0)
938
        return true;
939
940
    /* Detect addresses with upper bits set -- these can never be valid
941
     * stack addresses in the emulator's physical memory. */
942
    if (value > (u64)UINT32_MAX || (u32)value < stack_bottom() ||
943
        (u32)value > memory_top()) {
944
        printf(
945
            "%s (%s) out of stack bounds at PC=%08x: value=%016llx\n",
946
            label,
947
            reg_names[reg],
948
            pc,
949
            (unsigned long long)value
950
        );
951
        cpu->running = false;
952
        emit_fault_diagnostics(cpu, pc);
953
        return false;
954
    }
955
956
    u32 guard = stack_guard_bytes();
957
958
    if (stack_guard_contains(guard, (u32)value)) {
959
        printf(
960
            "Stack guard triggered by %s (%s) at PC=%08x: value=%08x "
961
            "guard=%u\n",
962
            label,
963
            reg_names[reg],
964
            pc,
965
            (u32)value,
966
            guard
967
        );
968
        cpu->running = false;
969
        emit_fault_diagnostics(cpu, pc);
970
        return false;
971
    }
972
    return true;
973
}
974
975
/* Toggle stack guarding in the TUI and re-validate live stack registers. */
976
static void toggle_stack_guard(struct cpu *cpu) {
977
    g_opts.stack_guard = !g_opts.stack_guard;
978
979
    printf(
980
        "\nStack guard %s (%u bytes)\n",
981
        g_opts.stack_guard ? "enabled" : "disabled",
982
        STACK_GUARD_BYTES
983
    );
984
985
    if (g_opts.stack_guard && cpu->running) {
986
        validate_stack_register(cpu, SP, "SP", cpu->pc, false);
987
        if (cpu->running) {
988
            validate_stack_register(cpu, FP, "FP", cpu->pc, true);
989
        }
990
    }
991
}
992
993
/* Get terminal dimensions. */
994
static struct termsize termsize(void) {
995
    struct winsize  w;
996
    struct termsize size = { 24, 80 }; /* Default fallback. */
997
998
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) {
999
        size.rows = w.ws_row;
1000
        size.cols = w.ws_col;
1001
    }
1002
    return size;
1003
}
1004
1005
/* Take a snapshot of the current CPU and memory state. */
1006
static void snapshot_save(struct cpu *cpu) {
1007
    int nexti = (snapshots.head + 1 + MAX_SNAPSHOTS) % MAX_SNAPSHOTS;
1008
1009
    memcpy(&snapshots.snapshots[nexti].cpu, cpu, sizeof(struct cpu));
1010
    memcpy(snapshots.snapshots[nexti].memory, memory, g_opts.memory_size);
1011
1012
    /* Fix the program pointer to reference the snapshot's own memory. */
1013
    snapshots.snapshots[nexti].cpu.program =
1014
        (instr_t *)snapshots.snapshots[nexti].memory;
1015
1016
    snapshots.head = nexti;
1017
    if (snapshots.count < MAX_SNAPSHOTS)
1018
        snapshots.count++;
1019
}
1020
1021
/* Restore the most recent snapshot, returning false if none remain. */
1022
static bool snapshot_restore(struct cpu *cpu) {
1023
    if (snapshots.count <= 1)
1024
        return false;
1025
1026
    snapshots.head = (snapshots.head + MAX_SNAPSHOTS - 1) % MAX_SNAPSHOTS;
1027
    snapshots.count--;
1028
1029
    int previ = snapshots.head;
1030
    memcpy(cpu, &snapshots.snapshots[previ].cpu, sizeof(struct cpu));
1031
    memcpy(memory, snapshots.snapshots[previ].memory, g_opts.memory_size);
1032
1033
    /* Fix the program pointer to reference the live memory buffer. */
1034
    cpu->program = (instr_t *)memory;
1035
1036
    return true;
1037
}
1038
1039
/* Initialize the snapshot buffer with an initial snapshot. */
1040
static void snapshot_init(struct cpu *cpu) {
1041
    snapshots.head  = -1;
1042
    snapshots.count = 0;
1043
    snapshot_save(cpu);
1044
}
1045
1046
/* Reset the headless instruction trace buffer. */
1047
static void trace_reset(void) {
1048
    headless_trace.head  = -1;
1049
    headless_trace.count = 0;
1050
}
1051
1052
/* Record the current instruction into the trace ring buffer. */
1053
static void trace_record(struct cpu *cpu, instr_t ins) {
1054
    if (!g_opts.trace_enabled || !g_opts.trace_headless)
1055
        return;
1056
1057
    int next = (headless_trace.head + 1 + TRACE_HISTORY) % TRACE_HISTORY;
1058
1059
    headless_trace.head                = next;
1060
    headless_trace.entries[next].pc    = cpu->pc;
1061
    headless_trace.entries[next].instr = ins;
1062
    memcpy(headless_trace.entries[next].regs, cpu->regs, sizeof(cpu->regs));
1063
    if (headless_trace.count < TRACE_HISTORY)
1064
        headless_trace.count++;
1065
}
1066
1067
/* Dump the headless instruction trace to stdout. */
1068
static bool trace_dump(u32 fault_pc) {
1069
    if (!g_opts.trace_enabled || !g_opts.trace_headless ||
1070
        headless_trace.count == 0)
1071
        return false;
1072
1073
    int limit = (int)g_opts.trace_depth;
1074
    if (limit <= 0)
1075
        limit = FAULT_TRACE_DEPTH;
1076
    if (limit > TRACE_HISTORY)
1077
        limit = TRACE_HISTORY;
1078
    if (limit > headless_trace.count)
1079
        limit = headless_trace.count;
1080
1081
    printf("Headless trace (newest first):\n");
1082
    for (int i = 0; i < limit; i++) {
1083
        int idx = (headless_trace.head - i + TRACE_HISTORY) % TRACE_HISTORY;
1084
        struct trace_entry *entry = &headless_trace.entries[idx];
1085
        char                istr[MAX_INSTR_STR_LEN] = { 0 };
1086
1087
        sprint_instr(entry->instr, istr, true);
1088
1089
        printf(
1090
            "  [%d] PC=%08x %s%s\n",
1091
            i,
1092
            entry->pc,
1093
            istr,
1094
            (entry->pc == fault_pc) ? "  <-- fault" : ""
1095
        );
1096
        printf(
1097
            "       SP=%08x FP=%08x RA=%08x A0=%08x A1=%08x A2=%08x\n",
1098
            (u32)entry->regs[SP],
1099
            (u32)entry->regs[FP],
1100
            (u32)entry->regs[RA],
1101
            (u32)entry->regs[A0],
1102
            (u32)entry->regs[A1],
1103
            (u32)entry->regs[A2]
1104
        );
1105
    }
1106
    return true;
1107
}
1108
1109
/* Dump recent snapshot history to stdout for fault diagnostics. */
1110
static bool snapshot_dump_history(struct cpu *cpu, u32 fault_pc) {
1111
    (void)cpu;
1112
    if (snapshots.count == 0)
1113
        return false;
1114
1115
    int limit = FAULT_TRACE_DEPTH;
1116
    if (limit > snapshots.count)
1117
        limit = snapshots.count;
1118
1119
    printf("Snapshot history (newest first):\n");
1120
    for (int i = 0; i < limit; i++) {
1121
        int idx = (snapshots.head - i + MAX_SNAPSHOTS) % MAX_SNAPSHOTS;
1122
        struct snapshot *snap    = &snapshots.snapshots[idx];
1123
        u32              next_pc = snap->cpu.pc;
1124
        u32  exec_pc = next_pc >= INSTR_SIZE ? next_pc - INSTR_SIZE : next_pc;
1125
        char istr[MAX_INSTR_STR_LEN] = { 0 };
1126
        u32  instr_index             = exec_pc / INSTR_SIZE;
1127
1128
        if (instr_index < snap->cpu.programsize) {
1129
            sprint_instr(snap->cpu.program[instr_index], istr, true);
1130
        } else {
1131
            snprintf(istr, sizeof(istr), "<pc %08x>", exec_pc);
1132
        }
1133
1134
        printf(
1135
            "  [%d] PC next=%08x prev=%08x %s%s\n",
1136
            i,
1137
            next_pc,
1138
            exec_pc,
1139
            istr,
1140
            (exec_pc == fault_pc || next_pc == fault_pc) ? "  <-- fault" : ""
1141
        );
1142
        printf(
1143
            "       SP=%08x FP=%08x RA=%08x A0=%08x\n",
1144
            (u32)snap->cpu.regs[SP],
1145
            (u32)snap->cpu.regs[FP],
1146
            (u32)snap->cpu.regs[RA],
1147
            (u32)snap->cpu.regs[A0]
1148
        );
1149
    }
1150
    return true;
1151
}
1152
1153
/* Emit runtime fault diagnostics including trace and snapshot history. */
1154
static void emit_fault_diagnostics(struct cpu *cpu, u32 pc) {
1155
    if (cpu->faulted)
1156
        return;
1157
1158
    cpu->faulted = true;
1159
1160
    printf("\n--- runtime fault diagnostics ---\n");
1161
    bool printed = false;
1162
1163
    printed |= trace_dump(pc);
1164
    printed |= snapshot_dump_history(cpu, pc);
1165
1166
    if (!printed) {
1167
        printf("No trace data available.\n");
1168
    }
1169
    printf("--- end diagnostics ---\n");
1170
    fflush(stdout);
1171
}
1172
1173
/* Return true if the CPU's PC is outside the loaded program bounds. */
1174
static inline bool cpu_out_of_bounds(struct cpu *cpu) {
1175
    if (!g_opts.validate_memory)
1176
        return false;
1177
    if (program_bytes == 0)
1178
        return true;
1179
    if (cpu->pc < program_base)
1180
        return true;
1181
    return (cpu->pc - program_base) >= program_bytes;
1182
}
1183
1184
/* Last executed PC, used for detecting branches/jumps in trace mode. */
1185
static u32 last_executed_pc = 0;
1186
1187
/* Reset CPU state (keeping program loaded). */
1188
static void cpu_reset(struct cpu *cpu) {
1189
    trace_reset();
1190
    memset(cpu->regs, 0, sizeof(cpu->regs));
1191
1192
    /* Set SP to the top of the usable stack, aligned to 16 bytes
1193
     * as required by the RISC-V ABI. */
1194
    cpu->regs[SP]    = stack_usable_top() & ~0xF;
1195
    cpu->pc          = program_base;
1196
    cpu->running     = true;
1197
    cpu->faulted     = false;
1198
    cpu->ebreak      = false;
1199
    cpu->modified    = ZERO;
1200
    last_executed_pc = 0;
1201
}
1202
1203
/* Initialize CPU and memory to a clean state. */
1204
static void cpu_init(struct cpu *cpu) {
1205
    memset(memory, 0, g_opts.memory_size);
1206
    cpu->program     = (instr_t *)memory;
1207
    cpu->programsize = 0;
1208
    trace_reset();
1209
    guest_fd_table_init();
1210
    cpu_reset(cpu);
1211
}
1212
1213
/* Open a file via the openat syscall (56). */
1214
static i32 ecall_openat(u32 pathname_addr, i32 flags) {
1215
    if (pathname_addr >= g_opts.memory_size)
1216
        return -1;
1217
1218
    /* Find the null terminator to validate the string is in bounds. */
1219
    u32 path_end = pathname_addr;
1220
    while (path_end < g_opts.memory_size && memory[path_end] != 0)
1221
        path_end++;
1222
    if (path_end >= g_opts.memory_size)
1223
        return -1;
1224
1225
    i32 host_fd = open((const char *)&memory[pathname_addr], flags, 0644);
1226
    if (host_fd < 0)
1227
        return -1;
1228
1229
    i32 guest_fd = guest_fd_table_add(host_fd);
1230
    if (guest_fd < 0) {
1231
        close(host_fd);
1232
        return -1;
1233
    }
1234
    return guest_fd;
1235
}
1236
1237
/* Close a file descriptor via the close syscall (57). */
1238
static i32 ecall_close(i32 guest_fd) {
1239
    /* Don't close standard streams. */
1240
    if (guest_fd < 3)
1241
        return 0;
1242
1243
    i32 host_fd = guest_fd_table_get(guest_fd);
1244
    if (host_fd >= 0) {
1245
        i32 result = close(host_fd);
1246
        guest_fd_table_remove(guest_fd);
1247
        return result;
1248
    }
1249
    return -1;
1250
}
1251
1252
/* Load a binary section from disk into emulator memory at the given offset. */
1253
static u32 load_section(
1254
    const char *filepath,
1255
    const char *suffix,
1256
    u32         offset,
1257
    u32         limit,
1258
    const char *label
1259
) {
1260
    char path[PATH_MAX];
1261
    snprintf(path, sizeof(path), "%s.%s", filepath, suffix);
1262
1263
    FILE *file = fopen(path, "rb");
1264
    if (!file)
1265
        return 0;
1266
1267
    if (fseek(file, 0, SEEK_END) != 0) {
1268
        fclose(file);
1269
        bail("failed to seek %s section", label);
1270
    }
1271
    long size = ftell(file);
1272
    if (size < 0) {
1273
        fclose(file);
1274
        bail("failed to determine size of %s section", label);
1275
    }
1276
    if (fseek(file, 0, SEEK_SET) != 0) {
1277
        fclose(file);
1278
        bail("failed to rewind %s section", label);
1279
    }
1280
    if (size == 0) {
1281
        fclose(file);
1282
        return 0;
1283
    }
1284
1285
    u32 u_size = (u32)size;
1286
    u64 end    = (u64)offset + (u64)u_size;
1287
    if (end > (u64)limit) {
1288
        fclose(file);
1289
        u32 max_size = limit - offset;
1290
        bail(
1291
            "%s section too large for emulator memory: required %u bytes, max "
1292
            "%u bytes",
1293
            label,
1294
            u_size,
1295
            max_size
1296
        );
1297
    }
1298
    if (end > (u64)g_opts.memory_size) {
1299
        fclose(file);
1300
        u32 max_size = g_opts.memory_size - offset;
1301
        bail(
1302
            "%s section exceeds physical memory: required %u bytes at offset "
1303
            "%u, "
1304
            "but only %u bytes available (total memory=%u, use "
1305
            "-memory-size=... or recompile emulator with a larger "
1306
            "MEMORY_SIZE)",
1307
            label,
1308
            u_size,
1309
            offset,
1310
            max_size,
1311
            g_opts.memory_size
1312
        );
1313
    }
1314
    size_t read = fread(&memory[offset], 1, u_size, file);
1315
    fclose(file);
1316
1317
    if (read != u_size) {
1318
        bail(
1319
            "could not read entire %s section: read %zu bytes, expected %u "
1320
            "bytes (offset=%u, limit=%u)",
1321
            label,
1322
            read,
1323
            u_size,
1324
            offset,
1325
            limit
1326
        );
1327
    }
1328
    return u_size;
1329
}
1330
1331
/* Prepare the environment block (argv) on the guest stack. */
1332
static void prepare_env(struct cpu *cpu, int argc, char **argv) {
1333
    if (argc < 0 || argv == NULL)
1334
        argc = 0;
1335
1336
    usize bytes = 0;
1337
    for (int i = 0; i < argc; i++)
1338
        bytes += strlen(argv[i]) + 1; /* Include terminating NUL. */
1339
1340
    /* In RV64, slices are 16 bytes (8-byte ptr + 4-byte len + 4 padding). */
1341
    u32 slice_size       = 16;
1342
    u32 slice_array_size = (u32)argc * slice_size;
1343
    u32 base_size        = slice_size + slice_array_size;
1344
    u32 total_size       = align(base_size + (u32)bytes, 16);
1345
1346
    /* Place the env block below the current stack pointer so it doesn't
1347
     * overlap with uninitialized static data.  The .rw.data file only
1348
     * contains initialized statics; undefined statics occupy memory after
1349
     * the loaded data but are not in the file.  Placing the env block in
1350
     * the data region would clobber those zero-initialized areas. */
1351
    u32 sp       = (u32)cpu->regs[SP];
1352
    u32 env_addr = (sp - total_size) & ~0xFu;
1353
1354
    if (env_addr <= DATA_MEMORY_START + data_bytes)
1355
        bail("not enough memory to prepare environment block");
1356
1357
    /* Move SP below the env block so the program's stack doesn't overwrite it.
1358
     */
1359
    cpu->regs[SP] = env_addr;
1360
1361
    u32 slices_addr  = env_addr + slice_size;
1362
    u32 strings_addr = slices_addr + (argc > 0 ? slice_array_size : 0);
1363
1364
    /* Write the Env slice header. */
1365
    memory_store_u64(env_addr, argc > 0 ? slices_addr : 0);
1366
    memory_store_u32(env_addr + 8, (u32)argc);
1367
1368
    /* Copy argument strings and populate slices. */
1369
    u32 curr = strings_addr;
1370
    for (int i = 0; i < argc; i++) {
1371
        size_t len = strlen(argv[i]);
1372
        if (curr + len >= g_opts.memory_size)
1373
            bail("environment string does not fit in emulator memory");
1374
1375
        memcpy(&memory[curr], argv[i], len);
1376
        memory[curr + len] = 0; /* Null-terminate for syscall compatibility. */
1377
1378
        u32 slice_entry = slices_addr + (u32)i * slice_size;
1379
        memory_store_u64(slice_entry, curr);
1380
        memory_store_u32(slice_entry + 8, (u32)len);
1381
1382
        curr += (u32)len + 1;
1383
    }
1384
    cpu->regs[A0] = env_addr;
1385
    cpu->regs[A1] = env_addr;
1386
}
1387
1388
/* Load debug information from the .debug file. */
1389
static void debug_load(const char *program_path) {
1390
    char debugpath[PATH_MAX];
1391
    snprintf(debugpath, sizeof(debugpath), "%s.debug", program_path);
1392
1393
    FILE *file = fopen(debugpath, "rb");
1394
    if (!file)
1395
        return; /* Debug file is optional. */
1396
1397
    g_debug.capacity = 64;
1398
    g_debug.entries  = malloc(sizeof(struct debug_entry) * g_debug.capacity);
1399
    g_debug.count    = 0;
1400
1401
    if (!g_debug.entries) {
1402
        fclose(file);
1403
        return;
1404
    }
1405
    while (!feof(file)) {
1406
        struct debug_entry entry;
1407
1408
        if (fread(&entry.pc, sizeof(u32), 1, file) != 1)
1409
            break;
1410
        if (fread(&entry.offset, sizeof(u32), 1, file) != 1)
1411
            break;
1412
1413
        /* Read null-terminated file path. */
1414
        size_t i = 0;
1415
        int    c;
1416
        while (i < PATH_MAX - 1 && (c = fgetc(file)) != EOF && c != '\0') {
1417
            entry.file[i++] = (char)c;
1418
        }
1419
        entry.file[i] = '\0';
1420
1421
        if (c == EOF && i == 0)
1422
            break;
1423
1424
        /* Grow array if needed. */
1425
        if (g_debug.count >= g_debug.capacity) {
1426
            g_debug.capacity *= 2;
1427
            g_debug.entries   = realloc(
1428
                g_debug.entries, sizeof(struct debug_entry) * g_debug.capacity
1429
            );
1430
            if (!g_debug.entries) {
1431
                fclose(file);
1432
                return;
1433
            }
1434
        }
1435
        g_debug.entries[g_debug.count++] = entry;
1436
    }
1437
    fclose(file);
1438
}
1439
1440
/* Look up source location for a given PC. */
1441
static struct debug_entry *debug_lookup(u32 pc) {
1442
    struct debug_entry *best = NULL;
1443
1444
    for (size_t i = 0; i < g_debug.count; i++) {
1445
        if (g_debug.entries[i].pc == pc) {
1446
            return &g_debug.entries[i];
1447
        }
1448
        /* Track the closest entry at or before this PC. */
1449
        if (g_debug.entries[i].pc <= pc) {
1450
            best = &g_debug.entries[i];
1451
        }
1452
    }
1453
    return best;
1454
}
1455
1456
/* Compute the line number from a file path and byte offset. */
1457
static int line_from_offset(const char *filepath, u32 offset) {
1458
    FILE *file = fopen(filepath, "r");
1459
    if (!file)
1460
        return 0;
1461
1462
    u32 line = 1;
1463
    for (u32 i = 0; i < offset; i++) {
1464
        int c = fgetc(file);
1465
        if (c == EOF)
1466
            break;
1467
        if (c == '\n')
1468
            line++;
1469
    }
1470
    fclose(file);
1471
1472
    return line;
1473
}
1474
1475
/* Load the program binary and data sections into memory. */
1476
static void program_init(struct cpu *cpu, const char *filepath) {
1477
    program_bytes = 0;
1478
    data_bytes    = 0;
1479
    rodata_bytes  = load_section(
1480
        filepath, "ro.data", DATA_RO_OFFSET, DATA_MEMORY_START, "ro.data"
1481
    );
1482
    program_base = align(DATA_RO_OFFSET + rodata_bytes, WORD_SIZE);
1483
1484
    if (g_opts.debug_enabled)
1485
        debug_load(filepath);
1486
1487
    FILE *file = fopen(filepath, "rb");
1488
    if (!file)
1489
        bail("failed to open file '%s'", filepath);
1490
    if (fseek(file, 0, SEEK_END) != 0) {
1491
        fclose(file);
1492
        bail("failed to seek program '%s'", filepath);
1493
    }
1494
    long size = ftell(file);
1495
    if (size <= 0 || size > PROGRAM_SIZE) {
1496
        fclose(file);
1497
        bail(
1498
            "invalid file size: %ld; maximum program size is %d bytes",
1499
            size,
1500
            PROGRAM_SIZE
1501
        );
1502
    }
1503
    if (program_base + (u32)size > DATA_MEMORY_START) {
1504
        fclose(file);
1505
        bail("text section exceeds available program memory");
1506
    }
1507
    if (fseek(file, 0, SEEK_SET) != 0) {
1508
        fclose(file);
1509
        bail("failed to rewind program '%s'", filepath);
1510
    }
1511
1512
    usize read = fread(&memory[program_base], 1, size, file);
1513
    fclose(file);
1514
    if (read != (size_t)size)
1515
        bail("could not read entire file");
1516
1517
    program_bytes    = (u32)size;
1518
    cpu->programsize = (u32)size / sizeof(instr_t);
1519
1520
    u32 data_limit = DATA_MEMORY_START + g_opts.data_memory_size;
1521
    if (data_limit > g_opts.memory_size)
1522
        data_limit = g_opts.memory_size;
1523
1524
    data_bytes = load_section(
1525
        filepath, "rw.data", DATA_MEMORY_START, data_limit, "rw.data"
1526
    );
1527
    cpu->pc = program_base;
1528
}
1529
1530
/* Execute a single instruction. */
1531
static void cpu_execute(struct cpu *cpu, enum display display, bool headless) {
1532
    if (cpu_out_of_bounds(cpu)) {
1533
        cpu->running = false;
1534
        emit_fault_diagnostics(cpu, cpu->pc);
1535
        if (headless) {
1536
            fprintf(stderr, "program is out of bounds\n");
1537
            return;
1538
        }
1539
        bail("program is out of bounds");
1540
    }
1541
1542
    u32     executed_pc = cpu->pc;
1543
    instr_t ins         = cpu->program[cpu->pc / sizeof(instr_t)];
1544
    u32     pc_next     = cpu->pc + INSTR_SIZE;
1545
    u32     opcode      = ins.r.opcode;
1546
1547
    cpu->modified = ZERO;
1548
    trace_record(cpu, ins);
1549
1550
    /* Print instruction if tracing is enabled in headless mode.
1551
     * Skip NOPs (addi x0, x0, 0 = 0x00000013). */
1552
    if (headless && g_opts.trace_print_instructions && ins.raw != 0x00000013) {
1553
        /* Print ellipsis if we jumped to a non-sequential instruction. */
1554
        if (last_executed_pc != 0 &&
1555
            executed_pc != last_executed_pc + INSTR_SIZE) {
1556
            printf("%s  :%s\n", COLOR_GREY, COLOR_RESET);
1557
        }
1558
1559
        char istr[MAX_INSTR_STR_LEN] = { 0 };
1560
        int  len                     = sprint_instr(ins, istr, true);
1561
        int  padding                 = INSTR_STR_LEN - len;
1562
        if (padding < 0)
1563
            padding = 0;
1564
        printf(
1565
            "%s%08x%s %s%-*s%s",
1566
            COLOR_GREY,
1567
            executed_pc,
1568
            COLOR_RESET,
1569
            istr,
1570
            padding,
1571
            "",
1572
            COLOR_GREY
1573
        );
1574
1575
        /* Print all non-zero registers. */
1576
        bool first = true;
1577
        for (int i = 0; i < REGISTERS; i++) {
1578
            if (cpu->regs[i] != 0) {
1579
                if (!first)
1580
                    printf(" ");
1581
                printf("%s=%08x", reg_names[i], (u32)cpu->regs[i]);
1582
                first = false;
1583
            }
1584
        }
1585
        printf("%s\n", COLOR_RESET);
1586
    }
1587
1588
    switch (opcode) {
1589
    case OP_LUI:
1590
        if (ins.u.rd != 0) {
1591
            u32 lui_val = ins.u.imm_31_12 << 12;
1592
            cpu->regs[ins.u.rd] =
1593
                (u64)(i64)(i32)lui_val; /* RV64: sign-extend to 64 bits. */
1594
            cpu->modified = ins.u.rd;
1595
        }
1596
        break;
1597
1598
    case OP_AUIPC:
1599
        if (ins.u.rd != 0) {
1600
            u32 auipc_val = ins.u.imm_31_12 << 12;
1601
            cpu->regs[ins.u.rd] =
1602
                cpu->pc +
1603
                (u64)(i64)(i32)auipc_val; /* RV64: sign-extend offset. */
1604
            cpu->modified = (reg_t)ins.u.rd;
1605
        }
1606
        break;
1607
1608
    case OP_JAL: {
1609
        i32 imm = get_j_imm(ins);
1610
        if (ins.j.rd != 0) {
1611
            cpu->regs[ins.j.rd] = pc_next;
1612
            cpu->modified       = (reg_t)ins.j.rd;
1613
        }
1614
        pc_next = cpu->pc + imm;
1615
        break;
1616
    }
1617
1618
    case OP_JALR: {
1619
        i32 imm = get_i_imm(ins);
1620
        if (ins.i.rd != 0) {
1621
            cpu->regs[ins.i.rd] = pc_next;
1622
            cpu->modified       = (reg_t)ins.i.rd;
1623
        }
1624
        /* Calculate target address in full 64-bit precision. */
1625
        u64 jalr_target = (cpu->regs[ins.i.rs1] + (i64)imm) & ~(u64)1;
1626
        /* Check if this is a RET instruction (jalr x0, ra, 0). */
1627
        if (ins.i.rd == 0 && ins.i.rs1 == 1 && imm == 0 && jalr_target == 0) {
1628
            cpu->running = false;
1629
            if (!headless) {
1630
                ui_render(cpu, display);
1631
1632
                printf(
1633
                    "\n%sProgram terminated with return value %d (0x%08x)%s ",
1634
                    COLOR_BOLD_GREEN,
1635
                    (i32)cpu->regs[A0],
1636
                    (u32)cpu->regs[A0],
1637
                    COLOR_RESET
1638
                );
1639
            }
1640
        } else {
1641
            pc_next = (u32)jalr_target;
1642
        }
1643
        break;
1644
    }
1645
1646
    case OP_BRANCH: {
1647
        bool jump = false;
1648
        i32  imm  = get_b_imm(ins);
1649
1650
        switch (ins.b.funct3) {
1651
        case FUNCT3_BYTE: /* beq.  */
1652
            jump = (cpu->regs[ins.b.rs1] == cpu->regs[ins.b.rs2]);
1653
            break;
1654
        case FUNCT3_HALF: /* bne.  */
1655
            jump = (cpu->regs[ins.b.rs1] != cpu->regs[ins.b.rs2]);
1656
            break;
1657
        case FUNCT3_BYTE_U: /* blt.  */
1658
            jump = ((i64)cpu->regs[ins.b.rs1] < (i64)cpu->regs[ins.b.rs2]);
1659
            break;
1660
        case FUNCT3_HALF_U: /* bge.  */
1661
            jump = ((i64)cpu->regs[ins.b.rs1] >= (i64)cpu->regs[ins.b.rs2]);
1662
            break;
1663
        case FUNCT3_OR: /* bltu. */
1664
            jump = (cpu->regs[ins.b.rs1] < cpu->regs[ins.b.rs2]);
1665
            break;
1666
        case FUNCT3_AND: /* bgeu. */
1667
            jump = (cpu->regs[ins.b.rs1] >= cpu->regs[ins.b.rs2]);
1668
            break;
1669
        }
1670
        if (jump) {
1671
            pc_next = cpu->pc + imm;
1672
        }
1673
        break;
1674
    }
1675
1676
    case OP_LOAD: {
1677
        i32 imm  = get_i_imm(ins);
1678
        u64 addr = cpu->regs[ins.i.rs1] + (i64)imm;
1679
1680
        if (ins.i.rd == ZERO)
1681
            break;
1682
1683
        cpu->modified = (reg_t)ins.i.rd;
1684
        bool fault    = false;
1685
1686
        switch (ins.i.funct3) {
1687
        case FUNCT3_BYTE: /* lb. */
1688
            if (!validate_memory_access(cpu, addr, 1, ins.i.rs1, "lb", false)) {
1689
                fault = true;
1690
                break;
1691
            }
1692
            /* sign_extend returns i32; on RV64 we sign-extend to 64 bits. */
1693
            cpu->regs[ins.i.rd] = (u64)(i64)sign_extend(memory[addr], 8);
1694
            break;
1695
        case FUNCT3_HALF: /* lh. */
1696
            if (!validate_memory_access(cpu, addr, 2, ins.i.rs1, "lh", false)) {
1697
                fault = true;
1698
                break;
1699
            }
1700
            cpu->regs[ins.i.rd] =
1701
                (u64)(i64)sign_extend(memory_load_u16(addr), 16);
1702
            break;
1703
        case FUNCT3_WORD: /* lw. */
1704
            if (!validate_memory_access(cpu, addr, 4, ins.i.rs1, "lw", false)) {
1705
                fault = true;
1706
                break;
1707
            }
1708
            /* RV64: lw sign-extends the 32-bit value to 64 bits. */
1709
            cpu->regs[ins.i.rd] = (u64)(i64)(i32)memory_load_u32(addr);
1710
            break;
1711
        case 0x6: /* lwu (RV64). */
1712
            if (!validate_memory_access(
1713
                    cpu, addr, 4, ins.i.rs1, "lwu", false
1714
                )) {
1715
                fault = true;
1716
                break;
1717
            }
1718
            cpu->regs[ins.i.rd] = (u64)memory_load_u32(addr);
1719
            break;
1720
        case FUNCT3_BYTE_U: /* lbu. */
1721
            if (!validate_memory_access(
1722
                    cpu, addr, 1, ins.i.rs1, "lbu", false
1723
                )) {
1724
                fault = true;
1725
                break;
1726
            }
1727
            cpu->regs[ins.i.rd] = memory[addr];
1728
            break;
1729
        case FUNCT3_HALF_U: /* lhu. */
1730
            if (!validate_memory_access(
1731
                    cpu, addr, 2, ins.i.rs1, "lhu", false
1732
                )) {
1733
                fault = true;
1734
                break;
1735
            }
1736
            cpu->regs[ins.i.rd] = memory_load_u16(addr);
1737
            break;
1738
        case 0x3: /* ld (RV64). */
1739
            if (!validate_memory_access(cpu, addr, 8, ins.i.rs1, "ld", false)) {
1740
                fault = true;
1741
                break;
1742
            }
1743
            cpu->regs[ins.i.rd] = memory_load_u64(addr);
1744
            break;
1745
        }
1746
        if (fault || !cpu->running)
1747
            break;
1748
        break;
1749
    }
1750
1751
    case OP_STORE: {
1752
        i32 imm  = get_s_imm(ins);
1753
        u64 addr = cpu->regs[ins.s.rs1] + (i64)imm;
1754
1755
        switch (ins.s.funct3) {
1756
        case FUNCT3_BYTE: /* sb. */
1757
            if (!validate_memory_access(cpu, addr, 1, ins.s.rs1, "sb", true))
1758
                break;
1759
            watch_store(cpu, (u32)addr, 1, (u32)cpu->regs[ins.s.rs2]);
1760
            memory_store_u8(addr, (u8)cpu->regs[ins.s.rs2]);
1761
            break;
1762
        case FUNCT3_HALF: /* sh. */
1763
            if (!validate_memory_access(cpu, addr, 2, ins.s.rs1, "sh", true))
1764
                break;
1765
            watch_store(cpu, (u32)addr, 2, (u32)cpu->regs[ins.s.rs2]);
1766
            memory_store_u16(addr, (u16)cpu->regs[ins.s.rs2]);
1767
            break;
1768
        case FUNCT3_WORD: /* sw. */
1769
            if (!validate_memory_access(cpu, addr, 4, ins.s.rs1, "sw", true))
1770
                break;
1771
            watch_store(cpu, (u32)addr, 4, (u32)cpu->regs[ins.s.rs2]);
1772
            memory_store_u32(addr, (u32)cpu->regs[ins.s.rs2]);
1773
            break;
1774
        case 0x3: /* sd (RV64). */
1775
            if (!validate_memory_access(cpu, addr, 8, ins.s.rs1, "sd", true))
1776
                break;
1777
            watch_store(cpu, (u32)addr, 8, (u32)cpu->regs[ins.s.rs2]);
1778
            memory_store_u64(addr, cpu->regs[ins.s.rs2]);
1779
            break;
1780
        }
1781
        break;
1782
    }
1783
1784
    case OP_IMM: {
1785
        i32 imm        = get_i_imm(ins);
1786
        u32 shamt_mask = 0x3F; /* RV64: 6-bit shift amounts. */
1787
1788
        if (ins.i.rd == ZERO)
1789
            break;
1790
1791
        cpu->modified = (reg_t)ins.i.rd;
1792
1793
        switch (ins.i.funct3) {
1794
        case FUNCT3_ADD: /* addi.  */
1795
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] + imm;
1796
            break;
1797
        case FUNCT3_SLL: /* slli.  */
1798
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] << (imm & shamt_mask);
1799
            break;
1800
        case FUNCT3_SLT: /* slti.  */
1801
            cpu->regs[ins.i.rd] =
1802
                ((i64)cpu->regs[ins.i.rs1] < (i64)imm) ? 1 : 0;
1803
            break;
1804
        case FUNCT3_SLTU: /* sltiu. */
1805
            cpu->regs[ins.i.rd] =
1806
                (cpu->regs[ins.i.rs1] < (u64)(i64)imm) ? 1 : 0;
1807
            break;
1808
        case FUNCT3_XOR: /* xori.  */
1809
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] ^ imm;
1810
            break;
1811
        case FUNCT3_SRL: /* srli/srai. */
1812
            if ((imm & 0x400) == 0) {
1813
                /* srli -- logical right shift. */
1814
                cpu->regs[ins.i.rd] =
1815
                    cpu->regs[ins.i.rs1] >> (imm & shamt_mask);
1816
            } else {
1817
                /* srai -- arithmetic right shift. */
1818
                cpu->regs[ins.i.rd] =
1819
                    (u64)((i64)cpu->regs[ins.i.rs1] >> (imm & shamt_mask));
1820
            }
1821
            break;
1822
        case FUNCT3_OR: /* ori.   */
1823
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] | imm;
1824
            break;
1825
        case FUNCT3_AND: /* andi.  */
1826
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] & imm;
1827
            break;
1828
        }
1829
        break;
1830
    }
1831
1832
    case OP_IMM_32: {
1833
        /* RV64I: 32-bit immediate operations (ADDIW, SLLIW, SRLIW, SRAIW).
1834
         * These operate on the lower 32 bits and sign-extend the result. */
1835
        i32 imm = get_i_imm(ins);
1836
1837
        if (ins.i.rd == ZERO)
1838
            break;
1839
1840
        cpu->modified = (reg_t)ins.i.rd;
1841
1842
        switch (ins.i.funct3) {
1843
        case FUNCT3_ADD: { /* addiw. */
1844
            i32 result          = (i32)cpu->regs[ins.i.rs1] + imm;
1845
            cpu->regs[ins.i.rd] = (u64)(i64)result;
1846
            break;
1847
        }
1848
        case FUNCT3_SLL: { /* slliw. */
1849
            i32 result = (i32)((u32)cpu->regs[ins.i.rs1] << (imm & 0x1F));
1850
            cpu->regs[ins.i.rd] = (u64)(i64)result;
1851
            break;
1852
        }
1853
        case FUNCT3_SRL: { /* srliw/sraiw. */
1854
            if ((imm & 0x400) == 0) {
1855
                /* srliw -- logical right shift, then sign-extend. */
1856
                i32 result = (i32)((u32)cpu->regs[ins.i.rs1] >> (imm & 0x1F));
1857
                cpu->regs[ins.i.rd] = (u64)(i64)result;
1858
            } else {
1859
                /* sraiw -- arithmetic right shift, then sign-extend. */
1860
                i32 result          = (i32)cpu->regs[ins.i.rs1] >> (imm & 0x1F);
1861
                cpu->regs[ins.i.rd] = (u64)(i64)result;
1862
            }
1863
            break;
1864
        }
1865
        }
1866
        break;
1867
    }
1868
1869
    case OP_OP: {
1870
        if (ins.r.rd == ZERO)
1871
            break;
1872
1873
        cpu->modified = (reg_t)ins.r.rd;
1874
1875
        switch (ins.r.funct7) {
1876
        case FUNCT7_NORMAL: {
1877
            u32 shamt_mask = 0x3F;
1878
            switch (ins.r.funct3) {
1879
            case FUNCT3_ADD: /* add.  */
1880
                cpu->regs[ins.r.rd] =
1881
                    cpu->regs[ins.r.rs1] + cpu->regs[ins.r.rs2];
1882
                break;
1883
            case FUNCT3_SLL: /* sll.  */
1884
                cpu->regs[ins.r.rd] = cpu->regs[ins.r.rs1]
1885
                                      << (cpu->regs[ins.r.rs2] & shamt_mask);
1886
                break;
1887
            case FUNCT3_SLT: /* slt.  */
1888
                cpu->regs[ins.r.rd] =
1889
                    ((i64)cpu->regs[ins.r.rs1] < (i64)cpu->regs[ins.r.rs2]) ? 1
1890
                                                                            : 0;
1891
                break;
1892
            case FUNCT3_SLTU: /* sltu. */
1893
                cpu->regs[ins.r.rd] =
1894
                    (cpu->regs[ins.r.rs1] < cpu->regs[ins.r.rs2]) ? 1 : 0;
1895
                break;
1896
            case FUNCT3_XOR: /* xor.  */
1897
                cpu->regs[ins.r.rd] =
1898
                    cpu->regs[ins.r.rs1] ^ cpu->regs[ins.r.rs2];
1899
                break;
1900
            case FUNCT3_SRL: /* srl.  */
1901
                cpu->regs[ins.r.rd] =
1902
                    cpu->regs[ins.r.rs1] >> (cpu->regs[ins.r.rs2] & shamt_mask);
1903
                break;
1904
            case FUNCT3_OR: /* or.   */
1905
                cpu->regs[ins.r.rd] =
1906
                    cpu->regs[ins.r.rs1] | cpu->regs[ins.r.rs2];
1907
                break;
1908
            case FUNCT3_AND: /* and.  */
1909
                cpu->regs[ins.r.rd] =
1910
                    cpu->regs[ins.r.rs1] & cpu->regs[ins.r.rs2];
1911
                break;
1912
            }
1913
            break;
1914
        }
1915
1916
        case FUNCT7_SUB:
1917
            switch (ins.r.funct3) {
1918
            case FUNCT3_ADD: /* sub. */
1919
                cpu->regs[ins.r.rd] =
1920
                    cpu->regs[ins.r.rs1] - cpu->regs[ins.r.rs2];
1921
                break;
1922
            case FUNCT3_SRL: /* sra. */
1923
                cpu->regs[ins.r.rd] = (u64)((i64)cpu->regs[ins.r.rs1] >>
1924
                                            (cpu->regs[ins.r.rs2] & 0x3F));
1925
                break;
1926
            }
1927
            break;
1928
1929
        case FUNCT7_MUL:
1930
            switch (ins.r.funct3) {
1931
            case FUNCT3_ADD: /* mul.  */
1932
                cpu->regs[ins.r.rd] =
1933
                    cpu->regs[ins.r.rs1] * cpu->regs[ins.r.rs2];
1934
                break;
1935
            case FUNCT3_XOR: /* div.  */
1936
                if (cpu->regs[ins.r.rs2] != 0) {
1937
                    cpu->regs[ins.r.rd] = (u64)((i64)cpu->regs[ins.r.rs1] /
1938
                                                (i64)cpu->regs[ins.r.rs2]);
1939
                } else {
1940
                    cpu->regs[ins.r.rd] = (u64)-1; /* Division by zero. */
1941
                }
1942
                break;
1943
            case FUNCT3_SRL: /* divu. */
1944
                if (cpu->regs[ins.r.rs2] != 0) {
1945
                    cpu->regs[ins.r.rd] =
1946
                        cpu->regs[ins.r.rs1] / cpu->regs[ins.r.rs2];
1947
                } else {
1948
                    cpu->regs[ins.r.rd] = (u64)-1; /* Division by zero. */
1949
                }
1950
                break;
1951
            case FUNCT3_OR: /* rem.  */
1952
                if (cpu->regs[ins.r.rs2] != 0) {
1953
                    cpu->regs[ins.r.rd] = (u64)((i64)cpu->regs[ins.r.rs1] %
1954
                                                (i64)cpu->regs[ins.r.rs2]);
1955
                } else {
1956
                    cpu->regs[ins.r.rd] = cpu->regs[ins.r.rs1];
1957
                }
1958
                break;
1959
            case FUNCT3_AND: /* remu. */
1960
                if (cpu->regs[ins.r.rs2] != 0) {
1961
                    cpu->regs[ins.r.rd] =
1962
                        cpu->regs[ins.r.rs1] % cpu->regs[ins.r.rs2];
1963
                } else {
1964
                    cpu->regs[ins.r.rd] = cpu->regs[ins.r.rs1];
1965
                }
1966
                break;
1967
            }
1968
            break;
1969
        }
1970
        break;
1971
    }
1972
1973
    case OP_OP_32: {
1974
        /* RV64I: 32-bit register-register operations (ADDW, SUBW, SLLW, SRLW,
1975
         * SRAW, MULW, DIVW, DIVUW, REMW, REMUW). These operate on the lower 32
1976
         * bits and sign-extend the result to 64 bits. */
1977
        if (ins.r.rd == ZERO)
1978
            break;
1979
1980
        cpu->modified = (reg_t)ins.r.rd;
1981
        u32 rs1_32    = (u32)cpu->regs[ins.r.rs1];
1982
        u32 rs2_32    = (u32)cpu->regs[ins.r.rs2];
1983
1984
        switch (ins.r.funct7) {
1985
        case FUNCT7_NORMAL:
1986
            switch (ins.r.funct3) {
1987
            case FUNCT3_ADD: { /* addw. */
1988
                i32 result          = (i32)(rs1_32 + rs2_32);
1989
                cpu->regs[ins.r.rd] = (u64)(i64)result;
1990
                break;
1991
            }
1992
            case FUNCT3_SLL: { /* sllw. */
1993
                i32 result          = (i32)(rs1_32 << (rs2_32 & 0x1F));
1994
                cpu->regs[ins.r.rd] = (u64)(i64)result;
1995
                break;
1996
            }
1997
            case FUNCT3_SRL: { /* srlw. */
1998
                i32 result          = (i32)(rs1_32 >> (rs2_32 & 0x1F));
1999
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2000
                break;
2001
            }
2002
            }
2003
            break;
2004
2005
        case FUNCT7_SUB:
2006
            switch (ins.r.funct3) {
2007
            case FUNCT3_ADD: { /* subw. */
2008
                i32 result          = (i32)(rs1_32 - rs2_32);
2009
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2010
                break;
2011
            }
2012
            case FUNCT3_SRL: { /* sraw. */
2013
                i32 result          = (i32)rs1_32 >> (rs2_32 & 0x1F);
2014
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2015
                break;
2016
            }
2017
            }
2018
            break;
2019
2020
        case FUNCT7_MUL:
2021
            switch (ins.r.funct3) {
2022
            case FUNCT3_ADD: { /* mulw.  */
2023
                i32 result          = (i32)(rs1_32 * rs2_32);
2024
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2025
                break;
2026
            }
2027
            case FUNCT3_XOR: { /* divw.  */
2028
                if (rs2_32 != 0) {
2029
                    i32 result          = (i32)rs1_32 / (i32)rs2_32;
2030
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2031
                } else {
2032
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)-1;
2033
                }
2034
                break;
2035
            }
2036
            case FUNCT3_SRL: { /* divuw. */
2037
                if (rs2_32 != 0) {
2038
                    i32 result          = (i32)(rs1_32 / rs2_32);
2039
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2040
                } else {
2041
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)-1;
2042
                }
2043
                break;
2044
            }
2045
            case FUNCT3_OR: { /* remw.  */
2046
                if (rs2_32 != 0) {
2047
                    i32 result          = (i32)rs1_32 % (i32)rs2_32;
2048
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2049
                } else {
2050
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)rs1_32;
2051
                }
2052
                break;
2053
            }
2054
            case FUNCT3_AND: { /* remuw. */
2055
                if (rs2_32 != 0) {
2056
                    i32 result          = (i32)(rs1_32 % rs2_32);
2057
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2058
                } else {
2059
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)rs1_32;
2060
                }
2061
                break;
2062
            }
2063
            }
2064
            break;
2065
        }
2066
        break;
2067
    }
2068
2069
    case OP_SYSTEM: {
2070
        u32 funct12 = ins.i.imm_11_0;
2071
2072
        if (funct12 == 0) {
2073
            u32 syscall_num = (u32)cpu->regs[A7];
2074
2075
            switch (syscall_num) {
2076
            case 64: { /* write. */
2077
                int guest_fd = (int)cpu->regs[A0];
2078
                u64 addr     = cpu->regs[A1];
2079
                u64 count    = cpu->regs[A2];
2080
2081
                if (addr + count > g_opts.memory_size ||
2082
                    addr > (u64)g_opts.memory_size) {
2083
                    printf(
2084
                        "sys_write out of bounds: addr=%016llx len=%llu\n",
2085
                        (unsigned long long)addr,
2086
                        (unsigned long long)count
2087
                    );
2088
                    cpu->running = false;
2089
                    emit_fault_diagnostics(cpu, executed_pc);
2090
                    break;
2091
                }
2092
                ssize_t written = 0;
2093
                int     host_fd = guest_fd_table_get(guest_fd);
2094
2095
                if (host_fd >= 0 && count > 0) {
2096
                    written = write(host_fd, &memory[(u32)addr], (u32)count);
2097
                    if (written < 0) {
2098
                        written = 0;
2099
                    }
2100
                }
2101
                cpu->regs[A0] = (u64)written;
2102
                break;
2103
            }
2104
            case 63: { /* read. */
2105
                int guest_fd = (int)cpu->regs[A0];
2106
                u64 addr     = cpu->regs[A1];
2107
                u64 count    = cpu->regs[A2];
2108
2109
                if (addr + count > g_opts.memory_size ||
2110
                    addr > (u64)g_opts.memory_size) {
2111
                    printf(
2112
                        "sys_read out of bounds: addr=%016llx len=%llu\n",
2113
                        (unsigned long long)addr,
2114
                        (unsigned long long)count
2115
                    );
2116
                    cpu->running = false;
2117
                    emit_fault_diagnostics(cpu, executed_pc);
2118
                    break;
2119
                }
2120
                ssize_t read_bytes = 0;
2121
                int     host_fd    = guest_fd_table_get(guest_fd);
2122
2123
                if (host_fd >= 0 && count > 0) {
2124
                    read_bytes = read(host_fd, &memory[(u32)addr], (u32)count);
2125
                    if (read_bytes < 0) {
2126
                        read_bytes = 0;
2127
                    }
2128
                }
2129
                cpu->regs[A0] = (u64)read_bytes;
2130
                break;
2131
            }
2132
            case 93: { /* exit. */
2133
                cpu->running = false;
2134
                break;
2135
            }
2136
            case 56: { /* openat. */
2137
                u64 pathname_addr = cpu->regs[A1];
2138
                i32 flags         = (i32)cpu->regs[A2];
2139
                if (pathname_addr > (u64)g_opts.memory_size) {
2140
                    cpu->regs[A0] = (u64)(i64)(i32)-1;
2141
                    break;
2142
                }
2143
                cpu->regs[A0] =
2144
                    (u64)(i64)(i32)ecall_openat((u32)pathname_addr, flags);
2145
                break;
2146
            }
2147
            case 57: { /* close. */
2148
                i32 guest_fd  = (i32)cpu->regs[A0];
2149
                cpu->regs[A0] = (u64)(i64)ecall_close(guest_fd);
2150
                break;
2151
            }
2152
            default:
2153
                cpu->regs[A0] = (u32)syscall_num;
2154
                break;
2155
            }
2156
        } else if (funct12 == 1) {
2157
            /* Look up source location for this EBREAK.  PC in the debug
2158
             * file is relative to program start, so subtract base. */
2159
            u32                 relative_pc = executed_pc - program_base;
2160
            struct debug_entry *entry       = debug_lookup(relative_pc);
2161
2162
            printf("\n%sRuntime error (EBREAK)%s", COLOR_BOLD_RED, COLOR_RESET);
2163
            if (entry) {
2164
                u32 line = line_from_offset(entry->file, entry->offset);
2165
                printf(
2166
                    " at %s%s:%d%s", COLOR_CYAN, entry->file, line, COLOR_RESET
2167
                );
2168
            }
2169
            printf("\n");
2170
2171
            cpu->running  = false;
2172
            cpu->regs[A0] = EBREAK_EXIT_CODE;
2173
            cpu->ebreak   = true;
2174
            emit_fault_diagnostics(cpu, executed_pc);
2175
            cpu->faulted = false;
2176
        } else {
2177
            printf(
2178
                "\n%sUnknown system instruction (imm=%08x)%s\n",
2179
                COLOR_BOLD_RED,
2180
                funct12,
2181
                COLOR_RESET
2182
            );
2183
            cpu->running = false;
2184
            emit_fault_diagnostics(cpu, executed_pc);
2185
        }
2186
        break;
2187
    }
2188
2189
    case OP_FENCE:
2190
        /* Memory barriers are not implemented. */
2191
        break;
2192
2193
    default:
2194
        printf("Unknown opcode %02x at PC=%08x\n", opcode, cpu->pc);
2195
        cpu->running = false;
2196
        emit_fault_diagnostics(cpu, executed_pc);
2197
        break;
2198
    }
2199
    /* Register x0 is hardwired to zero. */
2200
    cpu->regs[ZERO] = 0;
2201
    cpu->pc         = pc_next;
2202
2203
    if (cpu->running) {
2204
        validate_stack_register(cpu, SP, "SP", executed_pc, false);
2205
        if (cpu->running)
2206
            validate_stack_register(cpu, FP, "FP", executed_pc, true);
2207
    }
2208
2209
    /* Track last executed PC for trace mode. */
2210
    if (headless && g_opts.trace_print_instructions)
2211
        last_executed_pc = executed_pc;
2212
}
2213
2214
/* Render the instructions column. */
2215
static void ui_render_instructions(
2216
    struct cpu *cpu, int col, int width, int height
2217
) {
2218
    int row = 1;
2219
2220
    u32 program_start_idx = program_base / INSTR_SIZE;
2221
    u32 program_end_idx   = program_start_idx + cpu->programsize;
2222
2223
    /* Calculate PC index in program. */
2224
    u32 pc_idx = cpu->pc / sizeof(instr_t);
2225
    if (pc_idx < program_start_idx || pc_idx >= program_end_idx)
2226
        pc_idx = program_start_idx;
2227
2228
    /* Calculate first instruction to display, centering PC if possible. */
2229
    i32 progstart = (i32)pc_idx - height / 2;
2230
    i32 min_start = (i32)program_start_idx;
2231
    i32 max_start = (i32)program_end_idx - height;
2232
2233
    if (max_start < min_start)
2234
        max_start = min_start;
2235
    if (progstart < min_start)
2236
        progstart = min_start;
2237
    if (progstart > max_start)
2238
        progstart = max_start;
2239
2240
    printf(TTY_GOTO_RC, row++, col);
2241
    printf("  INSTRUCTIONS");
2242
2243
    for (int i = 0; i < height; i++) {
2244
        u32 idx = (u32)progstart + i;
2245
        if (idx >= program_end_idx)
2246
            break;
2247
2248
        printf(TTY_GOTO_RC, row + i + 1, col);
2249
2250
        char istr[MAX_INSTR_STR_LEN] = { 0 };
2251
        int  len = sprint_instr(cpu->program[idx], istr, true);
2252
2253
        if (idx == pc_idx) { /* Highlight current instruction. */
2254
            printf("%s>%s %04x: ", COLOR_GREEN, COLOR_RESET, idx * INSTR_SIZE);
2255
        } else {
2256
            printf("  %s%04x:%s ", COLOR_GREY, idx * INSTR_SIZE, COLOR_RESET);
2257
        }
2258
        printf("%s", istr);
2259
        printf("%-*s", width - len - 8, "");
2260
    }
2261
}
2262
2263
/* Render the registers column. */
2264
static void ui_render_registers(
2265
    struct cpu *cpu, enum display display, int col, int height
2266
) {
2267
    int row = 1;
2268
2269
    printf(TTY_GOTO_RC, row++, col);
2270
    printf("REGISTERS");
2271
2272
    int reg_count =
2273
        sizeof(registers_displayed) / sizeof(registers_displayed[0]);
2274
    if (reg_count > height)
2275
        reg_count = height;
2276
2277
    for (int i = 0; i < reg_count; i++) {
2278
        printf(TTY_GOTO_RC, row + i + 1, col);
2279
2280
        reg_t       r = registers_displayed[i];
2281
        const char *reg_color =
2282
            (r == cpu->modified) ? COLOR_BOLD_BLUE : COLOR_BLUE;
2283
        u64  reg_value = cpu->regs[r];
2284
        bool is_stack_addr =
2285
            reg_value <= (u64)UINT32_MAX && stack_contains((u32)reg_value);
2286
2287
        /* Always show registers that contain stack addresses in hex. */
2288
        printf("%s%-2s%s = ", COLOR_GREEN, reg_names[r], COLOR_RESET);
2289
        if (display == DISPLAY_HEX || is_stack_addr) {
2290
            printf("%s0x%08x%s", reg_color, (u32)reg_value, COLOR_RESET);
2291
        } else {
2292
            printf("%s%-10d%s", reg_color, (i32)reg_value, COLOR_RESET);
2293
        }
2294
    }
2295
}
2296
2297
/* Render the stack column. */
2298
static void ui_render_stack(
2299
    struct cpu *cpu, enum display display, int col, int height
2300
) {
2301
    int row = 1;
2302
2303
    printf(TTY_GOTO_RC, row++, col);
2304
    printf("     STACK FRAME");
2305
2306
    assert(cpu->regs[SP] <= memory_top() && cpu->regs[FP] <= memory_top());
2307
2308
    u32 fp   = (u32)cpu->regs[FP];
2309
    u32 sp   = (u32)cpu->regs[SP];
2310
    u32 rows = (u32)height;
2311
    if (rows > STACK_DISPLAY_WORDS)
2312
        rows = STACK_DISPLAY_WORDS;
2313
    if (rows == 0)
2314
        return;
2315
2316
    u32 top    = stack_usable_top();
2317
    u32 bottom = stack_usable_bottom();
2318
    if (sp > top)
2319
        sp = top;
2320
    if (fp > top)
2321
        fp = top;
2322
2323
    u32 used_bytes  = (top >= sp) ? (top - sp) : 0;
2324
    u32 total_words = (used_bytes / WORD_SIZE) + 1;
2325
    u32 frame_words = total_words;
2326
    if (frame_words > rows)
2327
        frame_words = rows;
2328
    if (frame_words == 0)
2329
        return;
2330
2331
    u32 start;
2332
    if (frame_words == total_words) {
2333
        start = top;
2334
    } else {
2335
        start = sp + (frame_words - 1) * WORD_SIZE;
2336
        if (start > top)
2337
            start = top;
2338
    }
2339
2340
    if (start < bottom)
2341
        start = bottom;
2342
2343
    u32 addr   = start;
2344
    i32 offset = (i32)(start - sp);
2345
2346
    for (u32 i = 0; i < frame_words; i++) {
2347
        if (addr < bottom)
2348
            break;
2349
2350
        assert(addr <= memory_top());
2351
        printf(TTY_GOTO_RC, row + i + 1, col);
2352
2353
        /* Mark SP and FP positions. */
2354
        const char *marker = "  ";
2355
2356
        if (addr == sp) {
2357
            marker = "sp";
2358
        } else if (addr == fp) {
2359
            marker = "fp";
2360
        }
2361
        u32 word = memory_load_u32(addr);
2362
2363
        char offset_buf[6];
2364
        if (addr == sp) {
2365
            memcpy(offset_buf, "    ", 5);
2366
        } else {
2367
            snprintf(offset_buf, sizeof(offset_buf), "%+4d", offset);
2368
        }
2369
2370
        printf(
2371
            "%s%s %s%s%s %08x: ",
2372
            COLOR_GREEN,
2373
            marker,
2374
            COLOR_GREY,
2375
            offset_buf,
2376
            COLOR_RESET,
2377
            addr
2378
        );
2379
        bool is_stack_addr = stack_contains(word);
2380
2381
        if (display == DISPLAY_HEX || is_stack_addr) {
2382
            printf("%s0x%08x%s", COLOR_BLUE, word, COLOR_RESET);
2383
        } else {
2384
            printf("%s%-10d%s", COLOR_BLUE, (i32)word, COLOR_RESET);
2385
        }
2386
        if (addr < WORD_SIZE)
2387
            break;
2388
2389
        addr   -= WORD_SIZE;
2390
        offset -= WORD_SIZE;
2391
    }
2392
}
2393
2394
/* Render the full debugger TUI. */
2395
static void ui_render(struct cpu *cpu, enum display display) {
2396
    printf(TTY_CLEAR);
2397
2398
    struct termsize tsize = termsize();
2399
2400
    /* Enforce a minimum display size. */
2401
    if (tsize.cols < 60)
2402
        tsize.cols = 60;
2403
    if (tsize.rows < 15)
2404
        tsize.rows = 15;
2405
2406
    /* Column layout: 40% instructions, 20% registers, rest for stack. */
2407
    int instr_width = (tsize.cols * 2) / 5;
2408
    int reg_width   = tsize.cols / 5;
2409
2410
    int instr_col = 1;
2411
    int reg_col   = instr_col + instr_width + 2;
2412
    int stack_col = reg_col + reg_width + 2;
2413
2414
    int display_height = tsize.rows - FOOTER_HEIGHT - HEADER_HEIGHT;
2415
    if (display_height > MAX_INSTR_DISPLAY)
2416
        display_height = MAX_INSTR_DISPLAY;
2417
    if (display_height <= 0)
2418
        display_height = 1;
2419
2420
    ui_render_instructions(cpu, instr_col, instr_width, display_height);
2421
    ui_render_registers(cpu, display, reg_col, display_height);
2422
    ui_render_stack(cpu, display, stack_col, display_height);
2423
2424
    printf(TTY_GOTO_RC, display_height + FOOTER_HEIGHT, 1);
2425
    printf(
2426
        "%sPress `j` to step forward, `k` to step backward, `q` to quit,\n"
2427
        "`d` to toggle decimal display, `r` to reset program.%s ",
2428
        COLOR_GREY,
2429
        COLOR_RESET
2430
    );
2431
}
2432
2433
/* Set up the terminal for interactive mode, saving the original settings. */
2434
static void term_init(struct termios *oldterm) {
2435
    struct termios term;
2436
2437
    tcgetattr(STDIN_FILENO, oldterm);
2438
    term          = *oldterm;
2439
    term.c_lflag &= ~(ICANON | ECHO);
2440
    tcsetattr(STDIN_FILENO, TCSANOW, &term);
2441
}
2442
2443
/* Restore terminal settings. */
2444
static void term_restore(struct termios *old) {
2445
    tcsetattr(STDIN_FILENO, TCSANOW, old);
2446
}
2447
2448
int main(int argc, char *argv[]) {
2449
    struct cpu        cpu;
2450
    enum display      display = DISPLAY_DEC;
2451
    struct cli_config cli     = { 0 };
2452
2453
    if (!parse_cli_args(argc, argv, &cli))
2454
        return 1;
2455
2456
    bool headless = cli.headless;
2457
2458
    g_opts.trace_headless = headless;
2459
2460
    cpu_init(&cpu);
2461
    program_init(&cpu, cli.program_path);
2462
    int    prog_argc = argc - cli.arg_index;
2463
    char **prog_argv = &argv[cli.arg_index];
2464
    prepare_env(&cpu, prog_argc, prog_argv);
2465
2466
    if (headless) {
2467
        u64 max_steps = g_opts.headless_max_steps;
2468
        u64 steps     = 0;
2469
2470
        /* Try to initialise the JIT for headless mode.  Falls back to the
2471
         * interpreter automatically when JIT is disabled, unavailable,
2472
         * or when the code cache fills up. */
2473
        static struct jit_state jit;
2474
        bool                    use_jit = false;
2475
2476
        if (!g_opts.jit_disabled && !g_opts.trace_enabled &&
2477
            !g_opts.trace_print_instructions && !g_opts.watch_enabled) {
2478
            use_jit = jit_init(&jit);
2479
        }
2480
2481
        if (use_jit) {
2482
            /* ---- JIT execution loop ---- */
2483
            while (cpu.running && steps < max_steps) {
2484
                struct jit_block *block = jit_get_block(
2485
                    &jit, cpu.pc, memory, program_base, program_bytes
2486
                );
2487
                if (!block) {
2488
                    /* Cache full or compilation error -- fall back to
2489
                     * interpreter for remainder. */
2490
                    while (cpu.running && steps++ < max_steps) {
2491
                        cpu_execute(&cpu, display, true);
2492
                    }
2493
                    break;
2494
                }
2495
                u32 next_pc = 0;
2496
                int exit_reason =
2497
                    jit_exec_block(block, cpu.regs, memory, &next_pc);
2498
                steps += block->insn_count;
2499
                jit.blocks_executed++;
2500
                jit.insns_executed += block->insn_count;
2501
2502
                switch (exit_reason) {
2503
                case JIT_EXIT_BRANCH:
2504
                case JIT_EXIT_CHAIN:
2505
                    cpu.pc = next_pc;
2506
                    break;
2507
2508
                case JIT_EXIT_RET:
2509
                    cpu.running = false;
2510
                    break;
2511
2512
                default:
2513
                    /* ECALL, EBREAK, FAULT -- interpreter handles it.
2514
                     * The instruction is already counted above,
2515
                     * so don't increment steps again. */
2516
                    cpu.pc = next_pc;
2517
                    cpu_execute(&cpu, display, true);
2518
                    break;
2519
                }
2520
            }
2521
            jit_destroy(&jit);
2522
        } else {
2523
            /* ---- Interpreter-only loop ---- */
2524
            while (cpu.running && steps++ < max_steps) {
2525
                cpu_execute(&cpu, display, true);
2526
            }
2527
        }
2528
2529
        if (cpu.running) {
2530
            fprintf(
2531
                stderr,
2532
                "program did not terminate within %zu steps\n",
2533
                (size_t)max_steps
2534
            );
2535
            return -1;
2536
        }
2537
        if (cpu.faulted) {
2538
            fprintf(stderr, "program terminated due to runtime fault\n");
2539
            return -1;
2540
        }
2541
        if (g_opts.count_instructions) {
2542
            fprintf(
2543
                stderr,
2544
                "Processed %llu instructions\n",
2545
                (unsigned long long)steps
2546
            );
2547
        }
2548
        return (int)cpu.regs[A0];
2549
    }
2550
    struct termios oldterm;
2551
    term_init(&oldterm);
2552
    snapshot_init(&cpu);
2553
2554
    for (;;) {
2555
        if (cpu.running)
2556
            ui_render(&cpu, display);
2557
2558
        int ch = getchar();
2559
2560
        if (ch == 'q' || ch == 'Q') {
2561
            printf("\n");
2562
            break;
2563
        } else if (ch == 'd' || ch == 'D') { /* Toggle display mode. */
2564
            display = (display == DISPLAY_HEX) ? DISPLAY_DEC : DISPLAY_HEX;
2565
        } else if (ch == 'r' || ch == 'R') { /* Reset program and state. */
2566
            cpu_reset(&cpu);
2567
            snapshot_init(&cpu);
2568
        } else if (ch == 'g' || ch == 'G') { /* Toggle stack guard. */
2569
            toggle_stack_guard(&cpu);
2570
        } else if (ch == 'j' && cpu.running) { /* Step forward. */
2571
            cpu_execute(&cpu, display, false);
2572
            snapshot_save(&cpu);
2573
        } else if (ch == 'k' && cpu.pc > 0) { /* Step backward. */
2574
            bool restored = snapshot_restore(&cpu);
2575
2576
            if (restored) {
2577
                cpu.running = true;
2578
            } else {
2579
                printf(
2580
                    "\n%sNo more history to go back to.%s\n",
2581
                    COLOR_BOLD_RED,
2582
                    COLOR_RESET
2583
                );
2584
            }
2585
        }
2586
    }
2587
    term_restore(&oldterm);
2588
2589
    return 0;
2590
}