Compare commits

...

6 Commits

Author SHA1 Message Date
215e053b9a chore: replace link() with getModule() 2023-03-19 20:44:36 -05:00
acb59994fc chore: update to latest zig master 2023-02-23 02:43:58 -06:00
6d6a109a08 fix: gracefully exit
fix stack overflow bug in State.deinit
allow for code in another thread to signal shutdown to gdbstub
2023-02-13 20:01:01 -06:00
c1158b547e chore: update to zig master
current zig master has changes to build system
TODO: make use of Zig's package manager
2023-02-07 17:02:33 -06:00
d7b8d7acb1 feat: implement software breakpoints 2023-01-29 07:18:43 -06:00
81ff227ea7 feat: implement memory writes 2023-01-29 07:04:19 -06:00
6 changed files with 211 additions and 99 deletions

View File

@@ -1,54 +1,44 @@
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 getModule(b: *std.Build) *std.build.Module {
const Pkg = std.build.Pkg;
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 { return b.createModule(.{
exe.addPackage(pkgs.gdbstub); .source_file = .{ .path = path("src/lib.zig") },
.dependencies = &.{.{ .name = "network", .module = network }},
});
} }
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();

View File

@@ -49,10 +49,7 @@ 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();
@@ -95,7 +92,28 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
return .{ .alloc = ret }; return .{ .alloc = ret };
}, },
'M' => @panic("TODO: Memory Write"), 'M' => {
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' => { 'c' => {
switch (emu.contd()) { switch (emu.contd()) {
.SingleStep => unreachable, .SingleStep => unreachable,
@@ -119,25 +137,28 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
}, },
// Breakpoints // Breakpoints
'z' => switch (self.contents[1]) { 'z' => {
'0' => return .{ .static = "" }, //TODO: Remove Software Breakpoint
'1' => {
var tokens = std.mem.tokenize(u8, self.contents[2..], ","); var tokens = std.mem.tokenize(u8, self.contents[2..], ",");
const addr_str = tokens.next() orelse return error.InvalidPacket; const addr_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);
emu.state.hw_bkpt.remove(addr); switch (self.contents[1]) {
'0' => {
emu.removeBkpt(.Software, addr);
return .{ .static = "OK" };
},
'1' => {
emu.removeBkpt(.Hardware, addr);
return .{ .static = "OK" }; return .{ .static = "OK" };
}, },
'2' => return .{ .static = "" }, // TODO: Remove Write Watchpoint '2' => return .{ .static = "" }, // TODO: Remove Write Watchpoint
'3' => return .{ .static = "" }, // TODO: Remove Read Watchpoint '3' => return .{ .static = "" }, // TODO: Remove Read Watchpoint
'4' => return .{ .static = "" }, // TODO: Remove Access Watchpoint '4' => return .{ .static = "" }, // TODO: Remove Access Watchpoint
else => return .{ .static = "" }, else => return .{ .static = "" },
}
}, },
'Z' => switch (self.contents[1]) { 'Z' => {
'0' => return .{ .static = "" }, //TODO: Insert Software Breakpoint
'1' => {
var tokens = std.mem.tokenize(u8, self.contents[2..], ","); var tokens = std.mem.tokenize(u8, self.contents[2..], ",");
const addr_str = tokens.next() orelse return error.InvalidPacket; const addr_str = tokens.next() orelse return error.InvalidPacket;
const kind_str = tokens.next() orelse return error.InvalidPacket; const kind_str = tokens.next() orelse return error.InvalidPacket;
@@ -145,9 +166,15 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
const addr = try std.fmt.parseInt(u32, addr_str, 16); const addr = try std.fmt.parseInt(u32, addr_str, 16);
const kind = try std.fmt.parseInt(u32, kind_str, 16); const kind = try std.fmt.parseInt(u32, kind_str, 16);
emu.state.hw_bkpt.add(addr, kind) catch |e| { switch (self.contents[1]) {
'0' => {
try emu.addBkpt(.Software, addr, kind);
return .{ .static = "OK" };
},
'1' => {
emu.addBkpt(.Hardware, addr, kind) catch |e| {
switch (e) { switch (e) {
error.OutOfSpace => return .{ .static = "E22" }, // FIXME: Which errno? error.OutOfSpace => return .{ .static = "E22" }, // FIXME: which errno?
else => return e, else => return e,
} }
}; };
@@ -158,20 +185,20 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
'3' => return .{ .static = "" }, // TODO: Insert Read Watchpoint '3' => return .{ .static = "" }, // TODO: Insert Read Watchpoint
'4' => return .{ .static = "" }, // TODO: Insert Access Watchpoint '4' => return .{ .static = "" }, // TODO: Insert Access Watchpoint
else => return .{ .static = "" }, else => return .{ .static = "" },
}
}, },
// Optional // 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' => {
@@ -182,7 +209,7 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
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});

