Compare commits
8 Commits
4bca44e5f2
...
acb59994fc
| Author | SHA1 | Date | |
|---|---|---|---|
| acb59994fc | |||
| 6d6a109a08 | |||
| c1158b547e | |||
| d7b8d7acb1 | |||
| 81ff227ea7 | |||
| 82bad92fcf | |||
| dbf00006e7 | |||
| 59b6b51466 |
59
build.zig
59
build.zig
@@ -1,54 +1,49 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const CompileStep = std.Build.CompileStep;
|
||||||
|
|
||||||
fn path(comptime suffix: []const u8) []const u8 {
|
fn path(comptime suffix: []const u8) []const u8 {
|
||||||
if (suffix[0] == '/') @compileError("expected a relative path");
|
if (suffix[0] == '/') @compileError("expected a relative path");
|
||||||
return comptime (std.fs.path.dirname(@src().file) orelse ".") ++ std.fs.path.sep_str ++ suffix;
|
return comptime (std.fs.path.dirname(@src().file) orelse ".") ++ std.fs.path.sep_str ++ suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pkgs = struct {
|
pub fn link(exe: *CompileStep) void {
|
||||||
const Pkg = std.build.Pkg;
|
// create zig-network module
|
||||||
|
const b = exe.builder;
|
||||||
pub const gdbstub: Pkg = .{
|
|
||||||
.name = "gdbstub",
|
|
||||||
.source = .{ .path = path("src/lib.zig") },
|
|
||||||
.dependencies = &[_]Pkg{network},
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://github.com/MasterQ32/zig-network
|
// https://github.com/MasterQ32/zig-network
|
||||||
pub const network: Pkg = .{
|
const network = b.createModule(.{ .source_file = .{ .path = path("lib/zig-network/network.zig") } });
|
||||||
.name = "network",
|
|
||||||
.source = .{ .path = path("lib/zig-network/network.zig") },
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn link(exe: *std.build.LibExeObjStep) void {
|
const gdbstub = b.createModule(.{
|
||||||
exe.addPackage(pkgs.gdbstub);
|
.source_file = .{ .path = path("src/lib.zig") },
|
||||||
|
.dependencies = &.{.{ .name = "network", .module = network }},
|
||||||
|
});
|
||||||
|
|
||||||
|
exe.addModule("gdbstub", gdbstub);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(b: *std.build.Builder) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
_ = target;
|
// const optimize = b.standardOptimizeOption(.{});
|
||||||
const mode = b.standardReleaseOptions();
|
|
||||||
|
|
||||||
// -- library --
|
// -- Library --
|
||||||
const lib = b.addStaticLibrary("gdbstub", "src/lib.zig");
|
|
||||||
lib.addPackage(pkgs.network);
|
|
||||||
|
|
||||||
lib.setBuildMode(mode);
|
const lib_test = b.addTest(.{
|
||||||
lib.install();
|
.root_source_file = .{ .path = "src/lib.zig" },
|
||||||
|
.target = target,
|
||||||
|
});
|
||||||
|
|
||||||
const lib_tests = b.addTest("src/lib.zig");
|
const test_step = b.step("test", "Run Library Tests");
|
||||||
lib_tests.setBuildMode(mode);
|
test_step.dependOn(&lib_test.step);
|
||||||
|
|
||||||
const test_step = b.step("lib-test", "Run Library Tests");
|
// -- Executable --
|
||||||
test_step.dependOn(&lib_tests.step);
|
|
||||||
|
|
||||||
// // -- Executable --
|
// const exe = b.addExecutable(.{
|
||||||
// const exe = b.addExecutable("gdbserver", "src/main.zig");
|
// .name = "gdbserver",
|
||||||
|
// .root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
// .target = target,
|
||||||
|
// .optimize = optimize,
|
||||||
|
// });
|
||||||
// link(exe);
|
// link(exe);
|
||||||
|
|
||||||
// exe.setTarget(target);
|
|
||||||
// exe.setBuildMode(mode);
|
|
||||||
// exe.install();
|
// exe.install();
|
||||||
|
|
||||||
// const run_cmd = exe.run();
|
// const run_cmd = exe.run();
|
||||||
|
|||||||
Submodule lib/zig-network updated: a8c4502538...45ae8cf0ce
132
src/Packet.zig
132
src/Packet.zig
@@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Emulator = @import("lib.zig").Emulator;
|
const Emulator = @import("lib.zig").Emulator;
|
||||||
|
const State = @import("State.zig");
|
||||||
|
|
||||||
const target = @import("Server.zig").target;
|
const target = @import("Server.zig").target;
|
||||||
const memory_map = @import("Server.zig").memory_map;
|
const memory_map = @import("Server.zig").memory_map;
|
||||||
@@ -45,13 +46,10 @@ const String = union(enum) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
|
pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
|
||||||
switch (self.contents[0]) {
|
switch (self.contents[0]) {
|
||||||
// Required
|
// Required
|
||||||
'?' => {
|
'?' => return .{ .static = "T05" }, // FIXME: which errno?
|
||||||
const ret = try std.fmt.allocPrint(allocator, "S{x:0>2}", .{@enumToInt(Signal.Int)});
|
|
||||||
return .{ .alloc = ret };
|
|
||||||
},
|
|
||||||
'g' => {
|
'g' => {
|
||||||
const r = emu.registers();
|
const r = emu.registers();
|
||||||
const cpsr = emu.cpsr();
|
const cpsr = emu.cpsr();
|
||||||
@@ -76,8 +74,8 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
|
|||||||
'G' => @panic("TODO: Register Write"),
|
'G' => @panic("TODO: Register Write"),
|
||||||
'm' => {
|
'm' => {
|
||||||
var tokens = std.mem.tokenize(u8, self.contents[1..], ",");
|
var tokens = std.mem.tokenize(u8, self.contents[1..], ",");
|
||||||
const addr_str = tokens.next() orelse return .{ .static = "E9999" }; // EUNKNOWN
|
const addr_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
const length_str = tokens.next() orelse return .{ .static = "E9999" }; // EUNKNOWN
|
const length_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
|
||||||
const addr = try std.fmt.parseInt(u32, addr_str, 16);
|
const addr = try std.fmt.parseInt(u32, addr_str, 16);
|
||||||
const len = try std.fmt.parseInt(u32, length_str, 16);
|
const len = try std.fmt.parseInt(u32, length_str, 16);
|
||||||
@@ -87,9 +85,6 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
|
|||||||
{
|
{
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < len) : (i += 1) {
|
while (i < len) : (i += 1) {
|
||||||
const value = emu.read(addr + i);
|
|
||||||
log.debug("read 0x{X:0>2} from 0x{X:0>8}", .{ value, addr + i });
|
|
||||||
|
|
||||||
// writes the formatted integer to the buffer, returns a slice to the buffer but we ignore that
|
// writes the formatted integer to the buffer, returns a slice to the buffer but we ignore that
|
||||||
_ = std.fmt.bufPrintIntToSlice(ret[i * 2 ..][0..2], emu.read(addr + i), 16, .lower, .{ .fill = '0', .width = 2 });
|
_ = std.fmt.bufPrintIntToSlice(ret[i * 2 ..][0..2], emu.read(addr + i), 16, .lower, .{ .fill = '0', .width = 2 });
|
||||||
}
|
}
|
||||||
@@ -97,40 +92,124 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
|
|||||||
|
|
||||||
return .{ .alloc = ret };
|
return .{ .alloc = ret };
|
||||||
},
|
},
|
||||||
'M' => @panic("TODO: Memory Write"),
|
'M' => {
|
||||||
'c' => @panic("TODO: Continue"),
|
var tokens = std.mem.tokenize(u8, self.contents[1..], ",:");
|
||||||
|
|
||||||
|
const addr_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
const length_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
const bytes = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
|
||||||
|
const addr = try std.fmt.parseInt(u32, addr_str, 16);
|
||||||
|
const len = try std.fmt.parseInt(u32, length_str, 16);
|
||||||
|
|
||||||
|
{
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < len) : (i += 1) {
|
||||||
|
const str = bytes[2 * i ..][0..2];
|
||||||
|
const value = try std.fmt.parseInt(u8, str, 16);
|
||||||
|
|
||||||
|
emu.write(addr + i, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .static = "OK" };
|
||||||
|
},
|
||||||
|
'c' => {
|
||||||
|
switch (emu.contd()) {
|
||||||
|
.SingleStep => unreachable,
|
||||||
|
.Trap => |r| switch (r) {
|
||||||
|
.HwBkpt => return .{ .static = "T05 hwbreak:;" },
|
||||||
|
.SwBkpt => return .{ .static = "T05 swbreak:;" },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
's' => {
|
's' => {
|
||||||
// var tokens = std.mem.tokenize(u8, self.contents[1..], " ");
|
// var tokens = std.mem.tokenize(u8, self.contents[1..], " ");
|
||||||
// const addr = if (tokens.next()) |s| try std.fmt.parseInt(u32, s, 16) else null;
|
// const addr = if (tokens.next()) |s| try std.fmt.parseInt(u32, s, 16) else null;
|
||||||
|
|
||||||
emu.step();
|
switch (emu.step()) {
|
||||||
|
.SingleStep => return .{ .static = "T05" },
|
||||||
const ret = try std.fmt.allocPrint(allocator, "S{x:0>2}", .{@enumToInt(Signal.Trap)});
|
.Trap => |r| switch (r) {
|
||||||
return .{ .alloc = ret };
|
.HwBkpt => return .{ .static = "T05 hwbreak:;" },
|
||||||
|
.SwBkpt => return .{ .static = "T05 swbreak:;" },
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Optional
|
// Breakpoints
|
||||||
|
'z' => {
|
||||||
|
var tokens = std.mem.tokenize(u8, self.contents[2..], ",");
|
||||||
|
|
||||||
|
const addr_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
const addr = try std.fmt.parseInt(u32, addr_str, 16);
|
||||||
|
|
||||||
|
switch (self.contents[1]) {
|
||||||
|
'0' => {
|
||||||
|
emu.removeBkpt(.Software, addr);
|
||||||
|
return .{ .static = "OK" };
|
||||||
|
},
|
||||||
|
'1' => {
|
||||||
|
emu.removeBkpt(.Hardware, addr);
|
||||||
|
return .{ .static = "OK" };
|
||||||
|
},
|
||||||
|
'2' => return .{ .static = "" }, // TODO: Remove Write Watchpoint
|
||||||
|
'3' => return .{ .static = "" }, // TODO: Remove Read Watchpoint
|
||||||
|
'4' => return .{ .static = "" }, // TODO: Remove Access Watchpoint
|
||||||
|
else => return .{ .static = "" },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Z' => {
|
||||||
|
var tokens = std.mem.tokenize(u8, self.contents[2..], ",");
|
||||||
|
const addr_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
const kind_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
|
||||||
|
const addr = try std.fmt.parseInt(u32, addr_str, 16);
|
||||||
|
const kind = try std.fmt.parseInt(u32, kind_str, 16);
|
||||||
|
|
||||||
|
switch (self.contents[1]) {
|
||||||
|
'0' => {
|
||||||
|
try emu.addBkpt(.Software, addr, kind);
|
||||||
|
return .{ .static = "OK" };
|
||||||
|
},
|
||||||
|
'1' => {
|
||||||
|
emu.addBkpt(.Hardware, addr, kind) catch |e| {
|
||||||
|
switch (e) {
|
||||||
|
error.OutOfSpace => return .{ .static = "E22" }, // FIXME: which errno?
|
||||||
|
else => return e,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{ .static = "OK" };
|
||||||
|
},
|
||||||
|
'2' => return .{ .static = "" }, // TODO: Insert Write Watchpoint
|
||||||
|
'3' => return .{ .static = "" }, // TODO: Insert Read Watchpoint
|
||||||
|
'4' => return .{ .static = "" }, // TODO: Insert Access Watchpoint
|
||||||
|
else => return .{ .static = "" },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Figure out the difference between 'M' and 'X'
|
||||||
'D' => {
|
'D' => {
|
||||||
log.info("Disconnecting...", .{});
|
log.info("Disconnecting...", .{});
|
||||||
return .{ .static = "OK" };
|
return .{ .static = "OK" };
|
||||||
},
|
},
|
||||||
'H' => return .{ .static = "" },
|
'H' => return .{ .static = "" },
|
||||||
'v' => {
|
'v' => {
|
||||||
if (substr(self.contents[1..], "MustReplyEmpty")) {
|
if (!substr(self.contents[1..], "MustReplyEmpty")) {
|
||||||
return .{ .static = "" };
|
log.warn("Unimplemented: {s}", .{self.contents});
|
||||||
}
|
}
|
||||||
|
|
||||||
log.warn("Unimplemented: {s}", .{self.contents});
|
|
||||||
return .{ .static = "" };
|
return .{ .static = "" };
|
||||||
},
|
},
|
||||||
'q' => {
|
'q' => {
|
||||||
if (self.contents[1] == 'C' and self.contents.len == 2) return .{ .static = "QC1" };
|
if (self.contents[1] == 'C' and self.contents.len == 2) return .{ .static = "QC1" };
|
||||||
|
|
||||||
if (substr(self.contents[1..], "fThreadInfo")) return .{ .static = "m1" };
|
if (substr(self.contents[1..], "fThreadInfo")) return .{ .static = "m1" };
|
||||||
if (substr(self.contents[1..], "sThreadInfo")) return .{ .static = "l" };
|
if (substr(self.contents[1..], "sThreadInfo")) return .{ .static = "l" };
|
||||||
if (substr(self.contents[1..], "Attached")) return .{ .static = "1" }; // Tell GDB we're attached to a process
|
if (substr(self.contents[1..], "Attached")) return .{ .static = "1" }; // Tell GDB we're attached to a process
|
||||||
|
|
||||||
if (substr(self.contents[1..], "Supported")) {
|
if (substr(self.contents[1..], "Supported")) {
|
||||||
const format = "PacketSize={x:};qXfer:features:read+;qXfer:memory-map:read+";
|
const format = "PacketSize={x:};swbreak+;hwbreak+;qXfer:features:read+;qXfer:memory-map:read+";
|
||||||
// TODO: Anything else?
|
// TODO: Anything else?
|
||||||
|
|
||||||
const ret = try std.fmt.allocPrint(allocator, format, .{Self.max_len});
|
const ret = try std.fmt.allocPrint(allocator, format, .{Self.max_len});
|
||||||
@@ -142,9 +221,10 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
|
|||||||
_ = tokens.next(); // Xfer
|
_ = tokens.next(); // Xfer
|
||||||
_ = tokens.next(); // features
|
_ = tokens.next(); // features
|
||||||
_ = tokens.next(); // read
|
_ = tokens.next(); // read
|
||||||
const annex = tokens.next() orelse return .{ .static = "E9999" };
|
|
||||||
const offset_str = tokens.next() orelse return .{ .static = "E99999" };
|
const annex = tokens.next() orelse return error.InvalidPacket;
|
||||||
const length_str = tokens.next() orelse return .{ .static = "E9999" };
|
const offset_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
const length_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
|
||||||
if (std.mem.eql(u8, annex, "target.xml")) {
|
if (std.mem.eql(u8, annex, "target.xml")) {
|
||||||
const offset = try std.fmt.parseInt(usize, offset_str, 16);
|
const offset = try std.fmt.parseInt(usize, offset_str, 16);
|
||||||
@@ -173,8 +253,8 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
|
|||||||
_ = tokens.next(); // Xfer
|
_ = tokens.next(); // Xfer
|
||||||
_ = tokens.next(); // memory-map
|
_ = tokens.next(); // memory-map
|
||||||
_ = tokens.next(); // read
|
_ = tokens.next(); // read
|
||||||
const offset_str = tokens.next() orelse return .{ .static = "E9999" };
|
const offset_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
const length_str = tokens.next() orelse return .{ .static = "E9999" };
|
const length_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
|
|
||||||
const offset = try std.fmt.parseInt(usize, offset_str, 16);
|
const offset = try std.fmt.parseInt(usize, offset_str, 16);
|
||||||
const length = try std.fmt.parseInt(usize, length_str, 16);
|
const length = try std.fmt.parseInt(usize, length_str, 16);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const Packet = @import("Packet.zig");
|
|||||||
|
|
||||||
const Socket = network.Socket;
|
const Socket = network.Socket;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Atomic = std.atomic.Atomic;
|
||||||
const Emulator = @import("lib.zig").Emulator;
|
const Emulator = @import("lib.zig").Emulator;
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
@@ -94,16 +95,21 @@ const Action = union(enum) {
|
|||||||
nack,
|
nack,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn run(self: *Self, allocator: Allocator) !void {
|
pub fn run(self: *Self, allocator: Allocator, quit: *Atomic(bool)) !void {
|
||||||
var buf: [Packet.max_len]u8 = undefined;
|
var buf: [Packet.max_len]u8 = undefined;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const len = try self.client.receive(&buf);
|
const len = try self.client.receive(&buf);
|
||||||
if (len == 0) break;
|
if (len == 0) break;
|
||||||
|
|
||||||
|
if (quit.load(.Monotonic)) break;
|
||||||
const action = try self.parse(allocator, buf[0..len]);
|
const action = try self.parse(allocator, buf[0..len]);
|
||||||
try self.send(allocator, action);
|
try self.send(allocator, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Just in case its the gdbstub that exited first,
|
||||||
|
// attempt to signal to the GUI that it should also exit
|
||||||
|
quit.store(true, .Monotonic);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(self: *Self, allocator: Allocator, input: []const u8) !Action {
|
fn parse(self: *Self, allocator: Allocator, input: []const u8) !Action {
|
||||||
@@ -129,7 +135,7 @@ fn handlePacket(self: *Self, allocator: Allocator, input: []const u8) !Action {
|
|||||||
var packet = Packet.from(allocator, input) catch return .nack;
|
var packet = Packet.from(allocator, input) catch return .nack;
|
||||||
defer packet.deinit(allocator);
|
defer packet.deinit(allocator);
|
||||||
|
|
||||||
var string = packet.parse(allocator, self.emu) catch return .nack;
|
var string = packet.parse(allocator, &self.emu) catch return .nack;
|
||||||
defer string.deinit(allocator);
|
defer string.deinit(allocator);
|
||||||
|
|
||||||
const reply = string.inner();
|
const reply = string.inner();
|
||||||
|
|||||||
122
src/State.zig
Normal file
122
src/State.zig
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
|
||||||
|
hw_bkpt: HwBkpt = .{},
|
||||||
|
sw_bkpt: SwBkpt,
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) @This() {
|
||||||
|
return .{ .sw_bkpt = SwBkpt.init(allocator) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.sw_bkpt.deinit();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SwBkpt = struct {
|
||||||
|
const log = std.log.scoped(.SwBkpt);
|
||||||
|
|
||||||
|
list: std.ArrayList(Bkpt),
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) @This() {
|
||||||
|
return .{ .list = ArrayList(Bkpt).init(allocator) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.list.deinit();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isHit(self: *const @This(), addr: u32) bool {
|
||||||
|
for (self.list.items) |bkpt| {
|
||||||
|
if (bkpt.addr == addr) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: *@This(), addr: u32, kind: u32) !void {
|
||||||
|
for (self.list.items) |bkpt| {
|
||||||
|
if (bkpt.addr == addr) return; // indempotent
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.list.append(.{ .addr = addr, .kind = try Bkpt.Kind.from(u32, kind) });
|
||||||
|
log.warn("Added Breakpoint at 0x{X:0>8}", .{addr});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *@This(), addr: u32) void {
|
||||||
|
for (self.list.items, 0..) |bkpt, i| {
|
||||||
|
if (bkpt.addr == addr) {
|
||||||
|
_ = self.list.orderedRemove(i);
|
||||||
|
log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HwBkpt = struct {
|
||||||
|
const log = std.log.scoped(.HwBkpt);
|
||||||
|
|
||||||
|
list: [2]?Bkpt = .{ null, null },
|
||||||
|
|
||||||
|
pub fn isHit(self: *const @This(), addr: u32) bool {
|
||||||
|
for (self.list) |bkpt_opt| {
|
||||||
|
const bkpt = bkpt_opt orelse continue;
|
||||||
|
if (bkpt.addr == addr) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: *@This(), addr: u32, kind: u32) !void {
|
||||||
|
for (&self.list) |*bkpt_opt| {
|
||||||
|
if (bkpt_opt.*) |bkpt| {
|
||||||
|
if (bkpt.addr == addr) return; // idempotent
|
||||||
|
} else {
|
||||||
|
bkpt_opt.* = .{ .addr = addr, .kind = try Bkpt.Kind.from(u32, kind) };
|
||||||
|
log.debug("Added Breakpoint at 0x{X:0>8}", .{addr});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.OutOfSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *@This(), addr: u32) void {
|
||||||
|
for (&self.list) |*bkpt_opt| {
|
||||||
|
const bkpt = bkpt_opt.* orelse continue;
|
||||||
|
|
||||||
|
if (bkpt.addr == addr) {
|
||||||
|
bkpt_opt.* = null;
|
||||||
|
log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Bkpt = struct {
|
||||||
|
addr: u32,
|
||||||
|
kind: Kind,
|
||||||
|
|
||||||
|
const Kind = enum(u3) {
|
||||||
|
Arm = 2,
|
||||||
|
Thumb = 4,
|
||||||
|
|
||||||
|
pub fn from(comptime T: type, num: T) !@This() {
|
||||||
|
comptime std.debug.assert(@typeInfo(T) == .Int);
|
||||||
|
|
||||||
|
return switch (num) {
|
||||||
|
2 => .Arm,
|
||||||
|
4 => .Thumb,
|
||||||
|
else => error.UnknownBkptKind,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
71
src/lib.zig
71
src/lib.zig
@@ -1,10 +1,23 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
/// Re-export of the server interface
|
/// Re-export of the server interface
|
||||||
pub const Server = @import("Server.zig");
|
pub const Server = @import("Server.zig");
|
||||||
|
const State = @import("State.zig");
|
||||||
|
|
||||||
/// Interface for interacting between GDB and a GBA emu
|
/// Interface for interacting between GDB and a GBA emu
|
||||||
pub const Emulator = struct {
|
pub const Emulator = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
const Signal = union(enum) {
|
||||||
|
const Kind = enum { HwBkpt, SwBkpt };
|
||||||
|
|
||||||
|
Trap: Kind,
|
||||||
|
SingleStep: void,
|
||||||
|
};
|
||||||
|
|
||||||
|
state: State,
|
||||||
|
|
||||||
ptr: *anyopaque,
|
ptr: *anyopaque,
|
||||||
|
|
||||||
readFn: *const fn (*anyopaque, u32) u8,
|
readFn: *const fn (*anyopaque, u32) u8,
|
||||||
@@ -15,7 +28,7 @@ pub const Emulator = struct {
|
|||||||
|
|
||||||
stepFn: *const fn (*anyopaque) void,
|
stepFn: *const fn (*anyopaque) void,
|
||||||
|
|
||||||
pub fn init(ptr: anytype) Self {
|
pub fn init(allocator: Allocator, ptr: anytype) Self {
|
||||||
const Ptr = @TypeOf(ptr);
|
const Ptr = @TypeOf(ptr);
|
||||||
const ptr_info = @typeInfo(Ptr);
|
const ptr_info = @typeInfo(Ptr);
|
||||||
|
|
||||||
@@ -56,7 +69,21 @@ pub const Emulator = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{ .ptr = ptr, .readFn = gen.readImpl, .writeFn = gen.writeImpl, .registersFn = gen.registersImpl, .cpsrFn = gen.cpsrImpl, .stepFn = gen.stepImpl };
|
return .{
|
||||||
|
.ptr = ptr,
|
||||||
|
.readFn = gen.readImpl,
|
||||||
|
.writeFn = gen.writeImpl,
|
||||||
|
.registersFn = gen.registersImpl,
|
||||||
|
.cpsrFn = gen.cpsrImpl,
|
||||||
|
.stepFn = gen.stepImpl,
|
||||||
|
|
||||||
|
.state = State.init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.state.deinit();
|
||||||
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn read(self: Self, addr: u32) u8 {
|
pub inline fn read(self: Self, addr: u32) u8 {
|
||||||
@@ -75,7 +102,45 @@ pub const Emulator = struct {
|
|||||||
return self.cpsrFn(self.ptr);
|
return self.cpsrFn(self.ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn step(self: Self) void {
|
pub inline fn contd(self: *Self) Signal {
|
||||||
|
while (true) {
|
||||||
|
const signal = self.step();
|
||||||
|
|
||||||
|
switch (signal) {
|
||||||
|
.SingleStep => {},
|
||||||
|
.Trap => return signal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn step(self: *Self) Signal {
|
||||||
self.stepFn(self.ptr);
|
self.stepFn(self.ptr);
|
||||||
|
|
||||||
|
const r = self.registersFn(self.ptr);
|
||||||
|
const is_thumb = self.cpsrFn(self.ptr) >> 5 & 1 == 1;
|
||||||
|
|
||||||
|
const r15 = r[15] -| if (is_thumb) @as(u32, 4) else 8;
|
||||||
|
|
||||||
|
if (self.state.sw_bkpt.isHit(r15)) return .{ .Trap = .SwBkpt };
|
||||||
|
if (self.state.hw_bkpt.isHit(r15)) return .{ .Trap = .HwBkpt };
|
||||||
|
|
||||||
|
return .SingleStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BkptType = enum { Hardware, Software };
|
||||||
|
|
||||||
|
// TODO: Consider properly implementing Software interrupts?
|
||||||
|
pub fn addBkpt(self: *Self, comptime @"type": BkptType, addr: u32, kind: u32) !void {
|
||||||
|
switch (@"type") {
|
||||||
|
.Hardware => try self.state.hw_bkpt.add(addr, kind),
|
||||||
|
.Software => try self.state.sw_bkpt.add(addr, kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeBkpt(self: *Self, comptime @"type": BkptType, addr: u32) void {
|
||||||
|
switch (@"type") {
|
||||||
|
.Hardware => self.state.hw_bkpt.remove(addr),
|
||||||
|
.Software => self.state.sw_bkpt.remove(addr),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user