-
Notifications
You must be signed in to change notification settings - Fork 19
GDB RP: support qWasmCallStack and debugger breakpoints
#212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 109 commits
Commits
Show all changes
115 commits
Select commit
Hold shift + click to select a range
eb5f169
Basic LLDB remote protocol scaffolding
MaxDesiatov 0baf7a5
Implement more packet handling, update naming
MaxDesiatov 85a695a
Provide original license notice in `wasmkit-gdb-tool/Entrypoint.swift`
MaxDesiatov eba97a0
Apply formatter
MaxDesiatov 6546754
Make naming of types more specific to avoid future collisions
MaxDesiatov 7b2c6f6
More cleanups for type naming
MaxDesiatov 4a1c998
Remove unused `import struct Foundation.Date`
MaxDesiatov f8c08dd
Remove unused `else` clause from command decoder
MaxDesiatov 77bdb04
Add `swift-nio` in `SWIFTCI_USE_LOCAL_DEPS` clause
MaxDesiatov 256c70c
Add FIXME note for `.supportedFeatures` response
MaxDesiatov 111deb8
Use `Logger`, `NIOAsyncChannel`
MaxDesiatov e392d30
Add required dependencies to `Package@swift-6.1.swift`
MaxDesiatov a7c62b0
Add `iSeqToWasmMapping` to `InstanceEntity`
MaxDesiatov 21e1b44
Fix formatting and iOS compatibility
MaxDesiatov 329e4f5
Update Package@swift-6.1.swift
MaxDesiatov a3d20af
Update doc comment of `func instantiate` in `Module.swift`
MaxDesiatov 8d15b99
Add `targetStatus`/`?` host command
MaxDesiatov d9b60b1
Fix use of `OK#9a` instead of incorrect `ok#da` response
MaxDesiatov 05583d0
Add `CSystemExtras` to `Package@swift-6.1`
MaxDesiatov 39242d6
Handle `targetStatus` in `WasmKitDebugger`
MaxDesiatov cbff974
Build only `wasmkit-cli` product for WASI in `main.yml`
MaxDesiatov 982fa91
Fix formatting in `WasmKitDebugger.swift`
MaxDesiatov 3a74a91
Fix `QStartNoAckMode` handling
MaxDesiatov bc539e8
Add `qRegisterInfo` parsing to `GDBHostCommand.init`
MaxDesiatov fb4e2a2
Use `KeyValuePairs` response for `.registerInfo`
MaxDesiatov 042a2c4
Fix formatting and tests build error
MaxDesiatov 197abd3
Exclude `wasmkit-gdb-tool` on Windows, address PR feedback
MaxDesiatov a2899f7
Remove retroactive `FilePath` conformance
MaxDesiatov 0b42724
Handle more host commands, up to `qWasmCallStack`
MaxDesiatov 1783176
Fix formatting
MaxDesiatov c0a9cf6
Address PR feedback, add comment for `DebuggingAddress` sendability
MaxDesiatov 806e7ec
Add `DebuggerExecution` wrapper type
MaxDesiatov 26ea4e5
Implement `.readMemory` command handling
MaxDesiatov 8343902
Fix formatting
MaxDesiatov ff2fa0d
Pass `-package-name` in CMake to enable use of `package`
MaxDesiatov 3ed9013
Disable `WasmKitGDBHandler` on Windows
MaxDesiatov 3ea5aab
Fix formatting
MaxDesiatov 3fc8fdc
Try nightly `main` for `build-cmake` job
MaxDesiatov ea83134
Revert "Try nightly `main` for `build-cmake` job"
MaxDesiatov 2893799
Rename `DebuggerExecution` to `Debugger`, hide behind a trait
MaxDesiatov 7bdb16c
Enable `WasmDebuggingSupport` on CI where possible
MaxDesiatov 4ea0fb0
Fix formatting
MaxDesiatov ee3c7bf
Revert "Pass `-package-name` in CMake to enable use of `package`"
MaxDesiatov a6d0749
Disable traits in pre-6.1, guard use of `package` on trait
MaxDesiatov 4909d3b
Remove use of `package` from CMake-built code
MaxDesiatov cce2105
Fix non-CMake build breakage
MaxDesiatov 811589f
Fix formatting
MaxDesiatov bc2230d
Enable iseq -> wasm instruction mapping
MaxDesiatov db1ec2d
Fix formatting
MaxDesiatov 62592f1
Add missing `mutating` to `Debugger.swift`
MaxDesiatov bbfcb45
Make `init` throw effect untyped in `Debugger.swift`
MaxDesiatov c6213eb
Make throwing method effects untyped in `Debugger.swift`
MaxDesiatov a993b50
Make `debugger: Debugger` property mutable
MaxDesiatov 2d065e0
Clean up `WasmGen` code
MaxDesiatov fd0d1ee
Add reverse wasm->iseq mapping, breakpoints toggling PoC
MaxDesiatov a35b73e
Remove unused `functionAddresses` property from `Debugger`
MaxDesiatov 16ac0cf
Remove changes unrelated to the protocol
MaxDesiatov 410c470
Clarify licensing in `README.md`
MaxDesiatov 977b939
Add license files for separate modules
MaxDesiatov 2acd4f3
Basic doc comments for `GDBHostCommand`
MaxDesiatov 3b2e30f
Add doc comments for `GDBHostCommandDecoder`
MaxDesiatov fc740e5
Add remaining doc comments
MaxDesiatov a297f2d
Fix formatting
MaxDesiatov ff2e940
Fix var naming typo
MaxDesiatov 5f7fbb5
Clarify licensing in `README.md`
MaxDesiatov d100d1e
Add license files for separate modules
MaxDesiatov 2407284
Basic doc comments for `GDBHostCommand`
MaxDesiatov f628219
Add doc comments for `GDBHostCommandDecoder`
MaxDesiatov ab295db
Add remaining doc comments
MaxDesiatov e3505bc
Fix formatting
MaxDesiatov c4f9b3f
Fix var naming typo
MaxDesiatov f6bab20
Remove unused `throws` in `GDBTargetResponseEncoder.swift`
MaxDesiatov f33dc82
Remove unused `throws` in `GDBTargetResponseEncoder.swift`
MaxDesiatov a1d5183
Merge branch 'maxd/lldb-remote-protocol' of github.com:swiftwasm/Wasm…
MaxDesiatov dccc6af
Revert "Remove changes unrelated to the protocol"
MaxDesiatov a25399d
Restore base branch state
MaxDesiatov 6bb5df8
Merge branch 'main' into maxd/debugging-breakpoints
MaxDesiatov ae91e24
Add `DebuggerTests` test suite
MaxDesiatov 169d406
Merge remote-tracking branch 'origin/maxd/debugging-breakpoints' into…
MaxDesiatov d5d1600
Fix build error, add debug logging
MaxDesiatov 9d79c83
Fix `DebuggerTests`
MaxDesiatov f64bf23
Remove `print` debugging
MaxDesiatov 8bf6849
Allocate and modify `Pc` directly as `Debugger` property
MaxDesiatov ea04cc3
Rewind Pc back by 1 word after catching `Breakpoint`
MaxDesiatov c4d3ac9
Revert "Allocate and modify `Pc` directly as `Debugger` property"
MaxDesiatov 6246506
Fully implement `qWasmCallStack`
MaxDesiatov 27dbbc7
Pass `Sp` in `Breakpoint`, add `executeWasm` instance method
MaxDesiatov 036fcb8
Fix license header diff
MaxDesiatov a57ec75
Fix trailing comma compatibility with Swift 6.0
MaxDesiatov 935f877
Fix breakpoint resumption test expectation
MaxDesiatov a7fac8e
Mark `Sp.currentFunction` as `internal`
MaxDesiatov 4e8b062
Mark `Debugger.Error` as `@unchecked Sendable`
MaxDesiatov 969b992
Handle `qThreadStopInfo`, run up to breakpoint in handler
MaxDesiatov 2486ec8
Add missing `logger` argument in `Entrypoint.swift`
MaxDesiatov d594b49
Remove CLI harness for smaller diff
MaxDesiatov 76b2317
Revert "Remove CLI harness for smaller diff"
MaxDesiatov beff9a7
Remove only `wasmkit-gdb-tool`
MaxDesiatov eb9001b
Make some functions `fileprivate`
MaxDesiatov 687b223
Add doc comments for the debugger type
MaxDesiatov d25166b
Merge branch 'main' into maxd/debugging-breakpoints
MaxDesiatov 8d78ab6
Fix formatting
MaxDesiatov c39f9e2
Refine doc comment Sources/WasmKit/Execution/Debugger.swift
MaxDesiatov 55bfb6b
Refine doc comment in `Debugger.swift`
MaxDesiatov e7a5287
Refine doc comments wording
MaxDesiatov a2c701e
Fix build error
MaxDesiatov 1b271d5
Fix formatting
MaxDesiatov dec6850
Apply formatting to `Package@swift-6.1.swift`
MaxDesiatov b3953a5
Disable nightly toolchain in CI configuration
MaxDesiatov e81f68e
Pin `build-android` job to v2.6.4 of `swift-android-action`
MaxDesiatov ab8f080
Refine doc comments wording
MaxDesiatov 0eda671
Revert "Pin `build-android` job to v2.6.4 of `swift-android-action`"
MaxDesiatov 7f0e212
Merge branch 'main' of github.com:swiftwasm/WasmKit into maxd/debuggi…
MaxDesiatov da87018
Merge branch 'main' into maxd/debugging-breakpoints
MaxDesiatov ce6124c
Move mapping dictionaries to `DebuggerInstructionMapping`
MaxDesiatov dd36c4c
Fix formatting and CMake
MaxDesiatov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| #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 { | ||
| /// Return an address of WasmKit's iseq bytecode instruction that matches a given Wasm instruction address. | ||
MaxDesiatov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// - 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 { | ||
| package enum Error: Swift.Error, @unchecked Sendable { | ||
| case entrypointFunctionNotFound | ||
| case unknownCurrentFunctionForResumedBreakpoint(UnsafeMutablePointer<UInt64>) | ||
| case noInstructionMappingAvailable(Int) | ||
| case noReverseInstructionMappingAvailable(UnsafeMutablePointer<UInt64>) | ||
| } | ||
|
|
||
| private let valueStack: Sp | ||
| private var execution: Execution | ||
| private let store: Store | ||
|
|
||
| /// Parsed in-memory representation of a Wasm module instantiated for debugging. | ||
| private let module: Module | ||
|
|
||
| /// Instance of parsed Wasm ``module``. | ||
| private let instance: Instance | ||
|
|
||
| /// Reference to the entrypoint function of the currently debugged module, for use in ``stopAtEntrypoint``. | ||
| /// Currently assumed to be the WASI command `_start` entrypoint. | ||
| private let entrypointFunction: Function | ||
|
|
||
| /// Threading model of the Wasm engine configuration, cached for a potentially hot path. | ||
| private let threadingModel: EngineConfiguration.ThreadingModel | ||
|
|
||
| private(set) var breakpoints = [Int: CodeSlot]() | ||
|
|
||
| private var currentBreakpoint: (iseq: Execution.Breakpoint, wasmPc: Int)? | ||
|
|
||
| private var pc = Pc.allocate(capacity: 1) | ||
|
|
||
| /// Initializes a new debugger state instance. | ||
| /// - Parameters: | ||
| /// - module: Wasm module to instantiate. | ||
| /// - store: Store that instantiates the module. | ||
| /// - imports: Imports required by `module` for instantiation. | ||
| package init(module: Module, store: Store, imports: Imports) throws { | ||
| let limit = store.engine.configuration.stackSize / MemoryLayout<StackSlot>.stride | ||
| let instance = try module.instantiate(store: store, imports: imports, isDebuggable: true) | ||
|
|
||
| guard case .function(let entrypointFunction) = instance.exports["_start"] else { | ||
| throw Error.entrypointFunctionNotFound | ||
| } | ||
|
|
||
| self.instance = instance | ||
| self.module = module | ||
| self.entrypointFunction = entrypointFunction | ||
| self.valueStack = UnsafeMutablePointer<StackSlot>.allocate(capacity: limit) | ||
| 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) | ||
| } | ||
|
|
||
| /// Sets a breakpoint at the first instruction in the entrypoint function of the module instantiated by | ||
| /// this debugger. | ||
| package mutating func stopAtEntrypoint() throws { | ||
| try self.enableBreakpoint(address: self.originalAddress(function: entrypointFunction)) | ||
| } | ||
|
|
||
| /// 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 { | ||
| precondition(function.handle.isWasm) | ||
|
|
||
| switch function.handle.wasm.code { | ||
| case .debuggable(let wasm, _): | ||
| return wasm.originalAddress | ||
| case .uncompiled: | ||
| try function.handle.wasm.ensureCompiled(store: StoreRef(self.store)) | ||
| return try self.originalAddress(function: function) | ||
| case .compiled: | ||
| fatalError() | ||
| } | ||
| } | ||
|
|
||
| /// Enables a breakpoint at a given Wasm address. | ||
| /// - Parameter address: byte offset of the Wasm instruction that will be replaced with a breakpoint. If no | ||
| /// direct internal bytecode matching instruction is found, the next closest internal bytecode instruction | ||
| /// is replaced with a breakpoint. The original instruction to be restored is preserved in debugger state | ||
| /// represented by `self`. | ||
| /// See also ``Debugger/disableBreakpoint(address:)``. | ||
| package mutating func enableBreakpoint(address: Int) throws(Error) { | ||
| guard self.breakpoints[address] == nil else { | ||
| return | ||
| } | ||
|
|
||
| let (iseq, wasm) = try self.instance.findIseq(forWasmAddress: address) | ||
|
|
||
| self.breakpoints[wasm] = iseq.pointee | ||
| iseq.pointee = Instruction.breakpoint.headSlot(threadingModel: self.threadingModel) | ||
| } | ||
|
|
||
| /// Disables a breakpoint at a given Wasm address. If no breakpoint at a given address was previously set with | ||
| /// `self.enableBreakpoint(address:), this function immediately returns. | ||
| /// - Parameter address: byte offset of the Wasm instruction that was replaced with a breakpoint. The original | ||
| /// instruction is restored from debugger state and replaces the breakpoint instruction. | ||
| /// See also ``Debugger/enableBreakpoint(address:)``. | ||
| package mutating func disableBreakpoint(address: Int) throws(Error) { | ||
| guard let oldCodeSlot = self.breakpoints[address] else { | ||
| return | ||
| } | ||
|
|
||
| let (iseq, wasm) = try self.instance.findIseq(forWasmAddress: address) | ||
|
|
||
| self.breakpoints[wasm] = nil | ||
| iseq.pointee = oldCodeSlot | ||
| } | ||
|
|
||
| /// If the module instantiated by the debugger is stopped at a breakpoint, the breakpoint is disabled | ||
| /// and execution is resumed until the next breakpoint is triggered or all remaining instructions are | ||
| /// executed. If the module is not stopped at a breakpoint, this function retusn immediately. | ||
| /// - Returns: `[Value]` result of `entrypointFunction` if current instance ran to completion, | ||
| /// `nil` if it stopped at a breakpoint. | ||
MaxDesiatov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| package mutating func run() throws -> [Value]? { | ||
| do { | ||
| if let currentBreakpoint { | ||
| // Remove the breakpoint before resuming | ||
| try self.disableBreakpoint(address: currentBreakpoint.wasmPc) | ||
| self.execution.resetError() | ||
|
|
||
| var sp = currentBreakpoint.iseq.sp | ||
| var pc = currentBreakpoint.iseq.pc | ||
| var md: Md = nil | ||
| var ms: Ms = 0 | ||
|
|
||
| guard let currentFunction = sp.currentFunction else { | ||
| throw Error.unknownCurrentFunctionForResumedBreakpoint(sp) | ||
| } | ||
|
|
||
| Execution.CurrentMemory.mayUpdateCurrentInstance( | ||
| instance: currentFunction.instance, | ||
| from: self.instance.handle, | ||
| md: &md, | ||
| ms: &ms | ||
| ) | ||
|
|
||
| do { | ||
| switch self.threadingModel { | ||
| case .direct: | ||
| try self.execution.runDirectThreaded(sp: sp, pc: pc, md: md, ms: ms) | ||
| case .token: | ||
| try self.execution.runTokenThreaded(sp: &sp, pc: &pc, md: &md, ms: &ms) | ||
| } | ||
| } catch is Execution.EndOfExecution { | ||
| } | ||
|
|
||
| let type = self.store.engine.funcTypeInterner.resolve(currentFunction.type) | ||
| return type.results.enumerated().map { (i, type) in | ||
| sp[VReg(i)].cast(to: type) | ||
| } | ||
| } else { | ||
| return try self.execution.executeWasm( | ||
| threadingModel: self.threadingModel, | ||
| function: self.entrypointFunction.handle, | ||
| type: self.entrypointFunction.type, | ||
| arguments: [], | ||
| sp: self.valueStack, | ||
| pc: self.pc | ||
| ) | ||
| } | ||
| } catch let breakpoint as Execution.Breakpoint { | ||
| let pc = breakpoint.pc | ||
| guard let wasmPc = self.instance.handle.iseqToWasmMapping[pc] else { | ||
| throw Error.noReverseInstructionMappingAvailable(pc) | ||
| } | ||
|
|
||
| self.currentBreakpoint = (breakpoint, wasmPc) | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| /// Array of addresses in the Wasm binary of executed instructions on the call stack. | ||
| package var currentCallStack: [Int] { | ||
| guard let currentBreakpoint else { | ||
| return [] | ||
| } | ||
|
|
||
| var result = Execution.captureBacktrace(sp: currentBreakpoint.iseq.sp, store: self.store).symbols.compactMap { | ||
| return self.instance.handle.iseqToWasmMapping[$0.address] | ||
| } | ||
| result.append(currentBreakpoint.wasmPc) | ||
|
|
||
| return result | ||
| } | ||
|
|
||
| deinit { | ||
| self.valueStack.deallocate() | ||
| self.pc.deallocate() | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.