Commit faf3d4c8 authored by Clemens Backes's avatar Clemens Backes Committed by Commit Bot

[debug][liftoff] Test inspection and stepping at trap

Add a test to stop at a trap, inspect values, and step from there.
For inspection, we need to spill all registers, which we usually don't
do because the trap never returns, so the values won't be used
afterwards.

R=thibaudm@chromium.org

Bug: v8:10235
Change-Id: Ia1c21aa0faa3ca656e40aae626e8e912eaf2c233
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2169890Reviewed-by: 's avatarThibaud Michaud <thibaudm@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67477}
parent edd5017d
......@@ -278,6 +278,17 @@ class LiftoffCompiler {
using FullDecoder = WasmFullDecoder<validate, LiftoffCompiler>;
// For debugging, we need to spill registers before a trap, to be able to
// inspect them.
struct SpilledRegistersBeforeTrap {
struct Entry {
int offset;
LiftoffRegister reg;
ValueType type;
};
std::vector<Entry> entries;
};
struct OutOfLineCode {
MovableLabel label;
MovableLabel continuation;
......@@ -285,20 +296,30 @@ class LiftoffCompiler {
WasmCodePosition position;
LiftoffRegList regs_to_save;
uint32_t pc; // for trap handler.
// These two pointers will only be used for debug code:
DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder;
std::unique_ptr<SpilledRegistersBeforeTrap> spilled_registers;
// Named constructors:
static OutOfLineCode Trap(
WasmCode::RuntimeStubId s, WasmCodePosition pos, uint32_t pc,
DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) {
DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder,
std::unique_ptr<SpilledRegistersBeforeTrap> spilled_registers) {
DCHECK_LT(0, pos);
return {{}, {}, s, pos, {}, pc, debug_sidetable_entry_builder};
return {{},
{},
s,
pos,
{},
pc,
debug_sidetable_entry_builder,
std::move(spilled_registers)};
}
static OutOfLineCode StackCheck(
WasmCodePosition pos, LiftoffRegList regs,
DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) {
return {{}, {}, WasmCode::kWasmStackGuard, pos,
regs, 0, debug_sidetable_entry_builder};
return {{}, {}, WasmCode::kWasmStackGuard, pos,
regs, 0, debug_sidetable_entry_builder, {}};
}
};
......@@ -662,7 +683,15 @@ class LiftoffCompiler {
return;
}
if (!ool->regs_to_save.is_empty()) __ PushRegisters(ool->regs_to_save);
// We cannot both push and spill registers.
DCHECK(ool->regs_to_save.is_empty() || ool->spilled_registers == nullptr);
if (!ool->regs_to_save.is_empty()) {
__ PushRegisters(ool->regs_to_save);
} else if (V8_UNLIKELY(ool->spilled_registers != nullptr)) {
for (auto& entry : ool->spilled_registers->entries) {
__ Spill(entry.offset, entry.reg, entry.type);
}
}
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(ool->position), true);
......@@ -1830,13 +1859,28 @@ class LiftoffCompiler {
__ cache_state()->Steal(c->else_state->state);
}
std::unique_ptr<SpilledRegistersBeforeTrap> GetSpilledRegistersBeforeTrap() {
if (V8_LIKELY(!for_debugging_)) return nullptr;
// If we are generating debugging code, we really need to spill all
// registers to make them inspectable when stopping at the trap.
auto spilled = std::make_unique<SpilledRegistersBeforeTrap>();
for (uint32_t i = 0, e = __ cache_state()->stack_height(); i < e; ++i) {
auto& slot = __ cache_state()->stack_state[i];
if (!slot.is_reg()) continue;
spilled->entries.push_back(SpilledRegistersBeforeTrap::Entry{
slot.offset(), slot.reg(), slot.type()});
}
return spilled;
}
Label* AddOutOfLineTrap(WasmCodePosition position,
WasmCode::RuntimeStubId stub, uint32_t pc = 0) {
DCHECK(FLAG_wasm_bounds_checks);
out_of_line_code_.push_back(OutOfLineCode::Trap(
stub, position, pc,
RegisterDebugSideTableEntry(DebugSideTableBuilder::kAssumeSpilling)));
RegisterDebugSideTableEntry(DebugSideTableBuilder::kAssumeSpilling),
GetSpilledRegistersBeforeTrap()));
return out_of_line_code_.back().label.get();
}
......
Test scope inspection and stepping after a trap.
Instantiating.
Calling div function.
Paused at:
--- 0 ---
Script wasm://wasm/a9a86c5e byte offset 46: Wasm opcode 0x6d
scope at div (0:46):
locals: "a": 1, "b": 0, "unused": 4711, "local_zero": 0, "local_const_11": 11
--- 1 ---
try {
instance.exports.#div(1, 0, 4711); // traps (div by zero)
} catch (e) {
--- 2 ---
#call_div()
-------------
-> stepInto
Paused at:
--- 0 ---
} catch (e) {
#e.stack; // step target of first pause
}
--- 1 ---
#call_div()
-------------
-> resume
Paused at:
--- 0 ---
Script wasm://wasm/a9a86c5e byte offset 46: Wasm opcode 0x6d
scope at div (0:46):
locals: "a": -2147483648, "b": -1, "unused": 4711, "local_zero": 0, "local_const_11": 11
--- 1 ---
try {
instance.exports.#div(0x80000000, -1, 4711); // traps (unrepresentable)
} catch (e) {
--- 2 ---
#call_div()
-------------
-> stepInto
Paused at:
--- 0 ---
} catch (e) {
#e.stack; // step target of second pause
}
--- 1 ---
#call_div()
-------------
-> resume
Finished.
// 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.
const {session, contextGroup, Protocol} =
InspectorTest.start('Test scope inspection and stepping after a trap.');
session.setupScriptMap();
utils.load('test/mjsunit/wasm/wasm-module-builder.js');
const builder = new WasmModuleBuilder();
// Create a function which computes the div of the first two arguments.
builder.addFunction('div', kSig_i_iii)
.addLocals(
{i32_count: 2}, ['a', 'b', 'unused', 'local_zero', 'local_const_11'])
.addBody([
kExprI32Const, 11, // const 11
kExprLocalSet, 4, // set local #4 ('local_const_11')
kExprLocalGet, 0, // param 0
kExprLocalGet, 1, // param 1
kExprI32DivS // div
])
.exportFunc();
const module_bytes = JSON.stringify(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);
return new WebAssembly.Instance(module);
}
function getShortLocationString(location) {
return `${location.lineNumber}:${location.columnNumber}`;
}
let actions =
['stepInto', 'resume', 'stepInto', 'resume', 'stepInfo', 'resume'];
Protocol.Debugger.onPaused(async msg => {
InspectorTest.log('Paused at:');
for (let [nr, frame] of msg.params.callFrames.entries()) {
InspectorTest.log(`--- ${nr} ---`);
await session.logSourceLocation(frame.location);
if (/^wasm/.test(frame.url)) await printLocalScope(frame);
}
InspectorTest.log('-------------');
let action = actions.shift();
InspectorTest.log(`-> ${action}`);
Protocol.Debugger[action]();
});
function call_div() {
instance.exports.div(0, 1, 4711); // does not trap
try {
instance.exports.div(1, 0, 4711); // traps (div by zero)
} catch (e) {
e.stack; // step target of first pause
}
try {
instance.exports.div(0x80000000, -1, 4711); // traps (unrepresentable)
} catch (e) {
e.stack; // step target of second pause
}
}
contextGroup.addScript(instantiate.toString());
contextGroup.addScript(call_div.toString());
(async function test() {
await Protocol.Debugger.enable();
await Protocol.Debugger.setPauseOnExceptions({state: 'all'});
InspectorTest.log('Instantiating.');
await Protocol.Runtime.evaluate(
{'expression': `const instance = instantiate(${module_bytes});`});
InspectorTest.log('Calling div function.');
await Protocol.Runtime.evaluate({'expression': 'call_div()'});
InspectorTest.log('Finished.');
InspectorTest.completeTest();
})();
async function printLocalScope(frame) {
InspectorTest.log(`scope at ${frame.functionName} (${
frame.location.lineNumber}:${frame.location.columnNumber}):`);
for (let scope of frame.scopeChain) {
if (scope.type != 'local') continue;
let properties = await Protocol.Runtime.getProperties(
{'objectId': scope.object.objectId});
for (let value of properties.result.result) {
let msg = await Protocol.Runtime.getProperties(
{objectId: value.value.objectId});
let prop_str = p => `"${p.name}": ${p.value.value}`;
let value_str = msg.result.result.map(prop_str).join(', ');
InspectorTest.log(` ${value.name}: ${value_str}`);
}
}
}
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