diff --git a/src/Packet.zig b/src/Packet.zig new file mode 100644 index 0000000..d947fc7 --- /dev/null +++ b/src/Packet.zig @@ -0,0 +1,166 @@ +const std = @import("std"); + +const target = @import("Server.zig").target; +const Allocator = std.mem.Allocator; + +const Self = @This(); +const log = std.log.scoped(.Packet); +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, "$#"); + const contents = tokens.next() orelse return error.InvalidPacket; + + const chksum_str = tokens.next() orelse return error.MissingCheckSum; + const chksum = std.fmt.parseInt(u8, chksum_str, 16) catch return error.InvalidChecksum; + + // log.info("Contents: {s}", .{contents}); + + if (!Self.verify(contents, chksum)) return error.ChecksumMismatch; + + return .{ .contents = try allocator.dupe(u8, contents) }; +} + +const String = union(enum) { + alloc: []const u8, + static: []const u8, + + pub fn inner(self: *const @This()) []const u8 { + return switch (self.*) { + .static => |str| str, + .alloc => |str| str, + }; + } + + pub fn deinit(self: *@This(), allocator: Allocator) void { + switch (self.*) { + .alloc => |string| allocator.free(string), + .static => {}, + } + + self.* = undefined; + } +}; + +pub fn parse(self: *Self, allocator: Allocator) !String { + switch (self.contents[0]) { + // Required + '?' => { + const ret: Signal = .Trap; + + // Deallocated by the caller + return .{ .alloc = try std.fmt.allocPrint(allocator, "T{x:0>2}thread:1;", .{@enumToInt(ret)}) }; + }, + 'g', 'G' => @panic("TODO: Register Access"), + 'm', 'M' => @panic("TODO: Memory Access"), + 'c' => @panic("TODO: Continue"), + 's' => @panic("TODO: Step"), + + // Optional + 'H' => { + log.warn("{s}", .{self.contents}); + + switch (self.contents[1]) { + 'g', 'c' => return .{ .static = "OK" }, + else => { + log.warn("Unimplemented: {s}", .{self.contents}); + return .{ .static = "" }; + }, + } + }, + 'v' => { + if (substr(self.contents[1..], "MustReplyEmpty")) { + return .{ .static = "" }; + } + + log.warn("Unimplemented: {s}", .{self.contents}); + return .{ .static = "" }; + }, + 'q' => { + if (self.contents[1] == 'C' and self.contents.len == 2) return .{ .static = "QC1" }; + if (substr(self.contents[1..], "fThreadInfo")) return .{ .static = "m1" }; + if (substr(self.contents[1..], "sThreadInfo")) return .{ .static = "l" }; + 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+"; + // TODO: Anything else? + + const ret = try std.fmt.allocPrint(allocator, format, .{Self.max_len}); + return .{ .alloc = ret }; + } + + if (substr(self.contents[1..], "Xfer:features:read")) { + var tokens = std.mem.tokenize(u8, self.contents[1..], ":,"); + _ = tokens.next(); // qXfer + _ = tokens.next(); // features + _ = tokens.next(); // read + const annex = tokens.next() orelse return .{ .static = "E00" }; + const offset_str = tokens.next() orelse return .{ .static = "E00" }; + const length_str = tokens.next() orelse return .{ .static = "E00" }; + + if (std.mem.eql(u8, annex, "target.xml")) { + log.info("Providing ARMv4T target description", .{}); + + const offset = try std.fmt.parseInt(usize, offset_str, 16); + const length = try std.fmt.parseInt(usize, length_str, 16); + + // + 2 to account for the "m " in the response + // subtract offset so that the allocated buffer isn't + // larger than it needs to be TODO: Test this? + const len = @min(length, (target.len + 1) - offset); + const ret = try allocator.alloc(u8, len); + + ret[0] = if (ret.len < length) 'l' else 'm'; + std.mem.copy(u8, ret[1..], target[offset..]); + + return .{ .alloc = ret }; + } else { + log.err("Unexpected Annex: {s}", .{annex}); + return .{ .static = "E00" }; + } + + return .{ .static = "" }; + } + + log.warn("Unimplemented: {s}", .{self.contents}); + return .{ .static = "" }; + }, + else => { + log.warn("Unknown: {s}", .{self.contents}); + + return .{ .static = "" }; + }, + } +} + +fn substr(haystack: []const u8, needle: []const u8) bool { + return std.mem.indexOf(u8, haystack, needle) != null; +} + +pub fn deinit(self: *Self, allocator: Allocator) void { + allocator.free(self.contents); + self.* = undefined; +} + +pub fn checksum(input: []const u8) u8 { + var sum: usize = 0; + for (input) |char| sum += char; + + return @truncate(u8, sum); +} + +fn verify(input: []const u8, chksum: u8) bool { + return Self.checksum(input) == chksum; +} + +const Signal = enum(u32) { + Hup, // Hangup + Int, // Interrupt + Quit, // Quit + Ill, // Illegal Instruction + Trap, // Trace/Breakponit trap + Abrt, // Aborted +}; diff --git a/src/Server.zig b/src/Server.zig new file mode 100644 index 0000000..72f5d90 --- /dev/null +++ b/src/Server.zig @@ -0,0 +1,141 @@ +const std = @import("std"); +const network = @import("network"); +const Packet = @import("Packet.zig"); + +const Socket = network.Socket; +const Allocator = std.mem.Allocator; + +const Self = @This(); +const log = std.log.scoped(.Server); +const port: u16 = 2424; + +pub const target: []const u8 = + \\ + \\ armv4t + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ +; + +// FIXME: Shouldn't this be a Packet Struct? +pkt_cache: ?[]const u8 = null, + +client: Socket, +_socket: Socket, + +pub fn init() !Self { + try network.init(); + + 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 .{ ._socket = socket, .client = client }; +} + +pub fn deinit(self: *Self, allocator: Allocator) void { + self.reset(allocator); + + self.client.close(); + self._socket.close(); + network.deinit(); + + self.* = undefined; +} + +const Action = union(enum) { + nothing, + send: []const u8, + retry, + ack, + nack, +}; + +pub fn run(self: *Self, allocator: Allocator) !void { + var buf: [Packet.max_len]u8 = undefined; + + while (true) { + const len = try self.client.receive(&buf); + if (len == 0) break; + + const action = try Self.parse(allocator, buf[0..len]); + try self.send(allocator, action); + } +} + +fn parse(allocator: Allocator, input: []const u8) !Action { + return switch (input[0]) { + '+' => .nothing, + '-' => .retry, + '$' => blk: { + // Packet + var packet = Packet.from(allocator, input) catch return .nack; + defer packet.deinit(allocator); + + var string = packet.parse(allocator) catch return .nack; + defer string.deinit(allocator); + + const reply = string.inner(); + + // deallocated by the caller + const response = try std.fmt.allocPrint(allocator, "${s}#{x:0>2}", .{ reply, Packet.checksum(reply) }); + + break :blk .{ .send = response }; + }, + else => std.debug.panic("Unknown: {s}", .{input}), + }; +} + +fn send(self: *Self, allocator: Allocator, action: Action) !void { + switch (action) { + .send => |pkt| { + _ = try self.client.send("+"); // ACK + _ = try self.client.send(pkt); + + self.reset(allocator); + self.pkt_cache = pkt; + }, + .retry => { + log.warn("received nack, resending: \"{?s}\"", .{self.pkt_cache}); + + if (self.pkt_cache) |pkt| _ = try self.client.send(pkt); // FIXME: is an ack to a nack necessary? + }, + .ack => { + _ = try self.client.send("+"); + self.reset(allocator); + }, + .nack => { + _ = try self.client.send("-"); + self.reset(allocator); + }, + .nothing => self.reset(allocator), + } +} + +fn reset(self: *Self, allocator: Allocator) void { + if (self.pkt_cache) |pkt| allocator.free(pkt); + self.pkt_cache = null; +} diff --git a/src/main.zig b/src/main.zig index f8f4e7f..c440df5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,12 +1,11 @@ const std = @import("std"); const network = @import("network"); +const Server = @import("Server.zig"); const Allocator = std.mem.Allocator; const Socket = network.Socket; -const port: u16 = 2424; - -const target: []const u8 = +pub const target: []const u8 = \\ \\ armv4t \\ @@ -33,259 +32,17 @@ const target: []const u8 = ; pub fn main() !void { - const log = std.log.scoped(.Server); + const log = std.log.scoped(.Main); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(!gpa.deinit()); const allocator = gpa.allocator(); - try network.init(); - defer network.deinit(); + var server = try Server.init(); + defer server.deinit(allocator); - var socket = try Socket.create(.ipv4, .tcp); - defer socket.close(); - - try socket.bindToPort(port); - try socket.listen(); - - var client = try socket.accept(); - defer client.close(); - - const endpoint = try client.getLocalEndPoint(); - log.info("client connected from {}", .{endpoint}); - - try gdbStubServer(allocator, client); + try server.run(allocator); log.info("Client disconnected", .{}); } - -fn gdbStubServer(allocator: Allocator, client: Socket) !void { - var buf: [Packet.size]u8 = undefined; - var previous: ?[]const u8 = null; - defer if (previous) |packet| allocator.free(packet); - - while (true) { - const len = try client.receive(&buf); - if (len == 0) break; - - switch (try parse(allocator, client, previous, buf[0..len])) { - .send => |response| { - if (previous) |packet| allocator.free(packet); - previous = response; - }, - .recv_ack => { - if (previous) |packet| allocator.free(packet); - previous = null; - }, - .recv_nack => {}, - } - } -} - -const Action = union(enum) { - send: []const u8, - recv_nack, - recv_ack, -}; - -fn parse(allocator: Allocator, client: Socket, previous: ?[]const u8, input: []const u8) !Action { - const log = std.log.scoped(.GdbStubParser); - - return switch (input[0]) { - '+' => .recv_ack, - '-' => blk: { - if (previous) |packet| { - log.warn("received nack, resending: \"{s}\"", .{packet}); - _ = try client.send(packet); - } else { - log.err("received nack, but we don't recall sending anything", .{}); - } - - break :blk .recv_nack; - }, - '$' => blk: { - // Packet - var packet = try Packet.from(allocator, input); - defer packet.deinit(allocator); - - var string = try packet.parse(allocator); - defer string.deinit(allocator); - - const reply = string.inner(); - - _ = try client.send("+"); // Acknowledge - - // deallocated by the caller - const response = try std.fmt.allocPrint(allocator, "${s}#{x:0>2}", .{ reply, Packet.checksum(reply) }); - _ = try client.send(response); - - break :blk .{ .send = response }; - }, - else => std.debug.panic("Unknown: {s}", .{input}), - }; -} - -const Packet = struct { - const Self = @This(); - const log = std.log.scoped(.Packet); - const size: usize = 0x1000; - - contents: []const u8, - - pub fn from(allocator: Allocator, str: []const u8) !Self { - var tokens = std.mem.tokenize(u8, str, "$#"); - const contents = tokens.next() orelse return error.InvalidPacket; - - const chksum_str = tokens.next() orelse return error.MissingCheckSum; - const chksum = std.fmt.parseInt(u8, chksum_str, 16) catch return error.InvalidChecksum; - - // log.info("Contents: {s}", .{contents}); - - if (!Self.verify(contents, chksum)) return error.ChecksumMismatch; - - return .{ .contents = try allocator.dupe(u8, contents) }; - } - - const String = union(enum) { - alloc: []const u8, - static: []const u8, - - pub fn inner(self: *const @This()) []const u8 { - return switch (self.*) { - .static => |str| str, - .alloc => |str| str, - }; - } - - pub fn deinit(self: *@This(), allocator: Allocator) void { - switch (self.*) { - .alloc => |string| allocator.free(string), - .static => {}, - } - - self.* = undefined; - } - }; - - pub fn parse(self: *Self, allocator: Allocator) !String { - switch (self.contents[0]) { - // Required - '?' => { - const ret: Signal = .Trap; - - // Deallocated by the caller - return .{ .alloc = try std.fmt.allocPrint(allocator, "T{x:0>2}thread:1;", .{@enumToInt(ret)}) }; - }, - 'g', 'G' => @panic("TODO: Register Access"), - 'm', 'M' => @panic("TODO: Memory Access"), - 'c' => @panic("TODO: Continue"), - 's' => @panic("TODO: Step"), - - // Optional - 'H' => { - log.warn("{s}", .{self.contents}); - - switch (self.contents[1]) { - 'g', 'c' => return .{ .static = "OK" }, - else => { - log.warn("Unimplemented: {s}", .{self.contents}); - return .{ .static = "" }; - }, - } - }, - 'v' => { - if (substr(self.contents[1..], "MustReplyEmpty")) { - return .{ .static = "" }; - } - - log.warn("Unimplemented: {s}", .{self.contents}); - return .{ .static = "" }; - }, - 'q' => { - if (self.contents[1] == 'C' and self.contents.len == 2) return .{ .static = "QC1" }; - if (substr(self.contents[1..], "fThreadInfo")) return .{ .static = "m1" }; - if (substr(self.contents[1..], "sThreadInfo")) return .{ .static = "l" }; - 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+"; - // TODO: Anything else? - - const ret = try std.fmt.allocPrint(allocator, format, .{Packet.size}); - return .{ .alloc = ret }; - } - - if (substr(self.contents[1..], "Xfer:features:read")) { - var tokens = std.mem.tokenize(u8, self.contents[1..], ":,"); - _ = tokens.next(); // qXfer - _ = tokens.next(); // features - _ = tokens.next(); // read - const annex = tokens.next() orelse return .{ .static = "E00" }; - const offset_str = tokens.next() orelse return .{ .static = "E00" }; - const length_str = tokens.next() orelse return .{ .static = "E00" }; - - if (std.mem.eql(u8, annex, "target.xml")) { - log.info("Providing ARMv4T target description", .{}); - - const offset = try std.fmt.parseInt(usize, offset_str, 16); - const length = try std.fmt.parseInt(usize, length_str, 16); - - // + 2 to account for the "m " in the response - // subtract offset so that the allocated buffer isn't - // larger than it needs to be TODO: Test this? - const len = @min(length, (target.len + 1) - offset); - const ret = try allocator.alloc(u8, len); - - ret[0] = if (ret.len < length) 'l' else 'm'; - std.mem.copy(u8, ret[1..], target[offset..]); - - return .{ .alloc = ret }; - } else { - log.err("Unexpected Annex: {s}", .{annex}); - return .{ .static = "E00" }; - } - - return .{ .static = "" }; - } - - log.warn("Unimplemented: {s}", .{self.contents}); - return .{ .static = "" }; - }, - else => { - log.warn("Unknown: {s}", .{self.contents}); - - return .{ .static = "" }; - }, - } - } - - fn substr(haystack: []const u8, needle: []const u8) bool { - return std.mem.indexOf(u8, haystack, needle) != null; - } - - fn deinit(self: *Self, allocator: Allocator) void { - allocator.free(self.contents); - self.* = undefined; - } - - pub fn checksum(input: []const u8) u8 { - var sum: usize = 0; - for (input) |char| sum += char; - - return @truncate(u8, sum); - } - - fn verify(input: []const u8, chksum: u8) bool { - return Self.checksum(input) == chksum; - } -}; - -const Signal = enum(u32) { - Hup, // Hangup - Int, // Interrupt - Quit, // Quit - Ill, // Illegal Instruction - Trap, // Trace/Breakponit trap - Abrt, // Aborted -};