|
20 | 20 | case unknownCurrentFunctionForResumedBreakpoint(UnsafeMutablePointer<UInt64>) |
21 | 21 | case noInstructionMappingAvailable(Int) |
22 | 22 | case noReverseInstructionMappingAvailable(UnsafeMutablePointer<UInt64>) |
| 23 | + case stackFrameIndexOOB(UInt) |
| 24 | + case stackLocalIndexOOB(UInt) |
| 25 | + case notStoppedAtBreakpoint |
| 26 | + case linearMemoryNotInitialized |
| 27 | + case linearMemoryOOB(Range<Int>) |
23 | 28 | } |
24 | 29 |
|
25 | 30 | private let valueStack: Sp |
|
43 | 48 |
|
44 | 49 | package private(set) var state: State |
45 | 50 |
|
46 | | - private var pc = Pc.allocate(capacity: 1) |
| 51 | + /// Pc of the final instruction that a successful program will execute, initialized with `Instruction.endofExecution` |
| 52 | + private var endOfExecution: CodeSlot |
| 53 | + |
| 54 | + private var md: Md = nil |
| 55 | + private var ms: Ms = 0 |
47 | 56 |
|
48 | 57 | /// Addresses of functions in the original Wasm binary, used for looking up functions when a breakpoint |
49 | 58 | /// is enabled at an arbitrary address if it isn't present in ``InstructionMapping`` yet (i.e. the |
|
76 | 85 | self.entrypointFunction = entrypointFunction |
77 | 86 | self.valueStack = UnsafeMutablePointer<StackSlot>.allocate(capacity: limit) |
78 | 87 | self.store = store |
79 | | - self.execution = Execution(store: StoreRef(store), stackEnd: valueStack.advanced(by: limit)) |
| 88 | + self.execution = Execution( |
| 89 | + store: StoreRef(store), |
| 90 | + stackEnd: valueStack.advanced(by: limit) |
| 91 | + ) |
80 | 92 | self.threadingModel = store.engine.configuration.threadingModel |
81 | | - self.pc.pointee = Instruction.endOfExecution.headSlot(threadingModel: threadingModel) |
| 93 | + self.endOfExecution = Instruction.endOfExecution.headSlot(threadingModel: threadingModel) |
82 | 94 | self.state = .instantiated |
83 | 95 | } |
84 | 96 |
|
|
91 | 103 | /// Finds a Wasm address for the first instruction in a given function. |
92 | 104 | /// - Parameter function: the Wasm function to find the first Wasm instruction address for. |
93 | 105 | /// - Returns: byte offset of the first Wasm instruction of given function in the module it was parsed from. |
94 | | - private func originalAddress(function: Function) throws -> Int { |
| 106 | + package func originalAddress(function: Function) throws -> Int { |
95 | 107 | precondition(function.handle.isWasm) |
96 | 108 |
|
97 | 109 | switch function.handle.wasm.code { |
|
140 | 152 | return wasm |
141 | 153 | } |
142 | 154 |
|
| 155 | + package mutating func enableBreakpoint( |
| 156 | + module: Module, |
| 157 | + function: Int, |
| 158 | + offsetWithinFunction: Int = 0 |
| 159 | + ) throws -> Int { |
| 160 | + try self.enableBreakpoint(address: module.functions[function].code.originalAddress + offsetWithinFunction) |
| 161 | + } |
| 162 | + |
143 | 163 | /// Disables a breakpoint at a given Wasm address. If no breakpoint at a given address was previously set with |
144 | 164 | /// `self.enableBreakpoint(address:), this function immediately returns. |
145 | 165 | /// - Parameter address: byte offset of the Wasm instruction that was replaced with a breakpoint. The original |
|
170 | 190 | let iseq = breakpoint.iseq |
171 | 191 | var sp = iseq.sp |
172 | 192 | var pc = iseq.pc |
173 | | - var md: Md = nil |
174 | | - var ms: Ms = 0 |
175 | 193 |
|
176 | 194 | guard let currentFunction = sp.currentFunction else { |
177 | 195 | throw Error.unknownCurrentFunctionForResumedBreakpoint(sp) |
|
206 | 224 | type: self.entrypointFunction.type, |
207 | 225 | arguments: [], |
208 | 226 | sp: self.valueStack, |
209 | | - pc: self.pc |
| 227 | + pc: &self.endOfExecution |
210 | 228 | ) |
211 | 229 | self.state = .entrypointReturned(result) |
212 | 230 |
|
|
237 | 255 | try self.run() |
238 | 256 | } |
239 | 257 |
|
| 258 | + package func getLocal(frameIndex: UInt, localIndex: UInt) throws -> UInt64 { |
| 259 | + guard case .stoppedAtBreakpoint(let breakpoint) = self.state else { |
| 260 | + throw Error.notStoppedAtBreakpoint |
| 261 | + } |
| 262 | + |
| 263 | + var i = 0 |
| 264 | + for frame in Execution.CallStack(sp: breakpoint.iseq.sp) { |
| 265 | + guard frameIndex == i else { |
| 266 | + i += 1 |
| 267 | + continue |
| 268 | + } |
| 269 | + |
| 270 | + guard let currentFunction = frame.sp.currentFunction else { |
| 271 | + throw Debugger.Error.unknownCurrentFunctionForResumedBreakpoint(frame.sp) |
| 272 | + } |
| 273 | + |
| 274 | + try currentFunction.ensureCompiled(store: StoreRef(store)) |
| 275 | + |
| 276 | + guard case .debuggable(let wasm, _) = currentFunction.code else { |
| 277 | + fatalError() |
| 278 | + } |
| 279 | + |
| 280 | + // Wasm function arguments are also addressed as locals. |
| 281 | + let functionType = store.engine.funcTypeInterner.resolve(currentFunction.type) |
| 282 | + |
| 283 | + let localsCount = functionType.parameters.count + wasm.locals.count |
| 284 | + |
| 285 | + guard localIndex < localsCount else { |
| 286 | + throw Debugger.Error.stackLocalIndexOOB(localIndex) |
| 287 | + } |
| 288 | + |
| 289 | + if localIndex < functionType.parameters.count { |
| 290 | + let localIndex = Int(localIndex) - 4 |
| 291 | + return frame.sp[localIndex].storage |
| 292 | + } else { |
| 293 | + let localIndex = Int(localIndex) - functionType.parameters.count |
| 294 | + return frame.sp[localIndex].storage |
| 295 | + } |
| 296 | + } |
| 297 | + |
| 298 | + throw Error.stackFrameIndexOOB(frameIndex) |
| 299 | + } |
| 300 | + |
| 301 | + package func readLinearMemory<T>(address: UInt, length: UInt, reader: (UnsafeRawBufferPointer) -> T) throws(Error) -> T { |
| 302 | + guard let md, ms > 0 else { |
| 303 | + throw Error.linearMemoryNotInitialized |
| 304 | + } |
| 305 | + |
| 306 | + let upperBound = address + length |
| 307 | + let range = Int(address)..<Int(upperBound) |
| 308 | + |
| 309 | + guard address + length < ms else { |
| 310 | + throw Error.linearMemoryOOB(range) |
| 311 | + } |
| 312 | + |
| 313 | + let memory = UnsafeRawBufferPointer(start: md, count: ms) |
| 314 | + |
| 315 | + return reader(UnsafeRawBufferPointer(rebasing: memory[range])) |
| 316 | + } |
| 317 | + |
240 | 318 | /// Array of addresses in the Wasm binary of executed instructions on the call stack. |
241 | 319 | package var currentCallStack: [Int] { |
242 | 320 | guard case .stoppedAtBreakpoint(let breakpoint) = self.state else { |
|
254 | 332 |
|
255 | 333 | deinit { |
256 | 334 | self.valueStack.deallocate() |
257 | | - self.pc.deallocate() |
258 | 335 | } |
259 | 336 | } |
260 | 337 |
|
|
0 commit comments