From dcfd0fba7f6970c09ee809f6d5f7b358fcad4561 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 19:52:37 +0100 Subject: [PATCH 01/22] Basic LLDB remote protocol scaffolding --- Package@swift-6.1.swift | 194 ++++++++++++++++++ .../LLDBRemoteProtocol/CommandDecoder.swift | 121 +++++++++++ Sources/LLDBRemoteProtocol/HostCommand.swift | 41 ++++ Sources/LLDBRemoteProtocol/Package.swift | 12 ++ .../LLDBRemoteProtocol/ResponseEncoder.swift | 24 +++ .../LLDBRemoteProtocol/TargetResponse.swift | 15 ++ .../WasmKitLLDBHandler/WasmKitHandler.swift | 55 +++++ Sources/wasmkit-lldb/Entrypoint.swift | 45 ++++ .../RemoteProtocolTests.swift | 33 +++ 9 files changed, 540 insertions(+) create mode 100644 Package@swift-6.1.swift create mode 100644 Sources/LLDBRemoteProtocol/CommandDecoder.swift create mode 100644 Sources/LLDBRemoteProtocol/HostCommand.swift create mode 100644 Sources/LLDBRemoteProtocol/Package.swift create mode 100644 Sources/LLDBRemoteProtocol/ResponseEncoder.swift create mode 100644 Sources/LLDBRemoteProtocol/TargetResponse.swift create mode 100644 Sources/WasmKitLLDBHandler/WasmKitHandler.swift create mode 100644 Sources/wasmkit-lldb/Entrypoint.swift create mode 100644 Tests/LLDBRemoteProtocolTests/RemoteProtocolTests.swift diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift new file mode 100644 index 00000000..ffb392a7 --- /dev/null +++ b/Package@swift-6.1.swift @@ -0,0 +1,194 @@ +// swift-tools-version:6.1 + +import PackageDescription + +import class Foundation.ProcessInfo + +let DarwinPlatforms: [Platform] = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] + +let package = Package( + name: "WasmKit", + platforms: [.macOS(.v10_13), .iOS(.v12)], + products: [ + .executable(name: "wasmkit-cli", targets: ["CLI"]), + .library(name: "WasmKit", targets: ["WasmKit"]), + .library(name: "WasmKitWASI", targets: ["WasmKitWASI"]), + .library(name: "WASI", targets: ["WASI"]), + .library(name: "WasmParser", targets: ["WasmParser"]), + .library(name: "WAT", targets: ["WAT"]), + .library(name: "WIT", targets: ["WIT"]), + .library(name: "_CabiShims", targets: ["_CabiShims"]), + ], + traits: [ + .default(enabledTraits: []), + "WasmDebuggingSupport" + ], + targets: [ + .executableTarget( + name: "CLI", + dependencies: [ + "WAT", + "WasmKit", + "WasmKitWASI", + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "SystemPackage", package: "swift-system"), + ], + exclude: ["CMakeLists.txt"] + ), + + .target( + name: "WasmKit", + dependencies: [ + "_CWasmKit", + "WasmParser", + "WasmTypes", + "SystemExtras", + .product(name: "SystemPackage", package: "swift-system"), + ], + exclude: ["CMakeLists.txt"] + ), + .target(name: "_CWasmKit"), + .target( + name: "WasmKitFuzzing", + dependencies: ["WasmKit"], + path: "FuzzTesting/Sources/WasmKitFuzzing" + ), + .testTarget( + name: "WasmKitTests", + dependencies: ["WasmKit", "WAT", "WasmKitFuzzing"], + exclude: ["ExtraSuite"] + ), + + .target( + name: "WAT", + dependencies: ["WasmParser"], + exclude: ["CMakeLists.txt"] + ), + .testTarget(name: "WATTests", dependencies: ["WAT"]), + + .target( + name: "WasmParser", + dependencies: [ + "WasmTypes", + .product(name: "SystemPackage", package: "swift-system"), + ], + exclude: ["CMakeLists.txt"] + ), + .testTarget(name: "WasmParserTests", dependencies: ["WasmParser"]), + + .target(name: "WasmTypes", exclude: ["CMakeLists.txt"]), + + .target( + name: "WasmKitWASI", + dependencies: ["WasmKit", "WASI"], + exclude: ["CMakeLists.txt"] + ), + .target( + name: "WASI", + dependencies: ["WasmTypes", "SystemExtras"], + exclude: ["CMakeLists.txt"] + ), + .testTarget(name: "WASITests", dependencies: ["WASI", "WasmKitWASI"]), + + .target( + name: "SystemExtras", + dependencies: [ + .product(name: "SystemPackage", package: "swift-system") + ], + exclude: ["CMakeLists.txt"], + swiftSettings: [ + .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: DarwinPlatforms)) + ] + ), + + .executableTarget( + name: "WITTool", + dependencies: [ + "WIT", + "WITOverlayGenerator", + "WITExtractor", + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ] + ), + + .target(name: "WIT"), + .testTarget(name: "WITTests", dependencies: ["WIT"]), + + .target(name: "WITOverlayGenerator", dependencies: ["WIT"]), + .target(name: "_CabiShims"), + + .target(name: "WITExtractor"), + .testTarget(name: "WITExtractorTests", dependencies: ["WITExtractor", "WIT"]), + + .target(name: "LLDBRemoteProtocol", + dependencies: [ + .product(name: "NIOCore", package: "swift-nio"), + ] + ), + .testTarget(name: "LLDBRemoteProtocolTests", dependencies: ["LLDBRemoteProtocol"]), + + .target( + name: "WasmKitLLDBHandler", + dependencies: [ + .product(name: "NIOCore", package: "swift-nio"), + "WasmKit", + "LLDBRemoteProtocol", + ], + ), + + .executableTarget( + name: "wasmkit-lldb", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + "LLDBRemoteProtocol", + "WasmKitLLDBHandler", + ] + ), + ], +) + +if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { + package.dependencies += [ + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.1"), + .package(url: "https://github.com/apple/swift-system", from: "1.5.0"), + .package(url: "https://github.com/apple/swift-nio", from: "2.86.2"), + ] +} else { + package.dependencies += [ + .package(path: "../swift-argument-parser"), + .package(path: "../swift-system"), + ] +} + +#if !os(Windows) + // Add build tool plugins only for non-Windows platforms + package.products.append(contentsOf: [ + .plugin(name: "WITOverlayPlugin", targets: ["WITOverlayPlugin"]), + .plugin(name: "WITExtractorPlugin", targets: ["WITExtractorPlugin"]), + ]) + + package.targets.append(contentsOf: [ + .plugin(name: "WITOverlayPlugin", capability: .buildTool(), dependencies: ["WITTool"]), + .plugin(name: "GenerateOverlayForTesting", capability: .buildTool(), dependencies: ["WITTool"]), + .testTarget( + name: "WITOverlayGeneratorTests", + dependencies: ["WITOverlayGenerator", "WasmKit", "WasmKitWASI"], + exclude: ["Fixtures", "Compiled", "Generated", "EmbeddedSupport"], + plugins: [.plugin(name: "GenerateOverlayForTesting")] + ), + .plugin( + name: "WITExtractorPlugin", + capability: .command( + intent: .custom(verb: "extract-wit", description: "Extract WIT definition from Swift module"), + permissions: [] + ), + dependencies: ["WITTool"] + ), + .testTarget( + name: "WITExtractorPluginTests", + exclude: ["Fixtures"] + ), + ]) +#endif diff --git a/Sources/LLDBRemoteProtocol/CommandDecoder.swift b/Sources/LLDBRemoteProtocol/CommandDecoder.swift new file mode 100644 index 00000000..6acd798e --- /dev/null +++ b/Sources/LLDBRemoteProtocol/CommandDecoder.swift @@ -0,0 +1,121 @@ +import NIOCore + +import struct Foundation.Date + +extension ByteBuffer { + var isChecksumDelimiterAtReader: Bool { + self.peekInteger(as: UInt8.self) == UInt8(ascii: "#") + } + + var isArgumentsDelimiterAtReader: Bool { + self.peekInteger(as: UInt8.self) == UInt8(ascii: ":") + } +} + +package struct CommandDecoder: ByteToMessageDecoder { + enum Error: Swift.Error { + case expectedCommandStart + case unknownCommandKind(String) + case expectedChecksum + case checksumIncorrect + } + + package typealias InboundOut = Packet + + private var accummulatedKind = [UInt8]() + private var accummulatedArguments = [UInt8]() + + package init() {} + + private var accummulatedSum = 0 + package var accummulatedChecksum: UInt8 { + UInt8(self.accummulatedSum % 256) + } + + mutating package func decode(buffer: inout ByteBuffer) throws -> Packet? { + // Command start delimiters. + guard + buffer.readInteger(as: UInt8.self) == UInt8(ascii: "+") + && buffer.readInteger(as: UInt8.self) == UInt8(ascii: "$") + else { + throw Error.expectedCommandStart + } + + // Byte offset for command start. + while !buffer.isChecksumDelimiterAtReader && !buffer.isArgumentsDelimiterAtReader, + let char = buffer.readInteger(as: UInt8.self) + { + self.accummulatedSum += Int(char) + self.accummulatedKind.append(char) + } + + if buffer.isArgumentsDelimiterAtReader, + let argumentsDelimiter = buffer.readInteger(as: UInt8.self) + { + self.accummulatedSum += Int(argumentsDelimiter) + + while !buffer.isChecksumDelimiterAtReader, let char = buffer.readInteger(as: UInt8.self) { + self.accummulatedSum += Int(char) + self.accummulatedArguments.append(char) + } + } + + // Command checksum delimiter. + if !buffer.isChecksumDelimiterAtReader { + // If delimiter not available yet, return `nil` to indicate that the caller needs to top up the buffer. + return nil + } + + defer { + self.accummulatedKind = [] + self.accummulatedArguments = [] + self.accummulatedSum = 0 + } + + let kindString = String(decoding: self.accummulatedKind, as: UTF8.self) + + if let commandKind = HostCommand.Kind(rawValue: kindString) { + buffer.moveReaderIndex(forwardBy: 1) + + guard let checksumString = buffer.readString(length: 2), + let first = checksumString.first?.hexDigitValue, + let last = checksumString.last?.hexDigitValue + else { + throw Error.expectedChecksum + } + + guard (first * 16) + last == self.accummulatedChecksum else { + // FIXME: better diagnostics + throw Error.checksumIncorrect + } + + return .init( + payload: .init( + kind: commandKind, + arguments: String(decoding: self.accummulatedArguments, as: UTF8.self) + ), + checksum: accummulatedChecksum, + ) + } else { + throw Error.unknownCommandKind(kindString) + } + } + + mutating package func decode( + context: ChannelHandlerContext, + buffer: inout ByteBuffer + ) throws -> DecodingState { + print(buffer.peekString(length: buffer.readableBytes)!) + + guard let command = try self.decode(buffer: &buffer) else { + return .needMoreData + } + + // Shift by checksum bytes + context.fireChannelRead(wrapInboundOut(command)) + return .continue + // } else { + // throw Error.unknownCommand(accummulated) + // } + } +} diff --git a/Sources/LLDBRemoteProtocol/HostCommand.swift b/Sources/LLDBRemoteProtocol/HostCommand.swift new file mode 100644 index 00000000..cb7f63fc --- /dev/null +++ b/Sources/LLDBRemoteProtocol/HostCommand.swift @@ -0,0 +1,41 @@ +/// See https://lldb.llvm.org/resources/lldbgdbremote.html for more details. +package struct HostCommand: Equatable { + package enum Kind: Equatable { + // Currently listed in the order that LLDB sends them in. + case generalRegisters + case startNoAckMode + case firstThreadInfo + case supportedFeatures + case isThreadSuffixSupported + case listThreadsInStopReply + case hostInfo + + package init?(rawValue: String) { + switch rawValue { + case "g": + self = .generalRegisters + case "QStartNoAckMode": + self = .startNoAckMode + case "qSupported": + self = .supportedFeatures + case "QThreadSuffixSupported": + self = .isThreadSuffixSupported + case "QListThreadsInStopReply": + self = .listThreadsInStopReply + case "qHostInfo": + self = .hostInfo + default: + return nil + } + } + } + + package let kind: Kind + + package let arguments: String + + package init(kind: Kind, arguments: String) { + self.kind = kind + self.arguments = arguments + } +} diff --git a/Sources/LLDBRemoteProtocol/Package.swift b/Sources/LLDBRemoteProtocol/Package.swift new file mode 100644 index 00000000..87faa03a --- /dev/null +++ b/Sources/LLDBRemoteProtocol/Package.swift @@ -0,0 +1,12 @@ +package struct Packet { + package let payload: Payload + + package let checksum: UInt8 + + package init(payload: Payload, checksum: UInt8) { + self.payload = payload + self.checksum = checksum + } +} + +extension Packet: Equatable where Payload: Equatable {} diff --git a/Sources/LLDBRemoteProtocol/ResponseEncoder.swift b/Sources/LLDBRemoteProtocol/ResponseEncoder.swift new file mode 100644 index 00000000..33a254a1 --- /dev/null +++ b/Sources/LLDBRemoteProtocol/ResponseEncoder.swift @@ -0,0 +1,24 @@ +import Foundation +import NIOCore + +package struct ResponseEncoder: MessageToByteEncoder { + package init() {} + package func encode(data: TargetResponse, out: inout ByteBuffer) throws { + if !data.isNoAckModeActive { + out.writeInteger(UInt8(ascii: "+")) + } + out.writeInteger(UInt8(ascii: "$")) + + switch data.kind { + case .ok: + out.writeBytes("ok#da".utf8) + + case .raw(let str): + out.writeBytes( + "\(str)#\(String(format:"%02X", str.utf8.reduce(0, { $0 + Int($1) }) % 256))".utf8) + + case .empty: + fatalError("unhandled") + } + } +} diff --git a/Sources/LLDBRemoteProtocol/TargetResponse.swift b/Sources/LLDBRemoteProtocol/TargetResponse.swift new file mode 100644 index 00000000..46960b50 --- /dev/null +++ b/Sources/LLDBRemoteProtocol/TargetResponse.swift @@ -0,0 +1,15 @@ +package struct TargetResponse { + package enum Kind { + case ok + case raw(String) + case empty + } + + package let kind: Kind + package let isNoAckModeActive: Bool + + package init(kind: Kind, isNoAckModeActive: Bool) { + self.kind = kind + self.isNoAckModeActive = isNoAckModeActive + } +} diff --git a/Sources/WasmKitLLDBHandler/WasmKitHandler.swift b/Sources/WasmKitLLDBHandler/WasmKitHandler.swift new file mode 100644 index 00000000..97b309f2 --- /dev/null +++ b/Sources/WasmKitLLDBHandler/WasmKitHandler.swift @@ -0,0 +1,55 @@ +import LLDBRemoteProtocol +import NIOCore +import WasmKit + +import struct Foundation.Date + +package final class WasmKitHandler: ChannelInboundHandler { + package typealias InboundIn = Packet + package typealias OutboundOut = TargetResponse + + /// Whether `QStartNoAckMode` command was previously sent. + private var isNoAckModeActive = false + + package init() {} + + package func channelRead( + context: ChannelHandlerContext, + data: NIOAny + ) { + let command = self.unwrapInboundIn(data).payload + let responseKind: TargetResponse.Kind + print(command.kind) + + switch command.kind { + case .startNoAckMode, .isThreadSuffixSupported, .listThreadsInStopReply: + responseKind = .ok + case .supportedFeatures: + responseKind = .raw(command.arguments) + default: + fatalError() + } + + context.writeAndFlush( + wrapOutboundOut(.init(kind: responseKind, isNoAckModeActive: self.isNoAckModeActive)), + promise: nil) + if command.kind == .startNoAckMode { + self.isNoAckModeActive = true + } + } + + package func channelReadComplete( + context: ChannelHandlerContext + ) { + context.flush() + } + + package func errorCaught( + context: ChannelHandlerContext, + error: Error + ) { + print(error) + + context.close(promise: nil) + } +} diff --git a/Sources/wasmkit-lldb/Entrypoint.swift b/Sources/wasmkit-lldb/Entrypoint.swift new file mode 100644 index 00000000..00ccaba7 --- /dev/null +++ b/Sources/wasmkit-lldb/Entrypoint.swift @@ -0,0 +1,45 @@ +import ArgumentParser +import LLDBRemoteProtocol +import NIOCore +import NIOPosix +import WasmKitLLDBHandler + +@main +struct Entrypoint: ParsableCommand { + @Option(help: "TCP port that a debugger can connect to") + var port = 8080 + + func run() throws { + let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) + let bootstrap = ServerBootstrap(group: group) + // Specify backlog and enable SO_REUSEADDR for the server itself + .serverChannelOption(.backlog, value: 256) + .serverChannelOption(.socketOption(.so_reuseaddr), value: 1) + + // Set the handlers that are applied to the accepted child `Channel`s. + .childChannelInitializer { channel in + // Ensure we don't read faster then we can write by adding the BackPressureHandler into the pipeline. + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandler(BackPressureHandler()) + // make sure to instantiate your `ChannelHandlers` inside of + // the closure as it will be invoked once per connection. + try channel.pipeline.syncOperations.addHandlers([ + ByteToMessageHandler(CommandDecoder()), + MessageToByteHandler(ResponseEncoder()), + WasmKitHandler(), + ]) + } + } + + // Enable SO_REUSEADDR for the accepted Channels + .childChannelOption(.socketOption(.so_reuseaddr), value: 1) + .childChannelOption(.maxMessagesPerRead, value: 16) + .childChannelOption(.recvAllocator, value: AdaptiveRecvByteBufferAllocator()) + let channel = try bootstrap.bind(host: "127.0.0.1", port: port).wait() + /* the server will now be accepting connections */ + print("listening on port \(port)") + + try channel.closeFuture.wait() // wait forever as we never close the Channel + try group.syncShutdownGracefully() + } +} diff --git a/Tests/LLDBRemoteProtocolTests/RemoteProtocolTests.swift b/Tests/LLDBRemoteProtocolTests/RemoteProtocolTests.swift new file mode 100644 index 00000000..c3805f7c --- /dev/null +++ b/Tests/LLDBRemoteProtocolTests/RemoteProtocolTests.swift @@ -0,0 +1,33 @@ +import LLDBRemoteProtocol +import NIOCore +import Testing + +@Suite +struct LLDBRemoteProtocolTests { + @Test + func decoding() throws { + var decoder = CommandDecoder() + + var buffer = ByteBuffer(string: "+$g#67") + var packet = try decoder.decode(buffer: &buffer) + #expect(packet == Packet(payload: HostCommand(kind: .generalRegisters, arguments: ""), checksum: 103)) + #expect(decoder.accummulatedChecksum == 0) + + buffer = ByteBuffer( + string: """ + +$qSupported:xmlRegisters=i386,arm,mips,arc;multiprocess+;fork-events+;vfork-events+#2e + """ + ) + + packet = try decoder.decode(buffer: &buffer) + let expectedPacket = Packet( + payload: HostCommand( + kind: .supportedFeatures, + arguments: "xmlRegisters=i386,arm,mips,arc;multiprocess+;fork-events+;vfork-events+" + ), + checksum: 0x2e, + ) + #expect(packet == expectedPacket) + #expect(decoder.accummulatedChecksum == 0) + } +} From 28fb40709ebb4b50e470fd45b38c01bf8d7b17bc Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 21:56:18 +0100 Subject: [PATCH 02/22] Implement more packet handling, update naming --- Package@swift-6.1.swift | 14 ++++---- .../CommandDecoder.swift | 18 ++++++---- .../HostCommand.swift | 21 +++++++++-- .../Package.swift | 0 .../GDBRemoteProtocol/ResponseEncoder.swift | 35 +++++++++++++++++++ .../GDBRemoteProtocol/TargetResponse.swift | 27 ++++++++++++++ .../LLDBRemoteProtocol/ResponseEncoder.swift | 24 ------------- .../LLDBRemoteProtocol/TargetResponse.swift | 15 -------- .../WasmKitHandler.swift | 29 ++++++++++++--- .../Entrypoint.swift | 8 ++--- .../RemoteProtocolTests.swift | 8 ++--- 11 files changed, 132 insertions(+), 67 deletions(-) rename Sources/{LLDBRemoteProtocol => GDBRemoteProtocol}/CommandDecoder.swift (83%) rename Sources/{LLDBRemoteProtocol => GDBRemoteProtocol}/HostCommand.swift (58%) rename Sources/{LLDBRemoteProtocol => GDBRemoteProtocol}/Package.swift (100%) create mode 100644 Sources/GDBRemoteProtocol/ResponseEncoder.swift create mode 100644 Sources/GDBRemoteProtocol/TargetResponse.swift delete mode 100644 Sources/LLDBRemoteProtocol/ResponseEncoder.swift delete mode 100644 Sources/LLDBRemoteProtocol/TargetResponse.swift rename Sources/{WasmKitLLDBHandler => WasmKitGDBHandler}/WasmKitHandler.swift (64%) rename Sources/{wasmkit-lldb => wasmkit-gdb-tool}/Entrypoint.swift (90%) rename Tests/{LLDBRemoteProtocolTests => GDBRemoteProtocolTests}/RemoteProtocolTests.swift (79%) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index ffb392a7..f70371f1 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -120,30 +120,30 @@ let package = Package( .target(name: "WITExtractor"), .testTarget(name: "WITExtractorTests", dependencies: ["WITExtractor", "WIT"]), - .target(name: "LLDBRemoteProtocol", + .target(name: "GDBRemoteProtocol", dependencies: [ .product(name: "NIOCore", package: "swift-nio"), ] ), - .testTarget(name: "LLDBRemoteProtocolTests", dependencies: ["LLDBRemoteProtocol"]), + .testTarget(name: "GDBRemoteProtocolTests", dependencies: ["GDBRemoteProtocol"]), .target( - name: "WasmKitLLDBHandler", + name: "WasmKitGDBHandler", dependencies: [ .product(name: "NIOCore", package: "swift-nio"), "WasmKit", - "LLDBRemoteProtocol", + "GDBRemoteProtocol", ], ), .executableTarget( - name: "wasmkit-lldb", + name: "wasmkit-gdb-tool", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOPosix", package: "swift-nio"), - "LLDBRemoteProtocol", - "WasmKitLLDBHandler", + "GDBRemoteProtocol", + "WasmKitGDBHandler", ] ), ], diff --git a/Sources/LLDBRemoteProtocol/CommandDecoder.swift b/Sources/GDBRemoteProtocol/CommandDecoder.swift similarity index 83% rename from Sources/LLDBRemoteProtocol/CommandDecoder.swift rename to Sources/GDBRemoteProtocol/CommandDecoder.swift index 6acd798e..c46b7a51 100644 --- a/Sources/LLDBRemoteProtocol/CommandDecoder.swift +++ b/Sources/GDBRemoteProtocol/CommandDecoder.swift @@ -12,7 +12,7 @@ extension ByteBuffer { } } -package struct CommandDecoder: ByteToMessageDecoder { +package struct GDBHostCommandDecoder: ByteToMessageDecoder { enum Error: Swift.Error { case expectedCommandStart case unknownCommandKind(String) @@ -20,7 +20,7 @@ package struct CommandDecoder: ByteToMessageDecoder { case checksumIncorrect } - package typealias InboundOut = Packet + package typealias InboundOut = Packet private var accummulatedKind = [UInt8]() private var accummulatedArguments = [UInt8]() @@ -32,12 +32,16 @@ package struct CommandDecoder: ByteToMessageDecoder { UInt8(self.accummulatedSum % 256) } - mutating package func decode(buffer: inout ByteBuffer) throws -> Packet? { + mutating package func decode(buffer: inout ByteBuffer) throws -> Packet? { // Command start delimiters. - guard - buffer.readInteger(as: UInt8.self) == UInt8(ascii: "+") - && buffer.readInteger(as: UInt8.self) == UInt8(ascii: "$") + let firstStartDelimiter = buffer.readInteger(as: UInt8.self) + let secondStartDelimiter = buffer.readInteger(as: UInt8.self) + guard firstStartDelimiter == UInt8(ascii: "+") + && secondStartDelimiter == UInt8(ascii: "$") else { + if let firstStartDelimiter, let secondStartDelimiter { + print("unexpected delimiter: \(Character(UnicodeScalar(firstStartDelimiter)))\(Character(UnicodeScalar(secondStartDelimiter)))") + } throw Error.expectedCommandStart } @@ -74,7 +78,7 @@ package struct CommandDecoder: ByteToMessageDecoder { let kindString = String(decoding: self.accummulatedKind, as: UTF8.self) - if let commandKind = HostCommand.Kind(rawValue: kindString) { + if let commandKind = GDBHostCommand.Kind(rawValue: kindString) { buffer.moveReaderIndex(forwardBy: 1) guard let checksumString = buffer.readString(length: 2), diff --git a/Sources/LLDBRemoteProtocol/HostCommand.swift b/Sources/GDBRemoteProtocol/HostCommand.swift similarity index 58% rename from Sources/LLDBRemoteProtocol/HostCommand.swift rename to Sources/GDBRemoteProtocol/HostCommand.swift index cb7f63fc..754270fd 100644 --- a/Sources/LLDBRemoteProtocol/HostCommand.swift +++ b/Sources/GDBRemoteProtocol/HostCommand.swift @@ -1,5 +1,7 @@ -/// See https://lldb.llvm.org/resources/lldbgdbremote.html for more details. -package struct HostCommand: Equatable { +/// See GDB and LLDB remote protocol documentation for more details: +/// * https://sourceware.org/gdb/current/onlinedocs/gdb.html/General-Query-Packets.html +/// * https://lldb.llvm.org/resources/lldbgdbremote.html +package struct GDBHostCommand: Equatable { package enum Kind: Equatable { // Currently listed in the order that LLDB sends them in. case generalRegisters @@ -9,6 +11,11 @@ package struct HostCommand: Equatable { case isThreadSuffixSupported case listThreadsInStopReply case hostInfo + case vContSupportedActions + case isVAttachOrWaitSupported + case enableErrorStrings + case processInfo + case currentThreadID package init?(rawValue: String) { switch rawValue { @@ -24,6 +31,16 @@ package struct HostCommand: Equatable { self = .listThreadsInStopReply case "qHostInfo": self = .hostInfo + case "vCont?": + self = .vContSupportedActions + case "qVAttachOrWaitSupported": + self = .isVAttachOrWaitSupported + case "QEnableErrorStrings": + self = .enableErrorStrings + case "qProcessInfo": + self = .processInfo + case "qC": + self = .currentThreadID default: return nil } diff --git a/Sources/LLDBRemoteProtocol/Package.swift b/Sources/GDBRemoteProtocol/Package.swift similarity index 100% rename from Sources/LLDBRemoteProtocol/Package.swift rename to Sources/GDBRemoteProtocol/Package.swift diff --git a/Sources/GDBRemoteProtocol/ResponseEncoder.swift b/Sources/GDBRemoteProtocol/ResponseEncoder.swift new file mode 100644 index 00000000..ddcbe872 --- /dev/null +++ b/Sources/GDBRemoteProtocol/ResponseEncoder.swift @@ -0,0 +1,35 @@ +import Foundation +import NIOCore + +extension String { + fileprivate var appendedChecksum: String.UTF8View { + "\(self)#\(String(format:"%02X", self.utf8.reduce(0, { $0 + Int($1) }) % 256))".utf8 + } +} + +package struct GDBTargetResponseEncoder: MessageToByteEncoder { + package init() {} + package func encode(data: TargetResponse, out: inout ByteBuffer) throws { + if !data.isNoAckModeActive { + out.writeInteger(UInt8(ascii: "+")) + } + out.writeInteger(UInt8(ascii: "$")) + + switch data.kind { + case .ok: + out.writeBytes("ok#da".utf8) + + case .hostInfo(let info): + out.writeBytes(info.map { (key, value) in "\(key):\(value);"}.joined().appendedChecksum) + + case .vContSupportedActions(let actions): + out.writeBytes("vCont;\(actions.map(\.rawValue).joined())".appendedChecksum) + + case .raw(let str): + out.writeBytes(str.appendedChecksum) + + case .empty: + out.writeBytes("".appendedChecksum) + } + } +} diff --git a/Sources/GDBRemoteProtocol/TargetResponse.swift b/Sources/GDBRemoteProtocol/TargetResponse.swift new file mode 100644 index 00000000..4373f1f2 --- /dev/null +++ b/Sources/GDBRemoteProtocol/TargetResponse.swift @@ -0,0 +1,27 @@ +/// Actions supported in the `vCont` host command. +package enum VContActions: String { + case `continue` = "c" + case continueWithSignal = "C" + case step = "s" + case stepWithSignal = "S" + case stop = "t" + case stepInRange = "r" +} + +package struct TargetResponse { + package enum Kind { + case ok + case hostInfo(KeyValuePairs) + case vContSupportedActions([VContActions]) + case raw(String) + case empty + } + + package let kind: Kind + package let isNoAckModeActive: Bool + + package init(kind: Kind, isNoAckModeActive: Bool) { + self.kind = kind + self.isNoAckModeActive = isNoAckModeActive + } +} diff --git a/Sources/LLDBRemoteProtocol/ResponseEncoder.swift b/Sources/LLDBRemoteProtocol/ResponseEncoder.swift deleted file mode 100644 index 33a254a1..00000000 --- a/Sources/LLDBRemoteProtocol/ResponseEncoder.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation -import NIOCore - -package struct ResponseEncoder: MessageToByteEncoder { - package init() {} - package func encode(data: TargetResponse, out: inout ByteBuffer) throws { - if !data.isNoAckModeActive { - out.writeInteger(UInt8(ascii: "+")) - } - out.writeInteger(UInt8(ascii: "$")) - - switch data.kind { - case .ok: - out.writeBytes("ok#da".utf8) - - case .raw(let str): - out.writeBytes( - "\(str)#\(String(format:"%02X", str.utf8.reduce(0, { $0 + Int($1) }) % 256))".utf8) - - case .empty: - fatalError("unhandled") - } - } -} diff --git a/Sources/LLDBRemoteProtocol/TargetResponse.swift b/Sources/LLDBRemoteProtocol/TargetResponse.swift deleted file mode 100644 index 46960b50..00000000 --- a/Sources/LLDBRemoteProtocol/TargetResponse.swift +++ /dev/null @@ -1,15 +0,0 @@ -package struct TargetResponse { - package enum Kind { - case ok - case raw(String) - case empty - } - - package let kind: Kind - package let isNoAckModeActive: Bool - - package init(kind: Kind, isNoAckModeActive: Bool) { - self.kind = kind - self.isNoAckModeActive = isNoAckModeActive - } -} diff --git a/Sources/WasmKitLLDBHandler/WasmKitHandler.swift b/Sources/WasmKitGDBHandler/WasmKitHandler.swift similarity index 64% rename from Sources/WasmKitLLDBHandler/WasmKitHandler.swift rename to Sources/WasmKitGDBHandler/WasmKitHandler.swift index 97b309f2..cbdff83c 100644 --- a/Sources/WasmKitLLDBHandler/WasmKitHandler.swift +++ b/Sources/WasmKitGDBHandler/WasmKitHandler.swift @@ -1,11 +1,11 @@ -import LLDBRemoteProtocol +import GDBRemoteProtocol import NIOCore import WasmKit import struct Foundation.Date package final class WasmKitHandler: ChannelInboundHandler { - package typealias InboundIn = Packet + package typealias InboundIn = Packet package typealias OutboundOut = TargetResponse /// Whether `QStartNoAckMode` command was previously sent. @@ -24,15 +24,36 @@ package final class WasmKitHandler: ChannelInboundHandler { switch command.kind { case .startNoAckMode, .isThreadSuffixSupported, .listThreadsInStopReply: responseKind = .ok + + case .hostInfo: + responseKind = .hostInfo([ + "arch": "wasm32", + "ptrsize": "4", + "endian": "little", + "ostype": "wasip1", + "vendor": "WasmKit" + ]) + case .supportedFeatures: responseKind = .raw(command.arguments) - default: + + case .vContSupportedActions: + responseKind = .vContSupportedActions([.continue, .step, .stop]) + + case .isVAttachOrWaitSupported, .enableErrorStrings, .processInfo: + responseKind = .empty + + case .currentThreadID: + responseKind = .raw("QC 1") + + case .generalRegisters, .firstThreadInfo: fatalError() } context.writeAndFlush( wrapOutboundOut(.init(kind: responseKind, isNoAckModeActive: self.isNoAckModeActive)), - promise: nil) + promise: nil + ) if command.kind == .startNoAckMode { self.isNoAckModeActive = true } diff --git a/Sources/wasmkit-lldb/Entrypoint.swift b/Sources/wasmkit-gdb-tool/Entrypoint.swift similarity index 90% rename from Sources/wasmkit-lldb/Entrypoint.swift rename to Sources/wasmkit-gdb-tool/Entrypoint.swift index 00ccaba7..8fa6095c 100644 --- a/Sources/wasmkit-lldb/Entrypoint.swift +++ b/Sources/wasmkit-gdb-tool/Entrypoint.swift @@ -1,8 +1,8 @@ import ArgumentParser -import LLDBRemoteProtocol +import GDBRemoteProtocol import NIOCore import NIOPosix -import WasmKitLLDBHandler +import WasmKitGDBHandler @main struct Entrypoint: ParsableCommand { @@ -24,8 +24,8 @@ struct Entrypoint: ParsableCommand { // make sure to instantiate your `ChannelHandlers` inside of // the closure as it will be invoked once per connection. try channel.pipeline.syncOperations.addHandlers([ - ByteToMessageHandler(CommandDecoder()), - MessageToByteHandler(ResponseEncoder()), + ByteToMessageHandler(GDBHostCommandDecoder()), + MessageToByteHandler(GDBTargetResponseEncoder()), WasmKitHandler(), ]) } diff --git a/Tests/LLDBRemoteProtocolTests/RemoteProtocolTests.swift b/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift similarity index 79% rename from Tests/LLDBRemoteProtocolTests/RemoteProtocolTests.swift rename to Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift index c3805f7c..22483310 100644 --- a/Tests/LLDBRemoteProtocolTests/RemoteProtocolTests.swift +++ b/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift @@ -1,4 +1,4 @@ -import LLDBRemoteProtocol +import GDBRemoteProtocol import NIOCore import Testing @@ -6,11 +6,11 @@ import Testing struct LLDBRemoteProtocolTests { @Test func decoding() throws { - var decoder = CommandDecoder() + var decoder = GDBHostCommandDecoder() var buffer = ByteBuffer(string: "+$g#67") var packet = try decoder.decode(buffer: &buffer) - #expect(packet == Packet(payload: HostCommand(kind: .generalRegisters, arguments: ""), checksum: 103)) + #expect(packet == Packet(payload: GDBHostCommand(kind: .generalRegisters, arguments: ""), checksum: 103)) #expect(decoder.accummulatedChecksum == 0) buffer = ByteBuffer( @@ -21,7 +21,7 @@ struct LLDBRemoteProtocolTests { packet = try decoder.decode(buffer: &buffer) let expectedPacket = Packet( - payload: HostCommand( + payload: GDBHostCommand( kind: .supportedFeatures, arguments: "xmlRegisters=i386,arm,mips,arc;multiprocess+;fork-events+;vfork-events+" ), From 8414f9317af303fd29852ded1b2ea655d326885d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 21:58:48 +0100 Subject: [PATCH 03/22] Provide original license notice in `wasmkit-gdb-tool/Entrypoint.swift` --- Sources/wasmkit-gdb-tool/Entrypoint.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/wasmkit-gdb-tool/Entrypoint.swift b/Sources/wasmkit-gdb-tool/Entrypoint.swift index 8fa6095c..3d1fa0b1 100644 --- a/Sources/wasmkit-gdb-tool/Entrypoint.swift +++ b/Sources/wasmkit-gdb-tool/Entrypoint.swift @@ -1,3 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2025 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + import ArgumentParser import GDBRemoteProtocol import NIOCore From c9ba5d63b66c6a06b6dfb3af111d3ac15d2f0f3b Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 22:00:43 +0100 Subject: [PATCH 04/22] Apply formatter --- Sources/GDBRemoteProtocol/CommandDecoder.swift | 3 ++- Sources/GDBRemoteProtocol/ResponseEncoder.swift | 2 +- Sources/WasmKitGDBHandler/WasmKitHandler.swift | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/GDBRemoteProtocol/CommandDecoder.swift b/Sources/GDBRemoteProtocol/CommandDecoder.swift index c46b7a51..46e035de 100644 --- a/Sources/GDBRemoteProtocol/CommandDecoder.swift +++ b/Sources/GDBRemoteProtocol/CommandDecoder.swift @@ -36,7 +36,8 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { // Command start delimiters. let firstStartDelimiter = buffer.readInteger(as: UInt8.self) let secondStartDelimiter = buffer.readInteger(as: UInt8.self) - guard firstStartDelimiter == UInt8(ascii: "+") + guard + firstStartDelimiter == UInt8(ascii: "+") && secondStartDelimiter == UInt8(ascii: "$") else { if let firstStartDelimiter, let secondStartDelimiter { diff --git a/Sources/GDBRemoteProtocol/ResponseEncoder.swift b/Sources/GDBRemoteProtocol/ResponseEncoder.swift index ddcbe872..96d89350 100644 --- a/Sources/GDBRemoteProtocol/ResponseEncoder.swift +++ b/Sources/GDBRemoteProtocol/ResponseEncoder.swift @@ -20,7 +20,7 @@ package struct GDBTargetResponseEncoder: MessageToByteEncoder { out.writeBytes("ok#da".utf8) case .hostInfo(let info): - out.writeBytes(info.map { (key, value) in "\(key):\(value);"}.joined().appendedChecksum) + out.writeBytes(info.map { (key, value) in "\(key):\(value);" }.joined().appendedChecksum) case .vContSupportedActions(let actions): out.writeBytes("vCont;\(actions.map(\.rawValue).joined())".appendedChecksum) diff --git a/Sources/WasmKitGDBHandler/WasmKitHandler.swift b/Sources/WasmKitGDBHandler/WasmKitHandler.swift index cbdff83c..f5817122 100644 --- a/Sources/WasmKitGDBHandler/WasmKitHandler.swift +++ b/Sources/WasmKitGDBHandler/WasmKitHandler.swift @@ -31,7 +31,7 @@ package final class WasmKitHandler: ChannelInboundHandler { "ptrsize": "4", "endian": "little", "ostype": "wasip1", - "vendor": "WasmKit" + "vendor": "WasmKit", ]) case .supportedFeatures: From b661bb1ee1d9ffb026e618bcea0436675806421d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 22:05:21 +0100 Subject: [PATCH 05/22] Make naming of types more specific to avoid future collisions --- .../{HostCommand.swift => GDBHostCommand.swift} | 0 .../{CommandDecoder.swift => GDBHostCommandDecoder.swift} | 4 ++-- .../GDBRemoteProtocol/{Package.swift => GDBPacket.swift} | 4 ++-- .../{TargetResponse.swift => GDBTargetResponse.swift} | 2 +- ...ResponseEncoder.swift => GDBTargetResponseEncoder.swift} | 2 +- Sources/WasmKitGDBHandler/WasmKitHandler.swift | 6 +++--- Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) rename Sources/GDBRemoteProtocol/{HostCommand.swift => GDBHostCommand.swift} (100%) rename Sources/GDBRemoteProtocol/{CommandDecoder.swift => GDBHostCommandDecoder.swift} (97%) rename Sources/GDBRemoteProtocol/{Package.swift => GDBPacket.swift} (67%) rename Sources/GDBRemoteProtocol/{TargetResponse.swift => GDBTargetResponse.swift} (94%) rename Sources/GDBRemoteProtocol/{ResponseEncoder.swift => GDBTargetResponseEncoder.swift} (92%) diff --git a/Sources/GDBRemoteProtocol/HostCommand.swift b/Sources/GDBRemoteProtocol/GDBHostCommand.swift similarity index 100% rename from Sources/GDBRemoteProtocol/HostCommand.swift rename to Sources/GDBRemoteProtocol/GDBHostCommand.swift diff --git a/Sources/GDBRemoteProtocol/CommandDecoder.swift b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift similarity index 97% rename from Sources/GDBRemoteProtocol/CommandDecoder.swift rename to Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift index 46e035de..6847e766 100644 --- a/Sources/GDBRemoteProtocol/CommandDecoder.swift +++ b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift @@ -20,7 +20,7 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { case checksumIncorrect } - package typealias InboundOut = Packet + package typealias InboundOut = GDBPacket private var accummulatedKind = [UInt8]() private var accummulatedArguments = [UInt8]() @@ -32,7 +32,7 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { UInt8(self.accummulatedSum % 256) } - mutating package func decode(buffer: inout ByteBuffer) throws -> Packet? { + mutating package func decode(buffer: inout ByteBuffer) throws -> GDBPacket? { // Command start delimiters. let firstStartDelimiter = buffer.readInteger(as: UInt8.self) let secondStartDelimiter = buffer.readInteger(as: UInt8.self) diff --git a/Sources/GDBRemoteProtocol/Package.swift b/Sources/GDBRemoteProtocol/GDBPacket.swift similarity index 67% rename from Sources/GDBRemoteProtocol/Package.swift rename to Sources/GDBRemoteProtocol/GDBPacket.swift index 87faa03a..1878efb4 100644 --- a/Sources/GDBRemoteProtocol/Package.swift +++ b/Sources/GDBRemoteProtocol/GDBPacket.swift @@ -1,4 +1,4 @@ -package struct Packet { +package struct GDBPacket { package let payload: Payload package let checksum: UInt8 @@ -9,4 +9,4 @@ package struct Packet { } } -extension Packet: Equatable where Payload: Equatable {} +extension GDBPacket: Equatable where Payload: Equatable {} diff --git a/Sources/GDBRemoteProtocol/TargetResponse.swift b/Sources/GDBRemoteProtocol/GDBTargetResponse.swift similarity index 94% rename from Sources/GDBRemoteProtocol/TargetResponse.swift rename to Sources/GDBRemoteProtocol/GDBTargetResponse.swift index 4373f1f2..b827ee55 100644 --- a/Sources/GDBRemoteProtocol/TargetResponse.swift +++ b/Sources/GDBRemoteProtocol/GDBTargetResponse.swift @@ -8,7 +8,7 @@ package enum VContActions: String { case stepInRange = "r" } -package struct TargetResponse { +package struct GDBTargetResponse { package enum Kind { case ok case hostInfo(KeyValuePairs) diff --git a/Sources/GDBRemoteProtocol/ResponseEncoder.swift b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift similarity index 92% rename from Sources/GDBRemoteProtocol/ResponseEncoder.swift rename to Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift index 96d89350..83e738b0 100644 --- a/Sources/GDBRemoteProtocol/ResponseEncoder.swift +++ b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift @@ -9,7 +9,7 @@ extension String { package struct GDBTargetResponseEncoder: MessageToByteEncoder { package init() {} - package func encode(data: TargetResponse, out: inout ByteBuffer) throws { + package func encode(data: GDBTargetResponse, out: inout ByteBuffer) throws { if !data.isNoAckModeActive { out.writeInteger(UInt8(ascii: "+")) } diff --git a/Sources/WasmKitGDBHandler/WasmKitHandler.swift b/Sources/WasmKitGDBHandler/WasmKitHandler.swift index f5817122..1c1b9c95 100644 --- a/Sources/WasmKitGDBHandler/WasmKitHandler.swift +++ b/Sources/WasmKitGDBHandler/WasmKitHandler.swift @@ -5,8 +5,8 @@ import WasmKit import struct Foundation.Date package final class WasmKitHandler: ChannelInboundHandler { - package typealias InboundIn = Packet - package typealias OutboundOut = TargetResponse + package typealias InboundIn = GDBPacket + package typealias OutboundOut = GDBTargetResponse /// Whether `QStartNoAckMode` command was previously sent. private var isNoAckModeActive = false @@ -18,7 +18,7 @@ package final class WasmKitHandler: ChannelInboundHandler { data: NIOAny ) { let command = self.unwrapInboundIn(data).payload - let responseKind: TargetResponse.Kind + let responseKind: GDBTargetResponse.Kind print(command.kind) switch command.kind { diff --git a/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift b/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift index 22483310..43a971d9 100644 --- a/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift +++ b/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift @@ -10,7 +10,7 @@ struct LLDBRemoteProtocolTests { var buffer = ByteBuffer(string: "+$g#67") var packet = try decoder.decode(buffer: &buffer) - #expect(packet == Packet(payload: GDBHostCommand(kind: .generalRegisters, arguments: ""), checksum: 103)) + #expect(packet == GDBPacket(payload: GDBHostCommand(kind: .generalRegisters, arguments: ""), checksum: 103)) #expect(decoder.accummulatedChecksum == 0) buffer = ByteBuffer( @@ -20,7 +20,7 @@ struct LLDBRemoteProtocolTests { ) packet = try decoder.decode(buffer: &buffer) - let expectedPacket = Packet( + let expectedPacket = GDBPacket( payload: GDBHostCommand( kind: .supportedFeatures, arguments: "xmlRegisters=i386,arm,mips,arc;multiprocess+;fork-events+;vfork-events+" From b1456de4fbca267d650e9588ddcaddd84609eb24 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 22:08:09 +0100 Subject: [PATCH 06/22] More cleanups for type naming --- .../{WasmKitHandler.swift => WasmKitGDBHandler.swift} | 2 +- Sources/wasmkit-gdb-tool/Entrypoint.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Sources/WasmKitGDBHandler/{WasmKitHandler.swift => WasmKitGDBHandler.swift} (96%) diff --git a/Sources/WasmKitGDBHandler/WasmKitHandler.swift b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift similarity index 96% rename from Sources/WasmKitGDBHandler/WasmKitHandler.swift rename to Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift index 1c1b9c95..3814f720 100644 --- a/Sources/WasmKitGDBHandler/WasmKitHandler.swift +++ b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift @@ -4,7 +4,7 @@ import WasmKit import struct Foundation.Date -package final class WasmKitHandler: ChannelInboundHandler { +package final class WasmKitGDBHandler: ChannelInboundHandler { package typealias InboundIn = GDBPacket package typealias OutboundOut = GDBTargetResponse diff --git a/Sources/wasmkit-gdb-tool/Entrypoint.swift b/Sources/wasmkit-gdb-tool/Entrypoint.swift index 3d1fa0b1..97f3bf62 100644 --- a/Sources/wasmkit-gdb-tool/Entrypoint.swift +++ b/Sources/wasmkit-gdb-tool/Entrypoint.swift @@ -40,7 +40,7 @@ struct Entrypoint: ParsableCommand { try channel.pipeline.syncOperations.addHandlers([ ByteToMessageHandler(GDBHostCommandDecoder()), MessageToByteHandler(GDBTargetResponseEncoder()), - WasmKitHandler(), + WasmKitGDBHandler(), ]) } } From 05a2ab1109dec3c5e8b81a7ccd40e99e4b173385 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 22:12:20 +0100 Subject: [PATCH 07/22] Remove unused `import struct Foundation.Date` --- Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift | 2 -- Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift | 2 -- 2 files changed, 4 deletions(-) diff --git a/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift index 6847e766..b187b704 100644 --- a/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift +++ b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift @@ -1,7 +1,5 @@ import NIOCore -import struct Foundation.Date - extension ByteBuffer { var isChecksumDelimiterAtReader: Bool { self.peekInteger(as: UInt8.self) == UInt8(ascii: "#") diff --git a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift index 3814f720..aec24de5 100644 --- a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift +++ b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift @@ -2,8 +2,6 @@ import GDBRemoteProtocol import NIOCore import WasmKit -import struct Foundation.Date - package final class WasmKitGDBHandler: ChannelInboundHandler { package typealias InboundIn = GDBPacket package typealias OutboundOut = GDBTargetResponse From a59f4ce8ae5a465f6b10f5cd160de02152972d68 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 23:10:18 +0100 Subject: [PATCH 08/22] Remove unused `else` clause from command decoder --- Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift index b187b704..a0091e0e 100644 --- a/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift +++ b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift @@ -117,8 +117,5 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { // Shift by checksum bytes context.fireChannelRead(wrapInboundOut(command)) return .continue - // } else { - // throw Error.unknownCommand(accummulated) - // } } } From 6dc5032389d8c11426184dda1941f700c51e9c88 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 23:12:27 +0100 Subject: [PATCH 09/22] Add `swift-nio` in `SWIFTCI_USE_LOCAL_DEPS` clause --- Package@swift-6.1.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index f70371f1..a63ef594 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -159,6 +159,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { package.dependencies += [ .package(path: "../swift-argument-parser"), .package(path: "../swift-system"), + .package(path: "../swift-nio"), ] } From e6e58b574bbfe1025cde25dea6ccd6188be785de Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 8 Oct 2025 23:16:27 +0100 Subject: [PATCH 10/22] Add FIXME note for `.supportedFeatures` response --- Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift index aec24de5..1e7d6cde 100644 --- a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift +++ b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift @@ -33,6 +33,7 @@ package final class WasmKitGDBHandler: ChannelInboundHandler { ]) case .supportedFeatures: + // FIXME: should return a different set of supported features instead of echoing. responseKind = .raw(command.arguments) case .vContSupportedActions: From 49de3170f09ba46804ada8455cef54c78002ea55 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 9 Oct 2025 13:06:24 +0100 Subject: [PATCH 11/22] Use `Logger`, `NIOAsyncChannel` --- .../GDBRemoteProtocol/GDBHostCommand.swift | 12 ++- .../GDBHostCommandDecoder.swift | 28 +++++-- Sources/GDBRemoteProtocol/GDBPacket.swift | 2 +- .../GDBRemoteProtocol/GDBTargetResponse.swift | 2 +- .../WasmKitGDBHandler/WasmKitDebugger.swift | 68 +++++++++++++++++ .../WasmKitGDBHandler/WasmKitGDBHandler.swift | 75 ------------------- Sources/wasmkit-gdb-tool/Entrypoint.swift | 72 ++++++++++++++++-- 7 files changed, 164 insertions(+), 95 deletions(-) create mode 100644 Sources/WasmKitGDBHandler/WasmKitDebugger.swift delete mode 100644 Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift diff --git a/Sources/GDBRemoteProtocol/GDBHostCommand.swift b/Sources/GDBRemoteProtocol/GDBHostCommand.swift index 754270fd..e83e2eea 100644 --- a/Sources/GDBRemoteProtocol/GDBHostCommand.swift +++ b/Sources/GDBRemoteProtocol/GDBHostCommand.swift @@ -2,11 +2,9 @@ /// * https://sourceware.org/gdb/current/onlinedocs/gdb.html/General-Query-Packets.html /// * https://lldb.llvm.org/resources/lldbgdbremote.html package struct GDBHostCommand: Equatable { - package enum Kind: Equatable { + package enum Kind: String, Equatable { // Currently listed in the order that LLDB sends them in. - case generalRegisters case startNoAckMode - case firstThreadInfo case supportedFeatures case isThreadSuffixSupported case listThreadsInStopReply @@ -16,6 +14,10 @@ package struct GDBHostCommand: Equatable { case enableErrorStrings case processInfo case currentThreadID + case firstThreadInfo + case subsequentThreadInfo + + case generalRegisters package init?(rawValue: String) { switch rawValue { @@ -41,6 +43,10 @@ package struct GDBHostCommand: Equatable { self = .processInfo case "qC": self = .currentThreadID + case "qfThreadInfo": + self = .firstThreadInfo + case "qsThreadInfo": + self = .subsequentThreadInfo default: return nil } diff --git a/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift index a0091e0e..46b696c0 100644 --- a/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift +++ b/Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift @@ -1,3 +1,4 @@ +import Logging import NIOCore extension ByteBuffer { @@ -20,10 +21,14 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { package typealias InboundOut = GDBPacket + private var accumulatedDelimiter: UInt8? + private var accummulatedKind = [UInt8]() private var accummulatedArguments = [UInt8]() - package init() {} + private let logger: Logger + + package init(logger: Logger) { self.logger = logger } private var accummulatedSum = 0 package var accummulatedChecksum: UInt8 { @@ -31,16 +36,24 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { } mutating package func decode(buffer: inout ByteBuffer) throws -> GDBPacket? { + guard let firstStartDelimiter = self.accumulatedDelimiter ?? buffer.readInteger(as: UInt8.self) else { + // Not enough data to parse. + return nil + } + guard let secondStartDelimiter = buffer.readInteger(as: UInt8.self) else { + // Preserve what we already read. + self.accumulatedDelimiter = firstStartDelimiter + + // Not enough data to parse. + return nil + } + // Command start delimiters. - let firstStartDelimiter = buffer.readInteger(as: UInt8.self) - let secondStartDelimiter = buffer.readInteger(as: UInt8.self) guard firstStartDelimiter == UInt8(ascii: "+") && secondStartDelimiter == UInt8(ascii: "$") else { - if let firstStartDelimiter, let secondStartDelimiter { - print("unexpected delimiter: \(Character(UnicodeScalar(firstStartDelimiter)))\(Character(UnicodeScalar(secondStartDelimiter)))") - } + logger.error("unexpected delimiter: \(Character(UnicodeScalar(firstStartDelimiter)))\(Character(UnicodeScalar(secondStartDelimiter)))") throw Error.expectedCommandStart } @@ -70,6 +83,7 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { } defer { + self.accumulatedDelimiter = nil self.accummulatedKind = [] self.accummulatedArguments = [] self.accummulatedSum = 0 @@ -108,7 +122,7 @@ package struct GDBHostCommandDecoder: ByteToMessageDecoder { context: ChannelHandlerContext, buffer: inout ByteBuffer ) throws -> DecodingState { - print(buffer.peekString(length: buffer.readableBytes)!) + logger.trace(.init(stringLiteral: buffer.peekString(length: buffer.readableBytes)!)) guard let command = try self.decode(buffer: &buffer) else { return .needMoreData diff --git a/Sources/GDBRemoteProtocol/GDBPacket.swift b/Sources/GDBRemoteProtocol/GDBPacket.swift index 1878efb4..10a24fa6 100644 --- a/Sources/GDBRemoteProtocol/GDBPacket.swift +++ b/Sources/GDBRemoteProtocol/GDBPacket.swift @@ -1,4 +1,4 @@ -package struct GDBPacket { +package struct GDBPacket: Sendable { package let payload: Payload package let checksum: UInt8 diff --git a/Sources/GDBRemoteProtocol/GDBTargetResponse.swift b/Sources/GDBRemoteProtocol/GDBTargetResponse.swift index b827ee55..5f55430d 100644 --- a/Sources/GDBRemoteProtocol/GDBTargetResponse.swift +++ b/Sources/GDBRemoteProtocol/GDBTargetResponse.swift @@ -11,7 +11,7 @@ package enum VContActions: String { package struct GDBTargetResponse { package enum Kind { case ok - case hostInfo(KeyValuePairs) + case keyValuePairs(KeyValuePairs) case vContSupportedActions([VContActions]) case raw(String) case empty diff --git a/Sources/WasmKitGDBHandler/WasmKitDebugger.swift b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift new file mode 100644 index 00000000..4dbaaf74 --- /dev/null +++ b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift @@ -0,0 +1,68 @@ +import GDBRemoteProtocol +import Logging +import SystemPackage +import WasmKit + +package actor WasmKitDebugger { + /// Whether `QStartNoAckMode` command was previously sent. + private var isNoAckModeActive = false + + private let module: Module + private let logger: Logger + + package init(logger: Logger, moduleFilePath: FilePath) throws { + self.logger = logger + self.module = try parseWasm(filePath: moduleFilePath) + } + + package func handle(command: GDBHostCommand) -> GDBTargetResponse { + let responseKind: GDBTargetResponse.Kind + logger.trace("handling GDB host command", metadata: ["GDBHostCommand": .string(command.kind.rawValue)]) + + responseKind = switch command.kind { + case .startNoAckMode, .isThreadSuffixSupported, .listThreadsInStopReply: + .ok + + case .hostInfo: + .keyValuePairs([ + "arch": "wasm32", + "ptrsize": "4", + "endian": "little", + "ostype": "wasip1", + "vendor": "WasmKit", + ]) + + case .supportedFeatures: + // FIXME: should return a different set of supported features instead of echoing. + .raw(command.arguments) + + case .vContSupportedActions: + .vContSupportedActions([.continue, .step, .stop]) + + case .isVAttachOrWaitSupported, .enableErrorStrings: + .empty + case .processInfo: + .raw("pid:1;parent-pid:1;arch:wasm32;endian:little;ptrsize:4;") + + case .currentThreadID: + .raw("QC1") + + case .firstThreadInfo: + .raw("m1") + + case .subsequentThreadInfo: + .raw("l") + + case .generalRegisters: + fatalError() + } + + defer { + if command.kind == .startNoAckMode { + self.isNoAckModeActive = true + } + } + return .init(kind: responseKind, isNoAckModeActive: self.isNoAckModeActive) + } + +} diff --git a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift deleted file mode 100644 index 1e7d6cde..00000000 --- a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift +++ /dev/null @@ -1,75 +0,0 @@ -import GDBRemoteProtocol -import NIOCore -import WasmKit - -package final class WasmKitGDBHandler: ChannelInboundHandler { - package typealias InboundIn = GDBPacket - package typealias OutboundOut = GDBTargetResponse - - /// Whether `QStartNoAckMode` command was previously sent. - private var isNoAckModeActive = false - - package init() {} - - package func channelRead( - context: ChannelHandlerContext, - data: NIOAny - ) { - let command = self.unwrapInboundIn(data).payload - let responseKind: GDBTargetResponse.Kind - print(command.kind) - - switch command.kind { - case .startNoAckMode, .isThreadSuffixSupported, .listThreadsInStopReply: - responseKind = .ok - - case .hostInfo: - responseKind = .hostInfo([ - "arch": "wasm32", - "ptrsize": "4", - "endian": "little", - "ostype": "wasip1", - "vendor": "WasmKit", - ]) - - case .supportedFeatures: - // FIXME: should return a different set of supported features instead of echoing. - responseKind = .raw(command.arguments) - - case .vContSupportedActions: - responseKind = .vContSupportedActions([.continue, .step, .stop]) - - case .isVAttachOrWaitSupported, .enableErrorStrings, .processInfo: - responseKind = .empty - - case .currentThreadID: - responseKind = .raw("QC 1") - - case .generalRegisters, .firstThreadInfo: - fatalError() - } - - context.writeAndFlush( - wrapOutboundOut(.init(kind: responseKind, isNoAckModeActive: self.isNoAckModeActive)), - promise: nil - ) - if command.kind == .startNoAckMode { - self.isNoAckModeActive = true - } - } - - package func channelReadComplete( - context: ChannelHandlerContext - ) { - context.flush() - } - - package func errorCaught( - context: ChannelHandlerContext, - error: Error - ) { - print(error) - - context.close(promise: nil) - } -} diff --git a/Sources/wasmkit-gdb-tool/Entrypoint.swift b/Sources/wasmkit-gdb-tool/Entrypoint.swift index 97f3bf62..5ca8adf5 100644 --- a/Sources/wasmkit-gdb-tool/Entrypoint.swift +++ b/Sources/wasmkit-gdb-tool/Entrypoint.swift @@ -14,16 +14,46 @@ import ArgumentParser import GDBRemoteProtocol +import Logging import NIOCore import NIOPosix +import SystemPackage import WasmKitGDBHandler +#if hasFeature(RetroactiveAttribute) + extension Logger.Level: @retroactive ExpressibleByArgument {} + extension FilePath: @retroactive ExpressibleByArgument { + public init?(argument: String) { + self.init(argument) + } + } +#else + extension Logger.Level: ExpressibleByArgument {} + extension FilePath: ExpressibleByArgument { + public init?(argument: String) { + self.init(argument) + } + } +#endif + @main -struct Entrypoint: ParsableCommand { +struct Entrypoint: AsyncParsableCommand { @Option(help: "TCP port that a debugger can connect to") var port = 8080 - func run() throws { + @Option(name: .shortAndLong) + var logLevel = Logger.Level.info + + @Argument + var wasmModulePath: FilePath + + func run() async throws { + let logger = { + var result = Logger(label: "org.swiftwasm.WasmKit") + result.logLevel = self.logLevel + return result + }() + let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) let bootstrap = ServerBootstrap(group: group) // Specify backlog and enable SO_REUSEADDR for the server itself @@ -38,9 +68,8 @@ struct Entrypoint: ParsableCommand { // make sure to instantiate your `ChannelHandlers` inside of // the closure as it will be invoked once per connection. try channel.pipeline.syncOperations.addHandlers([ - ByteToMessageHandler(GDBHostCommandDecoder()), + ByteToMessageHandler(GDBHostCommandDecoder(logger: logger)), MessageToByteHandler(GDBTargetResponseEncoder()), - WasmKitGDBHandler(), ]) } } @@ -49,11 +78,38 @@ struct Entrypoint: ParsableCommand { .childChannelOption(.socketOption(.so_reuseaddr), value: 1) .childChannelOption(.maxMessagesPerRead, value: 16) .childChannelOption(.recvAllocator, value: AdaptiveRecvByteBufferAllocator()) - let channel = try bootstrap.bind(host: "127.0.0.1", port: port).wait() + + let serverChannel = try await bootstrap.bind(host: "127.0.0.1", port: port) { childChannel in + childChannel.eventLoop.makeCompletedFuture { + try NIOAsyncChannel, GDBTargetResponse>( + wrappingChannelSynchronously: childChannel + ) + } + } /* the server will now be accepting connections */ - print("listening on port \(port)") + logger.info("listening on port \(port)") + + let debugger = try WasmKitDebugger(logger: logger, moduleFilePath: self.wasmModulePath) + + try await withThrowingDiscardingTaskGroup { group in + try await serverChannel.executeThenClose { serverChannelInbound in + for try await connectionChannel in serverChannelInbound { + group.addTask { + do { + try await connectionChannel.executeThenClose { connectionChannelInbound, connectionChannelOutbound in + for try await inboundData in connectionChannelInbound { + // Let's echo back all inbound data + try await connectionChannelOutbound.write(debugger.handle(command: inboundData.payload)) + } + } + } catch { + logger.error("Error in GDB remote protocol connection channel", metadata: ["error": "\(error)"]) + } + } + } + } + } - try channel.closeFuture.wait() // wait forever as we never close the Channel - try group.syncShutdownGracefully() + try await group.shutdownGracefully() } } From c9860769c2a81a597992f22f0aa208a27584d028 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 9 Oct 2025 13:06:42 +0100 Subject: [PATCH 12/22] Add required dependencies to `Package@swift-6.1.swift` --- Package@swift-6.1.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index a63ef594..2cafb158 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -8,7 +8,7 @@ let DarwinPlatforms: [Platform] = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] let package = Package( name: "WasmKit", - platforms: [.macOS(.v10_13), .iOS(.v12)], + platforms: [.macOS(.v14), .iOS(.v12)], products: [ .executable(name: "wasmkit-cli", targets: ["CLI"]), .library(name: "WasmKit", targets: ["WasmKit"]), @@ -122,6 +122,7 @@ let package = Package( .target(name: "GDBRemoteProtocol", dependencies: [ + .product(name: "Logging", package: "swift-log"), .product(name: "NIOCore", package: "swift-nio"), ] ), @@ -131,6 +132,7 @@ let package = Package( name: "WasmKitGDBHandler", dependencies: [ .product(name: "NIOCore", package: "swift-nio"), + .product(name: "SystemPackage", package: "swift-system"), "WasmKit", "GDBRemoteProtocol", ], @@ -140,8 +142,10 @@ let package = Package( name: "wasmkit-gdb-tool", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Logging", package: "swift-log"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "SystemPackage", package: "swift-system"), "GDBRemoteProtocol", "WasmKitGDBHandler", ] @@ -154,12 +158,14 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.1"), .package(url: "https://github.com/apple/swift-system", from: "1.5.0"), .package(url: "https://github.com/apple/swift-nio", from: "2.86.2"), + .package(url: "https://github.com/apple/swift-log", from: "1.6.4"), ] } else { package.dependencies += [ .package(path: "../swift-argument-parser"), .package(path: "../swift-system"), .package(path: "../swift-nio"), + .package(path: "../swift-log"), ] } From d8f93cdb046a6cf12faecc32b78260abd34153ca Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 9 Oct 2025 18:05:29 +0100 Subject: [PATCH 13/22] Add `iSeqToWasmMapping` to `InstanceEntity` --- .../GDBTargetResponseEncoder.swift | 2 +- Sources/WasmKit/Execution/Errors.swift | 20 ++++++++++++++++--- Sources/WasmKit/Execution/Execution.swift | 10 +++------- Sources/WasmKit/Execution/Function.swift | 12 +++++++---- Sources/WasmKit/Execution/Instances.swift | 6 +++++- .../WasmKit/Execution/StoreAllocator.swift | 7 +++++-- Sources/WasmKit/Module.swift | 17 ++++++++++++++-- Sources/WasmKit/Translator.swift | 8 ++------ .../RemoteProtocolTests.swift | 5 ++++- Tests/WasmKitTests/ExecutionTests.swift | 4 ++-- Utilities/Sources/WasmGen.swift | 5 +++++ 11 files changed, 67 insertions(+), 29 deletions(-) diff --git a/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift index 83e738b0..20ad3928 100644 --- a/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift +++ b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift @@ -19,7 +19,7 @@ package struct GDBTargetResponseEncoder: MessageToByteEncoder { case .ok: out.writeBytes("ok#da".utf8) - case .hostInfo(let info): + case .keyValuePairs(let info): out.writeBytes(info.map { (key, value) in "\(key):\(value);" }.joined().appendedChecksum) case .vContSupportedActions(let actions): diff --git a/Sources/WasmKit/Execution/Errors.swift b/Sources/WasmKit/Execution/Errors.swift index 07c1880b..fefba1ef 100644 --- a/Sources/WasmKit/Execution/Errors.swift +++ b/Sources/WasmKit/Execution/Errors.swift @@ -8,16 +8,30 @@ struct Backtrace: CustomStringConvertible, Sendable { struct Symbol { /// The name of the symbol. let name: String? + let debuggingAddress: DebuggingAddress + + /// Address of the symbol for debugging purposes. + enum DebuggingAddress: CustomStringConvertible, @unchecked Sendable { + case iseq(Pc) + case wasm(UInt64) + + var description: String { + switch self { + case .iseq(let pc): "iseq(\(Int(bitPattern: pc)))" + case .wasm(let wasmAddress): "wasm(\(wasmAddress))" + } + } + } } /// The symbols in the backtrace. - let symbols: [Symbol?] + let symbols: [Symbol] /// Textual description of the backtrace. var description: String { symbols.enumerated().map { (index, symbol) in - let name = symbol?.name ?? "unknown" - return " \(index): \(name)" + let name = symbol.name ?? "unknown" + return " \(symbol.debuggingAddress): \(name)" }.joined(separator: "\n") } } diff --git a/Sources/WasmKit/Execution/Execution.swift b/Sources/WasmKit/Execution/Execution.swift index b0d09dfa..56fe4bc1 100644 --- a/Sources/WasmKit/Execution/Execution.swift +++ b/Sources/WasmKit/Execution/Execution.swift @@ -61,18 +61,14 @@ struct Execution { static func captureBacktrace(sp: Sp, store: Store) -> Backtrace { var frames = FrameIterator(sp: sp) - var symbols: [Backtrace.Symbol?] = [] + var symbols: [Backtrace.Symbol] = [] while let frame = frames.next() { guard let function = frame.function else { - symbols.append(nil) + symbols.append(.init(name: nil, debuggingAddress: .iseq(frame.pc))) continue } let symbolName = store.nameRegistry.symbolicate(.wasm(function)) - symbols.append( - Backtrace.Symbol( - name: symbolName - ) - ) + symbols.append(.init(name: symbolName, debuggingAddress: .iseq(frame.pc))) } return Backtrace(symbols: symbols) } diff --git a/Sources/WasmKit/Execution/Function.swift b/Sources/WasmKit/Execution/Function.swift index 3786cf16..4af8c740 100644 --- a/Sources/WasmKit/Execution/Function.swift +++ b/Sources/WasmKit/Execution/Function.swift @@ -243,7 +243,7 @@ struct WasmFunctionEntity { switch code { case .uncompiled(let code): return try compile(store: store, code: code) - case .compiled(let iseq), .compiledAndPatchable(_, let iseq): + case .compiled(let iseq), .debuggable(_, let iseq): return iseq } } @@ -280,10 +280,14 @@ extension EntityHandle { case .uncompiled(let code): return try self.withValue { let iseq = try $0.compile(store: store, code: code) - $0.code = .compiled(iseq) + if $0.instance.isDebuggable { + $0.code = .debuggable(code, iseq) + } else { + $0.code = .compiled(iseq) + } return iseq } - case .compiled(let iseq), .compiledAndPatchable(_, let iseq): + case .compiled(let iseq), .debuggable(_, let iseq): return iseq } } @@ -316,7 +320,7 @@ struct InstructionSequence { enum CodeBody { case uncompiled(InternalUncompiledCode) case compiled(InstructionSequence) - case compiledAndPatchable(InternalUncompiledCode, InstructionSequence) + case debuggable(InternalUncompiledCode, InstructionSequence) } extension Reference { diff --git a/Sources/WasmKit/Execution/Instances.swift b/Sources/WasmKit/Execution/Instances.swift index 27a2452a..89ae3435 100644 --- a/Sources/WasmKit/Execution/Instances.swift +++ b/Sources/WasmKit/Execution/Instances.swift @@ -83,6 +83,8 @@ struct InstanceEntity /* : ~Copyable */ { var functionRefs: Set var features: WasmFeatureSet var dataCount: UInt32? + var isDebuggable: Bool + var iSeqToWasmMapping: [Pc: UInt64] static var empty: InstanceEntity { InstanceEntity( @@ -96,7 +98,9 @@ struct InstanceEntity /* : ~Copyable */ { exports: [:], functionRefs: [], features: [], - dataCount: nil + dataCount: nil, + isDebuggable: false, + iSeqToWasmMapping: [:] ) } diff --git a/Sources/WasmKit/Execution/StoreAllocator.swift b/Sources/WasmKit/Execution/StoreAllocator.swift index f060dc56..73109c51 100644 --- a/Sources/WasmKit/Execution/StoreAllocator.swift +++ b/Sources/WasmKit/Execution/StoreAllocator.swift @@ -251,7 +251,8 @@ extension StoreAllocator { module: Module, engine: Engine, resourceLimiter: any ResourceLimiter, - imports: Imports + imports: Imports, + isDebuggable: Bool ) throws -> InternalInstance { // Step 1 of module allocation algorithm, according to Wasm 2.0 spec. @@ -450,7 +451,9 @@ extension StoreAllocator { exports: exports, functionRefs: functionRefs, features: module.features, - dataCount: module.dataCount + dataCount: module.dataCount, + isDebuggable: isDebuggable, + iSeqToWasmMapping: [:] ) instancePointer.initialize(to: instanceEntity) instanceInitialized = true diff --git a/Sources/WasmKit/Module.swift b/Sources/WasmKit/Module.swift index 30070d1a..164264e9 100644 --- a/Sources/WasmKit/Module.swift +++ b/Sources/WasmKit/Module.swift @@ -138,9 +138,21 @@ public struct Module { Instance(handle: try self.instantiateHandle(store: store, imports: imports), store: store) } +#if WasmDebuggingSupport + /// Instantiate this module in the given imports. + /// + /// - Parameters: + /// - store: The ``Store`` to allocate the instance in. + /// - imports: The imports to use for instantiation. All imported entities + /// must be allocated in the given store. + public func instantiate(store: Store, imports: Imports = [:], isDebuggable: Bool) throws -> Instance { + Instance(handle: try self.instantiateHandle(store: store, imports: imports, isDebuggable: isDebuggable), store: store) + } +#endif + /// > Note: /// - private func instantiateHandle(store: Store, imports: Imports) throws -> InternalInstance { + private func instantiateHandle(store: Store, imports: Imports, isDebuggable: Bool = false) throws -> InternalInstance { try ModuleValidator(module: self).validate() // Steps 5-8. @@ -152,7 +164,8 @@ public struct Module { let instance = try store.allocator.allocate( module: self, engine: store.engine, resourceLimiter: store.resourceLimiter, - imports: imports + imports: imports, + isDebuggable: isDebuggable ) if let nameSection = customSections.first(where: { $0.name == "name" }) { diff --git a/Sources/WasmKit/Translator.swift b/Sources/WasmKit/Translator.swift index 6f3e1116..1f07a7c9 100644 --- a/Sources/WasmKit/Translator.swift +++ b/Sources/WasmKit/Translator.swift @@ -822,8 +822,6 @@ struct InstructionTranslator: InstructionVisitor { let functionIndex: FunctionIndex /// Whether a call to this function should be intercepted let isIntercepting: Bool - /// Whether Wasm debugging facilities are currently enabled. - let isDebugging: Bool var constantSlots: ConstSlots let validator: InstructionValidator @@ -836,8 +834,7 @@ struct InstructionTranslator: InstructionVisitor { locals: [WasmTypes.ValueType], functionIndex: FunctionIndex, codeSize: Int, - isIntercepting: Bool, - isDebugging: Bool = false + isIntercepting: Bool ) throws { self.allocator = allocator self.funcTypeInterner = funcTypeInterner @@ -854,7 +851,6 @@ struct InstructionTranslator: InstructionVisitor { self.locals = Locals(types: type.parameters + locals) self.functionIndex = functionIndex self.isIntercepting = isIntercepting - self.isDebugging = isDebugging self.constantSlots = ConstSlots(stackLayout: stackLayout) self.validator = InstructionValidator(context: module) @@ -2262,7 +2258,7 @@ struct InstructionTranslator: InstructionVisitor { } mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool { - guard self.isDebugging && opcode.count == 1 && opcode[0] == 0xFF else { + guard self.module.isDebuggable && opcode.count == 1 && opcode[0] == 0xFF else { return false } diff --git a/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift b/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift index 43a971d9..4a30f0e1 100644 --- a/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift +++ b/Tests/GDBRemoteProtocolTests/RemoteProtocolTests.swift @@ -1,4 +1,5 @@ import GDBRemoteProtocol +import Logging import NIOCore import Testing @@ -6,7 +7,9 @@ import Testing struct LLDBRemoteProtocolTests { @Test func decoding() throws { - var decoder = GDBHostCommandDecoder() + var logger = Logger(label: "com.swiftwasm.WasmKit.tests") + logger.logLevel = .critical + var decoder = GDBHostCommandDecoder(logger: logger) var buffer = ByteBuffer(string: "+$g#67") var packet = try decoder.decode(buffer: &buffer) diff --git a/Tests/WasmKitTests/ExecutionTests.swift b/Tests/WasmKitTests/ExecutionTests.swift index e5b61c87..42c8759d 100644 --- a/Tests/WasmKitTests/ExecutionTests.swift +++ b/Tests/WasmKitTests/ExecutionTests.swift @@ -111,7 +111,7 @@ """ ) { trap in #expect( - trap.backtrace?.symbols.compactMap(\.?.name) == [ + trap.backtrace?.symbols.compactMap(\.name) == [ "foo", "bar", "_start", @@ -138,7 +138,7 @@ """ ) { trap in #expect( - trap.backtrace?.symbols.compactMap(\.?.name) == [ + trap.backtrace?.symbols.compactMap(\.name) == [ "wasm function[1]", "bar", "_start", diff --git a/Utilities/Sources/WasmGen.swift b/Utilities/Sources/WasmGen.swift index af354614..541f409e 100644 --- a/Utilities/Sources/WasmGen.swift +++ b/Utilities/Sources/WasmGen.swift @@ -96,6 +96,8 @@ enum WasmGen { /// The visitor pattern is used while parsing WebAssembly expressions to allow for easy extensibility. /// See the expression parsing method ``Code/parseExpression(visitor:)`` public protocol InstructionVisitor { + /// Current offset in visitor's instruction stream. + var currentOffset: Int { get set } """ for instruction in instructions.categorized { @@ -534,6 +536,9 @@ enum WasmGen { /// Claim the next byte to be decoded @inlinable func claimNextByte() throws -> UInt8 + /// Current offset in decoder's instruction stream. + var currentOffset: Int { get } + /// Throw an error due to unknown opcode. func throwUnknown(_ opcode: [UInt8]) throws -> Never From bde70362a0229833b73cee16d6e093a81edb917f Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 9 Oct 2025 20:17:08 +0100 Subject: [PATCH 14/22] Fix formatting and iOS compatibility --- Package@swift-6.1.swift | 2 +- Sources/WasmKit/Execution/Errors.swift | 4 +- Sources/WasmKit/Module.swift | 22 +++---- .../WasmKitGDBHandler/WasmKitDebugger.swift | 59 ++++++++++--------- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index 2cafb158..33d7b2cc 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -8,7 +8,7 @@ let DarwinPlatforms: [Platform] = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] let package = Package( name: "WasmKit", - platforms: [.macOS(.v14), .iOS(.v12)], + platforms: [.macOS(.v14), .iOS(.v13)], products: [ .executable(name: "wasmkit-cli", targets: ["CLI"]), .library(name: "WasmKit", targets: ["WasmKit"]), diff --git a/Sources/WasmKit/Execution/Errors.swift b/Sources/WasmKit/Execution/Errors.swift index fefba1ef..6bfab3ac 100644 --- a/Sources/WasmKit/Execution/Errors.swift +++ b/Sources/WasmKit/Execution/Errors.swift @@ -17,8 +17,8 @@ struct Backtrace: CustomStringConvertible, Sendable { var description: String { switch self { - case .iseq(let pc): "iseq(\(Int(bitPattern: pc)))" - case .wasm(let wasmAddress): "wasm(\(wasmAddress))" + case .iseq(let pc): "iseq(\(Int(bitPattern: pc)))" + case .wasm(let wasmAddress): "wasm(\(wasmAddress))" } } } diff --git a/Sources/WasmKit/Module.swift b/Sources/WasmKit/Module.swift index 164264e9..c2a5b0b9 100644 --- a/Sources/WasmKit/Module.swift +++ b/Sources/WasmKit/Module.swift @@ -138,17 +138,17 @@ public struct Module { Instance(handle: try self.instantiateHandle(store: store, imports: imports), store: store) } -#if WasmDebuggingSupport - /// Instantiate this module in the given imports. - /// - /// - Parameters: - /// - store: The ``Store`` to allocate the instance in. - /// - imports: The imports to use for instantiation. All imported entities - /// must be allocated in the given store. - public func instantiate(store: Store, imports: Imports = [:], isDebuggable: Bool) throws -> Instance { - Instance(handle: try self.instantiateHandle(store: store, imports: imports, isDebuggable: isDebuggable), store: store) - } -#endif + #if WasmDebuggingSupport + /// Instantiate this module in the given imports. + /// + /// - Parameters: + /// - store: The ``Store`` to allocate the instance in. + /// - imports: The imports to use for instantiation. All imported entities + /// must be allocated in the given store. + public func instantiate(store: Store, imports: Imports = [:], isDebuggable: Bool) throws -> Instance { + Instance(handle: try self.instantiateHandle(store: store, imports: imports, isDebuggable: isDebuggable), store: store) + } + #endif /// > Note: /// diff --git a/Sources/WasmKitGDBHandler/WasmKitDebugger.swift b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift index 4dbaaf74..53043486 100644 --- a/Sources/WasmKitGDBHandler/WasmKitDebugger.swift +++ b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift @@ -19,43 +19,44 @@ package actor WasmKitDebugger { let responseKind: GDBTargetResponse.Kind logger.trace("handling GDB host command", metadata: ["GDBHostCommand": .string(command.kind.rawValue)]) - responseKind = switch command.kind { - case .startNoAckMode, .isThreadSuffixSupported, .listThreadsInStopReply: - .ok + responseKind = + switch command.kind { + case .startNoAckMode, .isThreadSuffixSupported, .listThreadsInStopReply: + .ok - case .hostInfo: - .keyValuePairs([ - "arch": "wasm32", - "ptrsize": "4", - "endian": "little", - "ostype": "wasip1", - "vendor": "WasmKit", - ]) + case .hostInfo: + .keyValuePairs([ + "arch": "wasm32", + "ptrsize": "4", + "endian": "little", + "ostype": "wasip1", + "vendor": "WasmKit", + ]) - case .supportedFeatures: - // FIXME: should return a different set of supported features instead of echoing. - .raw(command.arguments) + case .supportedFeatures: + // FIXME: should return a different set of supported features instead of echoing. + .raw(command.arguments) - case .vContSupportedActions: - .vContSupportedActions([.continue, .step, .stop]) + case .vContSupportedActions: + .vContSupportedActions([.continue, .step, .stop]) - case .isVAttachOrWaitSupported, .enableErrorStrings: - .empty - case .processInfo: - .raw("pid:1;parent-pid:1;arch:wasm32;endian:little;ptrsize:4;") + case .isVAttachOrWaitSupported, .enableErrorStrings: + .empty + case .processInfo: + .raw("pid:1;parent-pid:1;arch:wasm32;endian:little;ptrsize:4;") - case .currentThreadID: - .raw("QC1") + case .currentThreadID: + .raw("QC1") - case .firstThreadInfo: - .raw("m1") + case .firstThreadInfo: + .raw("m1") - case .subsequentThreadInfo: - .raw("l") + case .subsequentThreadInfo: + .raw("l") - case .generalRegisters: - fatalError() - } + case .generalRegisters: + fatalError() + } defer { if command.kind == .startNoAckMode { From d72462ffc121c243d243e85a817dacc1336f7d34 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 9 Oct 2025 20:25:24 +0100 Subject: [PATCH 15/22] Update Package@swift-6.1.swift --- Package@swift-6.1.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index 33d7b2cc..316ebdac 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -8,7 +8,7 @@ let DarwinPlatforms: [Platform] = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] let package = Package( name: "WasmKit", - platforms: [.macOS(.v14), .iOS(.v13)], + platforms: [.macOS(.v14), .iOS(.v17)], products: [ .executable(name: "wasmkit-cli", targets: ["CLI"]), .library(name: "WasmKit", targets: ["WasmKit"]), From cf1359b725197d0dccc98e74b0b6b15f7cc8ff28 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 9 Oct 2025 20:42:55 +0100 Subject: [PATCH 16/22] Update doc comment of `func instantiate` in `Module.swift` --- Sources/WasmKit/Module.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/WasmKit/Module.swift b/Sources/WasmKit/Module.swift index c2a5b0b9..499cb93b 100644 --- a/Sources/WasmKit/Module.swift +++ b/Sources/WasmKit/Module.swift @@ -145,6 +145,8 @@ public struct Module { /// - store: The ``Store`` to allocate the instance in. /// - imports: The imports to use for instantiation. All imported entities /// must be allocated in the given store. + /// - isDebuggable: Whether the module should support debugging actions + /// (breakpoints etc) after instantiation. public func instantiate(store: Store, imports: Imports = [:], isDebuggable: Bool) throws -> Instance { Instance(handle: try self.instantiateHandle(store: store, imports: imports, isDebuggable: isDebuggable), store: store) } From 1d3b7cfa1ad69317629f284fee6e92aa56edf260 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 10 Oct 2025 18:19:26 +0100 Subject: [PATCH 17/22] Add `targetStatus`/`?` host command --- Sources/GDBRemoteProtocol/GDBHostCommand.swift | 4 ++++ Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/GDBRemoteProtocol/GDBHostCommand.swift b/Sources/GDBRemoteProtocol/GDBHostCommand.swift index e83e2eea..13f94d9c 100644 --- a/Sources/GDBRemoteProtocol/GDBHostCommand.swift +++ b/Sources/GDBRemoteProtocol/GDBHostCommand.swift @@ -16,6 +16,7 @@ package struct GDBHostCommand: Equatable { case currentThreadID case firstThreadInfo case subsequentThreadInfo + case targetStatus case generalRegisters @@ -47,6 +48,9 @@ package struct GDBHostCommand: Equatable { self = .firstThreadInfo case "qsThreadInfo": self = .subsequentThreadInfo + case "?": + self = .targetStatus + default: return nil } diff --git a/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift index 20ad3928..b9344152 100644 --- a/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift +++ b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift @@ -23,7 +23,7 @@ package struct GDBTargetResponseEncoder: MessageToByteEncoder { out.writeBytes(info.map { (key, value) in "\(key):\(value);" }.joined().appendedChecksum) case .vContSupportedActions(let actions): - out.writeBytes("vCont;\(actions.map(\.rawValue).joined())".appendedChecksum) + out.writeBytes("vCont;\(actions.map { "\($0.rawValue);" }.joined())".appendedChecksum) case .raw(let str): out.writeBytes(str.appendedChecksum) From ee26e4bd5b03baded4ec2cd185487a323a4048e6 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 10 Oct 2025 18:20:15 +0100 Subject: [PATCH 18/22] Fix use of `OK#9a` instead of incorrect `ok#da` response --- Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift index b9344152..c70aa556 100644 --- a/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift +++ b/Sources/GDBRemoteProtocol/GDBTargetResponseEncoder.swift @@ -17,7 +17,7 @@ package struct GDBTargetResponseEncoder: MessageToByteEncoder { switch data.kind { case .ok: - out.writeBytes("ok#da".utf8) + out.writeBytes("OK#9a".utf8) case .keyValuePairs(let info): out.writeBytes(info.map { (key, value) in "\(key):\(value);" }.joined().appendedChecksum) From e2be1c73432db1ee71fd5d00d38d74fda46830cf Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 10 Oct 2025 18:25:06 +0100 Subject: [PATCH 19/22] Add `CSystemExtras` to `Package@swift-6.1` --- Package@swift-6.1.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index 316ebdac..8162f827 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -8,7 +8,7 @@ let DarwinPlatforms: [Platform] = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] let package = Package( name: "WasmKit", - platforms: [.macOS(.v14), .iOS(.v17)], + platforms: [.macOS(.v15), .iOS(.v17)], products: [ .executable(name: "wasmkit-cli", targets: ["CLI"]), .library(name: "WasmKit", targets: ["WasmKit"]), @@ -93,7 +93,8 @@ let package = Package( .target( name: "SystemExtras", dependencies: [ - .product(name: "SystemPackage", package: "swift-system") + .product(name: "SystemPackage", package: "swift-system"), + .target(name: "CSystemExtras", condition: .when(platforms: [.wasi])), ], exclude: ["CMakeLists.txt"], swiftSettings: [ @@ -101,6 +102,8 @@ let package = Package( ] ), + .target(name: "CSystemExtras"), + .executableTarget( name: "WITTool", dependencies: [ From cb73a4cce8d4ca445bc91b9a38d6717293d2040c Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 10 Oct 2025 18:26:16 +0100 Subject: [PATCH 20/22] Handle `targetStatus` in `WasmKitDebugger` --- Sources/WasmKitGDBHandler/WasmKitDebugger.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/WasmKitGDBHandler/WasmKitDebugger.swift b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift index 53043486..061ed255 100644 --- a/Sources/WasmKitGDBHandler/WasmKitDebugger.swift +++ b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift @@ -54,6 +54,13 @@ package actor WasmKitDebugger { case .subsequentThreadInfo: .raw("l") + case .targetStatus: + .keyValuePairs([ + "T05thread": "1", + "reason": "trace" + ] + ) + case .generalRegisters: fatalError() } From eb7c12844fabfb6e52ffb9b4c8be855983c6b3a1 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 10 Oct 2025 18:30:29 +0100 Subject: [PATCH 21/22] Build only `wasmkit-cli` product for WASI in `main.yml` --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f88eacb8..2ecbad41 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -266,5 +266,5 @@ jobs: - name: Install Swift SDK run: swift sdk install https://download.swift.org/swift-6.2-release/wasm/swift-6.2-RELEASE/swift-6.2-RELEASE_wasm.artifactbundle.tar.gz --checksum fe4e8648309fce86ea522e9e0d1dc48e82df6ba6e5743dbf0c53db8429fb5224 - name: Build with the Swift SDK - run: swift build --swift-sdk "$(swiftc -print-target-info | jq -r '.swiftCompilerTag')_wasm" + run: swift build --swift-sdk "$(swiftc -print-target-info | jq -r '.swiftCompilerTag')_wasm" --product wasmkit-cli From 05cd857c5b08df560ddfdf55effd4255e99e88c5 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 10 Oct 2025 18:32:15 +0100 Subject: [PATCH 22/22] Fix formatting in `WasmKitDebugger.swift` --- Sources/WasmKitGDBHandler/WasmKitDebugger.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/WasmKitGDBHandler/WasmKitDebugger.swift b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift index 061ed255..3905a4bb 100644 --- a/Sources/WasmKitGDBHandler/WasmKitDebugger.swift +++ b/Sources/WasmKitGDBHandler/WasmKitDebugger.swift @@ -56,10 +56,9 @@ package actor WasmKitDebugger { case .targetStatus: .keyValuePairs([ - "T05thread": "1", - "reason": "trace" - ] - ) + "T05thread": "1", + "reason": "trace", + ]) case .generalRegisters: fatalError()