Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
eb5f169
Basic LLDB remote protocol scaffolding
MaxDesiatov Oct 8, 2025
0baf7a5
Implement more packet handling, update naming
MaxDesiatov Oct 8, 2025
85a695a
Provide original license notice in `wasmkit-gdb-tool/Entrypoint.swift`
MaxDesiatov Oct 8, 2025
eba97a0
Apply formatter
MaxDesiatov Oct 8, 2025
6546754
Make naming of types more specific to avoid future collisions
MaxDesiatov Oct 8, 2025
7b2c6f6
More cleanups for type naming
MaxDesiatov Oct 8, 2025
4a1c998
Remove unused `import struct Foundation.Date`
MaxDesiatov Oct 8, 2025
f8c08dd
Remove unused `else` clause from command decoder
MaxDesiatov Oct 8, 2025
77bdb04
Add `swift-nio` in `SWIFTCI_USE_LOCAL_DEPS` clause
MaxDesiatov Oct 8, 2025
256c70c
Add FIXME note for `.supportedFeatures` response
MaxDesiatov Oct 8, 2025
111deb8
Use `Logger`, `NIOAsyncChannel`
MaxDesiatov Oct 9, 2025
e392d30
Add required dependencies to `[email protected]`
MaxDesiatov Oct 9, 2025
a7c62b0
Add `iSeqToWasmMapping` to `InstanceEntity`
MaxDesiatov Oct 9, 2025
21e1b44
Fix formatting and iOS compatibility
MaxDesiatov Oct 9, 2025
329e4f5
Update [email protected]
MaxDesiatov Oct 9, 2025
a3d20af
Update doc comment of `func instantiate` in `Module.swift`
MaxDesiatov Oct 9, 2025
8d15b99
Add `targetStatus`/`?` host command
MaxDesiatov Oct 10, 2025
d9b60b1
Fix use of `OK#9a` instead of incorrect `ok#da` response
MaxDesiatov Oct 10, 2025
05583d0
Add `CSystemExtras` to `[email protected]`
MaxDesiatov Oct 10, 2025
39242d6
Handle `targetStatus` in `WasmKitDebugger`
MaxDesiatov Oct 10, 2025
cbff974
Build only `wasmkit-cli` product for WASI in `main.yml`
MaxDesiatov Oct 10, 2025
982fa91
Fix formatting in `WasmKitDebugger.swift`
MaxDesiatov Oct 10, 2025
3a74a91
Fix `QStartNoAckMode` handling
MaxDesiatov Oct 14, 2025
bc539e8
Add `qRegisterInfo` parsing to `GDBHostCommand.init`
MaxDesiatov Oct 14, 2025
fb4e2a2
Use `KeyValuePairs` response for `.registerInfo`
MaxDesiatov Oct 14, 2025
042a2c4
Fix formatting and tests build error
MaxDesiatov Oct 14, 2025
197abd3
Exclude `wasmkit-gdb-tool` on Windows, address PR feedback
MaxDesiatov Oct 14, 2025
a2899f7
Remove retroactive `FilePath` conformance
MaxDesiatov Oct 14, 2025
0b42724
Handle more host commands, up to `qWasmCallStack`
MaxDesiatov Oct 14, 2025
1783176
Fix formatting
MaxDesiatov Oct 14, 2025
c0a9cf6
Address PR feedback, add comment for `DebuggingAddress` sendability
MaxDesiatov Oct 16, 2025
806e7ec
Add `DebuggerExecution` wrapper type
MaxDesiatov Oct 16, 2025
26ea4e5
Implement `.readMemory` command handling
MaxDesiatov Oct 16, 2025
8343902
Fix formatting
MaxDesiatov Oct 16, 2025
ff2fa0d
Pass `-package-name` in CMake to enable use of `package`
MaxDesiatov Oct 16, 2025
3ed9013
Disable `WasmKitGDBHandler` on Windows
MaxDesiatov Oct 16, 2025
3ea5aab
Fix formatting
MaxDesiatov Oct 16, 2025
3fc8fdc
Try nightly `main` for `build-cmake` job
MaxDesiatov Oct 17, 2025
ea83134
Revert "Try nightly `main` for `build-cmake` job"
MaxDesiatov Oct 17, 2025
2893799
Rename `DebuggerExecution` to `Debugger`, hide behind a trait
MaxDesiatov Oct 17, 2025
7bdb16c
Enable `WasmDebuggingSupport` on CI where possible
MaxDesiatov Oct 17, 2025
4ea0fb0
Fix formatting
MaxDesiatov Oct 17, 2025
ee3c7bf
Revert "Pass `-package-name` in CMake to enable use of `package`"
MaxDesiatov Oct 17, 2025
a6d0749
Disable traits in pre-6.1, guard use of `package` on trait
MaxDesiatov Oct 17, 2025
4909d3b
Remove use of `package` from CMake-built code
MaxDesiatov Oct 20, 2025
cce2105
Fix non-CMake build breakage
MaxDesiatov Oct 20, 2025
811589f
Fix formatting
MaxDesiatov Oct 20, 2025
bc2230d
Enable iseq -> wasm instruction mapping
MaxDesiatov Oct 20, 2025
db1ec2d
Fix formatting
MaxDesiatov Oct 20, 2025
62592f1
Add missing `mutating` to `Debugger.swift`
MaxDesiatov Oct 20, 2025
bbfcb45
Make `init` throw effect untyped in `Debugger.swift`
MaxDesiatov Oct 20, 2025
c6213eb
Make throwing method effects untyped in `Debugger.swift`
MaxDesiatov Oct 20, 2025
a993b50
Make `debugger: Debugger` property mutable
MaxDesiatov Oct 20, 2025
2d065e0
Clean up `WasmGen` code
MaxDesiatov Oct 21, 2025
fd0d1ee
Add reverse wasm->iseq mapping, breakpoints toggling PoC
MaxDesiatov Oct 21, 2025
a35b73e
Remove unused `functionAddresses` property from `Debugger`
MaxDesiatov Oct 21, 2025
16ac0cf
Remove changes unrelated to the protocol
MaxDesiatov Oct 22, 2025
410c470
Clarify licensing in `README.md`
MaxDesiatov Oct 22, 2025
977b939
Add license files for separate modules
MaxDesiatov Oct 22, 2025
2acd4f3
Basic doc comments for `GDBHostCommand`
MaxDesiatov Oct 22, 2025
3b2e30f
Add doc comments for `GDBHostCommandDecoder`
MaxDesiatov Oct 22, 2025
fc740e5
Add remaining doc comments
MaxDesiatov Oct 22, 2025
a297f2d
Fix formatting
MaxDesiatov Oct 22, 2025
ff2e940
Fix var naming typo
MaxDesiatov Oct 22, 2025
5f7fbb5
Clarify licensing in `README.md`
MaxDesiatov Oct 22, 2025
d100d1e
Add license files for separate modules
MaxDesiatov Oct 22, 2025
2407284
Basic doc comments for `GDBHostCommand`
MaxDesiatov Oct 22, 2025
f628219
Add doc comments for `GDBHostCommandDecoder`
MaxDesiatov Oct 22, 2025
ab295db
Add remaining doc comments
MaxDesiatov Oct 22, 2025
e3505bc
Fix formatting
MaxDesiatov Oct 22, 2025
c4f9b3f
Fix var naming typo
MaxDesiatov Oct 22, 2025
f6bab20
Remove unused `throws` in `GDBTargetResponseEncoder.swift`
MaxDesiatov Oct 22, 2025
f33dc82
Remove unused `throws` in `GDBTargetResponseEncoder.swift`
MaxDesiatov Oct 22, 2025
a1d5183
Merge branch 'maxd/lldb-remote-protocol' of github.com:swiftwasm/Wasm…
MaxDesiatov Oct 22, 2025
dccc6af
Revert "Remove changes unrelated to the protocol"
MaxDesiatov Oct 22, 2025
a25399d
Restore base branch state
MaxDesiatov Oct 22, 2025
6bb5df8
Merge branch 'main' into maxd/debugging-breakpoints
MaxDesiatov Oct 22, 2025
ae91e24
Add `DebuggerTests` test suite
MaxDesiatov Oct 22, 2025
169d406
Merge remote-tracking branch 'origin/maxd/debugging-breakpoints' into…
MaxDesiatov Oct 22, 2025
d5d1600
Fix build error, add debug logging
MaxDesiatov Oct 23, 2025
9d79c83
Fix `DebuggerTests`
MaxDesiatov Oct 23, 2025
f64bf23
Remove `print` debugging
MaxDesiatov Oct 23, 2025
8bf6849
Allocate and modify `Pc` directly as `Debugger` property
MaxDesiatov Oct 23, 2025
ea04cc3
Rewind Pc back by 1 word after catching `Breakpoint`
MaxDesiatov Oct 23, 2025
c4d3ac9
Revert "Allocate and modify `Pc` directly as `Debugger` property"
MaxDesiatov Oct 24, 2025
6246506
Fully implement `qWasmCallStack`
MaxDesiatov Oct 24, 2025
27dbbc7
Pass `Sp` in `Breakpoint`, add `executeWasm` instance method
MaxDesiatov Oct 24, 2025
036fcb8
Fix license header diff
MaxDesiatov Oct 24, 2025
a57ec75
Fix trailing comma compatibility with Swift 6.0
MaxDesiatov Oct 24, 2025
935f877
Fix breakpoint resumption test expectation
MaxDesiatov Oct 24, 2025
a7fac8e
Mark `Sp.currentFunction` as `internal`
MaxDesiatov Oct 24, 2025
4e8b062
Mark `Debugger.Error` as `@unchecked Sendable`
MaxDesiatov Oct 24, 2025
969b992
Handle `qThreadStopInfo`, run up to breakpoint in handler
MaxDesiatov Oct 24, 2025
2486ec8
Add missing `logger` argument in `Entrypoint.swift`
MaxDesiatov Oct 24, 2025
d594b49
Remove CLI harness for smaller diff
MaxDesiatov Oct 27, 2025
76b2317
Revert "Remove CLI harness for smaller diff"
MaxDesiatov Oct 27, 2025
beff9a7
Remove only `wasmkit-gdb-tool`
MaxDesiatov Oct 27, 2025
eb9001b
Make some functions `fileprivate`
MaxDesiatov Oct 27, 2025
687b223
Add doc comments for the debugger type
MaxDesiatov Oct 27, 2025
d25166b
Merge branch 'main' into maxd/debugging-breakpoints
MaxDesiatov Oct 27, 2025
8d78ab6
Fix formatting
MaxDesiatov Oct 27, 2025
c39f9e2
Refine doc comment Sources/WasmKit/Execution/Debugger.swift
MaxDesiatov Oct 27, 2025
55bfb6b
Refine doc comment in `Debugger.swift`
MaxDesiatov Oct 27, 2025
e7a5287
Refine doc comments wording
MaxDesiatov Oct 27, 2025
a2c701e
Fix build error
MaxDesiatov Oct 27, 2025
1b271d5
Fix formatting
MaxDesiatov Oct 27, 2025
dec6850
Apply formatting to `[email protected]`
MaxDesiatov Oct 27, 2025
b3953a5
Disable nightly toolchain in CI configuration
MaxDesiatov Oct 27, 2025
e81f68e
Pin `build-android` job to v2.6.4 of `swift-android-action`
MaxDesiatov Oct 27, 2025
ab8f080
Refine doc comments wording
MaxDesiatov Oct 27, 2025
0eda671
Revert "Pin `build-android` job to v2.6.4 of `swift-android-action`"
MaxDesiatov Oct 27, 2025
7f0e212
Merge branch 'main' of github.com:swiftwasm/WasmKit into maxd/debuggi…
MaxDesiatov Oct 28, 2025
da87018
Merge branch 'main' into maxd/debugging-breakpoints
MaxDesiatov Oct 28, 2025
ce6124c
Move mapping dictionaries to `DebuggerInstructionMapping`
MaxDesiatov Oct 28, 2025
dd36c4c
Fix formatting and CMake
MaxDesiatov Oct 28, 2025
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
86 changes: 86 additions & 0 deletions Sources/WasmKit/DebuggerInstructionMapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@

