diff --git a/Sources/GDBRemoteProtocol/GDBHostCommand.swift b/Sources/GDBRemoteProtocol/GDBHostCommand.swift index 017e0f5b..b2a49745 100644 --- a/Sources/GDBRemoteProtocol/GDBHostCommand.swift +++ b/Sources/GDBRemoteProtocol/GDBHostCommand.swift @@ -47,6 +47,7 @@ package struct GDBHostCommand: Equatable { case kill case insertSoftwareBreakpoint case removeSoftwareBreakpoint + case wasmLocal case generalRegisters @@ -97,6 +98,8 @@ package struct GDBHostCommand: Equatable { self = .continue case "k": self = .kill + case "qWasmLocal": + self = .wasmLocal default: return nil diff --git a/Sources/WasmKit/Execution/Debugger.swift b/Sources/WasmKit/Execution/Debugger.swift index 937fe0ec..09559f69 100644 --- a/Sources/WasmKit/Execution/Debugger.swift +++ b/Sources/WasmKit/Execution/Debugger.swift @@ -4,6 +4,7 @@ /// debugger protocol, which allows any protocol implementation or direct API users to be layered on top if needed. package struct Debugger: ~Copyable { package struct BreakpointState { + let sp: Sp let iseq: Execution.Breakpoint package let wasmPc: Int } @@ -43,7 +44,13 @@ package private(set) var state: State - private var pc = Pc.allocate(capacity: 1) + /// Pc ofthe final instruction that a successful program will execute, initialized with `Instruction.endofExecution` + private let endOfExecution = Pc.allocate(capacity: 1) + + /// Addresses of functions in the original Wasm binary, used looking up functions when a breakpoint + /// is enabled at an arbitrary address if it isn't present in ``InstructionMapping`` yet (i.e. the + /// was not compiled yet in lazy compilation mode). + private let functionAddresses: [(address: Int, instanceFunctionIndex: Int)] /// Addresses of functions in the original Wasm binary, used for looking up functions when a breakpoint /// is enabled at an arbitrary address if it isn't present in ``InstructionMapping`` yet (i.e. the @@ -78,7 +85,7 @@ self.store = store self.execution = Execution(store: StoreRef(store), stackEnd: valueStack.advanced(by: limit)) self.threadingModel = store.engine.configuration.threadingModel - self.pc.pointee = Instruction.endOfExecution.headSlot(threadingModel: threadingModel) + self.endOfExecution.pointee = Instruction.endOfExecution.headSlot(threadingModel: threadingModel) self.state = .instantiated } @@ -91,7 +98,7 @@ /// Finds a Wasm address for the first instruction in a given function. /// - Parameter function: the Wasm function to find the first Wasm instruction address for. /// - Returns: byte offset of the first Wasm instruction of given function in the module it was parsed from. - private func originalAddress(function: Function) throws -> Int { + package func originalAddress(function: Function) throws -> Int { precondition(function.handle.isWasm) switch function.handle.wasm.code { @@ -206,7 +213,7 @@ type: self.entrypointFunction.type, arguments: [], sp: self.valueStack, - pc: self.pc + pc: self.endOfExecution ) self.state = .entrypointReturned(result) @@ -219,7 +226,7 @@ throw Error.noReverseInstructionMappingAvailable(pc) } - self.state = .stoppedAtBreakpoint(.init(iseq: breakpoint, wasmPc: wasmPc)) + self.state = .stoppedAtBreakpoint(.init(sp: breakpoint.sp, iseq: breakpoint, wasmPc: wasmPc)) } } @@ -237,6 +244,10 @@ try self.run() } + package func getLocal(frameIndex: Int, localIndex: Int) -> Address { + + } + /// Array of addresses in the Wasm binary of executed instructions on the call stack. package var currentCallStack: [Int] { guard case .stoppedAtBreakpoint(let breakpoint) = self.state else { @@ -254,7 +265,7 @@ deinit { self.valueStack.deallocate() - self.pc.deallocate() + self.endOfExecution.deallocate() } } diff --git a/Sources/WasmKit/Execution/Execution.swift b/Sources/WasmKit/Execution/Execution.swift index a66dfb92..9fec21a0 100644 --- a/Sources/WasmKit/Execution/Execution.swift +++ b/Sources/WasmKit/Execution/Execution.swift @@ -46,7 +46,7 @@ struct Execution: ~Copyable { struct FrameIterator: IteratorProtocol { struct Element { let pc: Pc - let function: EntityHandle? + let sp: Sp } /// The stack pointer currently traversed. @@ -62,7 +62,7 @@ struct Execution: ~Copyable { return nil } self.sp = sp.previousSP - return Element(pc: pc, function: sp.currentFunction) + return Element(pc: pc, sp: sp) } } @@ -71,7 +71,7 @@ struct Execution: ~Copyable { var symbols: [Backtrace.Symbol] = [] while let frame = frames.next() { - guard let function = frame.function else { + guard let function = frame.sp.currentFunction else { symbols.append(.init(name: nil, address: frame.pc)) continue } diff --git a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift index 8a47ef51..889f2356 100644 --- a/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift +++ b/Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift @@ -33,6 +33,7 @@ } private let codeOffset = UInt64(0x4000_0000_0000_0000) + private let stackOffset = UInt64(0x8000_0000_0000_0000) package actor WasmKitGDBHandler { enum ResumeThreadsAction: String { @@ -50,6 +51,7 @@ case exitCodeUnknown([Value]) case killRequestReceived case unknownHexEncodedArguments(String) + case unknownWasmLocalArguments(String) } private let wasmBinary: ByteBuffer @@ -229,17 +231,24 @@ let argumentsArray = command.arguments.split(separator: ",") guard argumentsArray.count == 2, - let address = UInt64(hexEncoded: argumentsArray[0]), + let hostAddress = UInt64(hexEncoded: argumentsArray[0]), var length = Int(hexEncoded: argumentsArray[1]) else { throw Error.unknownReadMemoryArguments } - let binaryOffset = Int(address - codeOffset) + if address > stackOffset { + let stackOffset = address - codeOffset - if binaryOffset + length > wasmBinary.readableBytes { - length = wasmBinary.readableBytes - binaryOffset - } + fatalError("Stack reads are not implemented in the debugger yet") + } else if address > codeOffset { + let binaryOffset = address - stackOffset + if binaryOffset + length > wasmBinary.readableBytes { + length = wasmBinary.readableBytes - binaryOffset + } - responseKind = .hexEncodedBinary(wasmBinary.readableBytesView[binaryOffset..<(binaryOffset + length)]) + responseKind = .hexEncodedBinary(wasmBinary.readableBytesView[binaryOffset..<(binaryOffset + length)]) + } else { + fatalError("Linear memory reads are not implemented in the debugger yet.") + } case .wasmCallStack: let callStack = self.debugger.currentCallStack @@ -299,6 +308,23 @@ ) responseKind = .ok + case .wasmLocal: + let arguments = command.arguments.split(separator: ";") + guard arguments.count == 2, + let frameIndexString = arguments.first, + let frameIndex = UInt32(frameIndexString), + let localIndexString = arguments.last, + let localIndex = UInt32(localIndexString) + else { + throw Error.unknownWasmLocalArguments(command.arguments) + } + + var response = self.allocator.buffer(capacity: 64) + response.writeInteger(frameIndex, endianness: .little) + response.writeInteger(localIndex, endianness: .little) + + responseKind = .hexEncodedBinary(response) + case .generalRegisters: throw Error.hostCommandNotImplemented(command.kind) }