Compare commits
6 Commits
215e053b9a
...
479319e7ca
Author | SHA1 | Date | |
---|---|---|---|
479319e7ca | |||
5947747533 | |||
93cd6b1c5b | |||
e5c1d4d2b7 | |||
dc159b4aeb | |||
39a4260ffd |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "lib/zig-network"]
|
||||
path = lib/zig-network
|
||||
url = https://github.com/MasterQ32/zig-network
|
||||
|
80
build.zig
80
build.zig
@@ -1,50 +1,54 @@
|
||||
const std = @import("std");
|
||||
const CompileStep = std.Build.CompileStep;
|
||||
|
||||
fn path(comptime suffix: []const u8) []const u8 {
|
||||
if (suffix[0] == '/') @compileError("expected a relative path");
|
||||
return comptime (std.fs.path.dirname(@src().file) orelse ".") ++ std.fs.path.sep_str ++ suffix;
|
||||
}
|
||||
|
||||
pub fn getModule(b: *std.Build) *std.build.Module {
|
||||
// https://github.com/MasterQ32/zig-network
|
||||
const network = b.createModule(.{ .source_file = .{ .path = path("lib/zig-network/network.zig") } });
|
||||
|
||||
return b.createModule(.{
|
||||
.source_file = .{ .path = path("src/lib.zig") },
|
||||
.dependencies = &.{.{ .name = "network", .module = network }},
|
||||
});
|
||||
}
|
||||
|
||||
// Although this function looks imperative, note that its job is to
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
// Standard target options allows 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(.{});
|
||||
// const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// -- Library --
|
||||
// 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(.{});
|
||||
|
||||
const lib_test = b.addTest(.{
|
||||
_ = b.addModule("gdbstub", .{
|
||||
.source_file = .{ .path = "src/lib.zig" },
|
||||
.dependencies = &.{},
|
||||
});
|
||||
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const lib_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/lib.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const test_step = b.step("test", "Run Library Tests");
|
||||
test_step.dependOn(&lib_test.step);
|
||||
const run_lib_tests = b.addRunArtifact(lib_tests);
|
||||
|
||||
// -- Executable --
|
||||
|
||||
// const exe = b.addExecutable(.{
|
||||
// .name = "gdbserver",
|
||||
// .root_source_file = .{ .path = "src/main.zig" },
|
||||
// .target = target,
|
||||
// .optimize = optimize,
|
||||
// });
|
||||
// link(exe);
|
||||
// exe.install();
|
||||
|
||||
// const run_cmd = exe.run();
|
||||
// run_cmd.step.dependOn(b.getInstallStep());
|
||||
// if (b.args) |args| run_cmd.addArgs(args);
|
||||
|
||||
// const run_step = b.step("run", "Run the app");
|
||||
// run_step.dependOn(&run_cmd.step);
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build test`
|
||||
// This will evaluate the `test` step rather than the default, which is "install".
|
||||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&run_lib_tests.step);
|
||||
}
|
||||
|
||||
pub fn module(b: *std.Build) *std.Build.Module {
|
||||
return b.createModule(.{
|
||||
.source_file = .{ .path = path("/src/lib.zig") },
|
||||
.dependencies = &.{},
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/MasterQ32/SDL.zig/blob/4d565b54227b862c1540719e0e21a36d649e87d5/build.zig#L114-L120
|
||||
fn path(comptime suffix: []const u8) []const u8 {
|
||||
if (suffix[0] != '/') @compileError("relToPath requires an absolute path!");
|
||||
return comptime blk: {
|
||||
const root_dir = std.fs.path.dirname(@src().file) orelse ".";
|
||||
break :blk root_dir ++ suffix;
|
||||
};
|
||||
}
|
||||
|
5
build.zig.zon
Normal file
5
build.zig.zon
Normal file
@@ -0,0 +1,5 @@
|
||||
.{
|
||||
.name = "zba-gdbstub",
|
||||
.version = "0.1.0",
|
||||
.dependencies = .{},
|
||||
}
|
Submodule lib/zig-network deleted from 45ae8cf0ce
@@ -3,6 +3,7 @@ const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Emulator = @import("lib.zig").Emulator;
|
||||
const State = @import("State.zig");
|
||||
const Server = @import("Server.zig");
|
||||
|
||||
const target = @import("Server.zig").target;
|
||||
const memory_map = @import("Server.zig").memory_map;
|
||||
@@ -46,7 +47,7 @@ const String = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
|
||||
pub fn parse(self: *Self, allocator: Allocator, state: *Server.State, emu: *Emulator) !String {
|
||||
switch (self.contents[0]) {
|
||||
// Required
|
||||
'?' => return .{ .static = "T05" }, // FIXME: which errno?
|
||||
@@ -61,7 +62,7 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
|
||||
var i: u32 = 0;
|
||||
while (i < r.len + 1) : (i += 1) {
|
||||
var reg: u32 = if (i < r.len) r[i] else cpsr;
|
||||
if (i == 15) reg -= if (cpsr >> 5 & 1 == 1) 4 else 8; // PC is ahead
|
||||
if (i == 15) reg -|= if (cpsr >> 5 & 1 == 1) 4 else 8; // PC is ahead
|
||||
|
||||
// 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
|
||||
@@ -191,6 +192,8 @@ pub fn parse(self: *Self, allocator: Allocator, emu: *Emulator) !String {
|
||||
// TODO: Figure out the difference between 'M' and 'X'
|
||||
'D' => {
|
||||
log.info("Disconnecting...", .{});
|
||||
|
||||
state.should_quit = true;
|
||||
return .{ .static = "OK" };
|
||||
},
|
||||
'H' => return .{ .static = "" },
|
||||
@@ -293,7 +296,7 @@ pub fn checksum(input: []const u8) u8 {
|
||||
var sum: usize = 0;
|
||||
for (input) |char| sum += char;
|
||||
|
||||
return @truncate(u8, sum);
|
||||
return @truncate(sum);
|
||||
}
|
||||
|
||||
fn verify(input: []const u8, chksum: u8) bool {
|
||||
|
@@ -1,12 +1,12 @@
|
||||
const std = @import("std");
|
||||
const network = @import("network");
|
||||
const Packet = @import("Packet.zig");
|
||||
|
||||
const Socket = network.Socket;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Atomic = std.atomic.Atomic;
|
||||
const Emulator = @import("lib.zig").Emulator;
|
||||
|
||||
const Atomic = std.atomic.Atomic;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Server = std.net.StreamServer;
|
||||
const Connection = Server.Connection;
|
||||
|
||||
const Self = @This();
|
||||
const log = std.log.scoped(.Server);
|
||||
const port: u16 = 2424;
|
||||
@@ -57,32 +57,23 @@ pub const memory_map: []const u8 =
|
||||
// FIXME: Shouldn't this be a Packet Struct?
|
||||
pkt_cache: ?[]const u8 = null,
|
||||
|
||||
client: Socket,
|
||||
_socket: Socket,
|
||||
socket: Server,
|
||||
state: State = .{},
|
||||
|
||||
emu: Emulator,
|
||||
|
||||
pub const State = struct { should_quit: bool = false };
|
||||
|
||||
pub fn init(emulator: Emulator) !Self {
|
||||
try network.init();
|
||||
var server = std.net.StreamServer.init(.{});
|
||||
try server.listen(std.net.Address.initIp4([_]u8{0} ** 4, port));
|
||||
|
||||
var socket = try Socket.create(.ipv4, .tcp);
|
||||
try socket.bindToPort(port);
|
||||
try socket.listen();
|
||||
|
||||
var client = try socket.accept(); // TODO: This blocks, is this OK?
|
||||
|
||||
const endpoint = try client.getLocalEndPoint();
|
||||
log.info("client connected from {}", .{endpoint});
|
||||
|
||||
return .{ .emu = emulator, ._socket = socket, .client = client };
|
||||
return .{ .emu = emulator, .socket = server };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, allocator: Allocator) void {
|
||||
self.reset(allocator);
|
||||
|
||||
self.client.close();
|
||||
self._socket.close();
|
||||
network.deinit();
|
||||
self.socket.deinit();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
@@ -98,13 +89,19 @@ const Action = union(enum) {
|
||||
pub fn run(self: *Self, allocator: Allocator, quit: *Atomic(bool)) !void {
|
||||
var buf: [Packet.max_len]u8 = undefined;
|
||||
|
||||
var client = try self.socket.accept();
|
||||
log.info("client connected from {}", .{client.address});
|
||||
|
||||
while (true) {
|
||||
const len = try self.client.receive(&buf);
|
||||
if (self.state.should_quit) break;
|
||||
|
||||
const len = try client.stream.read(&buf);
|
||||
if (len == 0) break;
|
||||
|
||||
if (quit.load(.Monotonic)) break;
|
||||
const action = try self.parse(allocator, buf[0..len]);
|
||||
try self.send(allocator, action);
|
||||
|
||||
try self.send(allocator, client, action);
|
||||
}
|
||||
|
||||
// Just in case its the gdbstub that exited first,
|
||||
@@ -113,7 +110,7 @@ pub fn run(self: *Self, allocator: Allocator, quit: *Atomic(bool)) !void {
|
||||
}
|
||||
|
||||
fn parse(self: *Self, allocator: Allocator, input: []const u8) !Action {
|
||||
// log.debug("-> {s}", .{input});
|
||||
log.debug("-> {s}", .{input});
|
||||
|
||||
return switch (input[0]) {
|
||||
'+' => blk: {
|
||||
@@ -135,7 +132,7 @@ fn handlePacket(self: *Self, allocator: Allocator, input: []const u8) !Action {
|
||||
var packet = Packet.from(allocator, input) catch return .nack;
|
||||
defer packet.deinit(allocator);
|
||||
|
||||
var string = packet.parse(allocator, &self.emu) catch return .nack;
|
||||
var string = packet.parse(allocator, &self.state, &self.emu) catch return .nack;
|
||||
defer string.deinit(allocator);
|
||||
|
||||
const reply = string.inner();
|
||||
@@ -146,11 +143,11 @@ fn handlePacket(self: *Self, allocator: Allocator, input: []const u8) !Action {
|
||||
return .{ .send = response };
|
||||
}
|
||||
|
||||
fn send(self: *Self, allocator: Allocator, action: Action) !void {
|
||||
fn send(self: *Self, allocator: Allocator, client: Server.Connection, action: Action) !void {
|
||||
switch (action) {
|
||||
.send => |pkt| {
|
||||
_ = try self.client.send(pkt);
|
||||
// log.debug("<- {s}", .{pkt});
|
||||
_ = try client.stream.writeAll(pkt);
|
||||
log.debug("<- {s}", .{pkt});
|
||||
|
||||
self.reset(allocator);
|
||||
self.pkt_cache = pkt;
|
||||
@@ -159,19 +156,19 @@ fn send(self: *Self, allocator: Allocator, action: Action) !void {
|
||||
log.warn("received nack, resending: \"{?s}\"", .{self.pkt_cache});
|
||||
|
||||
if (self.pkt_cache) |pkt| {
|
||||
_ = try self.client.send(pkt);
|
||||
// log.debug("<- {s}", .{pkt});
|
||||
_ = try client.stream.writeAll(pkt);
|
||||
log.debug("<- {s}", .{pkt});
|
||||
}
|
||||
},
|
||||
.ack => {
|
||||
_ = try self.client.send("+");
|
||||
// log.debug("<- +", .{});
|
||||
_ = try client.stream.writeAll("+");
|
||||
log.debug("<- +", .{});
|
||||
|
||||
self.reset(allocator);
|
||||
},
|
||||
.nack => {
|
||||
_ = try self.client.send("-");
|
||||
// log.debug("<- -", .{});
|
||||
_ = try client.stream.writeAll("-");
|
||||
log.debug("<- -", .{});
|
||||
|
||||
self.reset(allocator);
|
||||
},
|
||||
|
16
src/lib.zig
16
src/lib.zig
@@ -35,35 +35,33 @@ pub const Emulator = struct {
|
||||
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 alignment = ptr_info.Pointer.alignment;
|
||||
|
||||
const gen = struct {
|
||||
pub fn readImpl(pointer: *anyopaque, addr: u32) u8 {
|
||||
const self = @ptrCast(Ptr, @alignCast(alignment, pointer));
|
||||
const self: Ptr = @ptrCast(@alignCast(pointer));
|
||||
|
||||
return @call(.always_inline, ptr_info.Pointer.child.read, .{ self, addr });
|
||||
}
|
||||
|
||||
pub fn writeImpl(pointer: *anyopaque, addr: u32, value: u8) void {
|
||||
const self = @ptrCast(Ptr, @alignCast(alignment, pointer));
|
||||
const self: Ptr = @ptrCast(@alignCast(pointer));
|
||||
|
||||
return @call(.always_inline, ptr_info.Pointer.child.write, .{ self, addr, value });
|
||||
}
|
||||
|
||||
pub fn registersImpl(pointer: *anyopaque) *[16]u32 {
|
||||
const self = @ptrCast(Ptr, @alignCast(alignment, pointer));
|
||||
const self: Ptr = @ptrCast(@alignCast(pointer));
|
||||
|
||||
return @call(.always_inline, ptr_info.Pointer.child.registers, .{self});
|
||||
}
|
||||
|
||||
pub fn cpsrImpl(pointer: *anyopaque) u32 {
|
||||
const self = @ptrCast(Ptr, @alignCast(alignment, pointer));
|
||||
const self: Ptr = @ptrCast(@alignCast(pointer));
|
||||
|
||||
return @call(.always_inline, ptr_info.Pointer.child.cpsr, .{self});
|
||||
}
|
||||
|
||||
pub fn stepImpl(pointer: *anyopaque) void {
|
||||
const self = @ptrCast(Ptr, @alignCast(alignment, pointer));
|
||||
const self: Ptr = @ptrCast(@alignCast(pointer));
|
||||
|
||||
return @call(.always_inline, ptr_info.Pointer.child.step, .{self});
|
||||
}
|
||||
@@ -144,3 +142,7 @@ pub const Emulator = struct {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
_ = @import("test.zig");
|
||||
}
|
||||
|
18
src/main.zig
18
src/main.zig
@@ -1,18 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Server = @import("gdbstub").Server;
|
||||
|
||||
pub fn main() !void {
|
||||
const log = std.log.scoped(.Main);
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer std.debug.assert(!gpa.deinit());
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var server = try Server.init();
|
||||
defer server.deinit(allocator);
|
||||
|
||||
try server.run(allocator);
|
||||
|
||||
log.info("Client disconnected", .{});
|
||||
}
|
108
src/test.zig
Normal file
108
src/test.zig
Normal file
@@ -0,0 +1,108 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Emulator = @import("lib.zig").Emulator;
|
||||
const Server = @import("Server.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const BarebonesEmulator = struct {
|
||||
r: [16]u32 = [_]u32{0} ** 16,
|
||||
|
||||
pub fn interface(self: *@This(), allocator: Allocator) Emulator {
|
||||
return Emulator.init(allocator, self);
|
||||
}
|
||||
|
||||
pub fn read(_: *const @This(), _: u32) u8 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn write(_: *@This(), _: u32, _: u8) void {}
|
||||
|
||||
pub fn registers(self: *@This()) *[16]u32 {
|
||||
return &self.r;
|
||||
}
|
||||
|
||||
pub fn cpsr(_: *const @This()) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn step(_: *@This()) void {
|
||||
// execute 1 instruction
|
||||
}
|
||||
};
|
||||
|
||||
test Server {
|
||||
// https://github.com/ziglang/zig/blob/225fe6ddbfae016395762850e0cd5c51f9e7751c/lib/std/net/test.zig#L146C1-L156
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
if (builtin.os.tag == .windows)
|
||||
_ = try std.os.windows.WSAStartup(2, 2);
|
||||
|
||||
defer if (builtin.os.tag == .windows) std.os.windows.WSACleanup() catch unreachable;
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var impl = BarebonesEmulator{};
|
||||
var iface = impl.interface(allocator);
|
||||
defer iface.deinit();
|
||||
|
||||
const clientFn = struct {
|
||||
fn inner(address: std.net.Address) !void {
|
||||
const socket = try std.net.tcpConnectToAddress(address);
|
||||
defer socket.close();
|
||||
|
||||
_ = try socket.writer().writeAll("+");
|
||||
}
|
||||
}.inner;
|
||||
|
||||
var server = try Server.init(iface);
|
||||
defer server.deinit(allocator);
|
||||
|
||||
const t = try std.Thread.spawn(.{}, clientFn, .{server.socket.listen_address});
|
||||
defer t.join();
|
||||
|
||||
var should_quit = std.atomic.Atomic(bool).init(false);
|
||||
|
||||
try server.run(std.testing.allocator, &should_quit);
|
||||
}
|
||||
|
||||
test Emulator {
|
||||
const ExampleImpl = struct {
|
||||
r: [16]u32 = [_]u32{0} ** 16,
|
||||
|
||||
pub fn interface(self: *@This(), allocator: std.mem.Allocator) Emulator {
|
||||
return Emulator.init(allocator, self);
|
||||
}
|
||||
|
||||
pub fn read(_: *const @This(), _: u32) u8 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn write(_: *@This(), _: u32, _: u8) void {}
|
||||
|
||||
pub fn registers(self: *@This()) *[16]u32 {
|
||||
return &self.r;
|
||||
}
|
||||
|
||||
pub fn cpsr(_: *const @This()) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn step(_: *@This()) void {
|
||||
// execute 1 instruction
|
||||
}
|
||||
};
|
||||
|
||||
var impl = ExampleImpl{};
|
||||
var emu = Emulator.init(std.testing.allocator, &impl);
|
||||
|
||||
_ = emu.read(0x0000_0000);
|
||||
emu.write(0x0000_0000, 0x00);
|
||||
|
||||
_ = emu.registers();
|
||||
_ = emu.cpsr();
|
||||
|
||||
_ = emu.step();
|
||||
}
|
Reference in New Issue
Block a user