[wasm-debug-evaluate] Implement the foundation for wasm debug evaluate
This implements the first part of WebAssembly debug evaluate. The patch includes the foundation required to execute evaluator modules. It only implements the first of the APIs of the evaluator module spec. Bug: chromium:1020120 Change-Id: I06ec98a63d0a0ec8d81c2eac4319c4b85d3e16c1 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2089936 Commit-Queue: Philip Pfaffe <pfaffe@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#66787}
This commit is contained in:
parent
2193f691da
commit
9696695000
2
BUILD.gn
2
BUILD.gn
@ -3026,6 +3026,8 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"src/wasm/wasm-code-manager.cc",
|
||||
"src/wasm/wasm-code-manager.h",
|
||||
"src/wasm/wasm-constants.h",
|
||||
"src/wasm/wasm-debug-evaluate.cc",
|
||||
"src/wasm/wasm-debug-evaluate.h",
|
||||
"src/wasm/wasm-debug.cc",
|
||||
"src/wasm/wasm-engine.cc",
|
||||
"src/wasm/wasm-engine.h",
|
||||
|
274
src/wasm/wasm-debug-evaluate.cc
Normal file
274
src/wasm/wasm-debug-evaluate.cc
Normal file
@ -0,0 +1,274 @@
|
||||
// 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.
|
||||
|
||||
#include "src/wasm/wasm-debug-evaluate.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/codegen/machine-type.h"
|
||||
#include "src/wasm/value-type.h"
|
||||
#include "src/wasm/wasm-arguments.h"
|
||||
#include "src/wasm/wasm-constants.h"
|
||||
#include "src/wasm/wasm-module.h"
|
||||
#include "src/wasm/wasm-objects.h"
|
||||
#include "src/wasm/wasm-result.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
namespace {
|
||||
|
||||
static Handle<String> V8String(Isolate* isolate, const char* str) {
|
||||
return isolate->factory()->NewStringFromAsciiChecked(str);
|
||||
}
|
||||
|
||||
static bool CheckSignature(ValueType return_type,
|
||||
std::initializer_list<ValueType> argument_types,
|
||||
const FunctionSig* sig, ErrorThrower* thrower) {
|
||||
if (sig->return_count() != 1 && return_type != kWasmBottom) {
|
||||
thrower->CompileError("Invalid return type. Got none, expected %s",
|
||||
return_type.type_name());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sig->return_count() == 1) {
|
||||
if (sig->GetReturn(0) != return_type) {
|
||||
thrower->CompileError("Invalid return type. Got %s, expected %s",
|
||||
sig->GetReturn(0).type_name(),
|
||||
return_type.type_name());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sig->parameter_count() != argument_types.size()) {
|
||||
thrower->CompileError("Invalid number of arguments. Expected %zu, got %zu",
|
||||
sig->parameter_count(), argument_types.size());
|
||||
return false;
|
||||
}
|
||||
size_t p = 0;
|
||||
for (ValueType argument_type : argument_types) {
|
||||
if (sig->GetParam(p) != argument_type) {
|
||||
thrower->CompileError(
|
||||
"Invalid argument type for argument %zu. Got %s, expected %s", p,
|
||||
sig->GetParam(p).type_name(), argument_type.type_name());
|
||||
return false;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CheckRangeOutOfBounds(uint32_t offset, uint32_t size,
|
||||
size_t allocation_size,
|
||||
wasm::ErrorThrower* thrower) {
|
||||
if (size > std::numeric_limits<uint32_t>::max() - offset) {
|
||||
thrower->RuntimeError("Overflowing memory range\n");
|
||||
return true;
|
||||
}
|
||||
if (offset + size > allocation_size) {
|
||||
thrower->RuntimeError("Illegal access to out-of-bounds memory");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class DebugEvaluatorProxy {
|
||||
public:
|
||||
explicit DebugEvaluatorProxy(Isolate* isolate) : isolate_(isolate) {}
|
||||
|
||||
static void GetMemoryTrampoline(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
DebugEvaluatorProxy& proxy = GetProxy(args);
|
||||
|
||||
uint32_t offset = proxy.GetArgAsUInt32(args, 0);
|
||||
uint32_t size = proxy.GetArgAsUInt32(args, 1);
|
||||
uint32_t result = proxy.GetArgAsUInt32(args, 2);
|
||||
|
||||
proxy.GetMemory(offset, size, result);
|
||||
}
|
||||
|
||||
void GetMemory(uint32_t offset, uint32_t size, uint32_t result) {
|
||||
wasm::ScheduledErrorThrower thrower(isolate_, "debug evaluate proxy");
|
||||
// Check all overflows.
|
||||
if (CheckRangeOutOfBounds(result, size, debuggee_->memory_size(),
|
||||
&thrower) ||
|
||||
CheckRangeOutOfBounds(offset, size, evaluator_->memory_size(),
|
||||
&thrower)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(&evaluator_->memory_start()[result],
|
||||
&debuggee_->memory_start()[offset], size);
|
||||
}
|
||||
|
||||
template <typename CallableT>
|
||||
Handle<JSReceiver> WrapAsV8Function(CallableT callback) {
|
||||
v8::Isolate* api_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
|
||||
v8::Local<v8::Context> context = api_isolate->GetCurrentContext();
|
||||
std::string data;
|
||||
v8::Local<v8::Function> func =
|
||||
v8::Function::New(context, callback,
|
||||
v8::External::New(api_isolate, this))
|
||||
.ToLocalChecked();
|
||||
|
||||
return Utils::OpenHandle(*func);
|
||||
}
|
||||
|
||||
Handle<JSObject> CreateImports() {
|
||||
Handle<JSObject> imports_obj =
|
||||
isolate_->factory()->NewJSObject(isolate_->object_function());
|
||||
Handle<JSObject> import_module_obj =
|
||||
isolate_->factory()->NewJSObject(isolate_->object_function());
|
||||
Object::SetProperty(isolate_, imports_obj,
|
||||
isolate_->factory()->empty_string(), import_module_obj)
|
||||
.Assert();
|
||||
|
||||
Object::SetProperty(
|
||||
isolate_, import_module_obj, V8String(isolate_, "__getMemory"),
|
||||
WrapAsV8Function(DebugEvaluatorProxy::GetMemoryTrampoline))
|
||||
.Assert();
|
||||
return imports_obj;
|
||||
}
|
||||
|
||||
void SetInstances(Handle<WasmInstanceObject> evaluator,
|
||||
Handle<WasmInstanceObject> debuggee) {
|
||||
evaluator_ = evaluator;
|
||||
debuggee_ = debuggee;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t GetArgAsUInt32(const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
int index) {
|
||||
// No type/range checks needed on his because this is only called for {args}
|
||||
// where we have performed a signature check via {VerifyEvaluatorInterface}
|
||||
double number = Utils::OpenHandle(*args[index])->Number();
|
||||
return static_cast<uint32_t>(number);
|
||||
}
|
||||
|
||||
static DebugEvaluatorProxy& GetProxy(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
return *reinterpret_cast<DebugEvaluatorProxy*>(
|
||||
args.Data().As<v8::External>()->Value());
|
||||
}
|
||||
|
||||
Isolate* isolate_;
|
||||
Handle<WasmInstanceObject> evaluator_;
|
||||
Handle<WasmInstanceObject> debuggee_;
|
||||
};
|
||||
|
||||
static bool VerifyEvaluatorInterface(const WasmModule* raw_module,
|
||||
const ModuleWireBytes& bytes,
|
||||
ErrorThrower* thrower) {
|
||||
for (const WasmFunction& F : raw_module->functions) {
|
||||
WireBytesRef name_ref =
|
||||
raw_module->function_names.Lookup(bytes, F.func_index);
|
||||
std::string name(bytes.start() + name_ref.offset(),
|
||||
bytes.start() + name_ref.end_offset());
|
||||
if (F.exported && name == "wasm_format") {
|
||||
if (!CheckSignature(kWasmI32, {}, F.sig, thrower)) return false;
|
||||
} else if (F.imported) {
|
||||
if (name == "__getMemory") {
|
||||
if (!CheckSignature(kWasmBottom, {kWasmI32, kWasmI32, kWasmI32}, F.sig,
|
||||
thrower)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Maybe<std::string> DebugEvaluateImpl(
|
||||
Vector<const byte> snippet, Handle<WasmInstanceObject> debuggee_instance,
|
||||
WasmInterpreter::FramePtr frame) {
|
||||
Isolate* isolate = debuggee_instance->GetIsolate();
|
||||
HandleScope handle_scope(isolate);
|
||||
WasmEngine* engine = isolate->wasm_engine();
|
||||
wasm::ErrorThrower thrower(isolate, "wasm debug evaluate");
|
||||
|
||||
// Create module object.
|
||||
wasm::ModuleWireBytes bytes(snippet);
|
||||
wasm::WasmFeatures features = wasm::WasmFeatures::FromIsolate(isolate);
|
||||
Handle<WasmModuleObject> evaluator_module;
|
||||
if (!engine->SyncCompile(isolate, features, &thrower, bytes)
|
||||
.ToHandle(&evaluator_module)) {
|
||||
return Nothing<std::string>();
|
||||
}
|
||||
|
||||
// Verify interface.
|
||||
const WasmModule* raw_module = evaluator_module->module();
|
||||
if (!VerifyEvaluatorInterface(raw_module, bytes, &thrower)) {
|
||||
return Nothing<std::string>();
|
||||
}
|
||||
|
||||
// Set up imports.
|
||||
DebugEvaluatorProxy proxy(isolate);
|
||||
Handle<JSObject> imports = proxy.CreateImports();
|
||||
|
||||
// Instantiate Module.
|
||||
Handle<WasmInstanceObject> evaluator_instance;
|
||||
if (!engine->SyncInstantiate(isolate, &thrower, evaluator_module, imports, {})
|
||||
.ToHandle(&evaluator_instance)) {
|
||||
return Nothing<std::string>();
|
||||
}
|
||||
|
||||
proxy.SetInstances(evaluator_instance, debuggee_instance);
|
||||
|
||||
Handle<JSObject> exports_obj(evaluator_instance->exports_object(), isolate);
|
||||
Handle<Object> entry_point_obj;
|
||||
bool get_property_success =
|
||||
Object::GetProperty(isolate, exports_obj,
|
||||
V8String(isolate, "wasm_format"))
|
||||
.ToHandle(&entry_point_obj);
|
||||
if (!get_property_success ||
|
||||
!WasmExportedFunction::IsWasmExportedFunction(*entry_point_obj)) {
|
||||
thrower.LinkError("Missing export: \"wasm_format\"");
|
||||
return Nothing<std::string>();
|
||||
}
|
||||
Handle<WasmExportedFunction> entry_point =
|
||||
Handle<WasmExportedFunction>::cast(entry_point_obj);
|
||||
|
||||
Handle<WasmDebugInfo> debug_info =
|
||||
WasmInstanceObject::GetOrCreateDebugInfo(evaluator_instance);
|
||||
Handle<Code> wasm_entry =
|
||||
WasmDebugInfo::GetCWasmEntry(debug_info, entry_point->sig());
|
||||
CWasmArgumentsPacker packer(4 /* uint32_t return value, no parameters. */);
|
||||
Execution::CallWasm(isolate, wasm_entry, entry_point->GetWasmCallTarget(),
|
||||
evaluator_instance, packer.argv());
|
||||
if (isolate->has_pending_exception()) return Nothing<std::string>();
|
||||
|
||||
uint32_t offset = packer.Pop<uint32_t>();
|
||||
if (CheckRangeOutOfBounds(offset, 0, evaluator_instance->memory_size(),
|
||||
&thrower)) {
|
||||
return Nothing<std::string>();
|
||||
}
|
||||
|
||||
// Copy the zero-terminated string result but don't overflow.
|
||||
std::string result;
|
||||
byte* heap = evaluator_instance->memory_start() + offset;
|
||||
for (; offset < evaluator_instance->memory_size(); ++offset, ++heap) {
|
||||
if (*heap == 0) return Just(result);
|
||||
result.push_back(*heap);
|
||||
}
|
||||
|
||||
thrower.RuntimeError("The evaluation returned an invalid result");
|
||||
return Nothing<std::string>();
|
||||
}
|
||||
|
||||
MaybeHandle<String> DebugEvaluate(Vector<const byte> snippet,
|
||||
Handle<WasmInstanceObject> debuggee_instance,
|
||||
WasmInterpreter::FramePtr frame) {
|
||||
Maybe<std::string> result =
|
||||
DebugEvaluateImpl(snippet, debuggee_instance, std::move(frame));
|
||||
if (result.IsNothing()) return {};
|
||||
std::string result_str = result.ToChecked();
|
||||
return V8String(debuggee_instance->GetIsolate(), result_str.c_str());
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
25
src/wasm/wasm-debug-evaluate.h
Normal file
25
src/wasm/wasm-debug-evaluate.h
Normal file
@ -0,0 +1,25 @@
|
||||
// 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.
|
||||
|
||||
#ifndef V8_WASM_WASM_DEBUG_EVALUATE_H_
|
||||
#define V8_WASM_WASM_DEBUG_EVALUATE_H_
|
||||
|
||||
#include "src/base/macros.h"
|
||||
#include "src/handles/maybe-handles.h"
|
||||
#include "src/wasm/wasm-interpreter.h"
|
||||
#include "src/wasm/wasm-objects.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
MaybeHandle<String> V8_EXPORT_PRIVATE DebugEvaluate(
|
||||
Vector<const byte> snippet, Handle<WasmInstanceObject> debuggee_instance,
|
||||
WasmInterpreter::FramePtr frame);
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_WASM_WASM_DEBUG_EVALUATE_H_
|
@ -31,6 +31,7 @@
|
||||
#include "src/wasm/wasm-serialization.h"
|
||||
|
||||
using v8::internal::wasm::ErrorThrower;
|
||||
using v8::internal::wasm::ScheduledErrorThrower;
|
||||
|
||||
namespace v8 {
|
||||
|
||||
@ -138,35 +139,6 @@ namespace {
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
// Like an ErrorThrower, but turns all pending exceptions into scheduled
|
||||
// exceptions when going out of scope. Use this in API methods.
|
||||
// Note that pending exceptions are not necessarily created by the ErrorThrower,
|
||||
// but e.g. by the wasm start function. There might also be a scheduled
|
||||
// exception, created by another API call (e.g. v8::Object::Get). But there
|
||||
// should never be both pending and scheduled exceptions.
|
||||
class ScheduledErrorThrower : public ErrorThrower {
|
||||
public:
|
||||
ScheduledErrorThrower(i::Isolate* isolate, const char* context)
|
||||
: ErrorThrower(isolate, context) {}
|
||||
|
||||
~ScheduledErrorThrower();
|
||||
};
|
||||
|
||||
ScheduledErrorThrower::~ScheduledErrorThrower() {
|
||||
// There should never be both a pending and a scheduled exception.
|
||||
DCHECK(!isolate()->has_scheduled_exception() ||
|
||||
!isolate()->has_pending_exception());
|
||||
// Don't throw another error if there is already a scheduled error.
|
||||
if (isolate()->has_scheduled_exception()) {
|
||||
Reset();
|
||||
} else if (isolate()->has_pending_exception()) {
|
||||
Reset();
|
||||
isolate()->OptionalRescheduleException(false);
|
||||
} else if (error()) {
|
||||
isolate()->ScheduleThrow(*Reify());
|
||||
}
|
||||
}
|
||||
|
||||
i::Handle<i::String> v8_str(i::Isolate* isolate, const char* str) {
|
||||
return isolate->factory()->NewStringFromAsciiChecked(str);
|
||||
}
|
||||
|
@ -155,6 +155,21 @@ ErrorThrower::~ErrorThrower() {
|
||||
}
|
||||
}
|
||||
|
||||
ScheduledErrorThrower::~ScheduledErrorThrower() {
|
||||
// There should never be both a pending and a scheduled exception.
|
||||
DCHECK(!isolate()->has_scheduled_exception() ||
|
||||
!isolate()->has_pending_exception());
|
||||
// Don't throw another error if there is already a scheduled error.
|
||||
if (isolate()->has_scheduled_exception()) {
|
||||
Reset();
|
||||
} else if (isolate()->has_pending_exception()) {
|
||||
Reset();
|
||||
isolate()->OptionalRescheduleException(false);
|
||||
} else if (error()) {
|
||||
isolate()->ScheduleThrow(*Reify());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -168,6 +168,20 @@ class V8_EXPORT_PRIVATE ErrorThrower {
|
||||
DISALLOW_COPY_AND_ASSIGN(ErrorThrower);
|
||||
};
|
||||
|
||||
// Like an ErrorThrower, but turns all pending exceptions into scheduled
|
||||
// exceptions when going out of scope. Use this in API methods.
|
||||
// Note that pending exceptions are not necessarily created by the ErrorThrower,
|
||||
// but e.g. by the wasm start function. There might also be a scheduled
|
||||
// exception, created by another API call (e.g. v8::Object::Get). But there
|
||||
// should never be both pending and scheduled exceptions.
|
||||
class V8_EXPORT_PRIVATE ScheduledErrorThrower : public ErrorThrower {
|
||||
public:
|
||||
ScheduledErrorThrower(i::Isolate* isolate, const char* context)
|
||||
: ErrorThrower(isolate, context) {}
|
||||
|
||||
~ScheduledErrorThrower();
|
||||
};
|
||||
|
||||
// Use {nullptr_t} as data value to indicate that this only stores the error,
|
||||
// but no result value (the only valid value is {nullptr}).
|
||||
// [Storing {void} would require template specialization.]
|
||||
|
@ -282,6 +282,8 @@ v8_source_set("cctest_sources") {
|
||||
"wasm/test-streaming-compilation.cc",
|
||||
"wasm/test-wasm-breakpoints.cc",
|
||||
"wasm/test-wasm-codegen.cc",
|
||||
"wasm/test-wasm-debug-evaluate.cc",
|
||||
"wasm/test-wasm-debug-evaluate.h",
|
||||
"wasm/test-wasm-import-wrapper-cache.cc",
|
||||
"wasm/test-wasm-interpreter-entry.cc",
|
||||
"wasm/test-wasm-serialization.cc",
|
||||
|
@ -482,6 +482,7 @@
|
||||
'test-streaming-compilation/*': [SKIP],
|
||||
'test-wasm-breakpoints/*': [SKIP],
|
||||
'test-wasm-codegen/*': [SKIP],
|
||||
'test-wasm-debug-evaluate/*': [SKIP],
|
||||
'test-wasm-import-wrapper-cache/*': [SKIP],
|
||||
'test-wasm-interpreter-entry/*': [SKIP],
|
||||
'test-wasm-serialization/*': [SKIP],
|
||||
|
314
test/cctest/wasm/test-wasm-debug-evaluate.cc
Normal file
314
test/cctest/wasm/test-wasm-debug-evaluate.cc
Normal file
@ -0,0 +1,314 @@
|
||||
// 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.
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/base/macros.h"
|
||||
#include "src/codegen/assembler-inl.h"
|
||||
#include "src/compiler/heap-refs.h"
|
||||
#include "src/debug/debug-evaluate.h"
|
||||
#include "src/debug/debug-interface.h"
|
||||
#include "src/diagnostics/disassembler.h"
|
||||
#include "src/execution/frames-inl.h"
|
||||
#include "src/execution/frames.h"
|
||||
#include "src/objects/js-objects.h"
|
||||
#include "src/objects/property-descriptor.h"
|
||||
#include "src/utils/utils.h"
|
||||
#include "src/utils/vector.h"
|
||||
#include "src/wasm/compilation-environment.h"
|
||||
#include "src/wasm/module-decoder.h"
|
||||
#include "src/wasm/value-type.h"
|
||||
#include "src/wasm/wasm-constants.h"
|
||||
#include "src/wasm/wasm-debug-evaluate.h"
|
||||
#include "src/wasm/wasm-debug.h"
|
||||
#include "src/wasm/wasm-interpreter.h"
|
||||
#include "src/wasm/wasm-module-builder.h"
|
||||
#include "src/wasm/wasm-module.h"
|
||||
#include "src/wasm/wasm-objects-inl.h"
|
||||
#include "src/wasm/wasm-opcodes.h"
|
||||
#include "src/wasm/wasm-tier.h"
|
||||
#include "test/cctest/cctest.h"
|
||||
#include "test/cctest/compiler/value-helper.h"
|
||||
#include "test/cctest/wasm/wasm-run-utils.h"
|
||||
#include "test/common/wasm/test-signatures.h"
|
||||
#include "test/common/wasm/wasm-macro-gen.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
namespace {
|
||||
template <typename... FunctionArgsT>
|
||||
class TestCode {
|
||||
public:
|
||||
TestCode(WasmRunnerBase* runner, std::initializer_list<byte> code)
|
||||
: compiler_(&runner->NewFunction<FunctionArgsT...>()), code_(code) {
|
||||
compiler_->Build(code.begin(), code.end());
|
||||
}
|
||||
|
||||
Handle<BreakPoint> BreakOnReturn(WasmRunnerBase* runner) {
|
||||
uint32_t return_offset_in_function = FindReturn();
|
||||
|
||||
int function_index = compiler_->function_index();
|
||||
int function_offset =
|
||||
runner->builder().GetFunctionAt(function_index)->code.offset();
|
||||
int return_offset_in_module = function_offset + return_offset_in_function;
|
||||
|
||||
Handle<WasmInstanceObject> instance = runner->builder().instance_object();
|
||||
Handle<Script> script(instance->module_object().script(),
|
||||
runner->main_isolate());
|
||||
static int break_index = 0;
|
||||
Handle<BreakPoint> break_point =
|
||||
runner->main_isolate()->factory()->NewBreakPoint(
|
||||
break_index++, runner->main_isolate()->factory()->empty_string());
|
||||
CHECK(WasmScript::SetBreakPoint(script, &return_offset_in_module,
|
||||
break_point));
|
||||
int set_breakpoint_offset = return_offset_in_module - function_offset;
|
||||
// Also set breakpoint on the debug info of the instance directly, since
|
||||
// the instance chain is not set up properly in tests.
|
||||
Handle<WasmDebugInfo> debug_info =
|
||||
WasmInstanceObject::GetOrCreateDebugInfo(instance);
|
||||
WasmDebugInfo::SetBreakpoint(debug_info, function_index,
|
||||
set_breakpoint_offset);
|
||||
|
||||
return break_point;
|
||||
}
|
||||
|
||||
MaybeHandle<Object> Run(WasmRunnerBase* runner) {
|
||||
Isolate* isolate = runner->main_isolate();
|
||||
Handle<JSFunction> fun_wrapper =
|
||||
runner->builder().WrapCode(compiler_->function_index());
|
||||
Handle<Object> global(isolate->context().global_object(), isolate);
|
||||
return Execution::Call(isolate, fun_wrapper, global, 0, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t FindReturn() const {
|
||||
for (auto i = code_.begin(); i != code_.end();
|
||||
i += OpcodeLength(&*i, &*code_.end())) {
|
||||
if (*i == kExprReturn) {
|
||||
return static_cast<uint32_t>(std::distance(code_.begin(), i));
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
WasmFunctionCompiler* compiler_;
|
||||
std::vector<byte> code_;
|
||||
};
|
||||
|
||||
class WasmEvaluatorBuilder {
|
||||
public:
|
||||
explicit WasmEvaluatorBuilder(ExecutionTier execution_tier,
|
||||
uint32_t min_memory = 1,
|
||||
uint32_t max_memory = 1)
|
||||
: zone_(&allocator_, ZONE_NAME), builder_(&zone_) {
|
||||
get_memory_function_index = AddImport<void, uint32_t, uint32_t, uint32_t>(
|
||||
CStrVector("__getMemory"));
|
||||
wasm_format_function =
|
||||
builder_.AddFunction(WasmRunnerBase::CreateSig<uint32_t>(&zone_));
|
||||
wasm_format_function->SetName(CStrVector("wasm_format"));
|
||||
builder_.AddExport(CStrVector("wasm_format"), wasm_format_function);
|
||||
builder_.SetMinMemorySize(min_memory);
|
||||
builder_.SetMaxMemorySize(max_memory);
|
||||
}
|
||||
|
||||
template <typename ReturnT, typename... ArgTs>
|
||||
uint32_t AddImport(Vector<const char> name) {
|
||||
return builder_.AddImport(
|
||||
name, WasmRunnerBase::CreateSig<ReturnT, ArgTs...>(&zone_));
|
||||
}
|
||||
|
||||
void push_back(std::initializer_list<byte> code) {
|
||||
wasm_format_function->EmitCode(code.begin(),
|
||||
static_cast<uint32_t>(code.size()));
|
||||
}
|
||||
|
||||
void CallGetMemory(std::initializer_list<byte> args) {
|
||||
push_back(args);
|
||||
push_back({WASM_CALL_FUNCTION0(wasm_format_function->func_index())});
|
||||
}
|
||||
|
||||
ZoneBuffer bytes() {
|
||||
ZoneBuffer bytes(&zone_);
|
||||
builder_.WriteTo(&bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
v8::internal::AccountingAllocator allocator_;
|
||||
Zone zone_;
|
||||
WasmModuleBuilder builder_;
|
||||
uint32_t get_memory_function_index = 0;
|
||||
WasmFunctionBuilder* wasm_format_function = nullptr;
|
||||
};
|
||||
|
||||
class WasmBreakHandler : public debug::DebugDelegate {
|
||||
public:
|
||||
struct EvaluationResult {
|
||||
Maybe<std::string> result = Nothing<std::string>();
|
||||
Maybe<std::string> error = Nothing<std::string>();
|
||||
};
|
||||
|
||||
WasmBreakHandler(Isolate* isolate, ZoneBuffer evaluator_bytes)
|
||||
: isolate_(isolate),
|
||||
evaluator_bytes_(std::move(evaluator_bytes)),
|
||||
result_(Nothing<EvaluationResult>()) {
|
||||
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
|
||||
}
|
||||
|
||||
~WasmBreakHandler() override {
|
||||
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
const Maybe<EvaluationResult>& result() const { return result_; }
|
||||
|
||||
private:
|
||||
Isolate* isolate_;
|
||||
ZoneBuffer evaluator_bytes_;
|
||||
Maybe<EvaluationResult> result_;
|
||||
|
||||
Maybe<std::string> GetPendingExceptionAsString() {
|
||||
if (!isolate_->has_pending_exception()) return Nothing<std::string>();
|
||||
Handle<Object> exception(isolate_->pending_exception(), isolate_);
|
||||
isolate_->clear_pending_exception();
|
||||
|
||||
Handle<String> exception_string;
|
||||
if (!Object::ToString(isolate_, exception).ToHandle(&exception_string)) {
|
||||
return Just<std::string>("");
|
||||
}
|
||||
return Just<std::string>(exception_string->ToCString().get());
|
||||
}
|
||||
|
||||
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
|
||||
const std::vector<int>&) override {
|
||||
// Check the current position.
|
||||
StackTraceFrameIterator frame_it(isolate_);
|
||||
|
||||
FrameSummary::WasmInterpretedFrameSummary summary =
|
||||
FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
|
||||
Handle<WasmInstanceObject> instance = summary.wasm_instance();
|
||||
WasmInterpreter::FramePtr frame =
|
||||
instance->debug_info().GetInterpretedFrame(frame_it.frame()->fp(), 0);
|
||||
|
||||
MaybeHandle<String> result_handle = v8::internal::wasm::DebugEvaluate(
|
||||
{evaluator_bytes_.begin(), evaluator_bytes_.size()}, instance,
|
||||
std::move(frame));
|
||||
|
||||
Maybe<std::string> error_message = GetPendingExceptionAsString();
|
||||
Maybe<std::string> result_message =
|
||||
result_handle.is_null()
|
||||
? Nothing<std::string>()
|
||||
: Just<std::string>(
|
||||
result_handle.ToHandleChecked()->ToCString().get());
|
||||
|
||||
isolate_->clear_pending_exception();
|
||||
result_ = Just<EvaluationResult>({result_message, error_message});
|
||||
}
|
||||
};
|
||||
|
||||
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_CompileFailed) {
|
||||
WasmRunner<int> runner(execution_tier);
|
||||
|
||||
TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))});
|
||||
code.BreakOnReturn(&runner);
|
||||
|
||||
WasmEvaluatorBuilder evaluator(execution_tier);
|
||||
// Create a module that doesn't compile by missing the END bytecode
|
||||
evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33))});
|
||||
|
||||
Isolate* isolate = runner.main_isolate();
|
||||
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
||||
CHECK(!code.Run(&runner).is_null());
|
||||
|
||||
WasmBreakHandler::EvaluationResult result =
|
||||
break_handler.result().ToChecked();
|
||||
CHECK(result.result.IsNothing());
|
||||
CHECK_NE(result.error.ToChecked().find(
|
||||
"function body must end with \"end\" opcode"),
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_MissingEntrypoint) {
|
||||
WasmRunner<int> runner(execution_tier);
|
||||
|
||||
TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))});
|
||||
code.BreakOnReturn(&runner);
|
||||
|
||||
v8::internal::AccountingAllocator allocator;
|
||||
Zone zone(&allocator, ZONE_NAME);
|
||||
WasmModuleBuilder evaluator(&zone);
|
||||
ZoneBuffer evaluator_bytes(&zone);
|
||||
evaluator.WriteTo(&evaluator_bytes);
|
||||
|
||||
Isolate* isolate = runner.main_isolate();
|
||||
WasmBreakHandler break_handler(isolate, std::move(evaluator_bytes));
|
||||
CHECK(!code.Run(&runner).is_null());
|
||||
|
||||
WasmBreakHandler::EvaluationResult result =
|
||||
break_handler.result().ToChecked();
|
||||
CHECK(result.result.IsNothing());
|
||||
CHECK_NE(result.error.ToChecked().find("Missing export: \"wasm_format\""),
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_ExecuteFailed_SEGV) {
|
||||
WasmRunner<int> runner(execution_tier);
|
||||
runner.builder().AddMemoryElems<int32_t>(64);
|
||||
|
||||
TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))});
|
||||
|
||||
// Create a module that doesn't compile by missing the END bytecode
|
||||
WasmEvaluatorBuilder evaluator(execution_tier);
|
||||
code.BreakOnReturn(&runner);
|
||||
|
||||
// Load 1 byte from an address that's too high
|
||||
evaluator.CallGetMemory(
|
||||
{WASM_I32V_1(32), WASM_I32V_1(1), WASM_I32V_3((1 << 16) + 1)});
|
||||
evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END});
|
||||
|
||||
Isolate* isolate = runner.main_isolate();
|
||||
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
||||
CHECK(!code.Run(&runner).is_null());
|
||||
|
||||
WasmBreakHandler::EvaluationResult result =
|
||||
break_handler.result().ToChecked();
|
||||
CHECK(result.result.IsNothing());
|
||||
CHECK_NE(
|
||||
result.error.ToChecked().find("Illegal access to out-of-bounds memory"),
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_LinearMemory) {
|
||||
WasmRunner<int> runner(execution_tier);
|
||||
runner.builder().AddMemoryElems<int32_t>(64);
|
||||
|
||||
TestCode<int> code(
|
||||
&runner,
|
||||
{WASM_STORE_MEM(MachineType::Int32(), WASM_I32V_1(32), WASM_I32V_2('A')),
|
||||
WASM_RETURN1(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(32)))});
|
||||
code.BreakOnReturn(&runner);
|
||||
|
||||
WasmEvaluatorBuilder evaluator(execution_tier);
|
||||
// Load 4 bytes from debuggee memory at address 32, and store at the offset 33
|
||||
// of the linear memory.
|
||||
evaluator.CallGetMemory({WASM_I32V_1(32), WASM_I32V_1(4), WASM_I32V_1(33)});
|
||||
evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END});
|
||||
|
||||
Isolate* isolate = runner.main_isolate();
|
||||
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
||||
CHECK(!code.Run(&runner).is_null());
|
||||
|
||||
WasmBreakHandler::EvaluationResult result =
|
||||
break_handler.result().ToChecked();
|
||||
CHECK(result.error.IsNothing());
|
||||
CHECK_EQ(result.result.ToChecked(), "A");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
@ -579,13 +579,14 @@ WasmFunctionCompiler::WasmFunctionCompiler(Zone* zone, const FunctionSig* sig,
|
||||
|
||||
WasmFunctionCompiler::~WasmFunctionCompiler() = default;
|
||||
|
||||
const FunctionSig* WasmRunnerBase::CreateSig(MachineType return_type,
|
||||
Vector<MachineType> param_types) {
|
||||
/* static */
|
||||
FunctionSig* WasmRunnerBase::CreateSig(Zone* zone, MachineType return_type,
|
||||
Vector<MachineType> param_types) {
|
||||
int return_count = return_type.IsNone() ? 0 : 1;
|
||||
int param_count = param_types.length();
|
||||
|
||||
// Allocate storage array in zone.
|
||||
ValueType* sig_types = zone_.NewArray<ValueType>(return_count + param_count);
|
||||
ValueType* sig_types = zone->NewArray<ValueType>(return_count + param_count);
|
||||
|
||||
// Convert machine types to local types, and check that there are no
|
||||
// MachineType::None()'s in the parameters.
|
||||
@ -595,7 +596,7 @@ const FunctionSig* WasmRunnerBase::CreateSig(MachineType return_type,
|
||||
CHECK_NE(MachineType::None(), param);
|
||||
sig_types[idx++] = ValueType::For(param);
|
||||
}
|
||||
return new (&zone_) FunctionSig(return_count, param_count, sig_types);
|
||||
return new (zone) FunctionSig(return_count, param_count, sig_types);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -409,17 +409,22 @@ class WasmRunnerBase : public HandleAndZoneScope {
|
||||
bool interpret() { return builder_.interpret(); }
|
||||
|
||||
template <typename ReturnType, typename... ParamTypes>
|
||||
const FunctionSig* CreateSig() {
|
||||
FunctionSig* CreateSig() {
|
||||
return WasmRunnerBase::CreateSig<ReturnType, ParamTypes...>(&zone_);
|
||||
}
|
||||
|
||||
template <typename ReturnType, typename... ParamTypes>
|
||||
static FunctionSig* CreateSig(Zone* zone) {
|
||||
std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{
|
||||
{MachineTypeForC<ParamTypes>()...}};
|
||||
Vector<MachineType> param_vec(param_machine_types.data(),
|
||||
param_machine_types.size());
|
||||
return CreateSig(MachineTypeForC<ReturnType>(), param_vec);
|
||||
return CreateSig(zone, MachineTypeForC<ReturnType>(), param_vec);
|
||||
}
|
||||
|
||||
private:
|
||||
const FunctionSig* CreateSig(MachineType return_type,
|
||||
Vector<MachineType> param_types);
|
||||
static FunctionSig* CreateSig(Zone* zone, MachineType return_type,
|
||||
Vector<MachineType> param_types);
|
||||
|
||||
protected:
|
||||
v8::internal::AccountingAllocator allocator_;
|
||||
|
Loading…
Reference in New Issue
Block a user