Browse Source

Add Debugger

master
parent
commit
53fffe9079
No known key found for this signature in database GPG Key ID: DA34C790D267C164
4 changed files with 187 additions and 21 deletions
  1. +4
    -0
      README.md
  2. +1
    -1
      build.zig
  3. +177
    -19
      src/main.zig
  4. +5
    -1
      src/oidavm.zig

+ 4
- 0
README.md View File

@ -2,6 +2,10 @@
A minimal bytecode VM implemented in Zig.
## Usage
Run `oida run youroidasmfile` to run your program, optionally specifying an entry point in hex after the filename. To debug oidaVM programs, use the internal debugger oiDB, which you can invoke using `oida dbg youroidasmfile`. It has a GDB-*inspired* REPL and internal help accessible via `?` or `h` in oiDB.
## Specs
- 4096 16-bit words of memory


+ 1
- 1
build.zig View File

@ -2,7 +2,7 @@ const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("ovm", "src/main.zig");
const exe = b.addExecutable("oida", "src/main.zig");
exe.setBuildMode(mode);
exe.install();


+ 177
- 19
src/main.zig View File

@ -2,6 +2,11 @@ const std = @import("std");
usingnamespace @import("oidavm.zig");
const parser = @import("parser.zig");
const RunMode = enum {
Debug,
Run,
};
pub fn main() !void {
var allocator = std.heap.ArenaAllocator.init(std.heap.direct_allocator);
defer allocator.deinit();
@ -10,17 +15,22 @@ pub fn main() !void {
var arguments = try std.process.argsAlloc(alloc);
defer std.process.argsFree(alloc, arguments);
if (arguments.len == 1 or std.mem.eql(u8, arguments[1], "--help")) {
std.debug.warn(
\\OidaVM 0.1
\\
\\Usage: ovm [file.oidasm] <entry point>
\\
);
return;
var mode: RunMode = undefined;
if (arguments.len == 1) {
printUsageAndExit();
} else if (std.mem.eql(u8, arguments[1], "help") or std.mem.eql(u8, arguments[1], "--help")) {
printUsageAndExit();
} else if (std.mem.eql(u8, arguments[1], "run")) {
mode = .Run;
} else if (std.mem.eql(u8, arguments[1], "dbg")) {
mode = .Debug;
} else {
std.debug.warn("unknown argument {}\n", arguments[1]);
printUsageAndExit();
}
var file = try std.fs.File.openRead(arguments[1]);
var file = try std.fs.File.openRead(arguments[2]);
defer file.close();
var buffer = try std.Buffer.initSize(alloc, 0);
@ -32,17 +42,165 @@ pub fn main() !void {
.rng = std.rand.DefaultPrng.init(std.time.timestamp()).random,
};
const entry_point = if (arguments.len != 3) 0 else try std.fmt.parseInt(u12, arguments[2], 16);
// Just run the program and exit
vm.eval(entry_point);
}
if (mode == .Run) {
const entry_point = blk: {
if (arguments.len == 4) {
break :blk std.fmt.parseInt(u12, arguments[3], 16) catch 0;
} else break :blk 0;
};
vm.eval(entry_point);
return;
}
fn global_instruction(comptime op: GlobalOpcode, comptime addr: u12) u16 {
return (u16(@enumToInt(op)) << 12) + addr;
}
fn paged_instruction(comptime op: PagedOpcode, comptime addr: u8) u16 {
return (u16(@enumToInt(op)) << 8) + addr;
// Run oiDB REPL
var breakpoints = std.ArrayList(u12).init(alloc);
var in_buffer = std.Buffer.initSize(alloc, 0) catch @panic("OOM");
std.debug.warn("Welcome to oiDB!\n{}\n", oidb_usage);
std.debug.warn("oiDB@{X:0^3}) ", vm.instruction_ptr);
repl: while (true) : (std.debug.warn("\noiDB@{X:0^3}) ", vm.instruction_ptr)) {
const line = try std.io.readLine(&buffer);
var tokens = std.mem.tokenize(line, " ");
const instruction_token = tokens.next() orelse continue;
switch (instruction_token[0]) {
'h', '?' => {
// Display help
std.debug.warn("{}", oidb_usage);
},
'n' => {
// Step to next instruction
if (vm.memory[vm.instruction_ptr] == 0xf00f) {
std.debug.warn("Reached cease instruction");
continue :repl;
} else {
vm.step();
vm.instruction_ptr += 1;
}
},
'd' => {
// Dump VM state
std.debug.warn("\n");
vm.dump();
},
'q' => {
// Exit
std.debug.warn("\n");
std.process.exit(0);
},
'i' => {
// Set instruction pointer
const address_token = tokens.next() orelse continue;
const value = std.fmt.parseInt(u12, address_token, 16) catch continue;
vm.instruction_ptr = value;
std.debug.warn("Set instruction pointer to 0x{X:0^3}", value);
},
'a' => {
// Set ACC
const value_token = tokens.next() orelse continue;
const value = std.fmt.parseInt(u16, value_token, 16) catch continue;
vm.accumulator = value;
std.debug.warn("Set ACC to 0x{X:0^4}", value);
},
's' => {
// Set arbitrary memory
const address_token = tokens.next() orelse continue;
const value_token = tokens.next() orelse continue;
const address = std.fmt.parseInt(u12, address_token, 16) catch continue;
const value = std.fmt.parseInt(u16, value_token, 16) catch continue;
vm.memory[address] = value;
std.debug.warn("Set memory at 0x{X:0^3} to 0x{X:0^4}", address, value);
},
'p' => {
// Print memory
const address_token = tokens.next() orelse continue;
const address = std.fmt.parseInt(u12, address_token, 16) catch continue;
std.debug.warn("Memory at 0x{X:0^3}: 0x{X:0^4}", address, vm.memory[address]);
},
'b' => {
// Add breakpoint
const address_token = tokens.next() orelse continue;
const address = std.fmt.parseInt(u12, address_token, 16) catch continue;
for (breakpoints.toSlice()) |bp| {
if (bp == address) {
std.debug.warn("Breakpoint already present at 0x{X:0^3}", address);
continue :repl;
}
}
try breakpoints.append(address);
std.debug.warn("Added breakpoint at 0x{X:0^3}", address);
},
'l' => {
// List breakpoints
const num_breakpoints = breakpoints.toSlice().len;
std.debug.warn("{} Breakpoint{} ", num_breakpoints, if (num_breakpoints == 0) "s" else if (num_breakpoints == 1) ":" else "s:");
for (breakpoints.toSlice()) |bp, i| {
std.debug.warn("0x{X:0^3}{}", bp, if (i == num_breakpoints - 1) "" else ", ");
}
},
'r' => {
// Remove breakpoint
const address_token = tokens.next() orelse continue;
const address = std.fmt.parseInt(u12, address_token, 16) catch continue;
for (breakpoints.toSlice()) |bp, i| {
if (bp == address) {
_ = breakpoints.orderedRemove(i);
std.debug.warn("Removed breakpoint at 0x{X:0^3}", address);
continue :repl;
}
}
std.debug.warn("No breakpoint at 0x{X:0^3} to remove", address);
},
'c' => {
// Continue execution up to next breakpoint
const pointer_at_start = vm.instruction_ptr;
while (vm.instruction_ptr < 4095) : (vm.instruction_ptr += 1) {
if (vm.memory[vm.instruction_ptr] == 0xf00f) {
std.debug.warn("Reached cease instruction");
continue :repl;
}
for (breakpoints.toSlice()) |bp| {
if (vm.instruction_ptr == bp and !(pointer_at_start == bp)) continue :repl;
}
vm.step();
}
},
else => continue,
}
}
}
fn extended_instruction(comptime op: ExtendedOpcode) u16 {
return 0xf000 + u16(@enumToInt(op));
fn printUsageAndExit() noreturn {
std.debug.warn("{}", oidavm_usage);
std.process.exit(0);
}
const oidavm_usage =
\\OidaVM 0.1
\\
\\Usage: ovm run [file.oidasm] <entry point>
\\ ovm dbg [file.oidasm]
\\ ovm help
\\
;
const oidb_usage =
\\oiDB Commands
\\ d dump VM's state
\\ q quit oiDB
\\ h,? display this message
\\
\\ n execute and increase instruction pointer
\\ i set the instruction pointer: i 000
\\ a set ACC: a 0000
\\ s set memory: s 000 f00f
\\ p print memory: p 000
\\
\\ b set breakpoint: b 000
\\ l list breakpoints
\\ r remove breakpoint: r 000
\\ c continue execution to next breakpoint
\\
;

+ 5
- 1
src/oidavm.zig View File

@ -186,10 +186,14 @@ pub const OidaVm = struct {
this.instruction_ptr = addr;
while (this.instruction_ptr < 4095) : (this.instruction_ptr += 1) {
if (this.memory[this.instruction_ptr] == 0xf00f) return; // Extend-Halt opcode
this.exec(this.memory[this.instruction_ptr]);
this.step();
}
}
fn step(this: *OidaVm) void {
this.exec(this.memory[this.instruction_ptr]);
}
/// Writes `value` to `addr` in the VM's memory.
fn load(this: *OidaVm, addr: u12, value: u16) void {
this.memory[addr] = value;


Loading…
Cancel
Save