Commit c27c167c authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[inspector] Implement Debug Proxy API via Interceptors.

Previously the Debug Proxy API that's exposed on Wasm frames by
Runtime.evaluateOnCallFrame() was implemented via actual JSProxy
instances. That means that all entities such as "memories", "tables",
"stack", "globals", etc. were JSProxy instances with "get" and "has"
traps. But that has a couple of down-sides:

1. In DevTools front-end, the proxies are shown as JSProxy, which is not
   very useful to developers, since they cannot interact with them nor
   can they inspect their contents. And the object preview also only
   shows "Proxy {}" for them.
2. The performance doesn't scale well, which becomes a painful
   bottleneck with larger Wasm modules that contain hundreds of
   thousands of functions or globals.
3. We cannot use the JSProxy instances in the Scope view (for the
   reasons outlined in 1.) and hence we have different logic to provide
   Scope values than values in the rest of DevTools, which led to subtle
   but annoying bugs and inconsistencies.

This also changes the "locals" implementation by querying the values
ahead of time, similar to the object exposed to the Scope view, instead
of on-demand, since the "locals" object might survive the current
debugger pause and peeking into the stack afterwards would read invalid
memory (and might even be a security issue). For being able to change
locals we need to look into a similar solution as what we have for
JavaScript locals already. The expression stack already works this way.

For performance reasons (especially scaling to huge, realistic Wasm
modules), we cache the per-instance proxies ("functions", "memories",
"tables" and "globals") on the WasmInstanceObject and reuse them (which
is safe since they have a `null` prototype and are non-extensible), and
we also cache the proxy maps (with the interceptors) on the
JSGlobalObject per native context.

