Commit c6663ef1 authored by clemensh's avatar clemensh Committed by Commit bot

[inspector] Expose GetPossibleBreakpoints for wasm

and add an inspector test for it. Also test that a breakpoint can be
set on each reported position, and that it is also hit during
execution.
This CL also fixes two little things which were uncovered by the new
test.

R=yangguo@chromium.org, titzer@chromium.org, kozyatinskiy@chromium.org
BUG=v8:5822

Review-Url: https://codereview.chromium.org/2655653003
Cr-Commit-Position: refs/heads/master@{#42722}
parent fec83a2a
......@@ -9151,8 +9151,9 @@ bool debug::Script::GetPossibleBreakpoints(
CHECK(!start.IsEmpty());
i::Handle<i::Script> script = Utils::OpenHandle(this);
if (script->type() == i::Script::TYPE_WASM) {
// TODO(clemensh): Return the proper thing once we support wasm breakpoints.
return false;
i::Handle<i::WasmCompiledModule> compiled_module(
i::WasmCompiledModule::cast(script->wasm_compiled_module()));
return compiled_module->GetPossibleBreakpoints(start, end, locations);
}
i::Script::InitLineEnds(script);
......@@ -9248,8 +9249,26 @@ int debug::WasmScript::NumImportedFunctions() const {
return static_cast<int>(compiled_module->module()->num_imported_functions);
}
std::pair<int, int> debug::WasmScript::GetFunctionRange(
int function_index) const {
i::DisallowHeapAllocation no_gc;
i::Handle<i::Script> script = Utils::OpenHandle(this);
DCHECK_EQ(i::Script::TYPE_WASM, script->type());
i::WasmCompiledModule* compiled_module =
i::WasmCompiledModule::cast(script->wasm_compiled_module());
DCHECK_LE(0, function_index);
DCHECK_GT(compiled_module->module()->functions.size(), function_index);
i::wasm::WasmFunction& func =
compiled_module->module()->functions[function_index];
DCHECK_GE(i::kMaxInt, func.code_start_offset);
DCHECK_GE(i::kMaxInt, func.code_end_offset);
return std::make_pair(static_cast<int>(func.code_start_offset),
static_cast<int>(func.code_end_offset));
}
debug::WasmDisassembly debug::WasmScript::DisassembleFunction(
int function_index) const {
i::DisallowHeapAllocation no_gc;
i::Handle<i::Script> script = Utils::OpenHandle(this);
DCHECK_EQ(i::Script::TYPE_WASM, script->type());
i::WasmCompiledModule* compiled_module =
......
......@@ -139,6 +139,8 @@ class WasmScript : public Script {
int NumFunctions() const;
int NumImportedFunctions() const;
std::pair<int, int> GetFunctionRange(int function_index) const;
debug::WasmDisassembly DisassembleFunction(int function_index) const;
};
......
......@@ -473,6 +473,7 @@ V8DebuggerAgentImpl::resolveBreakpoint(const String16& breakpointId,
scriptIterator->second->endLine() < breakpoint.line_number)
return nullptr;
// Translate from protocol location to v8 location for the debugger.
ScriptBreakpoint translatedBreakpoint = breakpoint;
m_debugger->wasmTranslation()->TranslateProtocolLocationToWasmScriptLocation(
&translatedBreakpoint.script_id, &translatedBreakpoint.line_number,
......@@ -484,6 +485,10 @@ V8DebuggerAgentImpl::resolveBreakpoint(const String16& breakpointId,
translatedBreakpoint, &actualLineNumber, &actualColumnNumber);
if (debuggerBreakpointId.isEmpty()) return nullptr;
// Translate back from v8 location to protocol location for the return value.
m_debugger->wasmTranslation()->TranslateWasmScriptLocationToProtocolLocation(
&translatedBreakpoint.script_id, &actualLineNumber, &actualColumnNumber);
m_serverBreakpoints[debuggerBreakpointId] =
std::make_pair(breakpointId, source);
CHECK(!breakpointId.isEmpty());
......
......@@ -6,6 +6,7 @@
#include "src/inspector/inspected-context.h"
#include "src/inspector/string-util.h"
#include "src/inspector/wasm-translation.h"
namespace v8_inspector {
......@@ -69,6 +70,32 @@ String16 calculateHash(const String16& str) {
return hash.toString();
}
void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation,
v8::debug::Location* loc,
const String16& scriptId,
const String16& expectedV8ScriptId) {
if (loc->IsEmpty()) return;
int lineNumber = loc->GetLineNumber();
int columnNumber = loc->GetColumnNumber();
String16 translatedScriptId = scriptId;
wasmTranslation->TranslateProtocolLocationToWasmScriptLocation(
&translatedScriptId, &lineNumber, &columnNumber);
DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8());
*loc = v8::debug::Location(lineNumber, columnNumber);
}
void TranslateV8LocationToProtocolLocation(
WasmTranslation* wasmTranslation, v8::debug::Location* loc,
const String16& scriptId, const String16& expectedProtocolScriptId) {
int lineNumber = loc->GetLineNumber();
int columnNumber = loc->GetColumnNumber();
String16 translatedScriptId = scriptId;
wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
&translatedScriptId, &lineNumber, &columnNumber);
DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8());
*loc = v8::debug::Location(lineNumber, columnNumber);
}
class ActualScript : public V8DebuggerScript {
friend class V8DebuggerScript;
......@@ -171,11 +198,12 @@ class WasmVirtualScript : public V8DebuggerScript {
friend class V8DebuggerScript;
public:
WasmVirtualScript(v8::Isolate* isolate,
WasmVirtualScript(v8::Isolate* isolate, WasmTranslation* wasmTranslation,
v8::Local<v8::debug::WasmScript> script, String16 id,
String16 url, String16 source)
: V8DebuggerScript(isolate, std::move(id), std::move(url)),
m_script(isolate, script) {
m_script(isolate, script),
m_wasmTranslation(wasmTranslation) {
int num_lines = 0;
int last_newline = -1;
size_t next_newline = source.find('\n', last_newline + 1);
......@@ -196,10 +224,31 @@ class WasmVirtualScript : public V8DebuggerScript {
bool getPossibleBreakpoints(
const v8::debug::Location& start, const v8::debug::Location& end,
std::vector<v8::debug::Location>* locations) override {
// TODO(clemensh): Returning false produces the protocol error "Internal
// error". Implement and fix expected output of
// wasm-get-breakable-locations.js.
return false;
v8::HandleScope scope(m_isolate);
v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
String16 v8ScriptId = String16::fromInteger(script->Id());
v8::debug::Location translatedStart = start;
TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart,
scriptId(), v8ScriptId);
v8::debug::Location translatedEnd = end;
if (translatedEnd.IsEmpty()) {
// Stop before the start of the next function.
translatedEnd =
v8::debug::Location(translatedStart.GetLineNumber() + 1, 0);
} else {
TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd,
scriptId(), v8ScriptId);
}
bool success = script->GetPossibleBreakpoints(translatedStart,
translatedEnd, locations);
for (v8::debug::Location& loc : *locations) {
TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId,
scriptId());
}
return success;
}
void resetBlackboxedStateCache() override {}
......@@ -211,6 +260,7 @@ class WasmVirtualScript : public V8DebuggerScript {
}
v8::Global<v8::debug::WasmScript> m_script;
WasmTranslation* m_wasmTranslation;
};
} // namespace
......@@ -223,11 +273,12 @@ std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
}
std::unique_ptr<V8DebuggerScript> V8DebuggerScript::CreateWasm(
v8::Isolate* isolate, v8::Local<v8::debug::WasmScript> underlyingScript,
String16 id, String16 url, String16 source) {
v8::Isolate* isolate, WasmTranslation* wasmTranslation,
v8::Local<v8::debug::WasmScript> underlyingScript, String16 id,
String16 url, String16 source) {
return std::unique_ptr<WasmVirtualScript>(
new WasmVirtualScript(isolate, underlyingScript, std::move(id),
std::move(url), std::move(source)));
new WasmVirtualScript(isolate, wasmTranslation, underlyingScript,
std::move(id), std::move(url), std::move(source)));
}
V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
......
......@@ -39,14 +39,18 @@
namespace v8_inspector {
// Forward declaration.
class WasmTranslation;
class V8DebuggerScript {
public:
static std::unique_ptr<V8DebuggerScript> Create(
v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
bool isLiveEdit);
static std::unique_ptr<V8DebuggerScript> CreateWasm(
v8::Isolate* isolate, v8::Local<v8::debug::WasmScript> underlyingScript,
String16 id, String16 url, String16 source);
v8::Isolate* isolate, WasmTranslation* wasmTranslation,
v8::Local<v8::debug::WasmScript> underlyingScript, String16 id,
String16 url, String16 source);
virtual ~V8DebuggerScript();
......
This diff is collapsed.
......@@ -1653,7 +1653,8 @@ namespace {
int ScriptLinePositionWithOffset(Handle<Script> script, int line, int offset) {
if (line < 0 || offset < 0) return -1;
if (line == 0) return ScriptLinePosition(script, line) + offset;
if (line == 0 || offset == 0)
return ScriptLinePosition(script, line) + offset;
Script::PositionInfo info;
if (!Script::GetPositionInfo(script, offset, &info, Script::NO_OFFSET)) {
......
......@@ -169,6 +169,10 @@ RUNTIME_FUNCTION(Runtime_WasmRunInterpreter) {
CHECK(arg_buffer_obj->IsSmi());
uint8_t* arg_buffer = reinterpret_cast<uint8_t*>(*arg_buffer_obj);
// Set the current isolate's context, saving the previous one.
SaveContext save(isolate);
isolate->set_context(*instance->compiled_module()->native_context());
instance->debug_info()->RunInterpreter(func_index, arg_buffer);
return isolate->heap()->undefined_value();
}
......
Running testFunction...
Script nr 0 parsed. URL: v8://test/setup
Script nr 1 parsed. URL: v8://test/runTestFunction
Script nr 2 parsed. URL: wasm://wasm/wasm-824d794e/wasm-824d794e-0
This is a wasm script (nr 0).
Script nr 3 parsed. URL: wasm://wasm/wasm-824d794e/wasm-824d794e-1
This is a wasm script (nr 1).
Querying breakable locations for all wasm scripts now...
Requesting all breakable locations in wasm script 0
4 breakable location(s):
[0] 2:2 || >nop
[1] 3:2 || >i32.const 12
[2] 4:2 || >set_local 0
[3] 5:0 || >end
Requesting breakable locations in lines [0,3)
1 breakable location(s):
[0] 2:2 || >nop
Requesting breakable locations in lines [4,6)
2 breakable location(s):
[0] 4:2 || >set_local 0
[1] 5:0 || >end
Requesting all breakable locations in wasm script 1
7 breakable location(s):
[0] 1:2 || >get_local 0
[1] 2:2 || >if
[2] 3:4 || >block
[3] 4:6 || >call 0
[4] 5:4 || >end
[5] 6:2 || >end
[6] 7:0 || >end
Requesting breakable locations in lines [0,3)
2 breakable location(s):
[0] 1:2 || >get_local 0
[1] 2:2 || >if
Requesting breakable locations in lines [4,6)
2 breakable location(s):
[0] 4:6 || >call 0
[1] 5:4 || >end
Setting a breakpoint on each breakable location...
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-0:2:2
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-0:3:2
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-0:4:2
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-0:5:0
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-1:1:2
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-1:2:2
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-1:3:4
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-1:4:6
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-1:5:4
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-1:6:2
Success!
Setting at wasm://wasm/wasm-824d794e/wasm-824d794e-1:7:0
Success!
Running wasm code...
Missing breakpoints: 11
Script nr 4 parsed. URL: v8://test/runWasm
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-1:1:2
Missing breakpoints: 10
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-1:2:2
Missing breakpoints: 9
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-1:3:4
Missing breakpoints: 8
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-1:4:6
Missing breakpoints: 7
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-0:2:2
Missing breakpoints: 6
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-0:3:2
Missing breakpoints: 5
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-0:4:2
Missing breakpoints: 4
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-0:5:0
Missing breakpoints: 3
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-1:5:4
Missing breakpoints: 2
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-1:6:2
Missing breakpoints: 1
Stopped at wasm://wasm/wasm-824d794e/wasm-824d794e-1:7:0
Missing breakpoints: 0
Finished!
// Copyright 2016 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.
// Flags: --expose-wasm
load('test/mjsunit/wasm/wasm-constants.js');
load('test/mjsunit/wasm/wasm-module-builder.js');
var builder = new WasmModuleBuilder();
// clang-format off
var func_idx = builder.addFunction('helper', kSig_v_v)
.addLocals({i32_count: 1})
.addBody([
kExprNop,
kExprI32Const, 12,
kExprSetLocal, 0,
]).index;
builder.addFunction('main', kSig_v_i)
.addBody([
kExprGetLocal, 0,
kExprIf, kWasmStmt,
kExprBlock, kWasmStmt,
kExprCallFunction, func_idx,
kExprEnd,
kExprEnd
]).exportAs('main');
// clang-format on
var module_bytes = builder.toArray();
function testFunction(bytes) {
var buffer = new ArrayBuffer(bytes.length);
var view = new Uint8Array(buffer);
for (var i = 0; i < bytes.length; i++) {
view[i] = bytes[i] | 0;
}
var module = new WebAssembly.Module(buffer);
// Set global variable.
instance = new WebAssembly.Instance(module);
}
var evalWithUrl = (code, url) => Protocol.Runtime.evaluate(
{'expression': code + '\n//# sourceURL=v8://test/' + url});
var setupCode = testFunction.toString() + ';\nvar module_bytes = ' +
JSON.stringify(module_bytes) + ';\nvar instance;';
Protocol.Debugger.enable();
Protocol.Debugger.onScriptParsed(handleScriptParsed);
InspectorTest.log('Running testFunction...');
evalWithUrl(setupCode, 'setup')
.then(() => evalWithUrl('testFunction(module_bytes)', 'runTestFunction'))
.then(getBreakableLocationsForAllWasmScripts)
.then(setAllBreakableLocations)
.then(() => InspectorTest.log('Running wasm code...'))
.then(() => (evalWithUrl('instance.exports.main(1)', 'runWasm'), 0))
.then(waitForAllPauses)
.then(() => InspectorTest.log('Finished!'))
.then(InspectorTest.completeTest);
var allBreakableLocations = [];
var urls = {};
var numScripts = 0;
var wasmScripts = [];
function handleScriptParsed(messageObject) {
var scriptId = messageObject.params.scriptId;
var url = messageObject.params.url;
urls[scriptId] = url;
InspectorTest.log('Script nr ' + numScripts + ' parsed. URL: ' + url);
++numScripts;
if (url.startsWith('wasm://')) {
InspectorTest.log('This is a wasm script (nr ' + wasmScripts.length + ').');
wasmScripts.push(scriptId);
}
}
function printFailure(message) {
if (!message.result) {
InspectorTest.logMessage(message);
}
return message;
}
function printBreakableLocations(message, expectedScriptId, source) {
var lines = source.split('\n');
var locations = message.result.locations;
InspectorTest.log(locations.length + ' breakable location(s):');
for (var i = 0; i < locations.length; ++i) {
if (locations[i].scriptId != expectedScriptId) {
InspectorTest.log(
'SCRIPT ID MISMATCH!! ' + locations[i].scriptId + ' != ' +
expectedScriptId);
}
var line = '<illegal line number>';
if (locations[i].lineNumber < lines.length) {
line = lines[locations[i].lineNumber];
if (locations[i].columnNumber < line.length) {
line = line.substr(0, locations[i].columnNumber) + '>' +
line.substr(locations[i].columnNumber);
}
}
InspectorTest.log(
'[' + i + '] ' + locations[i].lineNumber + ':' +
locations[i].columnNumber + ' || ' + line);
}
}
function checkGetBreakableLocations(wasmScriptNr) {
InspectorTest.log(
'Requesting all breakable locations in wasm script ' + wasmScriptNr);
var scriptId = wasmScripts[wasmScriptNr];
var source;
return Protocol.Debugger.getScriptSource({scriptId: scriptId})
.then(msg => source = msg.result.scriptSource)
.then(
() => Protocol.Debugger.getPossibleBreakpoints(
{start: {lineNumber: 0, columnNumber: 0, scriptId: scriptId}}))
.then(printFailure)
.then(msg => (allBreakableLocations.push(...msg.result.locations), msg))
.then(msg => printBreakableLocations(msg, scriptId, source))
.then(
() => InspectorTest.log(
'Requesting breakable locations in lines [0,3)'))
.then(() => Protocol.Debugger.getPossibleBreakpoints({
start: {lineNumber: 0, columnNumber: 0, scriptId: scriptId},
end: {lineNumber: 3, columnNumber: 0, scriptId: scriptId}
}))
.then(printFailure)
.then(msg => printBreakableLocations(msg, scriptId, source))
.then(
() => InspectorTest.log(
'Requesting breakable locations in lines [4,6)'))
.then(() => Protocol.Debugger.getPossibleBreakpoints({
start: {lineNumber: 4, columnNumber: 0, scriptId: scriptId},
end: {lineNumber: 6, columnNumber: 0, scriptId: scriptId}
}))
.then(printFailure)
.then(msg => printBreakableLocations(msg, scriptId, source));
}
function getBreakableLocationsForAllWasmScripts() {
InspectorTest.log('Querying breakable locations for all wasm scripts now...');
var promise = Promise.resolve();
for (var wasmScriptNr = 0; wasmScriptNr < wasmScripts.length;
++wasmScriptNr) {
promise = promise.then(checkGetBreakableLocations.bind(null, wasmScriptNr));
}
return promise;
}
function locationMatches(loc1, loc2) {
return loc1.scriptId == loc2.scriptId && loc1.lineNumber == loc2.lineNumber &&
loc1.columnNumber == loc2.columnNumber;
}
function locationStr(loc) {
return urls[loc.scriptId] + ':' + loc.lineNumber + ':' + loc.columnNumber;
}
function setBreakpoint(loc) {
InspectorTest.log('Setting at ' + locationStr(loc));
function check(msg) {
if (locationMatches(loc, msg.result.actualLocation)) {
InspectorTest.log("Success!");
} else {
InspectorTest.log("Mismatch!");
InspectorTest.logMessage(msg);
}
}
return Protocol.Debugger.setBreakpoint({'location': loc})
.then(printFailure)
.then(check);
}
function setAllBreakableLocations() {
InspectorTest.log('Setting a breakpoint on each breakable location...');
var promise = Promise.resolve();
for (var loc of allBreakableLocations) {
promise = promise.then(setBreakpoint.bind(null, loc));
}
return promise;
}
function removePausedLocation(msg) {
var topLocation = msg.params.callFrames[0].location;
InspectorTest.log('Stopped at ' + locationStr(topLocation));
for (var i = 0; i < allBreakableLocations.length; ++i) {
if (locationMatches(topLocation, allBreakableLocations[i])) {
allBreakableLocations.splice(i, 1);
--i;
}
}
}
function waitForAllPauses() {
InspectorTest.log('Missing breakpoints: ' + allBreakableLocations.length);
if (allBreakableLocations.length == 0) return;
return Protocol.Debugger.oncePaused()
.then(removePausedLocation)
.then(Protocol.Debugger.resume())
.then(waitForAllPauses);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment