inspector: added Debugger.setInstrumentationBreakpoint method

There are two possible type:
- scriptParsed - breakpoint for any script,
- scriptWithSourceMapParsed - breakpoint for script with
  sourceMappingURL.

When one of the breakpoints is set then for each matched script
we add breakpoint on call to top level function of that script.

Node: https://github.com/nodejs/node/issues/24687

R=dgozman@chromium.org

Bug: chromium:887384,chromium:724793,chromium:882909
Change-Id: I9c08b2a2a5ba7006adfedd85fc92ae191517af00
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1354245
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
Reviewed-by: Alexei Filippov <alph@chromium.org>
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61353}
This commit is contained in:
Aleksei Koziatinskii 2019-04-29 13:24:35 -07:00 committed by Commit Bot
parent 89ed6b764a
commit b901591015
12 changed files with 358 additions and 21 deletions

View File

@ -9388,6 +9388,19 @@ bool debug::Script::SetBreakpoint(v8::Local<v8::String> condition,
return true;
}
bool debug::Script::SetBreakpointOnScriptEntry(BreakpointId* id) const {
i::Handle<i::Script> script = Utils::OpenHandle(this);
i::Isolate* isolate = script->GetIsolate();
i::SharedFunctionInfo::ScriptIterator it(isolate, *script);
for (i::SharedFunctionInfo sfi = it.Next(); !sfi.is_null(); sfi = it.Next()) {
if (sfi->is_toplevel()) {
return isolate->debug()->SetBreakpointForFunction(
handle(sfi, isolate), isolate->factory()->empty_string(), id);
}
}
return false;
}
void debug::RemoveBreakpoint(Isolate* v8_isolate, BreakpointId id) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i::HandleScope handle_scope(isolate);
@ -9781,8 +9794,8 @@ bool debug::SetFunctionBreakpoint(v8::Local<v8::Function> function,
i::Handle<i::String> condition_string =
condition.IsEmpty() ? isolate->factory()->empty_string()
: Utils::OpenHandle(*condition);
return isolate->debug()->SetBreakpointForFunction(jsfunction,
condition_string, id);
return isolate->debug()->SetBreakpointForFunction(
handle(jsfunction->shared(), isolate), condition_string, id);
}
debug::PostponeInterruptsScope::PostponeInterruptsScope(v8::Isolate* isolate)

View File

@ -147,6 +147,7 @@ class V8_EXPORT_PRIVATE Script {
LiveEditResult* result) const;
bool SetBreakpoint(v8::Local<v8::String> condition, debug::Location* location,
BreakpointId* id) const;
bool SetBreakpointOnScriptEntry(BreakpointId* id) const;
};
// Specialization for wasm Scripts.

View File

