Commit 5fcb414a authored by Clemens Backes's avatar Clemens Backes Committed by Commit Bot

[wasm][debug] Support multi-threaded breakpoints

This adds support for multiple isolates sharing the same module but
setting different breakpoints. This is simulated by having a debugger
test that runs in the "--isolates" variant, i.e. two isolates running
the same test at the same time. Both isolates will set and remove
breakpoints.

The DebugInfo will keep a separate list of breakpoints per isolate, and
when recompiling a function for debugging it will respect all
breakpoints in all isolates.
In order to ensure consistency if multiple isolates are setting or
removing breakpoints simultaneously, we go back to a more coarse-grained
locking scheme, where the DebugInfo lock is held while re-compiling
Liftoff functions.

While recompilation will install the code in the module-global code
table and jump table (and hence all isolates will use it for future
calls), only the stack of the requesting isolate is rewritten to
immediately use new code. This is OK, because other isolates are not
interested in the new breakpoint(s) anyway.
On {SetBreakpoint}, we always need to rewrite the stack of the
requesting isolate though, even if the breakpoint was set before by
another isolate.

Drive-by: Some fixes in SharedFunctionInfo in order to support setting
breakpoints via the Debug mirror.

R=thibaudm@chromium.org

Bug: v8:10359
Change-Id: If659afb273260fc5e8124b4b617fb4322de473c7
Cq-Include-Trybots: luci.v8.try:v8_linux64_tsan_rel
Cq-Include-Trybots: luci.v8.try:v8_linux64_tsan_isolates_rel_ng
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2218059Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarThibaud Michaud <thibaudm@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68096}
parent f3d46392
......@@ -820,7 +820,10 @@ void Debug::ClearAllBreakPoints() {
HeapObject raw_wasm_script;
if (wasm_scripts_with_breakpoints_->Get(idx).GetHeapObject(
&raw_wasm_script)) {
WasmScript::ClearAllBreakpoints(Script::cast(raw_wasm_script));
Script wasm_script = Script::cast(raw_wasm_script);
WasmScript::ClearAllBreakpoints(wasm_script);
wasm_script.wasm_native_module()->GetDebugInfo()->RemoveIsolate(
isolate_);
}
}
wasm_scripts_with_breakpoints_ = Handle<WeakArrayList>{};
......
......@@ -5527,13 +5527,21 @@ int SharedFunctionInfo::StartPosition() const {
if (info.HasPositionInfo()) {
return info.StartPosition();
}
} else if (HasUncompiledData()) {
}
if (HasUncompiledData()) {
// Works with or without scope.
return uncompiled_data().start_position();
} else if (IsApiFunction() || HasBuiltinId()) {
}
if (IsApiFunction() || HasBuiltinId()) {
DCHECK_IMPLIES(HasBuiltinId(), builtin_id() != Builtins::kCompileLazy);
return 0;
}
if (HasWasmExportedFunctionData()) {
WasmInstanceObject instance = wasm_exported_function_data().instance();
int func_index = wasm_exported_function_data().function_index();
auto& function = instance.module()->functions[func_index];
return static_cast<int>(function.code.offset());
}
return kNoSourcePosition;
}
......@@ -5544,13 +5552,21 @@ int SharedFunctionInfo::EndPosition() const {
if (info.HasPositionInfo()) {
return info.EndPosition();
}
} else if (HasUncompiledData()) {
}
if (HasUncompiledData()) {
// Works with or without scope.
return uncompiled_data().end_position();
} else if (IsApiFunction() || HasBuiltinId()) {
}
if (IsApiFunction() || HasBuiltinId()) {
DCHECK_IMPLIES(HasBuiltinId(), builtin_id() != Builtins::kCompileLazy);
return 0;
}
if (HasWasmExportedFunctionData()) {
WasmInstanceObject instance = wasm_exported_function_data().instance();
int func_index = wasm_exported_function_data().function_index();
auto& function = instance.module()->functions[func_index];
return static_cast<int>(function.code.end_offset());
}
return kNoSourcePosition;
}
......
......@@ -495,7 +495,8 @@ int ScriptLinePosition(Handle<Script> script, int line) {
if (line < 0) return -1;
if (script->type() == Script::TYPE_WASM) {
return GetWasmFunctionOffset(script->wasm_native_module()->module(), line);
// Wasm positions are relative to the start of the module.
return 0;
}
Script::InitLineEnds(script->GetIsolate(), script);
......
......@@ -1890,14 +1890,19 @@ void NativeModule::FreeCode(Vector<WasmCode* const> codes) {
// Free the code space.
code_allocator_.FreeCode(codes);
base::MutexGuard guard(&allocation_mutex_);
// Remove debug side tables for all removed code objects.
if (debug_info_) debug_info_->RemoveDebugSideTables(codes);
// Free the {WasmCode} objects. This will also unregister trap handler data.
for (WasmCode* code : codes) {
DCHECK_EQ(1, owned_code_.count(code->instruction_start()));
owned_code_.erase(code->instruction_start());
DebugInfo* debug_info = nullptr;
{
base::MutexGuard guard(&allocation_mutex_);
debug_info = debug_info_.get();
// Free the {WasmCode} objects. This will also unregister trap handler data.
for (WasmCode* code : codes) {
DCHECK_EQ(1, owned_code_.count(code->instruction_start()));
owned_code_.erase(code->instruction_start());
}
}
// Remove debug side tables for all removed code objects, after releasing our
// lock. This is to avoid lock order inversion.
if (debug_info) debug_info->RemoveDebugSideTables(codes);
}
size_t NativeModule::GetNumberOfCodeSpacesForTesting() const {
......
This diff is collapsed.
// 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.
// Test setting and removing breakpoints in Wasm.
// Similar tests exist as inspector tests already, but inspector tests are not
// run concurrently in multiple isolates (see `run-tests.py --isolates`).
load('test/mjsunit/wasm/wasm-module-builder.js');
const builder = new WasmModuleBuilder();
const body_a = [
kExprLocalGet, 0, // local.get i0
kExprI32Const, 1, // i32.const 1
kExprI32Add // i32.add i0 1
];
const fun_a = builder.addFunction('a', kSig_i_i).addBody(body_a).exportFunc();
const body_b = [
kExprLocalGet, 0, // local.get i0
kExprCallFunction, fun_a.index, // call a
kExprLocalGet, 1, // local.get i1
kExprI32Sub, // i32.sub a(i0) i1
kExprCallFunction, fun_a.index, // call a
];
const fun_b = builder.addFunction('b', kSig_i_ii).addBody(body_b).exportFunc();
const instance = builder.instantiate();
Debug = debug.Debug;
const a_localget_offset = body_a.indexOf(kExprLocalGet);
const a_const_offset = body_a.indexOf(kExprI32Const);
const a_add_offset = body_a.indexOf(kExprI32Add);
const b_sub_offset = body_b.indexOf(kExprI32Sub);
const expected_breaks = [
`a:1:${fun_a.body_offset + a_add_offset}`, // break in a at i32.add
`b:1:${fun_b.body_offset + b_sub_offset}`, // break in b at i32.sub
`a:1:${fun_a.body_offset + a_localget_offset}`, // break in a at local.get i0
`a:1:${fun_a.body_offset + a_const_offset}` // break in a at i32.const 1
];
let error;
function onBreak(event, exec_state, data) {
try {
if (event != Debug.DebugEvent.Break) return;
if (error) return;
const pos =
[data.functionName(), data.sourceLine(), data.sourceColumn()].join(':');
const loc = [pos, data.sourceLineText()].join(':');
print(`Break at ${loc}`);
assertTrue(expected_breaks.length > 0, 'expecting more breaks');
const expected_pos = expected_breaks.shift();
assertEquals(expected_pos, pos);
// When we stop in b, we add another breakpoint in a at offset 0 and remove
// the existing breakpoint.
if (data.functionName() == 'b') {
Debug.setBreakPoint(instance.exports.a, 0, a_localget_offset);
Debug.clearBreakPoint(breakpoint_a);
}
// When we stop at a at local.get, we set another breakpoint *in the same
// function*, one instruction later (at i32.const).
if (data.functionName() == 'a' &&
data.sourceColumn() == fun_a.body_offset) {
Debug.setBreakPoint(instance.exports.a, 0, a_const_offset);
}
} catch (e) {
if (!error) error = e;
}
}
Debug.setListener(onBreak);
const breakpoint_a = Debug.setBreakPoint(instance.exports.a, 0, a_add_offset);
const breakpoint_b = Debug.setBreakPoint(instance.exports.b, 0, b_sub_offset);
print('Running b(11).');
const result = instance.exports.b(11);
print('Returned from wasm.');
if (error) throw error;
assertEquals(0, expected_breaks.length, 'all breaks were hit');
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