From d7b8d7acb15ce3518480c59218bf2c0a17fa529f Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Sun, 29 Jan 2023 07:17:12 -0600 Subject: [PATCH] feat: implement software breakpoints --- src/Packet.zig | 83 ++++++++++++++++++++++++++---------------------- src/State.zig | 86 ++++++++++++++++++++++++++++++++++++++++---------- src/lib.zig | 41 ++++++++++++++++++++++-- 3 files changed, 153 insertions(+), 57 deletions(-) diff --git a/src/Packet.zig b/src/Packet.zig index a9ab9b9..e628b26 100644 --- a/src/Packet.zig +++ b/src/Packet.zig @@ -49,10 +49,7 @@ const String = union(enum) { pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String { switch (self.contents[0]) { // Required - '?' => { - const ret = try std.fmt.allocPrint(allocator, "S{x:0>2}", .{@enumToInt(Signal.Int)}); - return .{ .alloc = ret }; - }, + '?' => return .{ .static = "T05" }, // FIXME: which errno? 'g' => { const r = emu.registers(); const cpsr = emu.cpsr(); @@ -140,45 +137,55 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String { }, // Breakpoints - 'z' => switch (self.contents[1]) { - '0' => return .{ .static = "" }, //TODO: Remove Software Breakpoint - '1' => { - var tokens = std.mem.tokenize(u8, self.contents[2..], ","); + '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); + 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 = "" }, + 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' => 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; + '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); + 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, - } - }; + 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 = "" }, + 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' @@ -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..], "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? const ret = try std.fmt.allocPrint(allocator, format, .{Self.max_len}); diff --git a/src/State.zig b/src/State.zig index a372020..5c4e0f7 100644 --- a/src/State.zig +++ b/src/State.zig @@ -1,6 +1,62 @@ 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.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 log = std.log.scoped(.HwBkpt); @@ -9,9 +65,7 @@ const HwBkpt = struct { pub fn isHit(self: *const @This(), addr: u32) bool { for (self.list) |bkpt_opt| { - if (bkpt_opt == null) continue; - const bkpt = bkpt_opt.?; - + const bkpt = bkpt_opt orelse continue; if (bkpt.addr == addr) return true; } @@ -20,17 +74,14 @@ const HwBkpt = struct { 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 + 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}); - 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; @@ -38,11 +89,14 @@ const HwBkpt = struct { 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? + const bkpt = bkpt_opt.* orelse continue; - log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr}); - if (bkpt.addr == addr) bkpt_opt.* = null; + if (bkpt.addr == addr) { + bkpt_opt.* = null; + log.debug("Removed Breakpoint at 0x{X:0>8}", .{addr}); + + break; + } } } }; diff --git a/src/lib.zig b/src/lib.zig index f19a982..d039419 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,3 +1,6 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + /// Re-export of the server interface pub const Server = @import("Server.zig"); const State = @import("State.zig"); @@ -13,7 +16,7 @@ pub const Emulator = struct { SingleStep: void, }; - state: State = .{}, + state: State, ptr: *anyopaque, @@ -25,7 +28,7 @@ pub const Emulator = struct { 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_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 { @@ -104,8 +121,26 @@ pub const Emulator = struct { 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), + } + } };