Skip to content

Commit c5a5b35

Browse files
committed
Basic LLDB remote protocol scaffolding
1 parent d9b56a7 commit c5a5b35

File tree

9 files changed

+540
-0
lines changed

9 files changed

+540
-0
lines changed

[email protected]

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// swift-tools-version:6.1
2+
3+
import PackageDescription
4+
5+
import class Foundation.ProcessInfo
6+
7+
let DarwinPlatforms: [Platform] = [.macOS, .iOS, .watchOS, .tvOS, .visionOS]
8+
9+
let package = Package(
10+
name: "WasmKit",
11+
platforms: [.macOS(.v10_13), .iOS(.v12)],
12+
products: [
13+
.executable(name: "wasmkit-cli", targets: ["CLI"]),
14+
.library(name: "WasmKit", targets: ["WasmKit"]),
15+
.library(name: "WasmKitWASI", targets: ["WasmKitWASI"]),
16+
.library(name: "WASI", targets: ["WASI"]),
17+
.library(name: "WasmParser", targets: ["WasmParser"]),
18+
.library(name: "WAT", targets: ["WAT"]),
19+
.library(name: "WIT", targets: ["WIT"]),
20+
.library(name: "_CabiShims", targets: ["_CabiShims"]),
21+
],
22+
traits: [
23+
.default(enabledTraits: []),
24+
"WasmDebuggingSupport"
25+
],
26+
targets: [
27+
.executableTarget(
28+
name: "CLI",
29+
dependencies: [
30+
"WAT",
31+
"WasmKit",
32+
"WasmKitWASI",
33+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
34+
.product(name: "SystemPackage", package: "swift-system"),
35+
],
36+
exclude: ["CMakeLists.txt"]
37+
),
38+
39+
.target(
40+
name: "WasmKit",
41+
dependencies: [
42+
"_CWasmKit",
43+
"WasmParser",
44+
"WasmTypes",
45+
"SystemExtras",
46+
.product(name: "SystemPackage", package: "swift-system"),
47+
],
48+
exclude: ["CMakeLists.txt"]
49+
),
50+
.target(name: "_CWasmKit"),
51+
.target(
52+
name: "WasmKitFuzzing",
53+
dependencies: ["WasmKit"],
54+
path: "FuzzTesting/Sources/WasmKitFuzzing"
55+
),
56+
.testTarget(
57+
name: "WasmKitTests",
58+
dependencies: ["WasmKit", "WAT", "WasmKitFuzzing"],
59+
exclude: ["ExtraSuite"]
60+
),
61+
62+
.target(
63+
name: "WAT",
64+
dependencies: ["WasmParser"],
65+
exclude: ["CMakeLists.txt"]
66+
),
67+
.testTarget(name: "WATTests", dependencies: ["WAT"]),
68+
69+
.target(
70+
name: "WasmParser",
71+
dependencies: [
72+
"WasmTypes",
73+
.product(name: "SystemPackage", package: "swift-system"),
74+
],
75+
exclude: ["CMakeLists.txt"]
76+
),
77+
.testTarget(name: "WasmParserTests", dependencies: ["WasmParser"]),
78+
79+
.target(name: "WasmTypes", exclude: ["CMakeLists.txt"]),
80+
81+
.target(
82+
name: "WasmKitWASI",
83+
dependencies: ["WasmKit", "WASI"],
84+
exclude: ["CMakeLists.txt"]
85+
),
86+
.target(
87+
name: "WASI",
88+
dependencies: ["WasmTypes", "SystemExtras"],
89+
exclude: ["CMakeLists.txt"]
90+
),
91+
.testTarget(name: "WASITests", dependencies: ["WASI", "WasmKitWASI"]),
92+
93+
.target(
94+
name: "SystemExtras",
95+
dependencies: [
96+
.product(name: "SystemPackage", package: "swift-system")
97+
],
98+
exclude: ["CMakeLists.txt"],
99+
swiftSettings: [
100+
.define("SYSTEM_PACKAGE_DARWIN", .when(platforms: DarwinPlatforms))
101+
]
102+
),
103+
104+
.executableTarget(
105+
name: "WITTool",
106+
dependencies: [
107+
"WIT",
108+
"WITOverlayGenerator",
109+
"WITExtractor",
110+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
111+
]
112+
),
113+
114+
.target(name: "WIT"),
115+
.testTarget(name: "WITTests", dependencies: ["WIT"]),
116+
117+
.target(name: "WITOverlayGenerator", dependencies: ["WIT"]),
118+
.target(name: "_CabiShims"),
119+
120+
.target(name: "WITExtractor"),
121+
.testTarget(name: "WITExtractorTests", dependencies: ["WITExtractor", "WIT"]),
122+
123+
.target(name: "LLDBRemoteProtocol",
124+
dependencies: [
125+
.product(name: "NIOCore", package: "swift-nio"),
126+
]
127+
),
128+
.testTarget(name: "LLDBRemoteProtocolTests", dependencies: ["LLDBRemoteProtocol"]),
129+
130+
.target(
131+
name: "WasmKitLLDBHandler",
132+
dependencies: [
133+
.product(name: "NIOCore", package: "swift-nio"),
134+
"WasmKit",
135+
"LLDBRemoteProtocol",
136+
],
137+
),
138+
139+
.executableTarget(
140+
name: "wasmkit-lldb",
141+
dependencies: [
142+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
143+
.product(name: "NIOCore", package: "swift-nio"),
144+
.product(name: "NIOPosix", package: "swift-nio"),
145+
"LLDBRemoteProtocol",
146+
"WasmKitLLDBHandler",
147+
]
148+
),
149+
],
150+
)
151+
152+
if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
153+
package.dependencies += [
154+
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.1"),
155+
.package(url: "https://github.com/apple/swift-system", from: "1.5.0"),
156+
.package(url: "https://github.com/apple/swift-nio", from: "2.86.2"),
157+
]
158+
} else {
159+
package.dependencies += [
160+
.package(path: "../swift-argument-parser"),
161+
.package(path: "../swift-system"),
162+
]
163+
}
164+
165+
#if !os(Windows)
166+
// Add build tool plugins only for non-Windows platforms
167+
package.products.append(contentsOf: [
168+
.plugin(name: "WITOverlayPlugin", targets: ["WITOverlayPlugin"]),
169+
.plugin(name: "WITExtractorPlugin", targets: ["WITExtractorPlugin"]),
170+
])
171+
172+
package.targets.append(contentsOf: [
173+
.plugin(name: "WITOverlayPlugin", capability: .buildTool(), dependencies: ["WITTool"]),
174+
.plugin(name: "GenerateOverlayForTesting", capability: .buildTool(), dependencies: ["WITTool"]),
175+
.testTarget(
176+
name: "WITOverlayGeneratorTests",
177+
dependencies: ["WITOverlayGenerator", "WasmKit", "WasmKitWASI"],
178+
exclude: ["Fixtures", "Compiled", "Generated", "EmbeddedSupport"],
179+
plugins: [.plugin(name: "GenerateOverlayForTesting")]
180+
),
181+
.plugin(
182+
name: "WITExtractorPlugin",
183+
capability: .command(
184+
intent: .custom(verb: "extract-wit", description: "Extract WIT definition from Swift module"),
185+
permissions: []
186+
),
187+
dependencies: ["WITTool"]
188+
),
189+
.testTarget(
190+
name: "WITExtractorPluginTests",
191+
exclude: ["Fixtures"]
192+
),
193+
])
194+
#endif
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import NIOCore
2+
3+
import struct Foundation.Date
4+
5+
extension ByteBuffer {
6+
var isChecksumDelimiterAtReader: Bool {
7+
self.peekInteger(as: UInt8.self) == UInt8(ascii: "#")
8+
}
9+
10+
var isArgumentsDelimiterAtReader: Bool {
11+
self.peekInteger(as: UInt8.self) == UInt8(ascii: ":")
12+
}
13+
}
14+
15+
package struct CommandDecoder: ByteToMessageDecoder {
16+
enum Error: Swift.Error {
17+
case expectedCommandStart
18+
case unknownCommandKind(String)
19+
case expectedChecksum
20+
case checksumIncorrect
21+
}
22+
23+
package typealias InboundOut = Packet<HostCommand>
24+
25+
private var accummulatedKind = [UInt8]()
26+
private var accummulatedArguments = [UInt8]()
27+
28+
package init() {}
29+
30+
private var accummulatedSum = 0
31+
package var accummulatedChecksum: UInt8 {
32+
UInt8(self.accummulatedSum % 256)
33+
}
34+
35+
mutating package func decode(buffer: inout ByteBuffer) throws -> Packet<HostCommand>? {
36+
// Command start delimiters.
37+
guard
38+
buffer.readInteger(as: UInt8.self) == UInt8(ascii: "+")
39+
&& buffer.readInteger(as: UInt8.self) == UInt8(ascii: "$")
40+
else {
41+
throw Error.expectedCommandStart
42+
}
43+
44+
// Byte offset for command start.
45+
while !buffer.isChecksumDelimiterAtReader && !buffer.isArgumentsDelimiterAtReader,
46+
let char = buffer.readInteger(as: UInt8.self)
47+
{
48+
self.accummulatedSum += Int(char)
49+
self.accummulatedKind.append(char)
50+
}
51+
52+
if buffer.isArgumentsDelimiterAtReader,
53+
let argumentsDelimiter = buffer.readInteger(as: UInt8.self)
54+
{
55+
self.accummulatedSum += Int(argumentsDelimiter)
56+
57+
while !buffer.isChecksumDelimiterAtReader, let char = buffer.readInteger(as: UInt8.self) {
58+
self.accummulatedSum += Int(char)
59+
self.accummulatedArguments.append(char)
60+
}
61+
}
62+
63+
// Command checksum delimiter.
64+
if !buffer.isChecksumDelimiterAtReader {
65+
// If delimiter not available yet, return `nil` to indicate that the caller needs to top up the buffer.
66+
return nil
67+
}
68+
69+
defer {
70+
self.accummulatedKind = []
71+
self.accummulatedArguments = []
72+
self.accummulatedSum = 0
73+
}
74+
75+
let kindString = String(decoding: self.accummulatedKind, as: UTF8.self)
76+
77+
if let commandKind = HostCommand.Kind(rawValue: kindString) {
78+
buffer.moveReaderIndex(forwardBy: 1)
79+
80+
guard let checksumString = buffer.readString(length: 2),
81+
let first = checksumString.first?.hexDigitValue,
82+
let last = checksumString.last?.hexDigitValue
83+
else {
84+
throw Error.expectedChecksum
85+
}
86+
87+
guard (first * 16) + last == self.accummulatedChecksum else {
88+
// FIXME: better diagnostics
89+
throw Error.checksumIncorrect
90+
}
91+
92+
return .init(
93+
payload: .init(
94+
kind: commandKind,
95+
arguments: String(decoding: self.accummulatedArguments, as: UTF8.self)
96+
),
97+
checksum: accummulatedChecksum,
98+
)
99+
} else {
100+
throw Error.unknownCommandKind(kindString)
101+
}
102+
}
103+
104+
mutating package func decode(
105+
context: ChannelHandlerContext,
106+
buffer: inout ByteBuffer
107+
) throws -> DecodingState {
108+
print(buffer.peekString(length: buffer.readableBytes)!)
109+
110+
guard let command = try self.decode(buffer: &buffer) else {
111+
return .needMoreData
112+
}
113+
114+
// Shift by checksum bytes
115+
context.fireChannelRead(wrapInboundOut(command))
116+
return .continue
117+
// } else {
118+
// throw Error.unknownCommand(accummulated)
119+
// }
120+
}
121+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/// See https://lldb.llvm.org/resources/lldbgdbremote.html for more details.
2+
package struct HostCommand: Equatable {
3+
package enum Kind: Equatable {
4+
// Currently listed in the order that LLDB sends them in.
5+
case generalRegisters
6+
case startNoAckMode
7+
case firstThreadInfo
8+
case supportedFeatures
9+
case isThreadSuffixSupported
10+
case listThreadsInStopReply
11+
case hostInfo
12+
13+
package init?(rawValue: String) {
14+
switch rawValue {
15+
case "g":
16+
self = .generalRegisters
17+
case "QStartNoAckMode":
18+
self = .startNoAckMode
19+
case "qSupported":
20+
self = .supportedFeatures
21+
case "QThreadSuffixSupported":
22+
self = .isThreadSuffixSupported
23+
case "QListThreadsInStopReply":
24+
self = .listThreadsInStopReply
25+
case "qHostInfo":
26+
self = .hostInfo
27+
default:
28+
return nil
29+
}
30+
}
31+
}
32+
33+
package let kind: Kind
34+
35+
package let arguments: String
36+
37+
package init(kind: Kind, arguments: String) {
38+
self.kind = kind
39+
self.arguments = arguments
40+
}
41+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package struct Packet<Payload> {
2+
package let payload: Payload
3+
4+
package let checksum: UInt8
5+
6+
package init(payload: Payload, checksum: UInt8) {
7+
self.payload = payload
8+
self.checksum = checksum
9+
}
10+
}
11+
12+
extension Packet: Equatable where Payload: Equatable {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Foundation
2+
import NIOCore
3+
4+
package struct ResponseEncoder: MessageToByteEncoder {
5+
package init() {}
6+
package func encode(data: TargetResponse, out: inout ByteBuffer) throws {
7+
if !data.isNoAckModeActive {
8+
out.writeInteger(UInt8(ascii: "+"))
9+
}
10+
out.writeInteger(UInt8(ascii: "$"))
11+
12+
switch data.kind {
13+
case .ok:
14+
out.writeBytes("ok#da".utf8)
15+
16+
case .raw(let str):
17+
out.writeBytes(
18+
"\(str)#\(String(format:"%02X", str.utf8.reduce(0, { $0 + Int($1) }) % 256))".utf8)
19+
20+
case .empty:
21+
fatalError("unhandled")
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)