Skip to content

Commit fd0d1ee

Browse files
committed
Add reverse wasm->iseq mapping, breakpoints toggling PoC
1 parent 2d065e0 commit fd0d1ee

File tree

4 files changed

+76
-19
lines changed

4 files changed

+76
-19
lines changed

Sources/WasmKit/Execution/Debugger.swift

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,29 @@
33
package struct Debugger: ~Copyable {
44
enum Error: Swift.Error {
55
case entrypointFunctionNotFound
6+
case noInstructionMappingAvailable(Int)
67
}
78

8-
let valueStack: Sp
9-
let execution: Execution
10-
let store: Store
9+
private let valueStack: Sp
10+
private let execution: Execution
11+
private let store: Store
1112

13+
/// Parsed in-memory representation of a Wasm module instantiated for debugging.
1214
private let module: Module
15+
16+
/// Instance of parsed Wasm ``module``.
1317
private let instance: Instance
1418

1519
/// Addresses of each function in the code section of ``module``
16-
let functionAddresses: [Int]
17-
let entrypointFunction: Function
20+
private let functionAddresses: [FunctionAddress]
21+
22+
/// Reference to the entrypoint function of the currently debugged module, for use in ``stopAtEntrypoint``.
23+
private let entrypointFunction: Function
24+
25+
/// Threading model of the Wasm engine configuration cached for a potentially hot path.
26+
private let threadingModel: EngineConfiguration.ThreadingModel
27+
28+
private var breakpoints = [Int: CodeSlot]()
1829

1930
package init(module: Module, store: Store, imports: Imports) throws {
2031
let limit = store.engine.configuration.stackSize / MemoryLayout<StackSlot>.stride
@@ -31,10 +42,11 @@
3142
self.valueStack = UnsafeMutablePointer<StackSlot>.allocate(capacity: limit)
3243
self.store = store
3344
self.execution = Execution(store: StoreRef(store), stackEnd: valueStack.advanced(by: limit))
45+
self.threadingModel = store.engine.configuration.threadingModel
3446
}
3547

3648
package mutating func stopAtEntrypoint() throws {
37-
try self.toggleBreakpoint(address: self.originalAddress(function: entrypointFunction))
49+
try self.enableBreakpoint(address: self.originalAddress(function: entrypointFunction))
3850
}
3951

4052
package func originalAddress(function: Function) throws -> Int {
@@ -47,23 +59,50 @@
4759
try function.handle.wasm.ensureCompiled(store: StoreRef(self.store))
4860
return try self.originalAddress(function: function)
4961
case .compiled:
50-
print(function.handle.wasm.code)
5162
fatalError()
5263
}
5364
}
5465

