Compare commits

...

3 Commits

Author SHA1 Message Date
82bad92fcf fix: refactor how c and s track breakpoints
We move state from Server.zig to Emulator.zig (the interface)
2023-01-26 23:55:31 -06:00
dbf00006e7 fix: return E22 if hwbreak arr full
Also, ensure that the above case is the only time we return an errno
err. All the others are actually problems when parsing an invalid packet
so we should nack instead
2023-01-26 23:14:23 -06:00
59b6b51466 feat: add hardware breakpoints 2023-01-26 20:47:57 -06:00
4 changed files with 168 additions and 17 deletions

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const Emulator = @import("lib.zig").Emulator;
const State = @import("State.zig");
const target = @import("Server.zig").target;
const memory_map = @import("Server.zig").memory_map;
@@ -45,7 +46,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]) {
// Required
'?' => {
@@ -76,8 +77,8 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
'G' => @panic("TODO: Register Write"),
'm' => {
var tokens = std.mem.tokenize(u8, self.contents[1..], ",");
const addr_str = tokens.next() orelse return .{ .static = "E9999" }; // EUNKNOWN
const length_str = tokens.next() orelse return .{ .static = "E9999" }; // EUNKNOWN
const addr_str = tokens.next() orelse return error.InvalidPacket;
const length_str = 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);
@@ -87,9 +88,6 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
{
var i: u32 = 0;
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
_ = std.fmt.bufPrintIntToSlice(ret[i * 2 ..][0..2], emu.read(addr + i), 16, .lower, .{ .fill = '0', .width = 2 });
}
@@ -98,15 +96,68 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
return .{ .alloc = ret };
},
'M' => @panic("TODO: Memory Write"),
'c' => @panic("TODO: Continue"),
'c' => {
switch (emu.contd()) {
.SingleStep => unreachable,
.Trap => |r| switch (r) {
.HwBkpt => return .{ .static = "T05 hwbreak:;" },
.SwBkpt => return .{ .static = "T05 swbreak:;" },
},
}
},
's' => {
// var tokens = std.mem.tokenize(u8, self.contents[1..], " ");
// const addr = if (tokens.next()) |s| try std.fmt.parseInt(u32, s, 16) else null;
emu.step();
switch (emu.step()) {
.SingleStep => return .{ .static = "T05" },
.Trap => |r| switch (r) {
.HwBkpt => return .{ .static = "T05 hwbreak:;" },
.SwBkpt => return .{ .static = "T05 swbreak:;" },
},
}
},
const ret = try std.fmt.allocPrint(allocator, "S{x:0>2}", .{@enumToInt(Signal.Trap)});
return .{ .alloc = ret };
// Breakpoints
'z' => switch (self.contents[1]) {
'0' => return .{ .static = "" }, //TODO: Remove Software Breakpoint
'1' => {
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);
emu.state.hw_bkpt.remove(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' => switch (self.contents[1]) {
'0' => return .{ .static = "" }, //TODO: Insert Software Breakpoint
'1' => {
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);
emu.state.hw_bkpt.add(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 = "" },
},
// Optional
@@ -125,6 +176,7 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
},
'q' => {
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..], "sThreadInfo")) return .{ .static = "l" };
if (substr(self.contents[1..], "Attached")) return .{ .static = "1" }; // Tell GDB we're attached to a process
@@ -142,9 +194,10 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
_ = tokens.next(); // Xfer
_ = tokens.next(); // features
_ = tokens.next(); // read
const annex = tokens.next() orelse return .{ .static = "E9999" };
const offset_str = tokens.next() orelse return .{ .static = "E99999" };
const length_str = tokens.next() orelse return .{ .static = "E9999" };
const annex = tokens.next() orelse return error.InvalidPacket;
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")) {
const offset = try std.fmt.parseInt(usize, offset_str, 16);
@@ -173,8 +226,8 @@ pub fn parse(self: *Self, allocator: Allocator, emu: Emulator) !String {
_ = tokens.next(); // Xfer
_ = tokens.next(); // memory-map
_ = tokens.next(); // read
const offset_str = tokens.next() orelse return .{ .static = "E9999" };
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;
const offset = try std.fmt.parseInt(usize, offset_str, 16);
const length = try std.fmt.parseInt(usize, length_str, 16);

View File

@@ -129,7 +129,7 @@ fn handlePacket(self: *Self, allocator: Allocator, input: []const u8) !Action {
var packet = Packet.from(allocator, input) catch return .nack;
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);
const reply = string.inner();

68
src/State.zig Normal file
View File

@@ -0,0 +1,68 @@
const std = @import("std");
hw_bkpt: HwBkpt = .{},
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| {
if (bkpt_opt == null) continue;
const bkpt = bkpt_opt.?;
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.* != null) {
const bkpt = bkpt_opt.*.?;
if (bkpt.addr == addr) return; // makes this fn indempotent
continue;
}
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| {
if (bkpt_opt.* == null) continue;
const bkpt = bkpt_opt.*.?; // FIXME: bkpt_opt.?.addr works though?
log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr});
if (bkpt.addr == addr) bkpt_opt.* = null;
}
}
};
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,
};
}
};
};

View File

@@ -1,10 +1,20 @@
/// Re-export of the server interface
pub const Server = @import("Server.zig");
const State = @import("State.zig");
/// Interface for interacting between GDB and a GBA emu
pub const Emulator = struct {
const Self = @This();
const Signal = union(enum) {
const Kind = enum { HwBkpt, SwBkpt };
Trap: Kind,
SingleStep: void,
};
state: State = .{},
ptr: *anyopaque,
readFn: *const fn (*anyopaque, u32) u8,
@@ -75,7 +85,27 @@ pub const Emulator = struct {
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);
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.hw_bkpt.isHit(r15)) return .{ .Trap = .HwBkpt };
return .SingleStep;
}
};