Commit 96966950 authored by Philip Pfaffe's avatar Philip Pfaffe Committed by Commit Bot

[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: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66787}
parent 2193f691
......@@ -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",
......
// 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
// 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],
......
This diff is collapsed.
......@@ -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_;
......
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