/// Two-way mapping between Wasm and internal iseq bytecode instructions. The implementation of the mapping
/// is private and is empty when `WasmDebuggingSupport` package trait is disabled.
struct DebuggerInstructionMapping {
Copy link
Member Author

@MaxDesiatov MaxDesiatov Oct 28, 2025

Choose a reason for hiding this comment

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

Nice side-effect of moving to a separate type is that it's easier to switch to an empty implementation of the mapping based on package traits.

@kateinoigakukun I hope this addresses your review feedback, let me know if anything else is needed to move this forward.

#if WasmDebuggingSupport

/// Mapping from iseq Pc to instruction addresses in the original binary.
/// Used for handling current call stack requests issued by a ``Debugger`` instance.
private var iseqToWasm = [Pc: Int]()

/// Mapping from Wasm instruction addresses in the original binary to iseq instruction addresses.
/// Used for handling breakpoint requests issued by a ``Debugger`` instance.
private var wasmToIseq = [Int: Pc]()

/// Wasm addresses sorted in ascending order for binary search when of the next closest mapped
/// instruction, when no key is found in `wasmToIseqMapping`.
private var wasmMappings = [Int]()

mutating func add(wasm: Int, iseq: Pc) {
// Don't override the existing mapping, only store a new pair if there's no mapping for a given key.
if self.iseqToWasm[iseq] == nil {
self.iseqToWasm[iseq] = wasm
}
if self.wasmToIseq[wasm] == nil {
self.wasmToIseq[wasm] = iseq
}
self.wasmMappings.append(wasm)
}

/// Computes an address of WasmKit's iseq bytecode instruction that matches a given Wasm instruction address.
/// - Parameter address: the Wasm instruction to find a mapping for.
/// - Returns: A tuple with an address of found iseq instruction and the original Wasm instruction or next
/// closest match if no direct match was found.
func findIseq(forWasmAddress address: Int) -> (iseq: Pc, wasm: Int)? {
// Look in the main mapping
if let iseq = self.wasmToIseq[address] {
return (iseq, address)
}

// If nothing found, find the closest Wasm address using binary search
guard let nextAddress = self.wasmMappings.binarySearch(nextClosestTo: address),
// Look in the main mapping again with the next closest address if binary search produced anything
let iseq = self.wasmToIseq[nextAddress]
else {
return nil
}

return (iseq, nextAddress)
}

func findWasm(forIseqAddress pc: Pc) -> Int? {
self.iseqToWasm[pc]
}
#endif
}


