Skip to content

Commit 103422e

Browse files
committed
Bring back getLocal, add more thorough tests
1 parent f960641 commit 103422e

File tree

7 files changed

+144
-32
lines changed

7 files changed

+144
-32
lines changed

.swift-format

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"lineLength": 200,
44
"indentation": {
55
"spaces": 4
6-
}
6+
},
7+
"ReturnVoidInsteadOfEmptyTuple": false
78
}

Sources/WasmKit/Execution/Debugger.swift

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
case unknownCurrentFunctionForResumedBreakpoint(UnsafeMutablePointer<UInt64>)
2121
case noInstructionMappingAvailable(Int)
2222
case noReverseInstructionMappingAvailable(UnsafeMutablePointer<UInt64>)
23-
case stackFrameIndexOOB(Int)
24-
case stackLocalIndexOOB(Int)
23+
case stackFrameIndexOOB(UInt)
24+
case stackLocalIndexOOB(UInt)
2525
case notStoppedAtBreakpoint
26+
case linearMemoryNotInitialized
27+
case linearMemoryOOB(Range<Int>)
2628
}
2729

2830
private let valueStack: Sp
@@ -49,6 +51,9 @@
4951
/// Pc of the final instruction that a successful program will execute, initialized with `Instruction.endofExecution`
5052
private let endOfExecution = Pc.allocate(capacity: 1)
5153

54+
private var md: Md = nil
55+
private var ms: Ms = 0
56+
5257
/// Addresses of functions in the original Wasm binary, used for looking up functions when a breakpoint
5358
/// is enabled at an arbitrary address if it isn't present in ``InstructionMapping`` yet (i.e. the
5459
/// was not compiled yet in lazy compilation mode).
@@ -189,8 +194,6 @@
189194
let iseq = breakpoint.iseq
190195
var sp = iseq.sp
191196
var pc = iseq.pc
192-
var md: Md = nil
193-
var ms: Ms = 0
194197

195198
guard let currentFunction = sp.currentFunction else {
196199
throw Error.unknownCurrentFunctionForResumedBreakpoint(sp)
@@ -256,14 +259,74 @@
256259
try self.run()
257260
}
258261

