Skip to content

Commit 6cdf164

Browse files
committed
Use Logger, NIOAsyncChannel
1 parent 0b8c293 commit 6cdf164

File tree

7 files changed

+164
-95
lines changed

7 files changed

+164
-95
lines changed

Sources/GDBRemoteProtocol/GDBHostCommand.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
/// * https://sourceware.org/gdb/current/onlinedocs/gdb.html/General-Query-Packets.html
33
/// * https://lldb.llvm.org/resources/lldbgdbremote.html
44
package struct GDBHostCommand: Equatable {
5-
package enum Kind: Equatable {
5+
package enum Kind: String, Equatable {
66
// Currently listed in the order that LLDB sends them in.
7-
case generalRegisters
87
case startNoAckMode
9-
case firstThreadInfo
108
case supportedFeatures
119
case isThreadSuffixSupported
1210
case listThreadsInStopReply
@@ -16,6 +14,10 @@ package struct GDBHostCommand: Equatable {
1614
case enableErrorStrings
1715
case processInfo
1816
case currentThreadID
17+
case firstThreadInfo
18+
case subsequentThreadInfo
19+
20+
case generalRegisters
1921

2022
package init?(rawValue: String) {
2123
switch rawValue {
@@ -41,6 +43,10 @@ package struct GDBHostCommand: Equatable {
4143
self = .processInfo
4244
case "qC":
4345
self = .currentThreadID
46+
case "qfThreadInfo":
47+
self = .firstThreadInfo
48+
case "qsThreadInfo":
49+
self = .subsequentThreadInfo
4450
default:
4551
return nil
4652
}

Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Logging
12
import NIOCore
23

34
extension ByteBuffer {
@@ -20,27 +21,39 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder {
2021

2122
package typealias InboundOut = GDBPacket<GDBHostCommand>
2223

24+
private var accumulatedDelimiter: UInt8?
25+
2326
private var accummulatedKind = [UInt8]()
2427
private var accummulatedArguments = [UInt8]()
2528

26-
package init() {}
29+
private let logger: Logger
30+
31+
package init(logger: Logger) { self.logger = logger }
2732

2833
private var accummulatedSum = 0
2934
package var accummulatedChecksum: UInt8 {
3035
UInt8(self.accummulatedSum % 256)
3136
}
3237

3338
mutating package func decode(buffer: inout ByteBuffer) throws -> GDBPacket<GDBHostCommand>? {
39+
guard let firstStartDelimiter = self.accumulatedDelimiter ?? buffer.readInteger(as: UInt8.self) else {
40+
// Not enough data to parse.
41+
return nil
42+
}
43+
guard let secondStartDelimiter = buffer.readInteger(as: UInt8.self) else {
44+
// Preserve what we already read.
45+
self.accumulatedDelimiter = firstStartDelimiter
46+
47+
// Not enough data to parse.
48+
return nil
49+
}
50+
3451
// Command start delimiters.
35-
let firstStartDelimiter = buffer.readInteger(as: UInt8.self)
36-
let secondStartDelimiter = buffer.readInteger(as: UInt8.self)
3752
guard
3853
firstStartDelimiter == UInt8(ascii: "+")
3954
&& secondStartDelimiter == UInt8(ascii: "$")
4055
else {
41-
if let firstStartDelimiter, let secondStartDelimiter {
42-
print("unexpected delimiter: \(Character(UnicodeScalar(firstStartDelimiter)))\(Character(UnicodeScalar(secondStartDelimiter)))")
43-
}
56+
logger.error("unexpected delimiter: \(Character(UnicodeScalar(firstStartDelimiter)))\(Character(UnicodeScalar(secondStartDelimiter)))")
4457
throw Error.expectedCommandStart
4558
}
4659

@@ -70,6 +83,7 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder {
7083
}
7184

7285
defer {
86+
self.accumulatedDelimiter = nil
7387
self.accummulatedKind = []
7488
self.accummulatedArguments = []
7589
self.accummulatedSum = 0
@@ -108,7 +122,7 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder {
108122
context: ChannelHandlerContext,
109123
buffer: inout ByteBuffer
110124
) throws -> DecodingState {
111-
print(buffer.peekString(length: buffer.readableBytes)!)
125+
logger.trace(.init(stringLiteral: buffer.peekString(length: buffer.readableBytes)!))
112126

113127
guard let command = try self.decode(buffer: &buffer) else {
114128
return .needMoreData

Sources/GDBRemoteProtocol/GDBPacket.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package struct GDBPacket<Payload> {
1+
package struct GDBPacket<Payload: Sendable>: Sendable {
22
package let payload: Payload
33

44
package let checksum: UInt8

Sources/GDBRemoteProtocol/GDBTargetResponse.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ package enum VContActions: String {
1111
package struct GDBTargetResponse {
1212
package enum Kind {
1313
case ok
14-
case hostInfo(KeyValuePairs<String, String>)
14+
case keyValuePairs(KeyValuePairs<String, String>)
1515
case vContSupportedActions([VContActions])
1616
case raw(String)
1717
case empty
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import GDBRemoteProtocol
2+
import Logging
3+
import SystemPackage
4+
import WasmKit
5+
6+
package actor WasmKitDebugger {
7+
/// Whether `QStartNoAckMode` command was previously sent.
8+
private var isNoAckModeActive = false
9+
10+
private let module: Module
11+
private let logger: Logger
12+
13+
package init(logger: Logger, moduleFilePath: FilePath) throws {
14+
self.logger = logger
15+
self.module = try parseWasm(filePath: moduleFilePath)
16+
}
17+
18+
package func handle(command: GDBHostCommand) -> GDBTargetResponse {
19+
let responseKind: GDBTargetResponse.Kind
20+
logger.trace("handling GDB host command", metadata: ["GDBHostCommand": .string(command.kind.rawValue)])
21+
22+
responseKind = switch command.kind {
23+
case .startNoAckMode, .isThreadSuffixSupported, .listThreadsInStopReply:
24+
.ok
25+
26+
case .hostInfo:
27+
.keyValuePairs([
28+
"arch": "wasm32",
29+
"ptrsize": "4",
30+
"endian": "little",
31+
"ostype": "wasip1",
32+
"vendor": "WasmKit",
33+
])
34+
35+
case .supportedFeatures:
36+
// FIXME: should return a different set of supported features instead of echoing.
37+
.raw(command.arguments)
38+
39+
case .vContSupportedActions:
40+
.vContSupportedActions([.continue, .step, .stop])
41+
42+
case .isVAttachOrWaitSupported, .enableErrorStrings:
43+
.empty
44+
case .processInfo:
45+
.raw("pid:1;parent-pid:1;arch:wasm32;endian:little;ptrsize:4;")
46+
47+
case .currentThreadID:
48+
.raw("QC1")
49+
50+
case .firstThreadInfo:
51+
.raw("m1")
52+
53+
case .subsequentThreadInfo:
54+
.raw("l")
55+
56+
case .generalRegisters:
57+
fatalError()
58+
}
59+
60+
defer {
61+
if command.kind == .startNoAckMode {
62+
self.isNoAckModeActive = true
63+
}
64+
}
65+
return .init(kind: responseKind, isNoAckModeActive: self.isNoAckModeActive)
66+
}
67+
68+
}

Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift

Lines changed: 0 additions & 75 deletions
This file was deleted.

Sources/wasmkit-gdb-tool/Entrypoint.swift

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,46 @@
1414

1515
import ArgumentParser
1616
import GDBRemoteProtocol
17+
import Logging
1718
import NIOCore
1819
import NIOPosix
20+
import SystemPackage
1921
import WasmKitGDBHandler
2022

23+
#if hasFeature(RetroactiveAttribute)
24+
extension Logger.Level: @retroactive ExpressibleByArgument {}
25+
extension FilePath: @retroactive ExpressibleByArgument {
26+
public init?(argument: String) {
27+
self.init(argument)
28+
}
29+
}
30+
#else
31+
extension Logger.Level: ExpressibleByArgument {}
32+
extension FilePath: ExpressibleByArgument {
33+
public init?(argument: String) {
34+
self.init(argument)
35+
}
36+
}
37+
#endif
38+
2139
@main
22-
struct Entrypoint: ParsableCommand {
40+
struct Entrypoint: AsyncParsableCommand {
2341
@Option(help: "TCP port that a debugger can connect to")
2442
var port = 8080
2543

26-
func run() throws {
44+
@Option(name: .shortAndLong)
45+
var logLevel = Logger.Level.info
46+
47+
@Argument
48+
var wasmModulePath: FilePath
49+
50+
func run() async throws {
51+
let logger = {
52+
var result = Logger(label: "org.swiftwasm.WasmKit")
53+
result.logLevel = self.logLevel
54+
return result
55+
}()
56+
2757
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
2858
let bootstrap = ServerBootstrap(group: group)
2959
// Specify backlog and enable SO_REUSEADDR for the server itself
@@ -38,9 +68,8 @@ struct Entrypoint: ParsableCommand {
3868
// make sure to instantiate your `ChannelHandlers` inside of
3969
// the closure as it will be invoked once per connection.
4070
try channel.pipeline.syncOperations.addHandlers([
41-
ByteToMessageHandler(GDBHostCommandDecoder()),
71+
ByteToMessageHandler(GDBHostCommandDecoder(logger: logger)),
4272
MessageToByteHandler(GDBTargetResponseEncoder()),
43-
WasmKitGDBHandler(),
4473
])
4574
}
4675
}
@@ -49,11 +78,38 @@ struct Entrypoint: ParsableCommand {
4978
.childChannelOption(.socketOption(.so_reuseaddr), value: 1)
5079
.childChannelOption(.maxMessagesPerRead, value: 16)
5180
.childChannelOption(.recvAllocator, value: AdaptiveRecvByteBufferAllocator())
52-
let channel = try bootstrap.bind(host: "127.0.0.1", port: port).wait()
81+
82+
let serverChannel = try await bootstrap.bind(host: "127.0.0.1", port: port) { childChannel in
83+
childChannel.eventLoop.makeCompletedFuture {
84+
try NIOAsyncChannel<GDBPacket<GDBHostCommand>, GDBTargetResponse>(
85+
wrappingChannelSynchronously: childChannel
86+
)
87+
}
88+
}
5389
/* the server will now be accepting connections */
54-
print("listening on port \(port)")
90+
logger.info("listening on port \(port)")
91+
92+
let debugger = try WasmKitDebugger(logger: logger, moduleFilePath: self.wasmModulePath)
93+
94+
try await withThrowingDiscardingTaskGroup { group in
95+
try await serverChannel.executeThenClose { serverChannelInbound in
96+
for try await connectionChannel in serverChannelInbound {
97+
group.addTask {
98+
do {
99+
try await connectionChannel.executeThenClose { connectionChannelInbound, connectionChannelOutbound in
100+
for try await inboundData in connectionChannelInbound {
101+
// Let's echo back all inbound data
102+
try await connectionChannelOutbound.write(debugger.handle(command: inboundData.payload))
103+
}
104+
}
105+
} catch {
106+
logger.error("Error in GDB remote protocol connection channel", metadata: ["error": "\(error)"])
107+
}
108+
}
109+
}
110+
}
111+
}
55112

56-
try channel.closeFuture.wait() // wait forever as we never close the Channel
57-
try group.syncShutdownGracefully()
113+
try await group.shutdownGracefully()
58114
}
59115
}

0 commit comments

Comments
 (0)