| // Copyright 2020 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| utils.load('test/inspector/wasm-inspector-test.js'); |
| |
| let {session, contextGroup, Protocol} = |
| InspectorTest.start('Tests stepping through wasm scripts by byte offsets'); |
| session.setupScriptMap(); |
| |
| var builder = new WasmModuleBuilder(); |
| |
| var func_a_idx = |
| builder.addFunction('wasm_A', kSig_v_i).addBody([kExprNop, kExprNop]).index; |
| |
| // wasm_B calls wasm_A <param0> times. |
| var func_b = builder.addFunction('wasm_B', kSig_v_i) |
| .addBody([ |
| // clang-format off |
| kExprLoop, kWasmStmt, // while |
| kExprLocalGet, 0, // - |
| kExprIf, kWasmStmt, // if <param0> != 0 |
| kExprLocalGet, 0, // - |
| kExprI32Const, 1, // - |
| kExprI32Sub, // - |
| kExprLocalSet, 0, // decrease <param0> |
| ...wasmI32Const(1024), // some longer i32 const (2 byte imm) |
| kExprCallFunction, func_a_idx, // - |
| kExprBr, 1, // continue |
| kExprEnd, // - |
| kExprEnd, // break |
| // clang-format on |
| ]) |
| .exportAs('main'); |
| |
| let fact = builder.addFunction('fact', kSig_i_i) |
| .addLocals(kWasmI32, 1) |
| .addBody([ |
| // clang-format off |
| kExprLocalGet, 0, |
| kExprIf, kWasmI32, // if <param0> != 0 |
| kExprLocalGet, 0, |
| kExprI32Const, 1, |
| kExprI32Sub, |
| kExprCallFunction, 2, |
| kExprLocalGet, 0, |
| kExprI32Mul, // return fact(<param0> - 1) * <param0> |
| kExprElse, // else |
| kExprI32Const, 1, // return 1 |
| kExprEnd, |
| // clang-format on |
| ]) |
| .exportAs('fact'); |
| |
| var module_bytes = builder.toArray(); |
| |
| (async function test() { |
| for (const action of ['stepInto', 'stepOver', 'stepOut', 'resume']) |
| InspectorTest.logProtocolCommandCalls('Debugger.' + action); |
| |
| await Protocol.Debugger.enable(); |
| InspectorTest.log('Setting up global instance variable.'); |
| WasmInspectorTest.instantiate(module_bytes); |
| const [, {params: wasmScript}] = await Protocol.Debugger.onceScriptParsed(2); |
| |
| InspectorTest.log('Got wasm script: ' + wasmScript.url); |
| |
| // Set the breakpoint on a non-breakable position. This should resolve to the |
| // next instruction. |
| var offset = func_b.body_offset + 15; |
| InspectorTest.log( |
| `Setting breakpoint on offset ` + offset + ` (should be propagated to ` + |
| (offset + 1) + `, the offset of the call), url ${wasmScript.url}`); |
| let bpmsg = await Protocol.Debugger.setBreakpoint({ |
| location: {scriptId: wasmScript.scriptId, lineNumber: 0, columnNumber: offset} |
| }); |
| |
| InspectorTest.logMessage(bpmsg.result.actualLocation); |
| Protocol.Runtime.evaluate({ expression: 'instance.exports.main(4)' }); |
| await waitForPauseAndStep('stepInto'); // into call to wasm_A |
| await waitForPauseAndStep('stepOver'); // over first nop |
| await waitForPauseAndStep('stepOut'); // out of wasm_A |
| await waitForPauseAndStep('stepOut'); // out of wasm_B, stop on breakpoint |
| await waitForPauseAndStep('stepOver'); // over call |
| await waitForPauseAndStep('stepInto'); // == stepOver br |
| await waitForPauseAndStep('resume'); // to next breakpoint (3rd iteration) |
| await waitForPauseAndStep('stepInto'); // into wasm_A |
| await waitForPauseAndStep('stepOut'); // out to wasm_B |
| // Now step 10 times, until we are in wasm_A again. |
| for (let i = 0; i < 10; ++i) await waitForPauseAndStep('stepInto'); |
| // 3 more times, back to wasm_B. |
| for (let i = 0; i < 3; ++i) await waitForPauseAndStep('stepInto'); |
| // Then just resume. |
| await waitForPauseAndStep('resume'); |
| InspectorTest.log('exports.main returned!'); |
| |
| InspectorTest.log('Test stepping over a recursive call'); |
| // Set a breakpoint at the recursive call and run. |
| offset = fact.body_offset + 9; // Offset of the recursive call instruction. |
| InspectorTest.log( |
| `Setting breakpoint on the recursive call instruction @+` + offset + |
| `, url ${wasmScript.url}`); |
| bpmsg = await Protocol.Debugger.setBreakpoint({ |
| location: {scriptId: wasmScript.scriptId, lineNumber: 0, columnNumber: offset} |
| }); |
| actualLocation = bpmsg.result.actualLocation; |
| InspectorTest.logMessage(actualLocation); |
| Protocol.Runtime.evaluate({ expression: 'instance.exports.fact(4)' }); |
| await waitForPause(); |
| |
| // Remove the breakpoint before stepping over. |
| InspectorTest.log('Removing breakpoint'); |
| let breakpointId = bpmsg.result.breakpointId; |
| await Protocol.Debugger.removeBreakpoint({breakpointId}); |
| await Protocol.Debugger.stepOver(); |
| await waitForPauseAndStep('resume'); |
| InspectorTest.log('Finished!'); |
| })().catch(reason => InspectorTest.log(`Failed: ${reason}`)) |
| .finally(InspectorTest.completeTest); |
| |
| async function waitForPauseAndStep(stepAction) { |
| await waitForPause(); |
| Protocol.Debugger[stepAction](); |
| } |
| |
| async function waitForPause() { |
| const {params: {callFrames}} = await Protocol.Debugger.oncePaused(); |
| await session.logSourceLocation(callFrames[0].location); |
| } |