v8/test/inspector/debugger/wasm-stepping.js
Eric Leese 6ec6ed9cbe Report real module in addition to fake scripts
Currently the inspector reports Wasm in one of two ways:
 - If there is a source map, report one script per Wasm script, with
   bytecode but no source.
 - If there is no source map, report one script per Wasm function, with
   source (Wasm disassembly) but no bytecode.

With this change, behavior with source map is same, but without source
map it will report both ways. This will allow us to change the frontend
to do its own disassembly, allowing us to remove the per-function scripts
in a future change.

Bug: chromium:1013527
Change-Id: I0c559ad08896e8d0da419e3c6ad8d1edff3976fc
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1899782
Reviewed-by: Yang Guo <yangguo@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Eric Leese <leese@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64980}
2019-11-15 09:59:58 +00:00

195 lines
6.6 KiB
JavaScript

// Copyright 2017 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.
let {session, contextGroup, Protocol} = InspectorTest.start('Tests stepping through wasm scripts');
utils.load('test/mjsunit/wasm/wasm-module-builder.js');
let builder = new WasmModuleBuilder();
let func_a_idx =
builder.addFunction('wasm_A', kSig_v_v).addBody([kExprNop, kExprNop]).index;
// wasm_B calls wasm_A <param0> times.
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>
kExprCallFunction, func_a_idx, // -
kExprBr, 1, // continue
kExprEnd, // -
kExprEnd, // break
// clang-format on
])
.exportAs('main');
let module_bytes = builder.toArray();
function instantiate(bytes) {
let buffer = new ArrayBuffer(bytes.length);
let view = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; ++i) {
view[i] = bytes[i] | 0;
}
let module = new WebAssembly.Module(buffer);
// Set global variable.
instance = new WebAssembly.Instance(module);
}
let evalWithUrl = (code, url) => Protocol.Runtime.evaluate(
{'expression': code + '\n//# sourceURL=v8://test/' + url});
Protocol.Debugger.onPaused(handlePaused);
let wasm_B_scriptId;
let step_actions = [
'stepInto', // == stepOver, to call instruction
'stepInto', // into call to wasm_A
'stepOver', // over first nop
'stepOut', // out of wasm_A
'stepOut', // out of wasm_B, stop on breakpoint again
'stepOver', // to call
'stepOver', // over call
'resume', // to next breakpoint (third iteration)
'stepInto', // to call
'stepInto', // into wasm_A
'stepOut', // out to wasm_B
// now step 9 times, until we are in wasm_A again.
'stepInto', 'stepInto', 'stepInto', 'stepInto', 'stepInto', 'stepInto',
'stepInto', 'stepInto', 'stepInto',
// 3 more times, back to wasm_B.
'stepInto', 'stepInto', 'stepInto',
// then just resume.
'resume'
];
for (let action of step_actions) {
InspectorTest.logProtocolCommandCalls('Debugger.' + action)
}
let sources = {};
let urls = {};
let afterTwoSourcesCallback;
(async function Test() {
await Protocol.Debugger.enable();
InspectorTest.log('Installing code an global variable.');
await evalWithUrl('var instance;\n' + instantiate.toString(), 'setup');
InspectorTest.log('Calling instantiate function.');
evalWithUrl(
'instantiate(' + JSON.stringify(module_bytes) + ')', 'callInstantiate');
await waitForTwoWasmScripts();
InspectorTest.log(
'Setting breakpoint on line 7 (on the setlocal before the call), url ' +
urls[wasm_B_scriptId]);
let msg = await Protocol.Debugger.setBreakpoint(
{'location': {'scriptId': wasm_B_scriptId, 'lineNumber': 7}});
printFailure(msg);
InspectorTest.logMessage(msg.result.actualLocation);
await evalWithUrl('instance.exports.main(4)', 'runWasm');
InspectorTest.log('exports.main returned!');
InspectorTest.log('Finished!');
InspectorTest.completeTest();
})();
function printFailure(message) {
if (!message.result) {
InspectorTest.logMessage(message);
}
return message;
}
async function waitForTwoWasmScripts() {
let num = 0;
InspectorTest.log('Waiting for two wasm scripts to be parsed.');
let source_promises = [];
async function getWasmSource(scriptId) {
let msg = await Protocol.Debugger.getScriptSource({scriptId: scriptId});
printFailure(msg);
InspectorTest.log(msg.result.scriptSource);
sources[scriptId] = msg.result.scriptSource;
}
while (num < 2) {
let msg = await Protocol.Debugger.onceScriptParsed();
let url = msg.params.url;
if (!url.startsWith('wasm://') || url.split('/').length != 5) {
InspectorTest.log('Ignoring script with url ' + url);
continue;
}
num += 1;
let scriptId = msg.params.scriptId;
urls[scriptId] = url;
InspectorTest.log('Got wasm script: ' + url);
if (url.substr(-2) == '-1') wasm_B_scriptId = scriptId;
InspectorTest.log('Requesting source for ' + url + '...');
source_promises.push(getWasmSource(scriptId));
}
await Promise.all(source_promises);
}
function printPauseLocation(scriptId, lineNr, columnNr) {
let lines = sources[scriptId].split('\n');
let line = '<illegal line number>';
if (lineNr < lines.length) {
line = lines[lineNr];
if (columnNr < line.length) {
line = line.substr(0, columnNr) + '>' + line.substr(columnNr);
}
}
InspectorTest.log(
'Paused at ' + urls[scriptId] + ':' + lineNr + ':' + columnNr + ': ' +
line);
}
async function getValueString(value) {
if (value.type == 'object') {
let msg = await Protocol.Runtime.callFunctionOn({
objectId: value.objectId,
functionDeclaration: 'function () { return JSON.stringify(this); }'
});
printFailure(msg);
return msg.result.result.value + ' (' + value.description + ')';
}
return value.value + ' (' + value.type + ')';
}
async function dumpProperties(message) {
printFailure(message);
for (let value of message.result.result) {
let value_str = await getValueString(value.value);
InspectorTest.log(' ' + value.name + ': ' + value_str);
}
}
async function dumpScopeChainsOnPause(message) {
for (let frame of message.params.callFrames) {
let functionName = frame.functionName || '(anonymous)';
let lineNumber = frame.location ? frame.location.lineNumber : frame.lineNumber;
let columnNumber = frame.location ? frame.location.columnNumber : frame.columnNumber;
InspectorTest.log(`at ${functionName} (${lineNumber}:${columnNumber}):`);
for (let scope of frame.scopeChain) {
InspectorTest.logObject(' - scope (' + scope.type + '):');
if (scope.type == 'global') {
InspectorTest.logObject(' -- skipped');
} else {
let properties = await Protocol.Runtime.getProperties(
{'objectId': scope.object.objectId});
await dumpProperties(properties);
}
}
}
}
async function handlePaused(msg) {
let loc = msg.params.callFrames[0].location;
printPauseLocation(loc.scriptId, loc.lineNumber, loc.columnNumber);
await dumpScopeChainsOnPause(msg);
let action = step_actions.shift() || 'resume';
await Protocol.Debugger[action]();
}