#if WasmDebuggingSupport
extension [Int] {
/// Uses binary search to find an element in `self` that's next closest to a given value.
/// - Parameter value: the array element to search for or to use as a baseline when searching.
/// - Returns: array element `result`, where `result - value` is the smallest possible, while
/// `result > value` also holds.
package func binarySearch(nextClosestTo value: Int) -> Int? {
switch self.count {
case 0:
return nil
default:
var slice = self[0..<self.count]
while slice.count > 1 {
let middle = (slice.endIndex - slice.startIndex) / 2
if slice[middle] < value {
// Not found anything in the lower half, assigning higher half to `slice`.
slice = slice[(middle + 1)..<slice.endIndex]
} else {
// Not found anything in the higher half, assigning lower half to `slice`.
slice = slice[slice.startIndex..<middle]
}
}

return self[slice.startIndex]
}
}
}

#endif
63 changes: 8 additions & 55 deletions Sources/WasmKit/Execution/Debugger.swift
Original file line number Diff line number Diff line change
@@ -1,55 +1,4 @@
#if WasmDebuggingSupport

extension [Int] {
/// Uses binary search to find an element in `self` that's next closest to a given value.
/// - Parameter value: the array element to search for or to use as a baseline when searching.
/// - Returns: array element `result`, where `result - value` is the smallest possible, while
/// `result > value` also holds.
package func binarySearch(nextClosestTo value: Int) -> Int? {
switch self.count {
case 0:
return nil
default:
var slice = self[0..<self.count]
while slice.count > 1 {
let middle = (slice.endIndex - slice.startIndex) / 2
if slice[middle] < value {
// Not found anything in the lower half, assigning higher half to `slice`.
slice = slice[(middle + 1)..<slice.endIndex]
} else {
// Not found anything in the higher half, assigning lower half to `slice`.
slice = slice[slice.startIndex..<middle]
}
}

return self[slice.startIndex]
}
}
}