View File

@@ -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 {

View File

@@ -1,6 +1,62 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
hw_bkpt: HwBkpt = .{}, 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 HwBkpt = struct {
const log = std.log.scoped(.HwBkpt); const log = std.log.scoped(.HwBkpt);
@@ -9,9 +65,7 @@ const HwBkpt = struct {
pub fn isHit(self: *const @This(), addr: u32) bool { pub fn isHit(self: *const @This(), addr: u32) bool {
for (self.list) |bkpt_opt| { for (self.list) |bkpt_opt| {
if (bkpt_opt == null) continue; const bkpt = bkpt_opt orelse continue;
const bkpt = bkpt_opt.?;
if (bkpt.addr == addr) return true; if (bkpt.addr == addr) return true;
} }
@@ -19,30 +73,30 @@ const HwBkpt = struct {
} }
pub fn add(self: *@This(), addr: u32, kind: u32) !void { pub fn add(self: *@This(), addr: u32, kind: u32) !void {
for (self.list) |*bkpt_opt| { for (&self.list) |*bkpt_opt| {
if (bkpt_opt.* != null) { if (bkpt_opt.*) |bkpt| {
const bkpt = bkpt_opt.*.?; if (bkpt.addr == addr) return; // idempotent
if (bkpt.addr == addr) return; // makes this fn indempotent } else {
continue;
}
bkpt_opt.* = .{ .addr = addr, .kind = try Bkpt.Kind.from(u32, kind) }; bkpt_opt.* = .{ .addr = addr, .kind = try Bkpt.Kind.from(u32, kind) };
log.debug("Added Breakpoint at 0x{X:0>8}", .{addr}); log.debug("Added Breakpoint at 0x{X:0>8}", .{addr});
return; return;
} }
}
return error.OutOfSpace; return error.OutOfSpace;
} }
pub fn remove(self: *@This(), addr: u32) void { pub fn remove(self: *@This(), addr: u32) void {
for (self.list) |*bkpt_opt| { for (&self.list) |*bkpt_opt| {
if (bkpt_opt.* == null) continue; const bkpt = bkpt_opt.* orelse continue;
const bkpt = bkpt_opt.*.?; // FIXME: bkpt_opt.?.addr works though?
if (bkpt.addr == addr) {
bkpt_opt.* = null;
log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr}); log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr});
if (bkpt.addr == addr) bkpt_opt.* = null;
break;
}
} }
} }
}; };

View File

@@ -1,3 +1,6 @@
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"); const State = @import("State.zig");
@@ -13,7 +16,7 @@ pub const Emulator = struct {
SingleStep: void, SingleStep: void,
}; };
state: State = .{}, state: State,
ptr: *anyopaque, ptr: *anyopaque,
@@ -25,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);
@@ -66,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 {
@@ -104,8 +121,26 @@ pub const Emulator = struct {
const r15 = r[15] -| if (is_thumb) @as(u32, 4) else 8; 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 }; if (self.state.hw_bkpt.isHit(r15)) return .{ .Trap = .HwBkpt };
return .SingleStep; 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),
}
}
}; };