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");
// 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.
}

View File

@@ -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

View File

@@ -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

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 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) {

View File

@@ -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,

View File

@@ -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),
}
}

View File

@@ -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);