55-
package mutating func toggleBreakpoint(address: Int) throws {
66+
package mutating func enableBreakpoint(address: Int) throws {
5667
print("attempt to toggle a breakpoint at \(address)")
68+
69+
guard self.breakpoints[address] == nil else {
70+
print("breakpoint at \(address) already enabled")
71+
return
72+
}
73+
74+
guard let iseq = self.instance.handle.wasmToIseqMapping[address] else {
75+
throw Error.noInstructionMappingAvailable(address)
76+
}
77+
self.breakpoints[address] = iseq.pointee
78+
iseq.pointee = Instruction.breakpoint.headSlot(threadingModel: self.threadingModel)
79+
}
80+
81+
package mutating func disableBreakpoint(address: Int) throws {
82+
print("attempt to toggle a breakpoint at \(address)")
83+
84+
guard let oldCodeSlot = self.breakpoints[address] else {
85+
print("breakpoint at \(address) already disabled")
86+
return
87+
}
88+
89+
guard let iseq = self.instance.handle.wasmToIseqMapping[address] else {
90+
throw Error.noInstructionMappingAvailable(address)
91+
}
92+
iseq.pointee = oldCodeSlot
93+
self.breakpoints[address] = nil
94+
}
95+
96+
mutating func enableBreakpoint(functionIndex: FunctionIndex, offset: Int) {
5797
}
5898

5999
/// Array of addresses in the Wasm binary of executed instructions on the call stack.
60100
package var currentCallStack: [Int] {
61-
guard let instance = self.valueStack.currentInstance else { return [] }
62-
let isDebuggable = instance.isDebuggable
101+
let isDebuggable = self.instance.handle.isDebuggable
63102
print("isDebuggable is \(isDebuggable)")
64103

65104
return Execution.captureBacktrace(sp: self.valueStack, store: self.store).symbols.map {
66-
instance.iSeqToWasmMapping[$0.address]!
105+
self.instance.handle.iseqToWasmMapping[$0.address]!
67106
}
68107
}
69108

Sources/WasmKit/Execution/Instances.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,14 @@ struct InstanceEntity /* : ~Copyable */ {
8484
var features: WasmFeatureSet
8585
var dataCount: UInt32?
8686
var isDebuggable: Bool
87-
var iSeqToWasmMapping: [Pc: Int]
87+
88+
/// Mapping from iSeq Pc to instruction addresses in the original binary.
89+
/// Used for handling current call stack requests issued by a ``Debugger`` instance.
90+
var iseqToWasmMapping: [Pc: Int]
91+
92+
/// Mapping from Wasm instruction addresses in the original binary to iSeq instruction addresses.
93+
/// Used for handling breakpoint requests issued by a ``Debugger`` instance.
94+
var wasmToIseqMapping: [Int: Pc]
8895

8996
static var empty: InstanceEntity {
9097
InstanceEntity(
@@ -100,7 +107,8 @@ struct InstanceEntity /* : ~Copyable */ {
100107
features: [],
101108
dataCount: nil,
102109
isDebuggable: false,
103-
iSeqToWasmMapping: [:]
110+
iseqToWasmMapping: [:],
111+
wasmToIseqMapping: [:]
104112
)
105113
}
106114

Sources/WasmKit/Execution/StoreAllocator.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,8 @@ extension StoreAllocator {
453453
features: module.features,
454454
dataCount: module.dataCount,
455455
isDebuggable: isDebuggable,
456-
iSeqToWasmMapping: [:]
456+
iseqToWasmMapping: [:],
457+
wasmToIseqMapping: [:]
457458
)
458459
instancePointer.initialize(to: instanceEntity)
459460
instanceInitialized = true

Sources/WasmKit/Translator.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -830,8 +830,10 @@ struct InstructionTranslator: InstructionVisitor {
830830
/// Current offset to an instruction in the original Wasm binary processed by this translator.
831831
var binaryOffset: Int = 0
832832

833-
/// Mapping from `self.iseqBuilder.instructions` to Wasm instructions
834-
var iSeqToWasmMapping = [Int: Int]()
833+
/// Mapping from `self.iseqBuilder.instructions` to Wasm instructions.
834+
/// As mapping between iSeq to Wasm is many:many, but we only care about first mapping for overlapping address,
835+
/// we need to iterate on it in the order the mappings were stored to ensure we don't overwrite the frist mapping.
836+
var iseqToWasmMapping = [(iseq: Int, wasm: Int)]()
835837

836838
init(
837839
allocator: ISeqAllocator,
@@ -1098,9 +1100,16 @@ struct InstructionTranslator: InstructionVisitor {
10981100
let initializedElementsIndex = buffer.initialize(fromContentsOf: instructions)
10991101
assert(initializedElementsIndex == instructions.endIndex)
11001102

1101-
for (iseq, wasm) in self.iSeqToWasmMapping {
1103+
for (iseq, wasm) in self.iseqToWasmMapping {
11021104
self.module.withValue {
1103-
$0.iSeqToWasmMapping[iseq + buffer.baseAddress.unsafelyUnwrapped] = wasm
1105+
let absoluteISeq = iseq + buffer.baseAddress.unsafelyUnwrapped
1106+
// Don't override the existing mapping, only store a new pair if there's no mapping for a given key.
1107+
if $0.iseqToWasmMapping[absoluteISeq] == nil {
1108+
$0.iseqToWasmMapping[absoluteISeq] = wasm
1109+
}
1110+
if $0.wasmToIseqMapping[wasm] == nil {
1111+
$0.wasmToIseqMapping[wasm] = absoluteISeq
1112+
}
11041113
}
11051114
}
11061115

@@ -1117,7 +1126,7 @@ struct InstructionTranslator: InstructionVisitor {
11171126
#if WasmDebuggingSupport
11181127
guard self.module.isDebuggable else { return }
11191128

1120-
self.iSeqToWasmMapping[self.iseqBuilder.insertingPC.offsetFromHead] = self.binaryOffset
1129+
self.iseqToWasmMapping.append((self.iseqBuilder.insertingPC.offsetFromHead, self.binaryOffset))
11211130
#endif
11221131
}
11231132

0 commit comments

Comments
 (0)