diff --git a/build.zig b/build.zig index 845b69e..cb146b7 100644 --- a/build.zig +++ b/build.zig @@ -1,35 +1,63 @@ const std = @import("std"); -// Although this function looks imperative, note that its job is to -// declaratively construct a build graph that will be executed by an external -// runner. +// Although this function looks imperative, it does not perform the build +// directly and instead it mutates the build graph (`b`) that will be then +// executed by an external runner. The functions in `std.Build` implement a DSL +// for defining build steps and express dependencies between them, allowing the +// build runner to parallelize the build automatically (and the cache system to +// know when a step doesn't need to be re-run). pub fn build(b: *std.Build) void { - // Standard target options allows the person running `zig build` to choose + // Standard target options allow the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{}); - // Standard optimization options allow the person running `zig build` to select - // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not - // set a preferred release mode, allowing the user to decide how to optimize. - const optimize = b.standardOptimizeOption(.{}); - - _ = b.addModule("zba-gdbstub", .{ .root_source_file = b.path("src/lib.zig") }); - - // Creates a step for unit testing. This only builds the test executable - // but does not run it. - const lib_unit_tests = b.addTest(.{ + // This creates a module, which represents a collection of source files alongside + // some compilation options, such as optimization mode and linked system libraries. + // Zig modules are the preferred way of making Zig code available to consumers. + // addModule defines a module that we intend to make available for importing + // to our consumers. We must give it a name because a Zig package can expose + // multiple modules and consumers will need to be able to specify which + // module they want to access. + const mod = b.addModule("zba_gdbstub", .{ + // The root source file is the "entry point" of this module. Users of + // this module will only be able to access public declarations contained + // in this file, which means that if you have declarations that you + // intend to expose to consumers that were defined in other files part + // of this module, you will have to make sure to re-export them from + // the root file. .root_source_file = b.path("src/lib.zig"), + // Later on we'll use this module as the root module of a test executable + // which requires us to specify a target. .target = target, - .optimize = optimize, }); - const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + // Creates an executable that will run `test` blocks from the provided module. + // Here `mod` needs to define a target, which is why earlier we made sure to + // set the releative field. + const mod_tests = b.addTest(.{ + .root_module = mod, + }); - // Similar to creating the run step earlier, this exposes a `test` step to - // the `zig build --help` menu, providing a way for the user to request - // running the unit tests. - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_lib_unit_tests.step); + // A run step that will run the test executable. + const run_mod_tests = b.addRunArtifact(mod_tests); + + // A top level step for running all tests. dependOn can be called multiple + // times and since the two run steps do not depend on one another, this will + // make the two of them run in parallel. + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_mod_tests.step); + + // Just like flags, top level steps are also listed in the `--help` menu. + // + // The Zig build system is entirely implemented in userland, which means + // that it cannot hook into private compiler APIs. All compilation work + // orchestrated by the build system will result in other Zig compiler + // subcommands being invoked with the right flags defined. You can observe + // these invocations when one fails (or you pass a flag to increase + // verbosity) to validate assumptions and diagnose problems. + // + // Lastly, the Zig build system is relatively simple and self-contained, + // and reading its source code will allow you to master it. } diff --git a/build.zig.zon b/build.zig.zon index e79003b..2dc023f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,17 +6,26 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "zba-gdbstub", - + .name = .zba_gdbstub, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. - .version = "0.1.0", - - // This field is optional. - // This is currently advisory only; Zig does not yet do anything - // with this value. - //.minimum_zig_version = "0.11.0", - + .version = "0.0.0", + // Together with name, this represents a globally unique package + // identifier. This field is generated by the Zig toolchain when the + // package is first created, and then *never changes*. This allows + // unambiguous detection of one package being an updated version of + // another. + // + // When forking a Zig project, this id should be regenerated (delete the + // field and run `zig build`) if the upstream project is still maintained. + // Otherwise, the fork is *hostile*, attempting to take control over the + // original project's identity. Thus it is recommended to leave the comment + // on the following line intact, so that it shows up in code reviews that + // modify the field. + .fingerprint = 0x8006426818f63728, // Changing this has security and trust implications. + // Tracks the earliest Zig version that the package considers to be a + // supported use case. + .minimum_zig_version = "0.15.1", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. @@ -27,7 +36,8 @@ //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at - // // the new URL. + // // the new URL. If the contents of a URL change this will result in a hash mismatch + // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is @@ -45,14 +55,13 @@ // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", - + // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, - // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk diff --git a/src/Packet.zig b/src/Packet.zig index 1d60c60..bf83f14 100644 --- a/src/Packet.zig +++ b/src/Packet.zig @@ -12,7 +12,7 @@ pub const max_len: usize = 0x1000; contents: []const u8, pub fn from(allocator: Allocator, str: []const u8) !Self { - var tokens = std.mem.tokenize(u8, str, "$#"); + var tokens = std.mem.tokenizeAny(u8, str, "$#"); const contents = tokens.next() orelse return error.InvalidPacket; const chksum_str = tokens.next() orelse return error.MissingCheckSum; @@ -63,7 +63,9 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul // writes the formatted integer to the buffer, returns a slice to the buffer but we ignore that // GDB also expects the bytes to be in the opposite order for whatever reason - _ = std.fmt.bufPrintIntToSlice(ret[i * 8 ..][0..8], @byteSwap(reg), 16, .lower, .{ .fill = '0', .width = 8 }); + + _ = try std.fmt.bufPrint(ret[i * 8 ..][0..8], "{x:0>8}", .{@byteSwap(reg)}); + // _ = std.fmt.bufPrintIntToSlice(ret[i * 8 ..][0..8], @byteSwap(reg), 16, .lower, .{ .fill = '0', .width = 8 }); } } @@ -71,7 +73,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul }, 'G' => @panic("TODO: Register Write"), 'm' => { - var tokens = std.mem.tokenize(u8, self.contents[1..], ","); + var tokens = std.mem.tokenizeAny(u8, self.contents[1..], ","); const addr_str = tokens.next() orelse return error.InvalidPacket; const length_str = tokens.next() orelse return error.InvalidPacket; @@ -84,14 +86,16 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul var i: u32 = 0; while (i < len) : (i += 1) { // 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 }); + + _ = try std.fmt.bufPrint(ret[i * 2 ..][0..2], "{x:0>2}", .{emu.read(addr + i)}); + // _ = std.fmt.bufPrintIntToSlice(ret[i * 2 ..][0..2], emu.read(addr + i), 16, .lower, .{ .fill = '0', .width = 2 }); } } return .{ .alloc = ret }; }, 'M' => { - var tokens = std.mem.tokenize(u8, self.contents[1..], ",:"); + var tokens = std.mem.tokenizeAny(u8, self.contents[1..], ",:"); const addr_str = tokens.next() orelse return error.InvalidPacket; const length_str = tokens.next() orelse return error.InvalidPacket; @@ -122,7 +126,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul } }, 's' => { - // var tokens = std.mem.tokenize(u8, self.contents[1..], " "); + // var tokens = std.mem.tokenizeAny(u8, self.contents[1..], " "); // const addr = if (tokens.next()) |s| try std.fmt.parseInt(u32, s, 16) else null; switch (emu.step()) { @@ -136,7 +140,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul // Breakpoints 'z' => { - var tokens = std.mem.tokenize(u8, self.contents[2..], ","); + var tokens = std.mem.tokenizeAny(u8, self.contents[2..], ","); const addr_str = tokens.next() orelse return error.InvalidPacket; const addr = try std.fmt.parseInt(u32, addr_str, 16); @@ -157,7 +161,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul } }, 'Z' => { - var tokens = std.mem.tokenize(u8, self.contents[2..], ","); + var tokens = std.mem.tokenizeAny(u8, self.contents[2..], ","); const addr_str = tokens.next() orelse return error.InvalidPacket; const kind_str = tokens.next() orelse return error.InvalidPacket; @@ -166,11 +170,11 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul switch (self.contents[1]) { '0' => { - try emu.addBkpt(.Software, addr, kind); + try emu.addBkpt(.Software, allocator, addr, kind); return .{ .static = "OK" }; }, '1' => { - emu.addBkpt(.Hardware, addr, kind) catch |e| { + emu.addBkpt(.Hardware, allocator, addr, kind) catch |e| { switch (e) { error.OutOfSpace => return .{ .static = "E22" }, // FIXME: which errno? else => return e, @@ -232,7 +236,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul } if (substr(self.contents[1..], "Xfer:features:read")) { - var tokens = std.mem.tokenize(u8, self.contents[1..], ":,"); + var tokens = std.mem.tokenizeAny(u8, self.contents[1..], ":,"); _ = tokens.next(); // Xfer _ = tokens.next(); // features _ = tokens.next(); // read @@ -266,7 +270,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul if (substr(self.contents[1..], "Xfer:memory-map:read")) { const mem_map = state.memmap_xml.?; - var tokens = std.mem.tokenize(u8, self.contents[1..], ":,"); + var tokens = std.mem.tokenizeAny(u8, self.contents[1..], ":,"); _ = tokens.next(); // Xfer _ = tokens.next(); // memory-map _ = tokens.next(); // read diff --git a/src/Server.zig b/src/Server.zig index d939803..0e8fc10 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -54,7 +54,7 @@ pub fn run(self: *Self, allocator: Allocator, should_quit: *std.atomic.Value(boo var buf: [Packet.max_len]u8 = undefined; var client = try self.socket.accept(); - log.info("client connected from {}", .{client.address}); + log.info("client connected from {f}", .{client.address}); while (!should_quit.load(.monotonic)) { if (self.state.should_quit) { diff --git a/src/State.zig b/src/State.zig index 3280150..8243e0a 100644 --- a/src/State.zig +++ b/src/State.zig @@ -1,31 +1,21 @@ const std = @import("std"); - const Allocator = std.mem.Allocator; -const ArrayList = std.ArrayList; hw_bkpt: HwBkpt = .{}, -sw_bkpt: SwBkpt, +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(); +pub fn deinit(self: *@This(), allocator: Allocator) void { + self.sw_bkpt.deinit(allocator); self.* = undefined; } const SwBkpt = struct { const log = std.log.scoped(.SwBkpt); - list: std.ArrayList(Bkpt), + list: std.ArrayList(Bkpt) = .empty, - pub fn init(allocator: Allocator) @This() { - return .{ .list = ArrayList(Bkpt).init(allocator) }; - } - - pub fn deinit(self: *@This()) void { - self.list.deinit(); + pub fn deinit(self: *@This(), allocator: Allocator) void { + self.list.deinit(allocator); self.* = undefined; } @@ -37,12 +27,12 @@ const SwBkpt = struct { return false; } - pub fn add(self: *@This(), addr: u32, kind: u32) !void { + pub fn add(self: *@This(), allocator: Allocator, 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) }); + try self.list.append(allocator, .{ .addr = addr, .kind = try Bkpt.Kind.from(u32, kind) }); log.warn("Added Breakpoint at 0x{X:0>8}", .{addr}); } @@ -110,7 +100,7 @@ const Bkpt = struct { Thumb = 4, pub fn from(comptime T: type, num: T) !@This() { - comptime std.debug.assert(@typeInfo(T) == .Int); + comptime std.debug.assert(@typeInfo(T) == .int); return switch (num) { 2 => .Arm, diff --git a/src/lib.zig b/src/lib.zig index 061cc39..ebf5214 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -16,7 +16,7 @@ pub const Emulator = struct { SingleStep: void, }; - state: State, + state: State = .{}, ptr: *anyopaque, @@ -28,42 +28,42 @@ pub const Emulator = struct { stepFn: *const fn (*anyopaque) void, - pub fn init(allocator: Allocator, ptr: anytype) Self { + pub fn init(ptr: anytype) Self { const Ptr = @TypeOf(ptr); const ptr_info = @typeInfo(Ptr); - if (ptr_info != .Pointer) @compileError("ptr must be a pointer"); - if (ptr_info.Pointer.size != .One) @compileError("ptr must be a single-item pointer"); + if (ptr_info != .pointer) @compileError("ptr must be a pointer"); + if (ptr_info.pointer.size != .one) @compileError("ptr must be a single-item pointer"); const gen = struct { pub fn readImpl(pointer: *anyopaque, addr: u32) u8 { const self: Ptr = @ptrCast(@alignCast(pointer)); - return @call(.always_inline, ptr_info.Pointer.child.read, .{ self, addr }); + return @call(.always_inline, ptr_info.pointer.child.read, .{ self, addr }); } pub fn writeImpl(pointer: *anyopaque, addr: u32, value: u8) void { const self: Ptr = @ptrCast(@alignCast(pointer)); - return @call(.always_inline, ptr_info.Pointer.child.write, .{ self, addr, value }); + return @call(.always_inline, ptr_info.pointer.child.write, .{ self, addr, value }); } pub fn registersImpl(pointer: *anyopaque) *[16]u32 { const self: Ptr = @ptrCast(@alignCast(pointer)); - return @call(.always_inline, ptr_info.Pointer.child.registers, .{self}); + return @call(.always_inline, ptr_info.pointer.child.registers, .{self}); } pub fn cpsrImpl(pointer: *anyopaque) u32 { const self: Ptr = @ptrCast(@alignCast(pointer)); - return @call(.always_inline, ptr_info.Pointer.child.cpsr, .{self}); + return @call(.always_inline, ptr_info.pointer.child.cpsr, .{self}); } pub fn stepImpl(pointer: *anyopaque) void { const self: Ptr = @ptrCast(@alignCast(pointer)); - return @call(.always_inline, ptr_info.Pointer.child.step, .{self}); + return @call(.always_inline, ptr_info.pointer.child.step, .{self}); } }; @@ -74,13 +74,11 @@ pub const Emulator = struct { .registersFn = gen.registersImpl, .cpsrFn = gen.cpsrImpl, .stepFn = gen.stepImpl, - - .state = State.init(allocator), }; } - pub fn deinit(self: *Self) void { - self.state.deinit(); + pub fn deinit(self: *Self, allocator: Allocator) void { + self.state.deinit(allocator); self.* = undefined; } @@ -128,10 +126,10 @@ pub const Emulator = struct { const BkptType = enum { Hardware, Software }; // TODO: Consider properly implementing Software interrupts? - pub fn addBkpt(self: *Self, comptime @"type": BkptType, addr: u32, kind: u32) !void { + pub fn addBkpt(self: *Self, comptime @"type": BkptType, allocator: Allocator, 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), + .Software => try self.state.sw_bkpt.add(allocator, addr, kind), } } diff --git a/src/test.zig b/src/test.zig index 3d9e0ae..c6b3b26 100644 --- a/src/test.zig +++ b/src/test.zig @@ -52,8 +52,8 @@ const BarebonesEmulator = struct { r: [16]u32 = [_]u32{0} ** 16, - pub fn interface(self: *@This(), allocator: Allocator) Emulator { - return Emulator.init(allocator, self); + pub fn interface(self: *@This()) Emulator { + return Emulator.init(self); } pub fn read(_: *const @This(), _: u32) u8 { @@ -88,15 +88,19 @@ test Server { const allocator = std.testing.allocator; var impl = BarebonesEmulator{}; - var iface = impl.interface(allocator); - defer iface.deinit(); + var iface = impl.interface(); + defer iface.deinit(allocator); const clientFn = struct { fn inner(address: std.net.Address) !void { const socket = try std.net.tcpConnectToAddress(address); defer socket.close(); - _ = try socket.writer().writeAll("+"); + var buf: [1024]u8 = undefined; + + var writer = socket.writer(&buf).interface; + + _ = try writer.writeAll("+"); } }.inner; @@ -142,7 +146,7 @@ test Emulator { }; var impl = ExampleImpl{}; - var emu = Emulator.init(std.testing.allocator, &impl); + var emu = Emulator.init(&impl); _ = emu.read(0x0000_0000); emu.write(0x0000_0000, 0x00);