feat: implement software breakpoints
This commit is contained in:
parent
81ff227ea7
commit
d7b8d7acb1
|
@ -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();
|
||||||
|
@ -140,45 +137,55 @@ 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
|
var tokens = std.mem.tokenize(u8, self.contents[2..], ",");
|
||||||
'1' => {
|
|
||||||
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]) {
|
||||||
return .{ .static = "OK" };
|
'0' => {
|
||||||
},
|
emu.removeBkpt(.Software, addr);
|
||||||
'2' => return .{ .static = "" }, // TODO: Remove Write Watchpoint
|
return .{ .static = "OK" };
|
||||||
'3' => return .{ .static = "" }, // TODO: Remove Read Watchpoint
|
},
|
||||||
'4' => return .{ .static = "" }, // TODO: Remove Access Watchpoint
|
'1' => {
|
||||||
else => return .{ .static = "" },
|
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' => switch (self.contents[1]) {
|
'Z' => {
|
||||||
'0' => return .{ .static = "" }, //TODO: Insert Software Breakpoint
|
var tokens = std.mem.tokenize(u8, self.contents[2..], ",");
|
||||||
'1' => {
|
const addr_str = tokens.next() orelse return error.InvalidPacket;
|
||||||
var tokens = std.mem.tokenize(u8, self.contents[2..], ",");
|
const kind_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 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]) {
|
||||||
switch (e) {
|
'0' => {
|
||||||
error.OutOfSpace => return .{ .static = "E22" }, // FIXME: Which errno?
|
try emu.addBkpt(.Software, addr, kind);
|
||||||
else => return e,
|
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" };
|
return .{ .static = "OK" };
|
||||||
},
|
},
|
||||||
'2' => return .{ .static = "" }, // TODO: Insert Write Watchpoint
|
'2' => return .{ .static = "" }, // TODO: Insert Write Watchpoint
|
||||||
'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 = "" },
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Figure out the difference between 'M' and 'X'
|
// TODO: Figure out the difference between 'M' and 'X'
|
||||||
|
@ -202,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});
|
||||||
|
|
|
@ -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.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) |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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,17 +74,14 @@ 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 {
|
||||||
|
bkpt_opt.* = .{ .addr = addr, .kind = try Bkpt.Kind.from(u32, kind) };
|
||||||
|
log.debug("Added Breakpoint at 0x{X:0>8}", .{addr});
|
||||||
|
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return error.OutOfSpace;
|
||||||
|
@ -38,11 +89,14 @@ const HwBkpt = struct {
|
||||||
|
|
||||||
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?
|
|
||||||
|
|
||||||
log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr});
|
if (bkpt.addr == addr) {
|
||||||
if (bkpt.addr == addr) bkpt_opt.* = null;
|
bkpt_opt.* = null;
|
||||||
|
log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
41
src/lib.zig
41
src/lib.zig
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue