chore: upgrade to zig v0.15.1

This commit is contained in:
2025-09-22 20:31:31 -05:00
parent 9a50607d5f
commit 7b77964e14
7 changed files with 119 additions and 86 deletions

View File

@@ -1,35 +1,63 @@
const std = @import("std"); const std = @import("std");
// Although this function looks imperative, note that its job is to // Although this function looks imperative, it does not perform the build
// declaratively construct a build graph that will be executed by an external // directly and instead it mutates the build graph (`b`) that will be then
// runner. // 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 { 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 // 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 // means any target is allowed, and the default is native. Other options
// for restricting supported target set are available. // for restricting supported target set are available.
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select // This creates a module, which represents a collection of source files alongside
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // some compilation options, such as optimization mode and linked system libraries.
// set a preferred release mode, allowing the user to decide how to optimize. // Zig modules are the preferred way of making Zig code available to consumers.
const optimize = b.standardOptimizeOption(.{}); // 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
_ = b.addModule("zba-gdbstub", .{ .root_source_file = b.path("src/lib.zig") }); // multiple modules and consumers will need to be able to specify which
// module they want to access.
// Creates a step for unit testing. This only builds the test executable const mod = b.addModule("zba_gdbstub", .{
// but does not run it. // The root source file is the "entry point" of this module. Users of
const lib_unit_tests = b.addTest(.{ // 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"), .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, .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 // A run step that will run the test executable.
// the `zig build --help` menu, providing a way for the user to request const run_mod_tests = b.addRunArtifact(mod_tests);
// running the unit tests.
const test_step = b.step("test", "Run unit tests"); // A top level step for running all tests. dependOn can be called multiple
test_step.dependOn(&run_lib_unit_tests.step); // 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.
} }

View File

@@ -6,17 +6,26 @@
// //
// It is redundant to include "zig" in this name because it is already // It is redundant to include "zig" in this name because it is already
// within the Zig package namespace. // within the Zig package namespace.
.name = "zba-gdbstub", .name = .zba_gdbstub,
// This is a [Semantic Version](https://semver.org/). // This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication. // In a future version of Zig it will be used for package deduplication.
.version = "0.1.0", .version = "0.0.0",
// Together with name, this represents a globally unique package
// This field is optional. // identifier. This field is generated by the Zig toolchain when the
// This is currently advisory only; Zig does not yet do anything // package is first created, and then *never changes*. This allows
// with this value. // unambiguous detection of one package being an updated version of
//.minimum_zig_version = "0.11.0", // 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. // This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`. // 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. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
@@ -27,7 +36,8 @@
//.example = .{ //.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding // // 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 // // `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", // .url = "https://example.com/foo.tar.gz",
// //
// // This is computed from the file contents of the directory of files that is // // 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 // // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive. // // computed. This field and `url` are mutually exclusive.
// .path = "foo", // .path = "foo",
//
// // When this is set to `true`, a package is declared to be lazily // // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is // // fetched. This makes the dependency only get fetched if it is
// // actually used. // // actually used.
// .lazy = false, // .lazy = false,
//}, //},
}, },
// Specifies the set of files and directories that are included in this package. // 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 // 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 // is computed for this package. Only files listed here will remain on disk

View File

