Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions [email protected]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added separate Package.swift manifest to hide debugger support behind a trait for now, and traits aren't supported in 6.0.

Original file line number Diff line number Diff line change
@@ -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: "GDBRemoteProtocol",
dependencies: [
.product(name: "NIOCore", package: "swift-nio"),
]
),
.testTarget(name: "GDBRemoteProtocolTests", dependencies: ["GDBRemoteProtocol"]),
Comment on lines 123 to 129
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two modules are meant to be completely independent of WasmKit and in the future could live in a separate package to be adopted in any suitable environment that needs debugging facilities, not just Wasm.


.target(
name: "WasmKitGDBHandler",
dependencies: [
.product(name: "NIOCore", package: "swift-nio"),
"WasmKit",
"GDBRemoteProtocol",
],
),

.executableTarget(
name: "wasmkit-gdb-tool",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOPosix", package: "swift-nio"),
"GDBRemoteProtocol",
"WasmKitGDBHandler",
]
),
],
)

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
58 changes: 58 additions & 0 deletions Sources/GDBRemoteProtocol/GDBHostCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/// 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
case startNoAckMode
case firstThreadInfo
case supportedFeatures
case isThreadSuffixSupported
case listThreadsInStopReply
case hostInfo
case vContSupportedActions
case isVAttachOrWaitSupported
case enableErrorStrings
case processInfo
case currentThreadID

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
case "vCont?":
self = .vContSupportedActions
case "qVAttachOrWaitSupported":
self = .isVAttachOrWaitSupported
case "QEnableErrorStrings":
self = .enableErrorStrings
case "qProcessInfo":
self = .processInfo
case "qC":
self = .currentThreadID
default:
return nil
}
}
}

package let kind: Kind

package let arguments: String

package init(kind: Kind, arguments: String) {
self.kind = kind
self.arguments = arguments
}
}
121 changes: 121 additions & 0 deletions Sources/GDBRemoteProtocol/GDBHostCommandDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import NIOCore

extension ByteBuffer {
var isChecksumDelimiterAtReader: Bool {
self.peekInteger(as: UInt8.self) == UInt8(ascii: "#")
}

var isArgumentsDelimiterAtReader: Bool {
self.peekInteger(as: UInt8.self) == UInt8(ascii: ":")
}
}

package struct GDBHostCommandDecoder: ByteToMessageDecoder {
enum Error: Swift.Error {
case expectedCommandStart
case unknownCommandKind(String)
case expectedChecksum
case checksumIncorrect
}

package typealias InboundOut = GDBPacket<GDBHostCommand>

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 -> GDBPacket<GDBHostCommand>? {
// 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)))")
}
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 = GDBHostCommand.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
}
}
12 changes: 12 additions & 0 deletions Sources/GDBRemoteProtocol/GDBPacket.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package struct GDBPacket<Payload> {
package let payload: Payload

package let checksum: UInt8

package init(payload: Payload, checksum: UInt8) {
self.payload = payload
self.checksum = checksum
}
}

extension GDBPacket: Equatable where Payload: Equatable {}
Loading
Loading