extension Instance {
/// Computes an address of WasmKit's iseq bytecode instruction that matches a given Wasm instruction address.
/// - Parameter address: the Wasm instruction to find a mapping for.
/// - Returns: A tuple with an address of found iseq instruction and the original Wasm instruction or next
/// closest match if no direct match was found.
fileprivate func findIseq(forWasmAddress address: Int) throws(Debugger.Error) -> (iseq: Pc, wasm: Int) {
// Look in the main mapping
if let iseq = handle.wasmToIseqMapping[address] {
return (iseq, address)
}

// If nothing found, find the closest Wasm address using binary search
guard let nextAddress = handle.wasmMappings.binarySearch(nextClosestTo: address),
// Look in the main mapping again with the next closest address if binary search produced anything
let iseq = handle.wasmToIseqMapping[nextAddress]
else {
throw Debugger.Error.noInstructionMappingAvailable(address)
}

return (iseq, nextAddress)
}
}

/// User-facing debugger state driven by a debugger host. This implementation has no knowledge of the exact
/// debugger protocol, which allows any protocol implementation or direct API users to be layered on top if needed.
package struct Debugger: ~Copyable {
Expand Down Expand Up @@ -140,7 +89,9 @@
return
}

let (iseq, wasm) = try self.instance.findIseq(forWasmAddress: address)
guard let (iseq, wasm) = try self.instance.handle.instructionMapping.findIseq(forWasmAddress: address) else {
throw Error.noInstructionMappingAvailable(address)
}

self.breakpoints[wasm] = iseq.pointee
iseq.pointee = Instruction.breakpoint.headSlot(threadingModel: self.threadingModel)
Expand All @@ -156,7 +107,9 @@
return
}