@ -589,13 +589,12 @@ bool Debug::CheckBreakPoint(Handle<BreakPoint> break_point,
return result->BooleanValue(isolate_);
}
bool Debug::SetBreakPoint(Handle<JSFunction> function,
bool Debug::SetBreakpoint(Handle<SharedFunctionInfo> shared,
Handle<BreakPoint> break_point,
int* source_position) {
HandleScope scope(isolate_);
// Make sure the function is compiled and has set up the debug info.
Handle<SharedFunctionInfo> shared(function->shared(), isolate_);
if (!EnsureBreakInfo(shared)) return false;
PrepareFunctionForDebugExecution(shared);
@ -750,13 +749,13 @@ int Debug::GetFunctionDebuggingId(Handle<JSFunction> function) {
return id;
}
bool Debug::SetBreakpointForFunction(Handle<JSFunction> function,
bool Debug::SetBreakpointForFunction(Handle<SharedFunctionInfo> shared,
Handle<String> condition, int* id) {
*id = ++thread_local_.last_breakpoint_id_;
Handle<BreakPoint> breakpoint =
isolate_->factory()->NewBreakPoint(*id, condition);
int source_position = 0;
return SetBreakPoint(function, breakpoint, &source_position);
return SetBreakpoint(shared, breakpoint, &source_position);
}
void Debug::RemoveBreakpoint(int id) {

View File

@ -227,7 +227,7 @@ class V8_EXPORT_PRIVATE Debug {
Handle<FixedArray> GetLoadedScripts();
// Break point handling.
bool SetBreakPoint(Handle<JSFunction> function,
bool SetBreakpoint(Handle<SharedFunctionInfo> shared,
Handle<BreakPoint> break_point, int* source_position);
void ClearBreakPoint(Handle<BreakPoint> break_point);
void ChangeBreakOnException(ExceptionBreakType type, bool enable);
@ -235,7 +235,7 @@ class V8_EXPORT_PRIVATE Debug {
bool SetBreakPointForScript(Handle<Script> script, Handle<String> condition,
int* source_position, int* id);
bool SetBreakpointForFunction(Handle<JSFunction> function,
bool SetBreakpointForFunction(Handle<SharedFunctionInfo> shared,
Handle<String> condition, int* id);
void RemoveBreakpoint(int id);

View File

@ -317,6 +317,17 @@ domain Debugger
# Location this breakpoint resolved into.
Location actualLocation
# Sets instrumentation breakpoint.
command setInstrumentationBreakpoint
parameters
# Instrumentation name.
enum instrumentation
beforeScriptExecution
beforeScriptWithSourceMapExecution
returns
# Id of the created breakpoint for further reference.
BreakpointId breakpointId
# Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this
# command is issued, all existing parsed scripts will have breakpoints resolved and returned in
# `locations` property. Further matching script parsing will result in subsequent
@ -449,16 +460,17 @@ domain Debugger
array of CallFrame callFrames
# Pause reason.
enum reason
XHR
ambiguous
assert
debugCommand
DOM
EventListener
exception
assert
debugCommand
promiseRejection
instrumentation
OOM
other
ambiguous
promiseRejection
XHR
# Object containing break-specific auxiliary properties.
optional object data
# Hit breakpoints IDs

View File

@ -31,10 +31,13 @@ using protocol::Array;
using protocol::Maybe;
using protocol::Debugger::BreakpointId;
using protocol::Debugger::CallFrame;
using protocol::Runtime::ExceptionDetails;
using protocol::Runtime::ScriptId;
using protocol::Runtime::RemoteObject;
using protocol::Debugger::Scope;
using protocol::Runtime::ExceptionDetails;
using protocol::Runtime::RemoteObject;
using protocol::Runtime::ScriptId;
namespace InstrumentationEnum =
protocol::Debugger::SetInstrumentationBreakpoint::InstrumentationEnum;
namespace DebuggerAgentState {
static const char pauseOnExceptionsState[] = "pauseOnExceptionsState";
@ -47,6 +50,7 @@ static const char breakpointsByRegex[] = "breakpointsByRegex";
static const char breakpointsByUrl[] = "breakpointsByUrl";
static const char breakpointsByScriptHash[] = "breakpointsByScriptHash";
static const char breakpointHints[] = "breakpointHints";
static const char instrumentationBreakpoints[] = "instrumentationBreakpoints";
} // namespace DebuggerAgentState
@ -80,7 +84,8 @@ enum class BreakpointType {
kByScriptId,
kDebugCommand,
kMonitorCommand,
kBreakpointAtEntry
kBreakpointAtEntry,
kInstrumentationBreakpoint
};
String16 generateBreakpointId(BreakpointType type,
@ -106,6 +111,15 @@ String16 generateBreakpointId(BreakpointType type,
return builder.toString();
}
String16 generateInstrumentationBreakpointId(const String16& instrumentation) {
String16Builder builder;
builder.appendNumber(
static_cast<int>(BreakpointType::kInstrumentationBreakpoint));
builder.append(':');
builder.append(instrumentation);
return builder.toString();
}
bool parseBreakpointId(const String16& breakpointId, BreakpointType* type,
String16* scriptSelector = nullptr,
int* lineNumber = nullptr, int* columnNumber = nullptr) {
@ -114,14 +128,15 @@ bool parseBreakpointId(const String16& breakpointId, BreakpointType* type,
int rawType = breakpointId.substring(0, typeLineSeparator).toInteger();
if (rawType < static_cast<int>(BreakpointType::kByUrl) ||
rawType > static_cast<int>(BreakpointType::kBreakpointAtEntry)) {
rawType > static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) {
return false;
}
if (type) *type = static_cast<BreakpointType>(rawType);
if (rawType == static_cast<int>(BreakpointType::kDebugCommand) ||
rawType == static_cast<int>(BreakpointType::kMonitorCommand) ||
rawType == static_cast<int>(BreakpointType::kBreakpointAtEntry)) {
// The script and source position is not encoded in this case.
rawType == static_cast<int>(BreakpointType::kBreakpointAtEntry) ||
rawType == static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) {
// The script and source position are not encoded in this case.
return true;
}
@ -356,6 +371,7 @@ Response V8DebuggerAgentImpl::disable() {
m_state->remove(DebuggerAgentState::breakpointsByUrl);
m_state->remove(DebuggerAgentState::breakpointsByScriptHash);
m_state->remove(DebuggerAgentState::breakpointHints);
m_state->remove(DebuggerAgentState::instrumentationBreakpoints);
m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState,
v8::debug::NoBreakOnException);
@ -580,6 +596,20 @@ Response V8DebuggerAgentImpl::setBreakpointOnFunctionCall(
return Response::OK();
}
Response V8DebuggerAgentImpl::setInstrumentationBreakpoint(
const String16& instrumentation, String16* outBreakpointId) {
if (!enabled()) return Response::Error(kDebuggerNotEnabled);
String16 breakpointId = generateInstrumentationBreakpointId(instrumentation);
protocol::DictionaryValue* breakpoints = getOrCreateObject(
m_state, DebuggerAgentState::instrumentationBreakpoints);
if (breakpoints->get(breakpointId)) {
return Response::Error("Instrumentation breakpoint is already enabled.");
}
breakpoints->setBoolean(breakpointId, true);
*outBreakpointId = breakpointId;
return Response::OK();
}
Response V8DebuggerAgentImpl::removeBreakpoint(const String16& breakpointId) {
if (!enabled()) return Response::Error(kDebuggerNotEnabled);
BreakpointType type;
@ -606,6 +636,10 @@ Response V8DebuggerAgentImpl::removeBreakpoint(const String16& breakpointId) {
case BreakpointType::kByUrlRegex:
breakpoints = m_state->getObject(DebuggerAgentState::breakpointsByRegex);
break;
case BreakpointType::kInstrumentationBreakpoint:
breakpoints =
m_state->getObject(DebuggerAgentState::instrumentationBreakpoints);
break;
default:
break;
}
@ -1496,6 +1530,40 @@ void V8DebuggerAgentImpl::didParseSource(
m_frontend.breakpointResolved(breakpointId, std::move(location));
}
}
setScriptInstrumentationBreakpointIfNeeded(scriptRef);
}
void V8DebuggerAgentImpl::setScriptInstrumentationBreakpointIfNeeded(
V8DebuggerScript* scriptRef) {
protocol::DictionaryValue* breakpoints =
m_state->getObject(DebuggerAgentState::instrumentationBreakpoints);
if (!breakpoints) return;
bool isBlackboxed = isFunctionBlackboxed(
scriptRef->scriptId(), v8::debug::Location(0, 0),
v8::debug::Location(scriptRef->endLine(), scriptRef->endColumn()));
if (isBlackboxed) return;
String16 sourceMapURL = scriptRef->sourceMappingURL();
String16 breakpointId = generateInstrumentationBreakpointId(
InstrumentationEnum::BeforeScriptExecution);
if (!breakpoints->get(breakpointId)) {
if (sourceMapURL.isEmpty()) return;
breakpointId = generateInstrumentationBreakpointId(
InstrumentationEnum::BeforeScriptWithSourceMapExecution);
if (!breakpoints->get(breakpointId)) return;
}
v8::debug::BreakpointId debuggerBreakpointId;
if (!scriptRef->setBreakpointOnRun(&debuggerBreakpointId)) return;
std::unique_ptr<protocol::DictionaryValue> data =
protocol::DictionaryValue::create();
data->setString("url", scriptRef->sourceURL());
data->setString("scriptId", scriptRef->scriptId());
if (!sourceMapURL.isEmpty()) data->setString("sourceMapURL", sourceMapURL);
m_breakpointsOnScriptRun[debuggerBreakpointId] = std::move(data);
m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId;
m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back(
debuggerBreakpointId);
}
void V8DebuggerAgentImpl::didPause(
@ -1539,6 +1607,14 @@ void V8DebuggerAgentImpl::didPause(
std::unique_ptr<Array<String16>> hitBreakpointIds = Array<String16>::create();
for (const auto& id : hitBreakpoints) {
auto it = m_breakpointsOnScriptRun.find(id);
if (it != m_breakpointsOnScriptRun.end()) {
hitReasons.push_back(std::make_pair(
protocol::Debugger::Paused::ReasonEnum::Instrumentation,
std::move(it->second)));
m_breakpointsOnScriptRun.erase(it);
continue;
}
auto breakpointIterator = m_debuggerBreakpointIdToBreakpointId.find(id);
if (breakpointIterator == m_debuggerBreakpointIdToBreakpointId.end()) {
continue;

View File

@ -60,6 +60,8 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Response setBreakpointOnFunctionCall(const String16& functionObjectId,
Maybe<String16> optionalCondition,
String16* outBreakpointId) override;
Response setInstrumentationBreakpoint(const String16& instrumentation,
String16* outBreakpointId) override;
Response removeBreakpoint(const String16& breakpointId) override;
Response continueToLocation(std::unique_ptr<protocol::Debugger::Location>,
Maybe<String16> targetCallFrames) override;
@ -184,6 +186,8 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
bool isPaused() const;
void setScriptInstrumentationBreakpointIfNeeded(V8DebuggerScript* script);
using ScriptsMap =
std::unordered_map<String16, std::unique_ptr<V8DebuggerScript>>;
using BreakpointIdToDebuggerBreakpointIdsMap =
@ -201,6 +205,9 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
ScriptsMap m_scripts;
BreakpointIdToDebuggerBreakpointIdsMap m_breakpointIdToDebuggerBreakpointIds;
DebuggerBreakpointIdToBreakpointIdMap m_debuggerBreakpointIdToBreakpointId;
std::unordered_map<v8::debug::BreakpointId,
std::unique_ptr<protocol::DictionaryValue>>
m_breakpointsOnScriptRun;
size_t m_maxScriptCacheSize = 0;
size_t m_cachedScriptSize = 0;

View File

@ -235,6 +235,11 @@ class ActualScript : public V8DebuggerScript {
id);
}
bool setBreakpointOnRun(int* id) const override {
v8::HandleScope scope(m_isolate);
return script()->SetBreakpointOnScriptEntry(id);
}
const String16& hash() const override {
if (!m_hash.isEmpty()) return m_hash;
v8::HandleScope scope(m_isolate);
@ -424,6 +429,8 @@ class WasmVirtualScript : public V8DebuggerScript {
return true;
}
bool setBreakpointOnRun(int*) const override { return false; }
const String16& hash() const override {
if (m_hash.isEmpty()) {
m_hash = m_wasmTranslation->GetHash(m_id, m_functionIndex);

View File

@ -90,6 +90,7 @@ class V8DebuggerScript {
virtual bool setBreakpoint(const String16& condition,
v8::debug::Location* location, int* id) const = 0;
virtual void MakeWeak() = 0;
virtual bool setBreakpointOnRun(int* id) const = 0;
protected:
V8DebuggerScript(v8::Isolate*, String16 id, String16 url);

View File

@ -94,7 +94,8 @@ static i::Handle<i::BreakPoint> SetBreakPoint(v8::Local<v8::Function> fun,
i::Handle<i::BreakPoint> break_point =
isolate->factory()->NewBreakPoint(++break_point_index, condition_string);
debug->SetBreakPoint(function, break_point, &position);
debug->SetBreakpoint(handle(function->shared(), isolate), break_point,
&position);
return break_point;
}

View File

@ -0,0 +1,89 @@
Debugger.setInstrumentationBreakpoint
Running test: testSetTwice
set breakpoint..
{
breakpointId : <breakpointId>
}
set breakpoint again..
{
error : {
code : -32000
message : Instrumentation breakpoint is already enabled.
}
id : <messageId>
}
remove breakpoint..
{
id : <messageId>
result : {
}
}
Running test: testScriptParsed
set breakpoint and evaluate script..
paused with reason: instrumentation
{
scriptId : <scriptId>
url : foo.js
}
set breakpoint and evaluate script with sourceMappingURL..
paused with reason: instrumentation
{
scriptId : <scriptId>
sourceMapURL : map.js
url : foo.js
}
remove breakpoint..
{
id : <messageId>
result : {
}
}
evaluate script again..
Running test: testScriptWithSourceMapParsed
set breakpoint for scriptWithSourceMapParsed..
evaluate script without sourceMappingURL..
evaluate script with sourceMappingURL..
paused with reason: instrumentation
{
scriptId : <scriptId>
sourceMapURL : map.js
url : foo.js
}
remove breakpoint..
{
id : <messageId>
result : {
}
}
evaluate script without sourceMappingURL..
evaluate script with sourceMappingURL..
Running test: testBlackboxing
set breakpoint and evaluate blackboxed script..
evaluate not blackboxed script..
paused with reason: instrumentation
{
scriptId : <scriptId>
url : bar.js
}
evaluate blackboxed script that contains not blackboxed one..
paused with reason: instrumentation
{
scriptId : <scriptId>
url : bar.js
}
Running test: testCompileFirstRunLater
set breakpoint for scriptWithSourceMapParsed..
compile script with sourceMappingURL..
evaluate script without sourceMappingURL..
run previously compiled script with sourceMappingURL..
paused with reason: instrumentation
{
scriptId : <scriptId>
sourceMapURL : boo.js
url : foo.js
}

View File

@ -0,0 +1,131 @@
// Copyright 2019 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.
const { session, contextGroup, Protocol } = InspectorTest.start(
'Debugger.setInstrumentationBreakpoint');
InspectorTest.runAsyncTestSuite([
async function testSetTwice() {
await Protocol.Debugger.enable();
const { result : firstResult } = await Protocol.Debugger.setInstrumentationBreakpoint({
instrumentation: 'beforeScriptExecution'
});
InspectorTest.log('set breakpoint..');
InspectorTest.logMessage(firstResult);
InspectorTest.log('set breakpoint again..');
InspectorTest.logMessage(await Protocol.Debugger.setInstrumentationBreakpoint({
instrumentation: 'beforeScriptExecution'
}));
InspectorTest.log('remove breakpoint..');
InspectorTest.logMessage(await Protocol.Debugger.removeBreakpoint({
breakpointId: firstResult.breakpointId
}));
await Protocol.Debugger.disable();
},
async function testScriptParsed() {
await Protocol.Debugger.enable();
InspectorTest.log('set breakpoint and evaluate script..');
const { result : firstResult } = await Protocol.Debugger.setInstrumentationBreakpoint({
instrumentation: 'beforeScriptExecution'
});
Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js'});
{
const { params: { reason, data } } = await Protocol.Debugger.oncePaused();
InspectorTest.log(`paused with reason: ${reason}`);
InspectorTest.logMessage(data);
}
await Protocol.Debugger.resume();
InspectorTest.log('set breakpoint and evaluate script with sourceMappingURL..');
Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js\n//# sourceMappingURL=map.js'});
{
const { params: { reason, data } } = await Protocol.Debugger.oncePaused();
InspectorTest.log(`paused with reason: ${reason}`);
InspectorTest.logMessage(data);
}
InspectorTest.log('remove breakpoint..');
InspectorTest.logMessage(await Protocol.Debugger.removeBreakpoint({
breakpointId: firstResult.breakpointId
}));
InspectorTest.log('evaluate script again..');
await Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js'});
await Protocol.Debugger.disable();
},
async function testScriptWithSourceMapParsed() {
await Protocol.Debugger.enable();
InspectorTest.log('set breakpoint for scriptWithSourceMapParsed..');
const { result : firstResult } = await Protocol.Debugger.setInstrumentationBreakpoint({
instrumentation: 'beforeScriptWithSourceMapExecution'
});
InspectorTest.log('evaluate script without sourceMappingURL..')
await Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js'});
InspectorTest.log('evaluate script with sourceMappingURL..')
Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js\n//# sourceMappingURL=map.js'});
{
const { params: { reason, data } } = await Protocol.Debugger.oncePaused();
InspectorTest.log(`paused with reason: ${reason}`);
InspectorTest.logMessage(data);
}
InspectorTest.log('remove breakpoint..')
InspectorTest.logMessage(await Protocol.Debugger.removeBreakpoint({
breakpointId: firstResult.breakpointId
}));
InspectorTest.log('evaluate script without sourceMappingURL..')
await Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js'});
InspectorTest.log('evaluate script with sourceMappingURL..')
await Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js\n//# sourceMappingURL=map.js'});
await Protocol.Debugger.disable();
},
async function testBlackboxing() {
await Protocol.Debugger.enable();
await Protocol.Debugger.setBlackboxPatterns({patterns: ['foo\.js']});
InspectorTest.log('set breakpoint and evaluate blackboxed script..');
const { result : firstResult } = await Protocol.Debugger.setInstrumentationBreakpoint({
instrumentation: 'beforeScriptExecution'
});
await Protocol.Runtime.evaluate({expression: '//# sourceURL=foo.js'});
InspectorTest.log('evaluate not blackboxed script..');
Protocol.Runtime.evaluate({expression: '//# sourceURL=bar.js'});
{
const { params: { reason, data } } = await Protocol.Debugger.oncePaused();
InspectorTest.log(`paused with reason: ${reason}`);
InspectorTest.logMessage(data);
}
await Protocol.Debugger.resume();
InspectorTest.log('evaluate blackboxed script that contains not blackboxed one..');
Protocol.Runtime.evaluate({expression: `eval('//# sourceURL=bar.js')//# sourceURL=foo.js`});
{
const { params: { reason, data } } = await Protocol.Debugger.oncePaused();
InspectorTest.log(`paused with reason: ${reason}`);
InspectorTest.logMessage(data);
}
await Protocol.Debugger.resume();
await Protocol.Debugger.disable();
},
async function testCompileFirstRunLater() {
await Protocol.Runtime.enable();
await Protocol.Debugger.enable();
InspectorTest.log('set breakpoint for scriptWithSourceMapParsed..');
const { result : firstResult } = await Protocol.Debugger.setInstrumentationBreakpoint({
instrumentation: 'beforeScriptWithSourceMapExecution'
});
InspectorTest.log('compile script with sourceMappingURL..');
const { result: { scriptId } } = await Protocol.Runtime.compileScript({
expression: '//# sourceMappingURL=boo.js', sourceURL: 'foo.js', persistScript: true });
InspectorTest.log('evaluate script without sourceMappingURL..');
await Protocol.Runtime.evaluate({ expression: '' });
InspectorTest.log('run previously compiled script with sourceMappingURL..');
Protocol.Runtime.runScript({ scriptId });
{
const { params: { reason, data } } = await Protocol.Debugger.oncePaused();
InspectorTest.log(`paused with reason: ${reason}`);
InspectorTest.logMessage(data);
}
await Protocol.Debugger.disable();
await Protocol.Runtime.disable();
}
]);