Commit 6e70425b authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[wasm] Provide scope information via inspector

This CL implements the proposed change to show information about
WebAssembly values and call frames via the inspector interface.
Each interpreted WebAssembly frame will have two scopes: A global scope
showing information about the memory (to be extended for globals), and
a local scope showing information about parameters, local variables, and
stack values.
Names of local variables will be added later.

R=ahaas@chromium.org, yangguo@chromium.org
BUG=v8:6245,v8:5822

Change-Id: I0a35fddd0a353933c86adf62083233b08098a2c7
Reviewed-on: https://chromium-review.googlesource.com/474865
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarAleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44633}
parent 68b047d0
......@@ -1833,6 +1833,10 @@ int WasmInterpreterEntryFrame::position() const {
return FrameSummary::GetBottom(this).AsWasmInterpreted().SourcePosition();
}
Object* WasmInterpreterEntryFrame::context() const {
return wasm_instance()->compiled_module()->ptr_to_native_context();
}
Address WasmInterpreterEntryFrame::GetCallerStackPointer() const {
return fp() + ExitFrameConstants::kCallerSPOffset;
}
......
......@@ -1350,6 +1350,7 @@ class WasmInterpreterEntryFrame final : public StandardFrame {
WasmInstanceObject* wasm_instance() const;
Script* script() const override;
int position() const override;
Object* context() const override;
static WasmInterpreterEntryFrame* cast(StackFrame* frame) {
DCHECK(frame->is_wasm_interpreter_entry());
......
......@@ -402,10 +402,9 @@ DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror)
*/
function contextId()
{
var mirror = ensureFuncMirror();
var context = mirror.context();
if (context && context.data())
return Number(context.data());
var context =
ensureFuncMirror().context() || ensureScriptMirror().context();
if (context && context.data()) return Number(context.data());
return 0;
}
......
......@@ -355,6 +355,9 @@ ScriptMirror.prototype.value = function() {}
/** @return {number} */
ScriptMirror.prototype.id = function() {}
/** @return {ContextMirror} */
ScriptMirror.prototype.context = function() {}
/**
* @param {number} position
* @param {boolean=} includeResourceOffset
......
......@@ -551,7 +551,7 @@ RUNTIME_FUNCTION(Runtime_GetFrameDetails) {
// bit 0: invoked in the debugger context.
// bit 1: optimized frame.
// bit 2: inlined in optimized frame
int flags = 0;
int flags = inlined_frame_index << 2;
if (*save->context() == *isolate->debug()->debug_context()) {
flags |= 1 << 0;
}
......@@ -830,7 +830,7 @@ RUNTIME_FUNCTION(Runtime_GetAllScopesDetails) {
CHECK(isolate->debug()->CheckExecutionState(break_id));
CONVERT_SMI_ARG_CHECKED(wrapped_id, 1);
CONVERT_NUMBER_CHECKED(int, inlined_jsframe_index, Int32, args[2]);
CONVERT_NUMBER_CHECKED(int, inlined_frame_index, Int32, args[2]);
ScopeIterator::Option option = ScopeIterator::DEFAULT;
if (args.length() == 4) {
......@@ -842,9 +842,19 @@ RUNTIME_FUNCTION(Runtime_GetAllScopesDetails) {
StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id);
StackTraceFrameIterator frame_it(isolate, id);
StandardFrame* frame = frame_it.frame();
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
List<Handle<JSObject> > result(4);
// Handle wasm frames specially. They provide exactly two scopes (global /
// local).
if (frame->is_wasm_interpreter_entry()) {
Handle<WasmDebugInfo> debug_info(
WasmInterpreterEntryFrame::cast(frame)->wasm_instance()->debug_info(),
isolate);
return *WasmDebugInfo::GetScopeDetails(debug_info, frame->fp(),
inlined_frame_index);
}
FrameInspector frame_inspector(frame, inlined_frame_index, isolate);
List<Handle<JSObject>> result(4);
ScopeIterator it(isolate, &frame_inspector, option);
for (; !it.Done(); it.Next()) {
Handle<JSObject> details;
......
......@@ -7,6 +7,7 @@
#include "src/assembler-inl.h"
#include "src/assert-scope.h"
#include "src/compiler/wasm-compiler.h"
#include "src/debug/debug-scopes.h"
#include "src/debug/debug.h"
#include "src/factory.h"
#include "src/frames-inl.h"
......@@ -24,6 +25,42 @@ using namespace v8::internal::wasm;
namespace {
template <bool internal, typename... Args>
Handle<String> PrintFToOneByteString(Isolate* isolate, const char* format,
Args... args) {
// Maximum length of a formatted value name ("param#%d", "local#%d",
// "global#%d").
constexpr int kMaxStrLen = 18;
EmbeddedVector<char, kMaxStrLen> value;
int len = SNPrintF(value, format, args...);
CHECK(len > 0 && len < value.length());
Vector<uint8_t> name = Vector<uint8_t>::cast(value.SubVector(0, len));
return internal
? isolate->factory()->InternalizeOneByteString(name)
: isolate->factory()->NewStringFromOneByte(name).ToHandleChecked();
}
Handle<Object> WasmValToValueObject(Isolate* isolate, WasmVal value) {
switch (value.type) {
case kWasmI32:
if (Smi::IsValid(value.to<int32_t>()))
return handle(Smi::FromInt(value.to<int32_t>()), isolate);
return PrintFToOneByteString<false>(isolate, "%d", value.to<int32_t>());
case kWasmI64:
if (Smi::IsValid(value.to<int64_t>()))
return handle(Smi::FromIntptr(value.to<int64_t>()), isolate);
return PrintFToOneByteString<false>(isolate, "%" PRId64,
value.to<int64_t>());
case kWasmF32:
return isolate->factory()->NewNumber(value.to<float>());
case kWasmF64:
return isolate->factory()->NewNumber(value.to<double>());
default:
UNIMPLEMENTED();
return isolate->factory()->undefined_value();
}
}
// Forward declaration.
class InterpreterHandle;
InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info);
......@@ -367,6 +404,88 @@ class InterpreterHandle {
instance_.mem_start = reinterpret_cast<byte*>(new_memory->backing_store());
CHECK(new_memory->byte_length()->ToUint32(&instance_.mem_size));
}
Handle<JSArray> GetScopeDetails(Address frame_pointer, int frame_index,
Handle<WasmInstanceObject> instance) {
auto frame = GetInterpretedFrame(frame_pointer, frame_index);
Handle<FixedArray> global_scope =
isolate_->factory()->NewFixedArray(ScopeIterator::kScopeDetailsSize);
global_scope->set(ScopeIterator::kScopeDetailsTypeIndex,
Smi::FromInt(ScopeIterator::ScopeTypeGlobal));
Handle<JSObject> global_scope_object =
isolate_->factory()->NewJSObjectWithNullProto();
global_scope->set(ScopeIterator::kScopeDetailsObjectIndex,
*global_scope_object);
// TODO(clemensh): Add globals to the global scope.
if (instance->has_memory_buffer()) {
Handle<String> name = isolate_->factory()->InternalizeOneByteString(
STATIC_CHAR_VECTOR("memory"));
Handle<JSArrayBuffer> memory_buffer(instance->memory_buffer(), isolate_);
uint32_t byte_length;
CHECK(memory_buffer->byte_length()->ToUint32(&byte_length));
Handle<JSTypedArray> uint8_array = isolate_->factory()->NewJSTypedArray(
kExternalUint8Array, memory_buffer, 0, byte_length);
JSObject::SetOwnPropertyIgnoreAttributes(global_scope_object, name,
uint8_array, NONE)
.Check();
}
Handle<FixedArray> local_scope =
isolate_->factory()->NewFixedArray(ScopeIterator::kScopeDetailsSize);
local_scope->set(ScopeIterator::kScopeDetailsTypeIndex,
Smi::FromInt(ScopeIterator::ScopeTypeLocal));
Handle<JSObject> local_scope_object =
isolate_->factory()->NewJSObjectWithNullProto();
local_scope->set(ScopeIterator::kScopeDetailsObjectIndex,
*local_scope_object);
// Fill parameters and locals.
int num_params = frame->GetParameterCount();
int num_locals = frame->GetLocalCount();
DCHECK_LE(num_params, num_locals);
for (int i = 0; i < num_locals; ++i) {
// TODO(clemensh): Use names from name section if present.
const char* label = i < num_params ? "param#%d" : "local#%d";
Handle<String> name = PrintFToOneByteString<true>(isolate_, label, i);
WasmVal value = frame->GetLocalValue(i);
Handle<Object> value_obj = WasmValToValueObject(isolate_, value);
JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, name,
value_obj, NONE)
.Check();
}
// Fill stack values.
int stack_count = frame->GetStackHeight();
// Use an object without prototype instead of an Array, for nicer displaying
// in DevTools. For Arrays, the length field and prototype is displayed,
// which does not make too much sense here.
Handle<JSObject> stack_obj =
isolate_->factory()->NewJSObjectWithNullProto();
for (int i = 0; i < stack_count; ++i) {
WasmVal value = frame->GetStackValue(i);
Handle<Object> value_obj = WasmValToValueObject(isolate_, value);
JSObject::SetOwnElementIgnoreAttributes(
stack_obj, static_cast<uint32_t>(i), value_obj, NONE)
.Check();
}
Handle<String> stack_name = isolate_->factory()->InternalizeOneByteString(
STATIC_CHAR_VECTOR("stack"));
JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, stack_name,
stack_obj, NONE)
.Check();
Handle<JSArray> global_jsarr =
isolate_->factory()->NewJSArrayWithElements(global_scope);
Handle<JSArray> local_jsarr =
isolate_->factory()->NewJSArrayWithElements(local_scope);
Handle<FixedArray> all_scopes = isolate_->factory()->NewFixedArray(2);
all_scopes->set(0, *global_jsarr);
all_scopes->set(1, *local_jsarr);
return isolate_->factory()->NewJSArrayWithElements(all_scopes);
}
};
InterpreterHandle* GetOrCreateInterpreterHandle(
......@@ -553,3 +672,12 @@ void WasmDebugInfo::UpdateMemory(JSArrayBuffer* new_memory) {
if (!interp_handle) return;
interp_handle->UpdateMemory(new_memory);
}
// static
Handle<JSArray> WasmDebugInfo::GetScopeDetails(Handle<WasmDebugInfo> debug_info,
Address frame_pointer,
int frame_index) {
InterpreterHandle* interp_handle = GetInterpreterHandle(*debug_info);
Handle<WasmInstanceObject> instance(debug_info->wasm_instance());
return interp_handle->GetScopeDetails(frame_pointer, frame_index, instance);
}
......@@ -2259,8 +2259,10 @@ class InterpretedFrameImpl {
static_cast<size_t>(index_) + 1 == thread_->frames_.size();
size_t stack_limit =
is_top_frame ? thread_->stack_.size() : thread_->frames_[index_ + 1].sp;
DCHECK_LE(GetLocalCount(), stack_limit);
return static_cast<int>(stack_limit) - GetLocalCount();
DCHECK_LE(frame()->sp, stack_limit);
size_t frame_size = stack_limit - frame()->sp;
DCHECK_LE(GetLocalCount(), frame_size);
return static_cast<int>(frame_size) - GetLocalCount();
}
WasmVal GetLocalValue(int index) const {
......
......@@ -593,6 +593,17 @@ class WasmDebugInfo : public FixedArray {
// Update the memory view of the interpreter after executing GrowMemory in
// compiled code.
void UpdateMemory(JSArrayBuffer* new_memory);
// Get scope details for a specific interpreted frame.
// This returns a JSArray of length two: One entry for the global scope, one
// for the local scope. Both elements are JSArrays of size
// ScopeIterator::kScopeDetailsSize and layout as described in debug-scopes.h.
// The global scope contains information about globals and the memory.
// The local scope contains information about parameters, locals, and stack
// values.
static Handle<JSArray> GetScopeDetails(Handle<WasmDebugInfo>,
Address frame_pointer,
int frame_index);
};
class WasmInstanceWrapper : public FixedArray {
......
......@@ -32,52 +32,298 @@ Setting breakpoint on line 7 (on the setlocal before the call), url wasm://wasm/
scriptId : <scriptId>
}
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:7:6: >set_local 0
Step action: stepInto
at wasm_B (7:6):
- scope (global):
-- skipped
- scope (local):
param#0: 4 (number)
stack: {"0":3} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:8:6: >call 0
Step action: stepInto
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 3 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-0:1:2: >nop
Step action: stepOver
at wasm_A (1:2):
- scope (global):
-- skipped
- scope (local):
stack: {} (Object)
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 3 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepOver called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-0:2:2: >nop
Step action: stepOut
at wasm_A (2:2):
- scope (global):
-- skipped
- scope (local):
stack: {} (Object)
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 3 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepOut called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:9:6: >br 1
Step action: stepOut
at wasm_B (9:6):
- scope (global):
-- skipped
- scope (local):
param#0: 3 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepOut called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:7:6: >set_local 0
Step action: stepOver
at wasm_B (7:6):
- scope (global):
-- skipped
- scope (local):
param#0: 3 (number)
stack: {"0":2} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepOver called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:8:6: >call 0
Step action: stepOver
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 2 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepOver called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:9:6: >br 1
Step action: resume
at wasm_B (9:6):
- scope (global):
-- skipped
- scope (local):
param#0: 2 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.resume called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:7:6: >set_local 0
Step action: stepInto
at wasm_B (7:6):
- scope (global):
-- skipped
- scope (local):
param#0: 2 (number)
stack: {"0":1} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:8:6: >call 0
Step action: stepInto
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-0:1:2: >nop
Step action: stepOut
at wasm_A (1:2):
- scope (global):
-- skipped
- scope (local):
stack: {} (Object)
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepOut called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:9:6: >br 1
Step action: stepInto
at wasm_B (9:6):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:1:2: >loop
Step action: stepInto
at wasm_B (1:2):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:2:4: >get_local 0
Step action: stepInto
at wasm_B (2:4):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:3:4: >if
Step action: stepInto
at wasm_B (3:4):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {"0":1} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:4:6: >get_local 0
Step action: stepInto
at wasm_B (4:6):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:5:6: >i32.const 1
Step action: stepInto
at wasm_B (5:6):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {"0":1} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:6:6: >i32.sub
Step action: stepInto
at wasm_B (6:6):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {"0":1,"1":1} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:7:6: >set_local 0
Step action: stepInto
at wasm_B (7:6):
- scope (global):
-- skipped
- scope (local):
param#0: 1 (number)
stack: {"0":0} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:8:6: >call 0
Step action: stepInto
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 0 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-0:1:2: >nop
Step action: stepInto
at wasm_A (1:2):
- scope (global):
-- skipped
- scope (local):
stack: {} (Object)
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 0 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-0:2:2: >nop
Step action: stepInto
at wasm_A (2:2):
- scope (global):
-- skipped
- scope (local):
stack: {} (Object)
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 0 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-0:3:0: >end
Step action: stepInto
at wasm_A (3:0):
- scope (global):
-- skipped
- scope (local):
stack: {} (Object)
at wasm_B (8:6):
- scope (global):
-- skipped
- scope (local):
param#0: 0 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.stepInto called
Paused at wasm://wasm/wasm-0c10a5fe/wasm-0c10a5fe-1:9:6: >br 1
Step action: resume
at wasm_B (9:6):
- scope (global):
-- skipped
- scope (local):
param#0: 0 (number)
stack: {} (Object)
at (anonymous) (0:17):
- scope (global):
-- skipped
Debugger.resume called
exports.main returned!
Finished!
......@@ -68,6 +68,9 @@ var step_actions = [
// then just resume.
'resume'
];
for (var action of step_actions) {
InspectorTest.logProtocolCommandCalls('Debugger.' + action)
}
var sources = {};
var urls = {};
var afterTwoSourcesCallback;
......@@ -151,10 +154,48 @@ function printPauseLocation(scriptId, lineNr, columnNr) {
line);
}
async function getValueString(value) {
if (value.type == 'object') {
var 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 (var value of message.result.result) {
var value_str = await getValueString(value.value);
InspectorTest.log(' ' + value.name + ': ' + value_str);
}
}
async function dumpScopeChainsOnPause(message) {
for (var frame of message.params.callFrames) {
var functionName = frame.functionName || '(anonymous)';
var lineNumber = frame.location ? frame.location.lineNumber : frame.lineNumber;
var columnNumber = frame.location ? frame.location.columnNumber : frame.columnNumber;
InspectorTest.log(`at ${functionName} (${lineNumber}:${columnNumber}):`);
for (var scope of frame.scopeChain) {
InspectorTest.logObject(' - scope (' + scope.type + '):');
if (scope.type == 'global') {
InspectorTest.logObject(' -- skipped');
} else {
var properties = await Protocol.Runtime.getProperties(
{'objectId': scope.object.objectId});
await dumpProperties(properties);
}
}
}
}
function handlePaused(msg) {
var loc = msg.params.callFrames[0].location;
printPauseLocation(loc.scriptId, loc.lineNumber, loc.columnNumber);
var action = step_actions.shift();
InspectorTest.log('Step action: ' + action);
Protocol.Debugger[action]();
dumpScopeChainsOnPause(msg)
.then(Protocol.Debugger[step_actions.shift() || 'resume']);
}
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