259-
package mutating func packedStackFrame<T>(frameIndex: Int, reader: (RawSpan, DebuggerStackFrame.Layout) -> T) throws -> T {
262+
package mutating func packedStackFrame<T>(frameIndex: UInt, reader: (RawSpan, DebuggerStackFrame.Layout) -> T) throws -> T {
260263
guard case .stoppedAtBreakpoint(let breakpoint) = self.state else {
261264
throw Error.notStoppedAtBreakpoint
262265
}
263266

264267
return try self.stackFrame.withFrames(sp: breakpoint.iseq.sp, frameIndex: frameIndex, store: self.store, reader: reader)
265268
}
266269

270+
package func getLocal(localIndex: UInt, frameIndex: UInt) throws -> UInt64 {
271+
guard case .stoppedAtBreakpoint(let breakpoint) = self.state else {
272+
throw Error.notStoppedAtBreakpoint
273+
}
274+
275+
var i = 0
276+
for frame in Execution.CallStack(sp: breakpoint.iseq.sp) {
277+
guard frameIndex == i else {
278+
i += 1
279+
continue
280+
}
281+
282+
guard let currentFunction = frame.sp.currentFunction else {
283+
throw Debugger.Error.unknownCurrentFunctionForResumedBreakpoint(frame.sp)
284+
}
285+
286+
try currentFunction.ensureCompiled(store: StoreRef(store))
287+
288+
guard case .debuggable(let wasm, _) = currentFunction.code else {
289+
fatalError()
290+
}
291+
292+
// Wasm function arguments are also addressed as locals.
293+
let functionType = store.engine.funcTypeInterner.resolve(currentFunction.type)
294+
295+
let localsCount = functionType.parameters.count + wasm.locals.count
296+
297+
guard localIndex < localsCount else {
298+
throw Debugger.Error.stackLocalIndexOOB(localIndex)
299+
}
300+
301+
if localIndex < functionType.parameters.count {
302+
let localIndex = Int(localIndex) - 3
303+
return frame.sp[localIndex].storage
304+
} else {
305+
return frame.sp[localIndex].storage
306+
}
307+
}
308+
309+
throw Error.stackFrameIndexOOB(frameIndex)
310+
}
311+
312+
package func readLinearMemory<T>(address: UInt, length: UInt, reader: (RawSpan) -> T) throws(Error) -> T {
313+
guard let md, ms > 0 else {
314+
throw Error.linearMemoryNotInitialized
315+
}
316+
317+
let upperBound = address + length
318+
let range = Int(address)..<Int(upperBound)
319+
320+
guard address + length < ms else {
321+
throw Error.linearMemoryOOB(range)
322+
}
323+
324+
let memory = UnsafeRawBufferPointer(start: md, count: ms)
325+
print(Array(memory[range]))
326+
327+
return reader(UnsafeRawBufferPointer(rebasing: memory[range]).bytes)
328+
}
329+
267330
/// Array of addresses in the Wasm binary of executed instructions on the call stack.
268331
package var currentCallStack: [Int] {
269332
guard case .stoppedAtBreakpoint(let breakpoint) = self.state else {

Sources/WasmKit/Execution/DebuggerStackFrame.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
buffer.initializeMemory(as: Int.self, repeating: 0)
1515
}
1616

17-
mutating func withFrames<T>(sp: Sp, frameIndex: Int, store: Store, reader: (borrowing RawSpan, Layout) -> T) throws -> T {
17+
mutating func withFrames<T>(sp: Sp, frameIndex: UInt, store: Store, reader: (borrowing RawSpan, Layout) -> T) throws -> T {
1818
self.buffer.initializeMemory(as: Int.self, repeating: 0)
1919

2020
var i = 0

Sources/WasmKitGDBHandler/DebuggerMemoryCache.swift

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@
4444
self.stackFrames = allocator.buffer(capacity: 0)
4545
}
4646

47-
package mutating func getAddressOfLocal(debugger: inout Debugger, frameIndex: Int, localIndex: Int) throws -> UInt64 {
47+
package mutating func getAddressOfLocal(debugger: inout Debugger, frameIndex: UInt, localIndex: UInt) throws -> UInt64 {
4848
let frameBase: Int
4949
let frameLayout: DebuggerStackFrame.Layout
5050

51-
if let (base, layout) = self.stackFrameLayouts[frameIndex] {
51+
if let (base, layout) = self.stackFrameLayouts[Int(frameIndex)] {
5252
guard layout.localOffsets.count > localIndex else {
5353
throw Debugger.Error.stackLocalIndexOOB(localIndex)
5454
}
@@ -59,43 +59,49 @@
5959
let (base, layout) = try debugger.packedStackFrame(frameIndex: frameIndex) { span, layout in
6060
let baseAddress = self.stackFrames.writerIndex
6161
self.stackFrames.writeBytes(span)
62-
print("written to stackFrames: \(self.stackFrames.hexDump(format: .plain))")
6362
return (baseAddress, layout)
6463
}
6564

66-
self.stackFrameLayouts[frameIndex] = (base, layout)
65+
self.stackFrameLayouts[Int(frameIndex)] = (base, layout)
6766

6867
frameBase = base
6968
frameLayout = layout
7069
}
7170

72-
return self.stackOffsetInProtocolSpace + UInt64(frameBase + frameLayout.localOffsets[localIndex])
71+
return self.stackOffsetInProtocolSpace + UInt64(frameBase + frameLayout.localOffsets[Int(localIndex)])
7372
}
7473

7574
package func readMemory(
7675
debugger: borrowing Debugger,
7776
addressInProtocolSpace: UInt64,
78-
length: Int
79-
) -> ByteBufferView {
80-
var length = length
77+
length: UInt
78+
) throws(Debugger.Error)-> ByteBufferView {
8179

8280
if addressInProtocolSpace >= self.stackOffsetInProtocolSpace {
81+
var length = Int(length)
8382
let stackAddress = Int(addressInProtocolSpace - self.stackOffsetInProtocolSpace)
8483
if stackAddress + length > self.stackFrames.readableBytes {
8584
length = self.stackFrames.readableBytes - stackAddress
8685
}
8786

8887
return self.stackFrames.readableBytesView[stackAddress..<(stackAddress + length)]
8988
} else if addressInProtocolSpace >= Self.executableCodeOffset {
90-
print("wasmBinary")
89+
var length = Int(length)
9190
let codeAddress = Int(addressInProtocolSpace - Self.executableCodeOffset)
9291
if codeAddress + length > wasmBinary.readableBytes {
9392
length = wasmBinary.readableBytes - codeAddress
9493
}
9594

9695
return wasmBinary.readableBytesView[codeAddress..<(codeAddress + length)]
9796
} else {
98-
fatalError("Linear memory reads are not implemented in the debugger yet.")
97+
return try debugger.readLinearMemory(address: UInt(addressInProtocolSpace), length: length) { span in
98+
var buffer = self.allocator.buffer(capacity: span.byteCount)
99+
span.withUnsafeBytes {
100+
print(Array($0))
101+
}
102+
buffer.writeBytes(span)
103+
return buffer.readableBytesView
104+
}
99105
}
100106
}
101107

Sources/WasmKitGDBHandler/WasmKitGDBHandler.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,10 @@
219219
if command.arguments.starts(with: "libraries:read:") {
220220
responseKind = .string(
221221
"""
222-
l<library-list>
223-
<library name="\(self.moduleFilePath.string)"><section address="0x4000000000000000"/></library>
222+
l<library-list>\
223+
<library name="\(self.moduleFilePath.string)">\
224+
<section address="0x\(String(DebuggerMemoryCache.executableCodeOffset, radix: 16))"/>\
225+
</library>\
224226
</library-list>
225227
""")
226228
} else {
@@ -232,11 +234,11 @@
232234
guard
233235
argumentsArray.count == 2,
234236
let addressInProtocolSpace = UInt64(hexEncoded: argumentsArray[0]),
235-
let length = Int(hexEncoded: argumentsArray[1])
237+
let length = UInt(hexEncoded: argumentsArray[1])
236238
else { throw Error.unknownReadMemoryArguments }
237239

238240
responseKind = .hexEncodedBinary(
239-
self.memoryCache.readMemory(
241+
try self.memoryCache.readMemory(
240242
debugger: self.debugger,
241243
addressInProtocolSpace: addressInProtocolSpace,
242244
length: length
@@ -320,11 +322,7 @@
320322

321323
responseKind = .hexEncodedBinary(
322324
self.allocator.buffer(
323-
integer: try self.memoryCache.getAddressOfLocal(
324-
debugger: &self.debugger,
325-
frameIndex: Int(frameIndex),
326-
localIndex: Int(localIndex)
327-
),
325+
integer: try self.debugger.getLocal(localIndex: localIndex, frameIndex: frameIndex),
328326
endianness: .little
329327
).readableBytesView
330328
)

Tests/WasmKitGDBHandlerTests/DebuggerMemoryCacheTests.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,27 +42,25 @@
4242

4343
var memoryCache = DebuggerMemoryCache(allocator: .init(), wasmBinary: .init(bytes: bytes))
4444

45-
var breakpointAddress = try debugger.enableBreakpoint(module: module, function: 1)
46-
try debugger.enableBreakpoint(address: breakpointAddress)
45+
_ = try debugger.enableBreakpoint(module: module, function: 1)
4746
try debugger.run()
4847

4948
var localAddress = try memoryCache.getAddressOfLocal(debugger: &debugger, frameIndex: 0, localIndex: 0)
50-
var buffer = ByteBuffer(memoryCache.readMemory(debugger: debugger, addressInProtocolSpace: localAddress, length: 4))
49+
var buffer = ByteBuffer(try memoryCache.readMemory(debugger: debugger, addressInProtocolSpace: localAddress, length: 4))
5150
var value = buffer.readInteger(endianness: .little, as: UInt32.self)
5251
#expect(value == 42)
5352

54-
breakpointAddress = try debugger.enableBreakpoint(
53+
_ = try debugger.enableBreakpoint(
5554
module: module,
5655
function: 2,
5756
// i32.const 2 bytes + local.set 4 bytes
5857
offsetWithinFunction: 6
5958
)
60-
try debugger.enableBreakpoint(address: breakpointAddress)
6159
try debugger.run()
6260
memoryCache.invalidate()
6361

6462
localAddress = try memoryCache.getAddressOfLocal(debugger: &debugger, frameIndex: 0, localIndex: 1)
65-
buffer = ByteBuffer(memoryCache.readMemory(debugger: debugger, addressInProtocolSpace: localAddress, length: 4))
63+
buffer = ByteBuffer(try memoryCache.readMemory(debugger: debugger, addressInProtocolSpace: localAddress, length: 4))
6664
value = buffer.readInteger(endianness: .little, as: UInt32.self)
6765
#expect(value == 24)
6866
}

Tests/WasmKitTests/DebuggerTests.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@
3636
)
3737
"""
3838

39+
private let manyLocalsWAT = """
40+
(module
41+
(func (export "_start") (result i32) (local $x i32)
42+
(i32.const 42)
43+
(i32.const 0)
44+
(i32.eqz)
45+
(drop)
46+
(local.set $x)
47+
(local.get $x)
48+
(call $f)
49+
(call $g)
50+
)
51+
52+
(func $f (param $a i32) (result i32)
53+
(local.get $a)
54+
)
55+
56+
(func $g (param $a i32) (result i32) (local $x i32)
57+
(i32.const 24)
58+
(local.set $x)
59+
(local.get $a)
60+
)
61+
)
62+
"""
63+
3964
@Suite
4065
struct DebuggerTests {
4166
@Test
@@ -99,6 +124,27 @@
99124
#expect([106, 110, 111].binarySearch(nextClosestTo: 107) == 110)
100125
#expect([106, 110, 111, 113, 119, 120, 122, 128, 136].binarySearch(nextClosestTo: 121) == 122)
101126
}
127+
128+
@Test
129+
func getLocal() throws {
130+
let store = Store(engine: Engine())
131+
let bytes = try wat2wasm(manyLocalsWAT)
132+
let module = try parseWasm(bytes: bytes)
133+
var debugger = try Debugger(module: module, store: store, imports: [:])
134+
135+
_ = try debugger.enableBreakpoint(
136+
module: module,
137+
function: 2,
138+
// i32.const 2 bytes + local.set 4 bytes
139+
offsetWithinFunction: 6
140+
)
141+
142+
try debugger.run()
143+
let firstLocal = try debugger.getLocal(localIndex: 0, frameIndex: 0)
144+
#expect(firstLocal == 42)
145+
let secondLocal = try debugger.getLocal(localIndex: 0, frameIndex: 1)
146+
#expect(secondLocal == 24)
147+
}
102148
}
103149

104150
#endif

0 commit comments

Comments
 (0)