Skip to content
3 changes: 3 additions & 0 deletions Sources/GDBRemoteProtocol/GDBHostCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ package struct GDBHostCommand: Equatable {
case kill
case insertSoftwareBreakpoint
case removeSoftwareBreakpoint
case wasmLocal

case generalRegisters

Expand Down Expand Up @@ -97,6 +98,8 @@ package struct GDBHostCommand: Equatable {
self = .continue
case "k":
self = .kill
case "qWasmLocal":
self = .wasmLocal

default:
return nil
Expand Down
23 changes: 17 additions & 6 deletions Sources/WasmKit/Execution/Debugger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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 {
Expand Down Expand Up @@ -206,7 +213,7 @@
type: self.entrypointFunction.type,
arguments: [],
sp: self.valueStack,
pc: self.pc
pc: self.endOfExecution
)
self.state = .entrypointReturned(result)

Expand All @@ -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))
}
}

Expand All @@ -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 {
Expand All @@ -254,7 +265,7 @@

deinit {
self.valueStack.deallocate()
self.pc.deallocate()
self.endOfExecution.deallocate()
}
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/WasmKit/Execution/Execution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct Execution: ~Copyable {
struct FrameIterator: IteratorProtocol {
struct Element {
let pc: Pc
let function: EntityHandle<WasmFunctionEntity>?
let sp: Sp
}

/// The stack pointer currently traversed.
Expand All @@ -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)
}
}

Expand All @@ -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
}
Expand Down
38 changes: 32 additions & 6 deletions Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -50,6 +51,7 @@
case exitCodeUnknown([Value])
case killRequestReceived
case unknownHexEncodedArguments(String)
case unknownWasmLocalArguments(String)
}

private let wasmBinary: ByteBuffer
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Loading