let (iseq, wasm) = try self.instance.findIseq(forWasmAddress: address)
guard let (iseq, wasm) = try self.instance.handle.instructionMapping.findIseq(forWasmAddress: address) else {
throw Error.noInstructionMappingAvailable(address)
}

self.breakpoints[wasm] = nil
iseq.pointee = oldCodeSlot
Expand Down Expand Up @@ -216,7 +169,7 @@
}
} catch let breakpoint as Execution.Breakpoint {
let pc = breakpoint.pc
guard let wasmPc = self.instance.handle.iseqToWasmMapping[pc] else {
guard let wasmPc = self.instance.handle.instructionMapping.findWasm(forIseqAddress: pc) else {
throw Error.noReverseInstructionMappingAvailable(pc)
}

Expand All @@ -232,7 +185,7 @@
}

var result = Execution.captureBacktrace(sp: currentBreakpoint.iseq.sp, store: self.store).symbols.compactMap {
return self.instance.handle.iseqToWasmMapping[$0.address]
return self.instance.handle.instructionMapping.findWasm(forIseqAddress: $0.address)
}
result.append(currentBreakpoint.wasmPc)

Expand Down
15 changes: 2 additions & 13 deletions Sources/WasmKit/Execution/Instances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,7 @@ struct InstanceEntity /* : ~Copyable */ {
var dataCount: UInt32?
var isDebuggable: Bool

/// Mapping from iseq Pc to instruction addresses in the original binary.
/// Used for handling current call stack requests issued by a ``Debugger`` instance.
var iseqToWasmMapping = [Pc: Int]()

/// Mapping from Wasm instruction addresses in the original binary to iseq instruction addresses.
/// Used for handling breakpoint requests issued by a ``Debugger`` instance.
var wasmToIseqMapping = [Int: Pc]()

/// Wasm addresses sorted in ascending order for binary search when of the next closest mapped
/// instruction, when no key is found in `wasmToIseqMapping`.
var wasmMappings = [Int]()
var instructionMapping: DebuggerInstructionMapping

static var empty: InstanceEntity {
InstanceEntity(
Expand All @@ -111,8 +101,7 @@ struct InstanceEntity /* : ~Copyable */ {
features: [],
dataCount: nil,
isDebuggable: false,
iseqToWasmMapping: [:],
wasmToIseqMapping: [:]
instructionMapping: .init()
)
}

Expand Down
3 changes: 1 addition & 2 deletions Sources/WasmKit/Execution/StoreAllocator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,7 @@ extension StoreAllocator {
features: module.features,
dataCount: module.dataCount,
isDebuggable: isDebuggable,
iseqToWasmMapping: [:],
wasmToIseqMapping: [:]
instructionMapping: .init()
)
instancePointer.initialize(to: instanceEntity)
instanceInitialized = true
Expand Down
23 changes: 4 additions & 19 deletions Sources/WasmKit/Translator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1100,19 +1100,14 @@ struct InstructionTranslator: InstructionVisitor {
let initializedElementsIndex = buffer.initialize(fromContentsOf: instructions)
assert(initializedElementsIndex == instructions.endIndex)

#if WasmDebuggingSupport
for (iseq, wasm) in self.iseqToWasmMapping {
self.module.withValue {
let absoluteISeq = iseq + buffer.baseAddress.unsafelyUnwrapped
// Don't override the existing mapping, only store a new pair if there's no mapping for a given key.
if $0.iseqToWasmMapping[absoluteISeq] == nil {
$0.iseqToWasmMapping[absoluteISeq] = wasm
}
if $0.wasmToIseqMapping[wasm] == nil {
$0.wasmToIseqMapping[wasm] = absoluteISeq
}
$0.wasmMappings.append(wasm)
let absoluteIseq = iseq + buffer.baseAddress.unsafelyUnwrapped
$0.instructionMapping.add(wasm: wasm, iseq: absoluteIseq)
}
}
#endif

let constants = allocator.allocateConstants(self.constantSlots.values)
return InstructionSequence(
Expand Down Expand Up @@ -2305,16 +2300,6 @@ struct InstructionTranslator: InstructionVisitor {
return .tableSize(Instruction.TableSizeOperand(tableIndex: table, result: LVReg(result)))
}
}

mutating func visitUnknown(_ opcode: [UInt8]) throws -> Bool {
guard self.module.isDebuggable && opcode.count == 1 && opcode[0] == 0xFF else {
return false
}

emit(.breakpoint)

return true
}
}

struct TranslationError: Error, CustomStringConvertible {
Expand Down
Loading