Expose a proxy object to evaluateOnCallFrame for WebAssembly
When debugging WebAssembly, calls to evaluateOnCallFrame always return undefined. This CL enables evaluateOnCallFrame for WebAssembly and creates a proxy object that is injected into the evaluation context. Bug: chromium:1127914 Change-Id: I3f5cff3be2c9de45c7b1f3f7ed4fc2e1cc545ac6 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2429265 Commit-Queue: Philip Pfaffe <pfaffe@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Cr-Commit-Position: refs/heads/master@{#70315}
This commit is contained in:
parent
a5024f9b4f
commit
ae3f94bd2a
@ -18,6 +18,7 @@
|
||||
#include "src/objects/contexts.h"
|
||||
#include "src/snapshot/snapshot.h"
|
||||
#include "src/wasm/wasm-debug.h"
|
||||
#include "src/wasm/wasm-js.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -106,6 +107,9 @@ V8_EXPORT MaybeHandle<Object> DebugEvaluate::WebAssembly(
|
||||
|
||||
StackTraceFrameIterator it(isolate, frame_id);
|
||||
if (!it.is_wasm()) return isolate->factory()->undefined_value();
|
||||
WasmFrame* frame = WasmFrame::cast(it.frame());
|
||||
|
||||
Handle<JSProxy> context_extension = WasmJs::GetJSDebugProxy(frame);
|
||||
|
||||
DisableBreak disable_break_scope(isolate->debug(), /*disable=*/true);
|
||||
|
||||
@ -114,12 +118,14 @@ V8_EXPORT MaybeHandle<Object> DebugEvaluate::WebAssembly(
|
||||
return {};
|
||||
}
|
||||
|
||||
Handle<Context> context = isolate->native_context();
|
||||
Handle<JSObject> receiver(context->global_proxy(), isolate);
|
||||
Handle<ScopeInfo> scope_info =
|
||||
ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
|
||||
Handle<Context> context = isolate->factory()->NewWithContext(
|
||||
isolate->native_context(), scope_info, context_extension);
|
||||
|
||||
Handle<Object> result;
|
||||
if (!DebugEvaluate::Evaluate(isolate, shared_info, context, receiver, source,
|
||||
throw_on_side_effect)
|
||||
if (!DebugEvaluate::Evaluate(isolate, shared_info, context, context_extension,
|
||||
source, throw_on_side_effect)
|
||||
.ToHandle(&result)) {
|
||||
return {};
|
||||
}
|
||||
|
@ -340,6 +340,12 @@ class DebugInfoImpl {
|
||||
debug_break_fp);
|
||||
}
|
||||
|
||||
const WasmFunction& GetFunctionAtAddress(Address pc) {
|
||||
FrameInspectionScope scope(this, pc);
|
||||
auto* module = native_module_->module();
|
||||
return module->functions[scope.code->index()];
|
||||
}
|
||||
|
||||
Handle<JSObject> GetLocalScopeObject(Isolate* isolate, Address pc, Address fp,
|
||||
Address debug_break_fp) {
|
||||
FrameInspectionScope scope(this, pc);
|
||||
@ -909,6 +915,10 @@ WasmValue DebugInfo::GetStackValue(int index, Address pc, Address fp,
|
||||
return impl_->GetStackValue(index, pc, fp, debug_break_fp);
|
||||
}
|
||||
|
||||
const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) {
|
||||
return impl_->GetFunctionAtAddress(pc);
|
||||
}
|
||||
|
||||
Handle<JSObject> DebugInfo::GetLocalScopeObject(Isolate* isolate, Address pc,
|
||||
Address fp,
|
||||
Address debug_break_fp) {
|
||||
|
@ -34,6 +34,7 @@ class NativeModule;
|
||||
class WasmCode;
|
||||
class WireBytesRef;
|
||||
class WasmValue;
|
||||
struct WasmFunction;
|
||||
|
||||
// Side table storing information used to inspect Liftoff frames at runtime.
|
||||
// This table is only created on demand for debugging, so it is not optimized
|
||||
@ -153,6 +154,9 @@ class V8_EXPORT_PRIVATE DebugInfo {
|
||||
WasmValue GetLocalValue(int local, Address pc, Address fp,
|
||||
Address debug_break_fp);
|
||||
int GetStackDepth(Address pc);
|
||||
|
||||
const wasm::WasmFunction& GetFunctionAtAddress(Address pc);
|
||||
|
||||
WasmValue GetStackValue(int index, Address pc, Address fp,
|
||||
Address debug_break_fp);
|
||||
|
||||
|
@ -10,13 +10,16 @@
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/api/api-natives.h"
|
||||
#include "src/ast/ast.h"
|
||||
#include "src/base/logging.h"
|
||||
#include "src/base/overflowing-math.h"
|
||||
#include "src/common/assert-scope.h"
|
||||
#include "src/execution/execution.h"
|
||||
#include "src/execution/frames-inl.h"
|
||||
#include "src/execution/isolate.h"
|
||||
#include "src/handles/handles.h"
|
||||
#include "src/heap/factory.h"
|
||||
#include "src/init/v8.h"
|
||||
#include "src/objects/js-collection-inl.h"
|
||||
#include "src/objects/js-promise-inl.h"
|
||||
#include "src/objects/objects-inl.h"
|
||||
#include "src/objects/templates.h"
|
||||
@ -25,10 +28,12 @@
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "src/wasm/streaming-decoder.h"
|
||||
#include "src/wasm/value-type.h"
|
||||
#include "src/wasm/wasm-debug.h"
|
||||
#include "src/wasm/wasm-engine.h"
|
||||
#include "src/wasm/wasm-limits.h"
|
||||
#include "src/wasm/wasm-objects-inl.h"
|
||||
#include "src/wasm/wasm-serialization.h"
|
||||
#include "src/wasm/wasm-value.h"
|
||||
|
||||
using v8::internal::wasm::ErrorThrower;
|
||||
using v8::internal::wasm::ScheduledErrorThrower;
|
||||
@ -1581,7 +1586,7 @@ constexpr const char* kName_WasmTableObject = "WebAssembly.Table";
|
||||
}
|
||||
|
||||
void WebAssemblyInstanceGetExports(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||
HandleScope scope(isolate);
|
||||
@ -2020,9 +2025,9 @@ Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object,
|
||||
}
|
||||
|
||||
Handle<JSFunction> InstallConstructorFunc(Isolate* isolate,
|
||||
Handle<JSObject> object,
|
||||
const char* str,
|
||||
FunctionCallback func) {
|
||||
Handle<JSObject> object,
|
||||
const char* str,
|
||||
FunctionCallback func) {
|
||||
return InstallFunc(isolate, object, str, func, 1, true, DONT_ENUM);
|
||||
}
|
||||
|
||||
@ -2281,6 +2286,388 @@ void WasmJs::Install(Isolate* isolate, bool exposed_on_global_object) {
|
||||
runtime_error, DONT_ENUM);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void SetMapValue(Isolate* isolate, Handle<JSMap> map, Handle<Object> key,
|
||||
Handle<Object> value) {
|
||||
DCHECK(!map.is_null() && !key.is_null() && !value.is_null());
|
||||
Handle<Object> argv[] = {key, value};
|
||||
Execution::CallBuiltin(isolate, isolate->map_set(), map, arraysize(argv),
|
||||
argv)
|
||||
.Check();
|
||||
}
|
||||
|
||||
Handle<Object> GetMapValue(Isolate* isolate, Handle<JSMap> map,
|
||||
Handle<Object> key) {
|
||||
DCHECK(!map.is_null() && !key.is_null());
|
||||
Handle<Object> argv[] = {key};
|
||||
return Execution::CallBuiltin(isolate, isolate->map_get(), map,
|
||||
arraysize(argv), argv)
|
||||
.ToHandleChecked();
|
||||
}
|
||||
|
||||
// Look up a name in a name table. Name tables are stored under the "names"
|
||||
// property of the handler and map names to index.
|
||||
base::Optional<int> ResolveValueSelector(Isolate* isolate,
|
||||
Handle<Name> property,
|
||||
Handle<JSObject> handler,
|
||||
bool enable_index_lookup) {
|
||||
size_t index = 0;
|
||||
if (enable_index_lookup && property->AsIntegerIndex(&index) &&
|
||||
index < kMaxInt) {
|
||||
return static_cast<int>(index);
|
||||
}
|
||||
|
||||
Handle<Object> name_table =
|
||||
JSObject::GetProperty(isolate, handler, "names").ToHandleChecked();
|
||||
DCHECK(name_table->IsJSMap());
|
||||
|
||||
Handle<Object> object =
|
||||
GetMapValue(isolate, Handle<JSMap>::cast(name_table), property);
|
||||
if (object->IsUndefined()) return {};
|
||||
DCHECK(object->IsNumeric());
|
||||
return NumberToInt32(*object);
|
||||
}
|
||||
|
||||
// Helper for unpacking a maybe name that makes a default with an index if
|
||||
// the name is empty. If the name is not empty, it's prefixed with a $.
|
||||
Handle<String> GetNameOrDefault(Isolate* isolate,
|
||||
MaybeHandle<String> maybe_name,
|
||||
const char* default_name_prefix, int index) {
|
||||
Handle<String> name;
|
||||
if (maybe_name.ToHandle(&name)) {
|
||||
return isolate->factory()
|
||||
->NewConsString(isolate->factory()->NewStringFromAsciiChecked("$"),
|
||||
name)
|
||||
.ToHandleChecked();
|
||||
}
|
||||
|
||||
// Maximum length of the default names: $memory-2147483648\0
|
||||
static constexpr int kMaxStrLen = 19;
|
||||
EmbeddedVector<char, kMaxStrLen> value;
|
||||
DCHECK_LT(strlen(default_name_prefix) + /*strlen(kMinInt)*/ 11, kMaxStrLen);
|
||||
int len = SNPrintF(value, "%s%d", default_name_prefix, index);
|
||||
return isolate->factory()->InternalizeString(value.SubVector(0, len));
|
||||
}
|
||||
|
||||
// Generate names for the locals. Names either come from the name table,
|
||||
// otherwise the default $varX is used.
|
||||
std::vector<Handle<String>> GetLocalNames(Handle<WasmInstanceObject> instance,
|
||||
Address pc) {
|
||||
wasm::NativeModule* native_module = instance->module_object().native_module();
|
||||
wasm::DebugInfo* debug_info = native_module->GetDebugInfo();
|
||||
int num_locals = debug_info->GetNumLocals(pc);
|
||||
auto* isolate = instance->GetIsolate();
|
||||
|
||||
wasm::ModuleWireBytes module_wire_bytes(
|
||||
instance->module_object().native_module()->wire_bytes());
|
||||
const wasm::WasmFunction& function = debug_info->GetFunctionAtAddress(pc);
|
||||
|
||||
std::vector<Handle<String>> names;
|
||||
for (int i = 0; i < num_locals; ++i) {
|
||||
wasm::WireBytesRef local_name_ref =
|
||||
debug_info->GetLocalName(function.func_index, i);
|
||||
DCHECK(module_wire_bytes.BoundsCheck(local_name_ref));
|
||||
Vector<const char> name_vec =
|
||||
module_wire_bytes.GetNameOrNull(local_name_ref);
|
||||
names.emplace_back(GetNameOrDefault(
|
||||
isolate,
|
||||
name_vec.empty() ? MaybeHandle<String>()
|
||||
: isolate->factory()->NewStringFromUtf8(name_vec),
|
||||
"$var", i));
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
Handle<WasmInstanceObject> GetInstance(Isolate* isolate,
|
||||
Handle<JSObject> handler) {
|
||||
Handle<Object> instance =
|
||||
JSObject::GetProperty(isolate, handler, "instance").ToHandleChecked();
|
||||
DCHECK(instance->IsWasmInstanceObject());
|
||||
return Handle<WasmInstanceObject>::cast(instance);
|
||||
}
|
||||
|
||||
Address GetPC(Isolate* isolate, Handle<JSObject> handler) {
|
||||
Handle<Object> pc =
|
||||
JSObject::GetProperty(isolate, handler, "pc").ToHandleChecked();
|
||||
DCHECK(pc->IsBigInt());
|
||||
return Handle<BigInt>::cast(pc)->AsUint64();
|
||||
}
|
||||
|
||||
Address GetFP(Isolate* isolate, Handle<JSObject> handler) {
|
||||
Handle<Object> fp =
|
||||
JSObject::GetProperty(isolate, handler, "fp").ToHandleChecked();
|
||||
DCHECK(fp->IsBigInt());
|
||||
return Handle<BigInt>::cast(fp)->AsUint64();
|
||||
}
|
||||
|
||||
Address GetCalleeFP(Isolate* isolate, Handle<JSObject> handler) {
|
||||
Handle<Object> callee_fp =
|
||||
JSObject::GetProperty(isolate, handler, "callee_fp").ToHandleChecked();
|
||||
DCHECK(callee_fp->IsBigInt());
|
||||
return Handle<BigInt>::cast(callee_fp)->AsUint64();
|
||||
}
|
||||
|
||||
// Convert a WasmValue to an appropriate JS representation.
|
||||
static Handle<Object> WasmValueToObject(Isolate* isolate,
|
||||
wasm::WasmValue value) {
|
||||
auto* factory = isolate->factory();
|
||||
switch (value.type().kind()) {
|
||||
case wasm::ValueType::kI32:
|
||||
return factory->NewNumberFromInt(value.to_i32());
|
||||
case wasm::ValueType::kI64:
|
||||
return BigInt::FromInt64(isolate, value.to_i64());
|
||||
case wasm::ValueType::kF32:
|
||||
return factory->NewNumber(value.to_f32());
|
||||
case wasm::ValueType::kF64:
|
||||
return factory->NewNumber(value.to_f64());
|
||||
case wasm::ValueType::kS128: {
|
||||
wasm::Simd128 s128 = value.to_s128();
|
||||
Handle<JSArrayBuffer> buffer;
|
||||
if (!isolate->factory()
|
||||
->NewJSArrayBufferAndBackingStore(
|
||||
kSimd128Size, InitializedFlag::kUninitialized)
|
||||
.ToHandle(&buffer)) {
|
||||
isolate->FatalProcessOutOfHeapMemory(
|
||||
"failed to allocate backing store");
|
||||
}
|
||||
|
||||
memcpy(buffer->allocation_base(), s128.bytes(), buffer->byte_length());
|
||||
return isolate->factory()->NewJSTypedArray(kExternalUint8Array, buffer, 0,
|
||||
buffer->byte_length());
|
||||
}
|
||||
case wasm::ValueType::kRef:
|
||||
return value.to_externref();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return factory->undefined_value();
|
||||
}
|
||||
|
||||
bool HasLocalImpl(Isolate* isolate, Handle<Name> property,
|
||||
Handle<JSObject> handler, bool enable_index_lookup) {
|
||||
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
|
||||
|
||||
base::Optional<int> index =
|
||||
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
|
||||
if (!index) return false;
|
||||
Address pc = GetPC(isolate, handler);
|
||||
|
||||
wasm::DebugInfo* debug_info =
|
||||
instance->module_object().native_module()->GetDebugInfo();
|
||||
int num_locals = debug_info->GetNumLocals(pc);
|
||||
return 0 <= index && index < num_locals;
|
||||
}
|
||||
|
||||
// Has trap callback for the locals index space proxy.
|
||||
void HasLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (args.Length() < 2) return;
|
||||
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||
DCHECK(args.This()->IsObject());
|
||||
Handle<JSObject> handler =
|
||||
Handle<JSObject>::cast(Utils::OpenHandle(*args.This()));
|
||||
|
||||
DCHECK(args[1]->IsName());
|
||||
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||
args.GetReturnValue().Set(HasLocalImpl(isolate, property, handler, true));
|
||||
}
|
||||
|
||||
Handle<Object> GetLocalImpl(Isolate* isolate, Handle<Name> property,
|
||||
Handle<JSObject> handler,
|
||||
bool enable_index_lookup) {
|
||||
Factory* factory = isolate->factory();
|
||||
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
|
||||
|
||||
base::Optional<int> index =
|
||||
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
|
||||
if (!index) return factory->undefined_value();
|
||||
Address pc = GetPC(isolate, handler);
|
||||
Address fp = GetFP(isolate, handler);
|
||||
Address callee_fp = GetCalleeFP(isolate, handler);
|
||||
|
||||
wasm::DebugInfo* debug_info =
|
||||
instance->module_object().native_module()->GetDebugInfo();
|
||||
int num_locals = debug_info->GetNumLocals(pc);
|
||||
if (0 > index || index >= num_locals) return factory->undefined_value();
|
||||
wasm::WasmValue value = debug_info->GetLocalValue(*index, pc, fp, callee_fp);
|
||||
return WasmValueToObject(isolate, value);
|
||||
}
|
||||
|
||||
// Get trap callback for the locals index space proxy.
|
||||
void GetLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
DCHECK_GE(args.Length(), 2);
|
||||
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||
DCHECK(args.This()->IsObject());
|
||||
Handle<JSObject> handler =
|
||||
Handle<JSObject>::cast(Utils::OpenHandle(*args.This()));
|
||||
|
||||
DCHECK(args[1]->IsName());
|
||||
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||
args.GetReturnValue().Set(
|
||||
Utils::ToLocal(GetLocalImpl(isolate, property, handler, true)));
|
||||
}
|
||||
|
||||
// Has trap callback for the top-level proxy.
|
||||
void HasToplevelCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
DCHECK_GE(args.Length(), 2);
|
||||
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||
DCHECK(args[0]->IsObject());
|
||||
Handle<JSObject> target = Handle<JSObject>::cast(Utils::OpenHandle(*args[0]));
|
||||
|
||||
DCHECK(args[1]->IsName());
|
||||
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||
|
||||
// First check if the property exists on the target.
|
||||
if (JSObject::HasProperty(target, property).FromMaybe(false)) {
|
||||
args.GetReturnValue().Set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check the index space proxies in order if they know the property.
|
||||
Handle<Object> namespace_proxy =
|
||||
JSObject::GetProperty(isolate, target, "locals").ToHandleChecked();
|
||||
DCHECK(namespace_proxy->IsJSProxy());
|
||||
Handle<JSObject> namespace_handler(
|
||||
JSObject::cast(Handle<JSProxy>::cast(namespace_proxy)->handler()),
|
||||
isolate);
|
||||
if (HasLocalImpl(isolate, property, namespace_handler, false)) {
|
||||
args.GetReturnValue().Set(true);
|
||||
return;
|
||||
}
|
||||
args.GetReturnValue().Set(false);
|
||||
}
|
||||
|
||||
// Get trap callback for the top-level proxy.
|
||||
void GetToplevelCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (args.Length() < 2) return;
|
||||
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||
DCHECK(args[0]->IsObject());
|
||||
Handle<JSObject> target = Handle<JSObject>::cast(Utils::OpenHandle(*args[0]));
|
||||
|
||||
DCHECK(args[1]->IsName());
|
||||
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||
|
||||
// First, check if the property is a proper property on the target. If so,
|
||||
// return its value.
|
||||
Handle<Object> value =
|
||||
JSObject::GetProperty(isolate, target, property).ToHandleChecked();
|
||||
if (!value->IsUndefined()) {
|
||||
args.GetReturnValue().Set(Utils::ToLocal(value));
|
||||
return;
|
||||
}
|
||||
|
||||
// Try the index space proxies in the correct disambiguation order.
|
||||
Handle<Object> namespace_proxy =
|
||||
JSObject::GetProperty(isolate, target, "locals").ToHandleChecked();
|
||||
DCHECK(!namespace_proxy->IsUndefined() && namespace_proxy->IsJSProxy());
|
||||
Handle<JSObject> namespace_handler(
|
||||
JSObject::cast(Handle<JSProxy>::cast(namespace_proxy)->handler()),
|
||||
isolate);
|
||||
value = GetLocalImpl(isolate, property, namespace_handler, false);
|
||||
if (!value->IsUndefined()) args.GetReturnValue().Set(Utils::ToLocal(value));
|
||||
}
|
||||
|
||||
// Populate a JSMap with name->index mappings from an ordered list of names.
|
||||
Handle<JSMap> GetNameTable(Isolate* isolate,
|
||||
const std::vector<Handle<String>>& names) {
|
||||
Factory* factory = isolate->factory();
|
||||
Handle<JSMap> name_table = factory->NewJSMap();
|
||||
|
||||
for (size_t i = 0; i < names.size(); ++i) {
|
||||
SetMapValue(isolate, name_table, names[i], factory->NewNumberFromInt64(i));
|
||||
}
|
||||
return name_table;
|
||||
}
|
||||
|
||||
// Produce a JSProxy with a given name table and get and has trap handlers.
|
||||
Handle<JSProxy> GetJSProxy(
|
||||
WasmFrame* frame, Handle<JSMap> name_table,
|
||||
void (*get_callback)(const v8::FunctionCallbackInfo<v8::Value>&),
|
||||
void (*has_callback)(const v8::FunctionCallbackInfo<v8::Value>&)) {
|
||||
Isolate* isolate = frame->isolate();
|
||||
Factory* factory = isolate->factory();
|
||||
Handle<JSObject> target = factory->NewJSObjectWithNullProto();
|
||||
Handle<JSObject> handler = factory->NewJSObjectWithNullProto();
|
||||
|
||||
// Besides the name table, the get and has traps need access to the instance
|
||||
// and frame information.
|
||||
JSObject::AddProperty(isolate, handler, "names", name_table, DONT_ENUM);
|
||||
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
|
||||
JSObject::AddProperty(isolate, handler, "instance", instance, DONT_ENUM);
|
||||
Handle<BigInt> pc = BigInt::FromInt64(isolate, frame->pc());
|
||||
JSObject::AddProperty(isolate, handler, "pc", pc, DONT_ENUM);
|
||||
Handle<BigInt> fp = BigInt::FromInt64(isolate, frame->fp());
|
||||
JSObject::AddProperty(isolate, handler, "fp", fp, DONT_ENUM);
|
||||
Handle<BigInt> callee_fp = BigInt::FromInt64(isolate, frame->callee_fp());
|
||||
JSObject::AddProperty(isolate, handler, "callee_fp", callee_fp, DONT_ENUM);
|
||||
|
||||
InstallFunc(isolate, handler, "get", get_callback, 3, false, READ_ONLY);
|
||||
InstallFunc(isolate, handler, "has", has_callback, 2, false, READ_ONLY);
|
||||
|
||||
return factory->NewJSProxy(target, handler);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// This function generates the JS debug proxy for a given Wasm frame. The debug
|
||||
// proxy is used when evaluating debug JS expressions on a wasm frame and let's
|
||||
// the developer inspect the engine state from JS. The proxy provides the
|
||||
// following interface:
|
||||
//
|
||||
// type WasmSimdValue = Uint8Array;
|
||||
// type WasmValue = number | bigint | object | WasmSimdValue;
|
||||
// type WasmFunction = (... args : WasmValue[]) = > WasmValue;
|
||||
// type WasmExport = {name : string} & ({func : number} | {table : number} |
|
||||
// {mem : number} | {global : number});
|
||||
// type WasmImport = {name : string, module : string} &
|
||||
// ({func : number} | {table : number} | {mem : number} |
|
||||
// {global : number});
|
||||
// interface WasmInterface {
|
||||
// $globalX: WasmValue;
|
||||
// $varX: WasmValue;
|
||||
// $funcX(a : WasmValue /*, ...*/) : WasmValue;
|
||||
// readonly $memoryX : WebAssembly.Memory;
|
||||
// readonly $tableX : WebAssembly.Table;
|
||||
// readonly memories : {[nameOrIndex:string | number] : WebAssembly.Memory};
|
||||
// readonly tables : {[nameOrIndex:string | number] : WebAssembly.Table};
|
||||
// readonly stack : WasmValue[];
|
||||
// readonly imports : {[nameOrIndex:string | number] : WasmImport};
|
||||
// readonly exports : {[nameOrIndex:string | number] : WasmExport};
|
||||
// readonly globals : {[nameOrIndex:string | number] : WasmValue};
|
||||
// readonly locals : {[nameOrIndex:string | number] : WasmValue};
|
||||
// readonly functions : {[nameOrIndex:string | number] : WasmFunction};
|
||||
// }
|
||||
//
|
||||
// The wasm index spaces memories, tables, imports, exports, globals, locals
|
||||
// functions are JSProxies that lazily produce values either by index or by
|
||||
// name. A top level JSProxy is wrapped around those for top-level lookup of
|
||||
// names in the disambiguation order memory, local, table, function, global.
|
||||
// Import and export names are not globally resolved.
|
||||
|
||||
Handle<JSProxy> WasmJs::GetJSDebugProxy(WasmFrame* frame) {
|
||||
Isolate* isolate = frame->isolate();
|
||||
Factory* factory = isolate->factory();
|
||||
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
|
||||
|
||||
// The top level proxy delegates lookups to the index space proxies.
|
||||
Handle<JSObject> handler = factory->NewJSObjectWithNullProto();
|
||||
InstallFunc(isolate, handler, "get", GetToplevelCallback, 3, false,
|
||||
READ_ONLY);
|
||||
InstallFunc(isolate, handler, "has", HasToplevelCallback, 2, false,
|
||||
READ_ONLY);
|
||||
|
||||
Handle<JSObject> target = factory->NewJSObjectWithNullProto();
|
||||
|
||||
// Generate JSMaps per index space for name->index lookup. Every index space
|
||||
// proxy is associated with its table for local name lookup.
|
||||
auto local_name_table =
|
||||
GetNameTable(isolate, GetLocalNames(instance, frame->pc()));
|
||||
auto locals =
|
||||
GetJSProxy(frame, local_name_table, GetLocalCallback, HasLocalCallback);
|
||||
JSObject::AddProperty(isolate, target, "locals", locals, READ_ONLY);
|
||||
|
||||
return factory->NewJSProxy(target, handler);
|
||||
}
|
||||
|
||||
#undef ASSIGN
|
||||
#undef EXTRACT_THIS
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
class JSProxy;
|
||||
class WasmFrame;
|
||||
|
||||
namespace wasm {
|
||||
class StreamingDecoder;
|
||||
@ -19,6 +21,8 @@ class WasmJs {
|
||||
public:
|
||||
V8_EXPORT_PRIVATE static void Install(Isolate* isolate,
|
||||
bool exposed_on_global_object);
|
||||
|
||||
V8_EXPORT_PRIVATE static Handle<JSProxy> GetJSDebugProxy(WasmFrame* frame);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
@ -525,15 +525,37 @@ WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_JavaScript) {
|
||||
uint16_t index = 0;
|
||||
runner.builder().AddIndirectFunctionTable(&index, 1);
|
||||
|
||||
TestCode<int> code(
|
||||
TestCode<int64_t> code(
|
||||
&runner,
|
||||
{WASM_SET_GLOBAL(0, WASM_I32V_2('B')),
|
||||
WASM_SET_LOCAL(0, WASM_I32V_2('A')), WASM_RETURN1(WASM_GET_LOCAL(0))},
|
||||
{ValueType::kI32});
|
||||
WASM_SET_LOCAL(0, WASM_I64V_2('A')), WASM_RETURN1(WASM_GET_LOCAL(0))},
|
||||
{ValueType::kI64});
|
||||
code.BreakOnReturn(&runner);
|
||||
|
||||
Isolate* isolate = runner.main_isolate();
|
||||
Handle<String> snippet = V8String(isolate, "213");
|
||||
Handle<String> snippet =
|
||||
V8String(isolate,
|
||||
"JSON.stringify(["
|
||||
//"$global0, "
|
||||
//"$table0, "
|
||||
"$var0, "
|
||||
//"$main, "
|
||||
//"$memory0, "
|
||||
//"globals[0], "
|
||||
//"tables[0], "
|
||||
"locals[0], "
|
||||
//"functions[0], "
|
||||
//"memories[0], "
|
||||
//"memories, "
|
||||
//"tables, "
|
||||
//"stack, "
|
||||
//"imports, "
|
||||
//"exports, "
|
||||
//"globals, "
|
||||
"locals, "
|
||||
//"functions, "
|
||||
"], (k, v) => k === 'at' || typeof v === 'undefined' || typeof "
|
||||
"v === 'object' ? v : v.toString())");
|
||||
|
||||
WasmJSBreakHandler break_handler(isolate, snippet);
|
||||
CHECK(!code.Run(&runner).is_null());
|
||||
@ -541,7 +563,10 @@ WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_JavaScript) {
|
||||
WasmJSBreakHandler::EvaluationResult result =
|
||||
break_handler.result().ToChecked();
|
||||
CHECK_WITH_MSG(result.error.IsNothing(), result.error.ToChecked().c_str());
|
||||
CHECK_EQ(result.result.ToChecked(), "213");
|
||||
CHECK_EQ(result.result.ToChecked(), "[\"65\",\"65\",{}]");
|
||||
//"[\"66\",{},\"65\",\"function 0() { [native code] }\",{},"
|
||||
//"\"66\",{},\"65\",\"function 0() { [native code] }\",{},"
|
||||
//"{},{},{\"0\":\"65\"},{},{},{},{},{}]");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
Loading…
Reference in New Issue
Block a user