Doc: http://bit.ly/devtools-wasm-entities
Bug: chromium:1127914, chromium:1159402, chromium:1071432, chromium:1164241
Change-Id: I6191035fdfd887835ae533fcdaabb5bbc8e661ae
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2606058
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71981}
parent 5af79398
...@@ -100,7 +100,7 @@ MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate, ...@@ -100,7 +100,7 @@ MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
WasmFrame* frame = WasmFrame::cast(it.frame()); WasmFrame* frame = WasmFrame::cast(it.frame());
Handle<SharedFunctionInfo> outer_info( Handle<SharedFunctionInfo> outer_info(
isolate->native_context()->empty_function().shared(), isolate); isolate->native_context()->empty_function().shared(), isolate);
Handle<JSProxy> context_extension = wasm::GetJSDebugProxy(frame); Handle<JSObject> context_extension = wasm::GetJSDebugProxy(frame);
Handle<ScopeInfo> scope_info = Handle<ScopeInfo> scope_info =
ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null()); ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
Handle<Context> context = isolate->factory()->NewWithContext( Handle<Context> context = isolate->factory()->NewWithContext(
......
...@@ -367,7 +367,7 @@ ...@@ -367,7 +367,7 @@
V(_, wasm_exception_values_symbol) \ V(_, wasm_exception_values_symbol) \
V(_, wasm_uncatchable_symbol) \ V(_, wasm_uncatchable_symbol) \
V(_, wasm_wrapped_object_symbol) \ V(_, wasm_wrapped_object_symbol) \
V(_, wasm_debug_proxy_name_tables) \ V(_, wasm_debug_proxy_cache_symbol) \
V(_, uninitialized_symbol) V(_, uninitialized_symbol)
#define PUBLIC_SYMBOL_LIST_GENERATOR(V, _) \ #define PUBLIC_SYMBOL_LIST_GENERATOR(V, _) \
......
This diff is collapsed.
...@@ -195,7 +195,7 @@ class V8_EXPORT_PRIVATE DebugInfo { ...@@ -195,7 +195,7 @@ class V8_EXPORT_PRIVATE DebugInfo {
std::unique_ptr<DebugInfoImpl> impl_; std::unique_ptr<DebugInfoImpl> impl_;
}; };
Handle<JSProxy> GetJSDebugProxy(WasmFrame* frame); Handle<JSObject> GetJSDebugProxy(WasmFrame* frame);
} // namespace wasm } // namespace wasm
} // namespace internal } // namespace internal
......
...@@ -15,34 +15,36 @@ Set breakpoint in main. ...@@ -15,34 +15,36 @@ Set breakpoint in main.
Instantiate module. Instantiate module.
Call main. Call main.
Debugger paused in main. Debugger paused in main.
> globals = Globals
> typeof globals = "object" > typeof globals = "object"
> Object.keys(globals) = Array(2)
> globals[0] = 0 > globals[0] = 0
> globals[1] = 1 > globals[1] = 1
> globals[2] = 2n > globals[2] = 2n
> globals[3] = 3n > globals[3] = 3n
> globals["$global0"] = 1 > globals["$global0"] = 0
> $global0 = 1 > $global0 = 0
> globals["$global3"] = 3n > globals["$global3"] = 2n
> $global3 = 3n > $global3 = 2n
Stepping twice in main. Stepping twice in main.
Debugger paused in main. Debugger paused in main.
> globals[0] = 0 > globals[0] = 0
> globals[1] = 1 > globals[1] = 1
> globals[2] = 2n > globals[2] = 2n
> globals[3] = 42n > globals[3] = 42n
> globals["$global0"] = 1 > globals["$global0"] = 0
> $global0 = 1 > $global0 = 0
> globals["$global3"] = 42n > globals["$global3"] = 2n
> $global3 = 42n > $global3 = 2n
Changing global from JavaScript. Changing global from JavaScript.
> globals[0] = 0 > globals[0] = 0
> globals[1] = 21 > globals[1] = 21
> globals[2] = 2n > globals[2] = 2n
> globals[3] = 42n > globals[3] = 42n
> globals["$global0"] = 21 > globals["$global0"] = 0
> $global0 = 21 > $global0 = 0
> globals["$global3"] = 42n > globals["$global3"] = 2n
> $global3 = 42n > $global3 = 2n
Running test: testFunctions Running test: testFunctions
Compile module. Compile module.
...@@ -50,15 +52,17 @@ Set breakpoint in main. ...@@ -50,15 +52,17 @@ Set breakpoint in main.
Instantiate module. Instantiate module.
Call main. Call main.
Debugger paused in main. Debugger paused in main.
> functions = Functions
> typeof functions = "object" > typeof functions = "object"
> Object.keys(functions) = Array(3)
> functions[0] = function 0() { [native code] } > functions[0] = function 0() { [native code] }
> functions[1] = function 1() { [native code] } > functions[1] = function 1() { [native code] }
> functions[2] = function 2() { [native code] } > functions[2] = function 2() { [native code] }
> functions[3] = function 3() { [native code] } > functions[3] = function 3() { [native code] }
> functions["$main"] = function 0() { [native code] } > functions["$main"] = function 0() { [native code] }
> $main = function 0() { [native code] } > $main = function 0() { [native code] }
> functions["$func1"] = function 2() { [native code] } > functions["$func1"] = function 1() { [native code] }
> $func1 = function 2() { [native code] } > $func1 = function 1() { [native code] }
> functions["$func3"] = function 3() { [native code] } > functions["$func3"] = function 3() { [native code] }
> $func3 = function 3() { [native code] } > $func3 = function 3() { [native code] }
...@@ -68,12 +72,14 @@ Set breakpoint in main. ...@@ -68,12 +72,14 @@ Set breakpoint in main.
Instantiate module. Instantiate module.
Call main. Call main.
Debugger paused in main. Debugger paused in main.
> locals = Locals
> typeof locals = "object" > typeof locals = "object"
> Object.keys(locals) = Array(2)
> locals[0] = 3 > locals[0] = 3
> locals[1] = 6 > locals[1] = 6
> locals[2] = 0 > locals[2] = 0
> locals["$x"] = 6 > locals["$x"] = 3
> $x = 6 > $x = 3
> locals["$var2"] = 0 > locals["$var2"] = 0
> $var2 = 0 > $var2 = 0
Stepping twice in main. Stepping twice in main.
...@@ -81,8 +87,8 @@ Debugger paused in main. ...@@ -81,8 +87,8 @@ Debugger paused in main.
> locals[0] = 3 > locals[0] = 3
> locals[1] = 6 > locals[1] = 6
> locals[2] = 42 > locals[2] = 42
> locals["$x"] = 6 > locals["$x"] = 3
> $x = 6 > $x = 3
> locals["$var2"] = 42 > locals["$var2"] = 42
> $var2 = 42 > $var2 = 42
...@@ -92,7 +98,9 @@ Set breakpoint in main. ...@@ -92,7 +98,9 @@ Set breakpoint in main.
Instantiate module. Instantiate module.
Call main. Call main.
Debugger paused in main. Debugger paused in main.
> memories = Memories
> typeof memories = "object" > typeof memories = "object"
> Object.keys(memories) = Array(1)
> memories[0] = Memory(1) > memories[0] = Memory(1)
> memories["$foo"] = Memory(1) > memories["$foo"] = Memory(1)
> $foo = Memory(1) > $foo = Memory(1)
...@@ -103,7 +111,25 @@ Set breakpoint in main. ...@@ -103,7 +111,25 @@ Set breakpoint in main.
Instantiate module. Instantiate module.
Call main. Call main.
Debugger paused in main. Debugger paused in main.
> tables = Tables
> typeof tables = "object" > typeof tables = "object"
> Object.keys(tables) = Array(1)
> tables[0] = Table > tables[0] = Table
> tables["$bar"] = Table > tables["$bar"] = Table
> $bar = Table > $bar = Table
Running test: testStack
Compile module.
Set breakpoint in main.
Instantiate module.
Call main.
Debugger paused in main.
> stack = Stack
> typeof stack = "object"
> Object.keys(stack) = Array(0)
Stepping twice in main.
Debugger paused in main.
> stack = Stack
> Object.keys(stack) = Array(2)
> stack[0] = 5
> stack[1] = 42
...@@ -127,7 +127,9 @@ InspectorTest.runAsyncTestSuite([ ...@@ -127,7 +127,9 @@ InspectorTest.runAsyncTestSuite([
objectId: instance.objectId objectId: instance.objectId
}); });
let callFrameId = await waitForDebuggerPaused(); let callFrameId = await waitForDebuggerPaused();
await dumpOnCallFrame(callFrameId, `globals`);
await dumpOnCallFrame(callFrameId, `typeof globals`); await dumpOnCallFrame(callFrameId, `typeof globals`);
await dumpOnCallFrame(callFrameId, `Object.keys(globals)`);
await dumpKeysOnCallFrame(callFrameId, "globals", KEYS); await dumpKeysOnCallFrame(callFrameId, "globals", KEYS);
InspectorTest.log(`Stepping twice in main.`) InspectorTest.log(`Stepping twice in main.`)
...@@ -183,7 +185,9 @@ InspectorTest.runAsyncTestSuite([ ...@@ -183,7 +185,9 @@ InspectorTest.runAsyncTestSuite([
objectId: instance.objectId objectId: instance.objectId
}); });
let callFrameId = await waitForDebuggerPaused(); let callFrameId = await waitForDebuggerPaused();
await dumpOnCallFrame(callFrameId, `functions`);
await dumpOnCallFrame(callFrameId, `typeof functions`); await dumpOnCallFrame(callFrameId, `typeof functions`);
await dumpOnCallFrame(callFrameId, `Object.keys(functions)`);
await dumpKeysOnCallFrame(callFrameId, "functions", KEYS); await dumpKeysOnCallFrame(callFrameId, "functions", KEYS);
await Protocol.Debugger.resume(); await Protocol.Debugger.resume();
await callMainPromise; await callMainPromise;
...@@ -216,7 +220,9 @@ InspectorTest.runAsyncTestSuite([ ...@@ -216,7 +220,9 @@ InspectorTest.runAsyncTestSuite([
objectId: instance.objectId objectId: instance.objectId
}); });
let callFrameId = await waitForDebuggerPaused(); let callFrameId = await waitForDebuggerPaused();
await dumpOnCallFrame(callFrameId, `locals`);
await dumpOnCallFrame(callFrameId, `typeof locals`); await dumpOnCallFrame(callFrameId, `typeof locals`);
await dumpOnCallFrame(callFrameId, `Object.keys(locals)`);
await dumpKeysOnCallFrame(callFrameId, "locals", KEYS); await dumpKeysOnCallFrame(callFrameId, "locals", KEYS);
InspectorTest.log(`Stepping twice in main.`) InspectorTest.log(`Stepping twice in main.`)
...@@ -252,7 +258,9 @@ InspectorTest.runAsyncTestSuite([ ...@@ -252,7 +258,9 @@ InspectorTest.runAsyncTestSuite([
objectId: instance.objectId objectId: instance.objectId
}); });
let callFrameId = await waitForDebuggerPaused(); let callFrameId = await waitForDebuggerPaused();
await dumpOnCallFrame(callFrameId, `memories`);
await dumpOnCallFrame(callFrameId, `typeof memories`); await dumpOnCallFrame(callFrameId, `typeof memories`);
await dumpOnCallFrame(callFrameId, `Object.keys(memories)`);
await dumpKeysOnCallFrame(callFrameId, "memories", KEYS); await dumpKeysOnCallFrame(callFrameId, "memories", KEYS);
await Protocol.Debugger.resume(); await Protocol.Debugger.resume();
await callMainPromise; await callMainPromise;
...@@ -282,9 +290,52 @@ InspectorTest.runAsyncTestSuite([ ...@@ -282,9 +290,52 @@ InspectorTest.runAsyncTestSuite([
objectId: instance.objectId objectId: instance.objectId
}); });
let callFrameId = await waitForDebuggerPaused(); let callFrameId = await waitForDebuggerPaused();
await dumpOnCallFrame(callFrameId, `tables`);
await dumpOnCallFrame(callFrameId, `typeof tables`); await dumpOnCallFrame(callFrameId, `typeof tables`);
await dumpOnCallFrame(callFrameId, `Object.keys(tables)`);
await dumpKeysOnCallFrame(callFrameId, "tables", KEYS); await dumpKeysOnCallFrame(callFrameId, "tables", KEYS);
await Protocol.Debugger.resume(); await Protocol.Debugger.resume();
await callMainPromise; await callMainPromise;
},
async function testStack() {
const builder = new WasmModuleBuilder();
const main = builder.addFunction('main', kSig_i_i)
.addBody([
kExprLocalGet, 0,
kExprI32Const, 42,
kExprI32Add,
]).exportFunc();
InspectorTest.log('Compile module.');
const [module, scriptId] = await compileModule(builder);
InspectorTest.log('Set breakpoint in main.');
await Protocol.Debugger.setBreakpoint({
location: {scriptId, lineNumber: 0, columnNumber: main.body_offset}
});
InspectorTest.log('Instantiate module.');
const instance = await instantiateModule(module);
InspectorTest.log('Call main.');
const callMainPromise = Protocol.Runtime.callFunctionOn({
functionDeclaration: `function() { return this.exports.main(5); }`,
objectId: instance.objectId
});
let callFrameId = await waitForDebuggerPaused();
await dumpOnCallFrame(callFrameId, `stack`);
await dumpOnCallFrame(callFrameId, `typeof stack`);
await dumpOnCallFrame(callFrameId, `Object.keys(stack)`);
InspectorTest.log(`Stepping twice in main.`)
await Protocol.Debugger.stepOver(); // local.get $var0
await Protocol.Debugger.stepOver(); // i32.const 42
callFrameId = await waitForDebuggerPaused();
await dumpOnCallFrame(callFrameId, `stack`);
await dumpOnCallFrame(callFrameId, `Object.keys(stack)`);
await dumpKeysOnCallFrame(callFrameId, "stack", [0, 1]);
await Protocol.Debugger.resume();
await callMainPromise;
} }
]); ]);
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