[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:
Philip Pfaffe 2020-03-19 10:27:22 +01:00 committed by Commit Bot
parent 2193f691da
commit 9696695000
11 changed files with 662 additions and 37 deletions

View File

@ -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",

View 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

View 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_

View File

@ -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);
}

View File

@ -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

View File

@ -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.]

View File

@ -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",

View File

@ -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],

View 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

View File

@ -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

View File

@ -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_;