@@ -12,7 +12,7 @@ pub const max_len: usize = 0x1000;
contents: []const u8, contents: []const u8,
pub fn from(allocator: Allocator, str: []const u8) !Self { 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 contents = tokens.next() orelse return error.InvalidPacket;
const chksum_str = tokens.next() orelse return error.MissingCheckSum; 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 // 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 // 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"), 'G' => @panic("TODO: Register Write"),
'm' => { '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 addr_str = tokens.next() orelse return error.InvalidPacket;
const length_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; var i: u32 = 0;
while (i < len) : (i += 1) { while (i < len) : (i += 1) {
// writes the formatted integer to the buffer, returns a slice to the buffer but we ignore that // 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 }; return .{ .alloc = ret };
}, },
'M' => { '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 addr_str = tokens.next() orelse return error.InvalidPacket;
const length_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' => { '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; // const addr = if (tokens.next()) |s| try std.fmt.parseInt(u32, s, 16) else null;
switch (emu.step()) { switch (emu.step()) {
@@ -136,7 +140,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul
// Breakpoints // Breakpoints
'z' => { '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_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);
@@ -157,7 +161,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul
} }
}, },
'Z' => { '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_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;
@@ -166,11 +170,11 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul
switch (self.contents[1]) { switch (self.contents[1]) {
'0' => { '0' => {
try emu.addBkpt(.Software, addr, kind); try emu.addBkpt(.Software, allocator, addr, kind);
return .{ .static = "OK" }; return .{ .static = "OK" };
}, },
'1' => { '1' => {
emu.addBkpt(.Hardware, addr, kind) catch |e| { emu.addBkpt(.Hardware, allocator, 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,
@@ -232,7 +236,7 @@ pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emul
} }
if (substr(self.contents[1..], "Xfer:features:read")) { 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(); // Xfer
_ = tokens.next(); // features _ = tokens.next(); // features
_ = tokens.next(); // read _ = 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")) { if (substr(self.contents[1..], "Xfer:memory-map:read")) {
const mem_map = state.memmap_xml.?; 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(); // Xfer
_ = tokens.next(); // memory-map _ = tokens.next(); // memory-map
_ = tokens.next(); // read _ = tokens.next(); // read

View File

@@ -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 buf: [Packet.max_len]u8 = undefined;
var client = try self.socket.accept(); 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)) { while (!should_quit.load(.monotonic)) {
if (self.state.should_quit) { if (self.state.should_quit) {

View File

@@ -1,31 +1,21 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
hw_bkpt: HwBkpt = .{}, hw_bkpt: HwBkpt = .{},
sw_bkpt: SwBkpt, sw_bkpt: SwBkpt = .{},
pub fn init(allocator: Allocator) @This() { pub fn deinit(self: *@This(), allocator: Allocator) void {
return .{ .sw_bkpt = SwBkpt.init(allocator) }; self.sw_bkpt.deinit(allocator);
}
pub fn deinit(self: *@This()) void {
self.sw_bkpt.deinit();
self.* = undefined; self.* = undefined;
} }
const SwBkpt = struct { const SwBkpt = struct {
const log = std.log.scoped(.SwBkpt); const log = std.log.scoped(.SwBkpt);
list: std.ArrayList(Bkpt), list: std.ArrayList(Bkpt) = .empty,
pub fn init(allocator: Allocator) @This() { pub fn deinit(self: *@This(), allocator: Allocator) void {
return .{ .list = ArrayList(Bkpt).init(allocator) }; self.list.deinit(allocator);
}
pub fn deinit(self: *@This()) void {
self.list.deinit();
self.* = undefined; self.* = undefined;
} }
@@ -37,12 +27,12 @@ const SwBkpt = struct {
return false; 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| { for (self.list.items) |bkpt| {
if (bkpt.addr == addr) return; // indempotent 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}); log.warn("Added Breakpoint at 0x{X:0>8}", .{addr});
} }
@@ -110,7 +100,7 @@ const Bkpt = struct {
Thumb = 4, Thumb = 4,
pub fn from(comptime T: type, num: T) !@This() { 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) { return switch (num) {
2 => .Arm, 2 => .Arm,

View File

@@ -16,7 +16,7 @@ pub const Emulator = struct {
SingleStep: void, SingleStep: void,
}; };
state: State, state: State = .{},
ptr: *anyopaque, ptr: *anyopaque,
@@ -28,42 +28,42 @@ pub const Emulator = struct {
stepFn: *const fn (*anyopaque) void, 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 = @TypeOf(ptr);
const ptr_info = @typeInfo(Ptr); const ptr_info = @typeInfo(Ptr);
if (ptr_info != .Pointer) @compileError("ptr must be a 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"); if (ptr_info.pointer.size != .one) @compileError("ptr must be a single-item pointer");
const gen = struct { const gen = struct {
pub fn readImpl(pointer: *anyopaque, addr: u32) u8 { pub fn readImpl(pointer: *anyopaque, addr: u32) u8 {
const self: Ptr = @ptrCast(@alignCast(pointer)); 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 { pub fn writeImpl(pointer: *anyopaque, addr: u32, value: u8) void {
const self: Ptr = @ptrCast(@alignCast(pointer)); 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 { pub fn registersImpl(pointer: *anyopaque) *[16]u32 {
const self: Ptr = @ptrCast(@alignCast(pointer)); 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 { pub fn cpsrImpl(pointer: *anyopaque) u32 {
const self: Ptr = @ptrCast(@alignCast(pointer)); 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 { pub fn stepImpl(pointer: *anyopaque) void {
const self: Ptr = @ptrCast(@alignCast(pointer)); 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, .registersFn = gen.registersImpl,
.cpsrFn = gen.cpsrImpl, .cpsrFn = gen.cpsrImpl,
.stepFn = gen.stepImpl, .stepFn = gen.stepImpl,
.state = State.init(allocator),
}; };
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self, allocator: Allocator) void {
self.state.deinit(); self.state.deinit(allocator);
self.* = undefined; self.* = undefined;
} }
@@ -128,10 +126,10 @@ pub const Emulator = struct {
const BkptType = enum { Hardware, Software }; const BkptType = enum { Hardware, Software };
// TODO: Consider properly implementing Software interrupts? // 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") { switch (@"type") {
.Hardware => try self.state.hw_bkpt.add(addr, kind), .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),
} }
} }

View File

@@ -52,8 +52,8 @@ const BarebonesEmulator = struct {
r: [16]u32 = [_]u32{0} ** 16, r: [16]u32 = [_]u32{0} ** 16,
pub fn interface(self: *@This(), allocator: Allocator) Emulator { pub fn interface(self: *@This()) Emulator {
return Emulator.init(allocator, self); return Emulator.init(self);
} }
pub fn read(_: *const @This(), _: u32) u8 { pub fn read(_: *const @This(), _: u32) u8 {
@@ -88,15 +88,19 @@ test Server {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var impl = BarebonesEmulator{}; var impl = BarebonesEmulator{};
var iface = impl.interface(allocator); var iface = impl.interface();
defer iface.deinit(); defer iface.deinit(allocator);
const clientFn = struct { const clientFn = struct {
fn inner(address: std.net.Address) !void { fn inner(address: std.net.Address) !void {
const socket = try std.net.tcpConnectToAddress(address); const socket = try std.net.tcpConnectToAddress(address);
defer socket.close(); defer socket.close();
_ = try socket.writer().writeAll("+"); var buf: [1024]u8 = undefined;
var writer = socket.writer(&buf).interface;
_ = try writer.writeAll("+");
} }
}.inner; }.inner;
@@ -142,7 +146,7 @@ test Emulator {
}; };
var impl = ExampleImpl{}; var impl = ExampleImpl{};
var emu = Emulator.init(std.testing.allocator, &impl); var emu = Emulator.init(&impl);
_ = emu.read(0x0000_0000); _ = emu.read(0x0000_0000);
emu.write(0x0000_0000, 0x00); emu.write(0x0000_0000, 0x00);