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

[inspector][wasm] Remove obsolete Debugger.executeWasmEvaluator().

With https://crrev.com/c/2087396 we introduced a new CDP method
`Debugger.executeWasmEvaluator()`, which we originally intended
to use as the foundation for Debug-Evaluate on Wasm frames.

However in the process of prototyping we learned that it is too
costly and too inefficient to use WebAssembly modules here, and
we switched to regular Debug-Evaluate with JavaScript instead
(with a special debug proxy exposed that allows JavaScript to
peak into the Wasm frame), since JavaScript is better suited
for short-lived / short-running snippets and we don't need
clang and wasm-ld then to generate these snippets.

The JavaScript exposed debug proxy (as described in [1]) not
only enables more powerful and flexible Debug-Evaluate for the
DWARF C/C++ extension, but also serves as the basis for various
aspects of the Basic Wasm Developer Experience.

In order to pay down technical debt and to keep the maintenance
overhead low, we should remove the initial prototype now, also
to ensure that we don't accidentally attract other users of CDP
to rely on this unsupported API (despite it being marked as
"experimental").

[1]: https://docs.google.com/document/d/1VZOJrU2VsqOZe3IUzbwQWQQSZwgGySsm5119Ust1gUA

Fixed: chromium:1162062
Bug: chromium:1020120, chromium:1068571, chromium:1127914
Change-Id: I6dba8c906a8675ce6c29a52e3c32bb6626a27247
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2605186
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Yang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71882}
parent 76d6f06a
...@@ -3430,8 +3430,6 @@ v8_source_set("v8_base_without_compiler") { ...@@ -3430,8 +3430,6 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/wasm-code-manager.cc", "src/wasm/wasm-code-manager.cc",
"src/wasm/wasm-code-manager.h", "src/wasm/wasm-code-manager.h",
"src/wasm/wasm-constants.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-debug.cc",
"src/wasm/wasm-engine.cc", "src/wasm/wasm-engine.cc",
"src/wasm/wasm-engine.h", "src/wasm/wasm-engine.h",
......
...@@ -211,21 +211,6 @@ domain Debugger ...@@ -211,21 +211,6 @@ domain Debugger
# Exception details. # Exception details.
optional Runtime.ExceptionDetails exceptionDetails optional Runtime.ExceptionDetails exceptionDetails
# Execute a Wasm Evaluator module on a given call frame.
experimental command executeWasmEvaluator
parameters
# WebAssembly call frame identifier to evaluate on.
CallFrameId callFrameId
# Code of the evaluator module.
binary evaluator
# Terminate execution after timing out (number of milliseconds).
experimental optional Runtime.TimeDelta timeout
returns
# Object wrapper for the evaluation result.
Runtime.RemoteObject result
# Exception details.
optional Runtime.ExceptionDetails exceptionDetails
# Returns possible locations for breakpoint. scriptId in start and end range locations should be # Returns possible locations for breakpoint. scriptId in start and end range locations should be
# the same. # the same.
command getPossibleBreakpoints command getPossibleBreakpoints
......
...@@ -455,7 +455,6 @@ class V8_EXPORT_PRIVATE ScopeIterator { ...@@ -455,7 +455,6 @@ class V8_EXPORT_PRIVATE ScopeIterator {
class V8_EXPORT_PRIVATE StackTraceIterator { class V8_EXPORT_PRIVATE StackTraceIterator {
public: public:
static bool SupportsWasmDebugEvaluate();
static std::unique_ptr<StackTraceIterator> Create(Isolate* isolate, static std::unique_ptr<StackTraceIterator> Create(Isolate* isolate,
int index = 0); int index = 0);
StackTraceIterator() = default; StackTraceIterator() = default;
...@@ -478,8 +477,6 @@ class V8_EXPORT_PRIVATE StackTraceIterator { ...@@ -478,8 +477,6 @@ class V8_EXPORT_PRIVATE StackTraceIterator {
virtual bool Restart() = 0; virtual bool Restart() = 0;
virtual v8::MaybeLocal<v8::Value> Evaluate(v8::Local<v8::String> source, virtual v8::MaybeLocal<v8::Value> Evaluate(v8::Local<v8::String> source,
bool throw_on_side_effect) = 0; bool throw_on_side_effect) = 0;
virtual v8::MaybeLocal<v8::String> EvaluateWasm(
internal::Vector<const internal::byte> source, int frame_index) = 0;
}; };
class QueryObjectPredicate { class QueryObjectPredicate {
......
...@@ -6,22 +6,14 @@ ...@@ -6,22 +6,14 @@
#include "src/api/api-inl.h" #include "src/api/api-inl.h"
#include "src/debug/debug-evaluate.h" #include "src/debug/debug-evaluate.h"
#include "src/debug/debug-interface.h"
#include "src/debug/debug-scope-iterator.h" #include "src/debug/debug-scope-iterator.h"
#include "src/debug/debug.h" #include "src/debug/debug.h"
#include "src/debug/liveedit.h" #include "src/debug/liveedit.h"
#include "src/execution/frames-inl.h" #include "src/execution/frames-inl.h"
#include "src/execution/frames.h"
#include "src/execution/isolate.h" #include "src/execution/isolate.h"
#include "src/wasm/wasm-debug-evaluate.h"
#include "src/wasm/wasm-debug.h"
namespace v8 { namespace v8 {
bool debug::StackTraceIterator::SupportsWasmDebugEvaluate() {
return i::FLAG_wasm_expose_debug_eval;
}
std::unique_ptr<debug::StackTraceIterator> debug::StackTraceIterator::Create( std::unique_ptr<debug::StackTraceIterator> debug::StackTraceIterator::Create(
v8::Isolate* isolate, int index) { v8::Isolate* isolate, int index) {
return std::unique_ptr<debug::StackTraceIterator>( return std::unique_ptr<debug::StackTraceIterator>(
...@@ -209,25 +201,5 @@ v8::MaybeLocal<v8::Value> DebugStackTraceIterator::Evaluate( ...@@ -209,25 +201,5 @@ v8::MaybeLocal<v8::Value> DebugStackTraceIterator::Evaluate(
return Utils::ToLocal(value); return Utils::ToLocal(value);
} }
v8::MaybeLocal<v8::String> DebugStackTraceIterator::EvaluateWasm(
internal::Vector<const internal::byte> source, int frame_index) {
DCHECK(!Done());
if (!i::FLAG_wasm_expose_debug_eval || !iterator_.is_wasm()) {
return v8::MaybeLocal<v8::String>();
}
Handle<String> value;
i::SafeForInterruptsScope safe_for_interrupt_scope(isolate_);
FrameSummary summary = FrameSummary::Get(iterator_.frame(), 0);
const FrameSummary::WasmFrameSummary& wasmSummary = summary.AsWasm();
Handle<WasmInstanceObject> instance = wasmSummary.wasm_instance();
if (!v8::internal::wasm::DebugEvaluate(source, instance, iterator_.frame())
.ToHandle(&value)) {
isolate_->OptionalRescheduleException(false);
return v8::MaybeLocal<v8::String>();
}
return Utils::ToLocal(value);
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -34,8 +34,6 @@ class DebugStackTraceIterator final : public debug::StackTraceIterator { ...@@ -34,8 +34,6 @@ class DebugStackTraceIterator final : public debug::StackTraceIterator {
bool Restart() override; bool Restart() override;
v8::MaybeLocal<v8::Value> Evaluate(v8::Local<v8::String> source, v8::MaybeLocal<v8::Value> Evaluate(v8::Local<v8::String> source,
bool throw_on_side_effect) override; bool throw_on_side_effect) override;
v8::MaybeLocal<v8::String> EvaluateWasm(
internal::Vector<const internal::byte> source, int frame_index) override;
private: private:
Isolate* isolate_; Isolate* isolate_;
......
...@@ -836,9 +836,6 @@ DEFINE_BOOL(trace_wasm_memory, false, ...@@ -836,9 +836,6 @@ DEFINE_BOOL(trace_wasm_memory, false,
DEFINE_INT(wasm_tier_mask_for_testing, 0, DEFINE_INT(wasm_tier_mask_for_testing, 0,
"bitmask of functions to compile with TurboFan instead of Liftoff") "bitmask of functions to compile with TurboFan instead of Liftoff")
DEFINE_BOOL(wasm_expose_debug_eval, true,
"Expose wasm evaluator support on the CDP")
DEFINE_BOOL(validate_asm, true, "validate asm.js modules before compiling") DEFINE_BOOL(validate_asm, true, "validate asm.js modules before compiling")
DEFINE_BOOL(suppress_asm_messages, false, DEFINE_BOOL(suppress_asm_messages, false,
"don't emit asm.js related messages (for golden file testing)") "don't emit asm.js related messages (for golden file testing)")
......
...@@ -1241,55 +1241,6 @@ Response V8DebuggerAgentImpl::evaluateOnCallFrame( ...@@ -1241,55 +1241,6 @@ Response V8DebuggerAgentImpl::evaluateOnCallFrame(
result, exceptionDetails); result, exceptionDetails);
} }
Response V8DebuggerAgentImpl::executeWasmEvaluator(
const String16& callFrameId, const protocol::Binary& evaluator,
Maybe<double> timeout,
std::unique_ptr<protocol::Runtime::RemoteObject>* result,
Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
if (!v8::debug::StackTraceIterator::SupportsWasmDebugEvaluate()) {
return Response::ServerError(
"--wasm-expose-debug-eval is required to execte evaluator modules");
}
if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
InjectedScript::CallFrameScope scope(m_session, callFrameId);
Response response = scope.initialize();
if (!response.IsSuccess()) return response;
int frameOrdinal = static_cast<int>(scope.frameOrdinal());
std::unique_ptr<v8::debug::StackTraceIterator> it =
v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal);
if (it->Done()) {
return Response::ServerError("Could not find call frame with given id");
}
if (!it->GetScript()->IsWasm()) {
return Response::ServerError(
"executeWasmEvaluator can only be called on WebAssembly frames");
}
v8::MaybeLocal<v8::Value> maybeResultValue;
{
V8InspectorImpl::EvaluateScope evaluateScope(scope);
if (timeout.isJust()) {
response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0);
if (!response.IsSuccess()) return response;
}
v8::MaybeLocal<v8::String> eval_result =
it->EvaluateWasm({evaluator.data(), evaluator.size()}, frameOrdinal);
if (!eval_result.IsEmpty()) maybeResultValue = eval_result.ToLocalChecked();
}
// Re-initialize after running client's code, as it could have destroyed
// context or session.
response = scope.initialize();
if (!response.IsSuccess()) return response;
String16 object_group = "";
InjectedScript* injected_script = scope.injectedScript();
return injected_script->wrapEvaluateResult(maybeResultValue, scope.tryCatch(),
object_group, WrapMode::kNoPreview,
result, exceptionDetails);
}
Response V8DebuggerAgentImpl::setVariableValue( Response V8DebuggerAgentImpl::setVariableValue(
int scopeNumber, const String16& variableName, int scopeNumber, const String16& variableName,
std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument, std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument,
......
...@@ -118,11 +118,6 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend { ...@@ -118,11 +118,6 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Maybe<double> timeout, Maybe<double> timeout,
std::unique_ptr<protocol::Runtime::RemoteObject>* result, std::unique_ptr<protocol::Runtime::RemoteObject>* result,
Maybe<protocol::Runtime::ExceptionDetails>*) override; Maybe<protocol::Runtime::ExceptionDetails>*) override;
Response executeWasmEvaluator(
const String16& callFrameId, const protocol::Binary& evaluator,
Maybe<double> timeout,
std::unique_ptr<protocol::Runtime::RemoteObject>* result,
Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) override;
Response setVariableValue( Response setVariableValue(
int scopeNumber, const String16& variableName, int scopeNumber, const String16& variableName,
std::unique_ptr<protocol::Runtime::CallArgument> newValue, std::unique_ptr<protocol::Runtime::CallArgument> newValue,
......
// 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 <limits>
#include "src/api/api-inl.h"
#include "src/base/platform/wrappers.h"
#include "src/codegen/machine-type.h"
#include "src/compiler/wasm-compiler.h"
#include "src/execution/frames-inl.h"
#include "src/wasm/value-type.h"
#include "src/wasm/wasm-arguments.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-result.h"
#include "src/wasm/wasm-value.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.name().c_str());
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).name().c_str(),
return_type.name().c_str());
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).name().c_str(), argument_type.name().c_str());
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, CommonFrame* frame)
: isolate_(isolate), frame_(frame) {}
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, void* result);
void GetMemory(uint32_t offset, uint32_t size, uint32_t result) {
wasm::ScheduledErrorThrower thrower(isolate_, "debug evaluate proxy");
// Check all overflows.
if (CheckRangeOutOfBounds(offset, size, debuggee_->memory_size(),
&thrower) ||
CheckRangeOutOfBounds(result, size, evaluator_->memory_size(),
&thrower)) {
return;
}
std::memcpy(&evaluator_->memory_start()[result],
&debuggee_->memory_start()[offset], size);
}
// void* __sbrk(intptr_t increment);
uint32_t Sbrk(uint32_t increment) {
if (increment > 0 && evaluator_->memory_size() <=
std::numeric_limits<uint32_t>::max() - increment) {
Handle<WasmMemoryObject> memory(evaluator_->memory_object(), isolate_);
uint32_t new_pages =
(increment - 1 + wasm::kWasmPageSize) / wasm::kWasmPageSize;
WasmMemoryObject::Grow(isolate_, memory, new_pages);
}
return static_cast<uint32_t>(evaluator_->memory_size());
}
static void SbrkTrampoline(const v8::FunctionCallbackInfo<v8::Value>& args) {
DebugEvaluatorProxy& proxy = GetProxy(args);
uint32_t size = proxy.GetArgAsUInt32(args, 0);
uint32_t result = proxy.Sbrk(size);
args.GetReturnValue().Set(result);
}
// void __getLocal(uint32_t local, void* result);
void GetLocal(uint32_t local, uint32_t result_offset) {
DCHECK(frame_->is_wasm());
wasm::DebugInfo* debug_info =
WasmFrame::cast(frame_)->native_module()->GetDebugInfo();
WasmValue result = debug_info->GetLocalValue(
local, frame_->pc(), frame_->fp(), frame_->callee_fp());
WriteResult(result, result_offset);
}
void GetGlobal(uint32_t global, uint32_t result_offset) {
DCHECK(frame_->is_wasm());
const WasmGlobal& global_variable =
WasmFrame::cast(frame_)->native_module()->module()->globals.at(global);
Handle<WasmInstanceObject> instance(
WasmFrame::cast(frame_)->wasm_instance(), isolate_);
WasmValue result =
WasmInstanceObject::GetGlobalValue(instance, global_variable);
WriteResult(result, result_offset);
}
void GetOperand(uint32_t operand, uint32_t result_offset) {
DCHECK(frame_->is_wasm());
wasm::DebugInfo* debug_info =
WasmFrame::cast(frame_)->native_module()->GetDebugInfo();
WasmValue result = debug_info->GetStackValue(
operand, frame_->pc(), frame_->fp(), frame_->callee_fp());
WriteResult(result, result_offset);
}
static void GetLocalTrampoline(
const v8::FunctionCallbackInfo<v8::Value>& args) {
DebugEvaluatorProxy& proxy = GetProxy(args);
uint32_t local = proxy.GetArgAsUInt32(args, 0);
uint32_t result = proxy.GetArgAsUInt32(args, 1);
proxy.GetLocal(local, result);
}
static void GetGlobalTrampoline(
const v8::FunctionCallbackInfo<v8::Value>& args) {
DebugEvaluatorProxy& proxy = GetProxy(args);
uint32_t global = proxy.GetArgAsUInt32(args, 0);
uint32_t result = proxy.GetArgAsUInt32(args, 1);
proxy.GetGlobal(global, result);
}
static void GetOperandTrampoline(
const v8::FunctionCallbackInfo<v8::Value>& args) {
DebugEvaluatorProxy& proxy = GetProxy(args);
uint32_t operand = proxy.GetArgAsUInt32(args, 0);
uint32_t result = proxy.GetArgAsUInt32(args, 1);
proxy.GetOperand(operand, result);
}
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, V8String(isolate_, "env"),
import_module_obj)
.Assert();
AddImport(import_module_obj, "__getOperand",
DebugEvaluatorProxy::GetOperandTrampoline);
AddImport(import_module_obj, "__getGlobal",
DebugEvaluatorProxy::GetGlobalTrampoline);
AddImport(import_module_obj, "__getLocal",
DebugEvaluatorProxy::GetLocalTrampoline);
AddImport(import_module_obj, "__getMemory",
DebugEvaluatorProxy::GetMemoryTrampoline);
AddImport(import_module_obj, "__sbrk", DebugEvaluatorProxy::SbrkTrampoline);
return imports_obj;
}
void SetInstances(Handle<WasmInstanceObject> evaluator,
Handle<WasmInstanceObject> debuggee) {
evaluator_ = evaluator;
debuggee_ = debuggee;
}
private:
template <typename T>
void WriteResultImpl(const WasmValue& result, uint32_t result_offset) {
wasm::ScheduledErrorThrower thrower(isolate_, "debug evaluate proxy");
T val = result.to<T>();
STATIC_ASSERT(static_cast<uint32_t>(sizeof(T)) == sizeof(T));
if (CheckRangeOutOfBounds(result_offset, sizeof(T),
evaluator_->memory_size(), &thrower)) {
return;
}
base::Memcpy(&evaluator_->memory_start()[result_offset], &val, sizeof(T));
}
void WriteResult(const WasmValue& result, uint32_t result_offset) {
switch (result.type().kind()) {
case ValueType::kI32:
WriteResultImpl<uint32_t>(result, result_offset);
break;
case ValueType::kI64:
WriteResultImpl<int64_t>(result, result_offset);
break;
case ValueType::kF32:
WriteResultImpl<float>(result, result_offset);
break;
case ValueType::kF64:
WriteResultImpl<double>(result, result_offset);
break;
default:
UNIMPLEMENTED();
}
}
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());
}
template <typename CallableT>
void AddImport(Handle<JSObject> import_module_obj, const char* function_name,
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> v8_function =
v8::Function::New(context, callback,
v8::External::New(api_isolate, this))
.ToLocalChecked();
Handle<JSReceiver> wrapped_function = Utils::OpenHandle(*v8_function);
Object::SetProperty(isolate_, import_module_obj,
V8String(isolate_, function_name), wrapped_function)
.Assert();
}
Isolate* isolate_;
CommonFrame* frame_;
Handle<WasmInstanceObject> evaluator_;
Handle<WasmInstanceObject> debuggee_;
};
static bool VerifyEvaluatorInterface(const WasmModule* raw_module,
const ModuleWireBytes& bytes,
ErrorThrower* thrower) {
for (const WasmImport imported : raw_module->import_table) {
if (imported.kind != ImportExportKindCode::kExternalFunction) continue;
const WasmFunction& F = raw_module->functions.at(imported.index);
std::string module_name(bytes.start() + imported.module_name.offset(),
bytes.start() + imported.module_name.end_offset());
std::string field_name(bytes.start() + imported.field_name.offset(),
bytes.start() + imported.field_name.end_offset());
if (module_name == "env") {
if (field_name == "__getMemory") {
// void __getMemory(uint32_t offset, uint32_t size, void* result);
if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32, kWasmI32}, F.sig,
thrower)) {
continue;
}
} else if (field_name == "__getOperand") {
// void __getOperand(uint32_t local, void* result)
if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
continue;
}
} else if (field_name == "__getGlobal") {
// void __getGlobal(uint32_t local, void* result)
if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
continue;
}
} else if (field_name == "__getLocal") {
// void __getLocal(uint32_t local, void* result)
if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
continue;
}
} else if (field_name == "__debug") {
// void __debug(uint32_t flag, uint32_t value)
if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
continue;
}
} else if (field_name == "__sbrk") {
// uint32_t __sbrk(uint32_t increment)
if (CheckSignature(kWasmI32, {kWasmI32}, F.sig, thrower)) {
continue;
}
}
}
if (!thrower->error()) {
thrower->LinkError("Unknown import \"%s\" \"%s\"", module_name.c_str(),
field_name.c_str());
}
return false;
}
for (const WasmExport& exported : raw_module->export_table) {
if (exported.kind != ImportExportKindCode::kExternalFunction) continue;
const WasmFunction& F = raw_module->functions.at(exported.index);
std::string field_name(bytes.start() + exported.name.offset(),
bytes.start() + exported.name.end_offset());
if (field_name == "wasm_format") {
if (!CheckSignature(kWasmI32, {}, F.sig, thrower)) return false;
}
}
return true;
}
} // namespace
Maybe<std::string> DebugEvaluateImpl(
Vector<const byte> snippet, Handle<WasmInstanceObject> debuggee_instance,
CommonFrame* 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, frame);
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);
// TODO(wasm): Cache this code.
Handle<Code> wasm_entry = compiler::CompileCWasmEntry(
isolate, entry_point->sig(), debuggee_instance->module());
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,
CommonFrame* frame) {
Maybe<std::string> result =
DebugEvaluateImpl(snippet, debuggee_instance, 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-objects.h"
namespace v8 {
namespace internal {
namespace wasm {
MaybeHandle<String> V8_EXPORT_PRIVATE
DebugEvaluate(Vector<const byte> snippet,
Handle<WasmInstanceObject> debuggee_instance, CommonFrame* frame);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_WASM_DEBUG_EVALUATE_H_
...@@ -317,8 +317,6 @@ v8_source_set("cctest_sources") { ...@@ -317,8 +317,6 @@ v8_source_set("cctest_sources") {
"wasm/test-streaming-compilation.cc", "wasm/test-streaming-compilation.cc",
"wasm/test-wasm-breakpoints.cc", "wasm/test-wasm-breakpoints.cc",
"wasm/test-wasm-codegen.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-import-wrapper-cache.cc",
"wasm/test-wasm-metrics.cc", "wasm/test-wasm-metrics.cc",
"wasm/test-wasm-serialization.cc", "wasm/test-wasm-serialization.cc",
......
...@@ -418,7 +418,6 @@ ...@@ -418,7 +418,6 @@
# Liftoff is not currently supported on ppc and s390 # Liftoff is not currently supported on ppc and s390
'test-liftoff-*': [SKIP], 'test-liftoff-*': [SKIP],
'test-wasm-breakpoints/*' : [SKIP], 'test-wasm-breakpoints/*' : [SKIP],
'test-wasm-debug-evaluate/*': [SKIP],
'test-run-wasm-module/Run_WasmModule_CompilationHintsNoTiering': [SKIP], 'test-run-wasm-module/Run_WasmModule_CompilationHintsNoTiering': [SKIP],
'test-wasm-serialization/TierDownAfterDeserialization': [SKIP], 'test-wasm-serialization/TierDownAfterDeserialization': [SKIP],
'test-gc/RunWasmLiftoff*': [SKIP], 'test-gc/RunWasmLiftoff*': [SKIP],
...@@ -514,7 +513,6 @@ ...@@ -514,7 +513,6 @@
'test-streaming-compilation/*': [SKIP], 'test-streaming-compilation/*': [SKIP],
'test-wasm-breakpoints/*': [SKIP], 'test-wasm-breakpoints/*': [SKIP],
'test-wasm-codegen/*': [SKIP], 'test-wasm-codegen/*': [SKIP],
'test-wasm-debug-evaluate/*': [SKIP],
'test-wasm-import-wrapper-cache/*': [SKIP], 'test-wasm-import-wrapper-cache/*': [SKIP],
'test-wasm-metrics/*': [SKIP], 'test-wasm-metrics/*': [SKIP],
'test-wasm-serialization/*': [SKIP], 'test-wasm-serialization/*': [SKIP],
......
// 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-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 {
static Handle<String> V8String(Isolate* isolate, const char* str) {
return isolate->factory()->NewStringFromAsciiChecked(str);
}
namespace {
template <typename... FunctionArgsT>
class TestCode {
public:
TestCode(WasmRunnerBase* runner, std::initializer_list<byte> code,
std::initializer_list<ValueType::Kind> locals = {})
: compiler_(&runner->NewFunction<FunctionArgsT...>()),
code_(code),
locals_(static_cast<int32_t>(locals.size())) {
for (ValueType::Kind T : locals) {
compiler_->AllocateLocal(ValueType::Primitive(T));
}
compiler_->Build(code.begin(), code.end());
}
Handle<BreakPoint> BreakOnReturn(WasmRunnerBase* runner) {
runner->TierDown();
uint32_t return_idx = FindReturn();
uint32_t return_offset_in_function =
static_cast<uint32_t>(LEBHelper::sizeof_i32v(locals_)) + 2 * locals_ +
return_idx;
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());
int expected_breakpoint_position = return_offset_in_module;
CHECK(WasmScript::SetBreakPoint(script, &return_offset_in_module,
break_point));
// Check that the breakpoint doesn't slide
DCHECK_EQ(expected_breakpoint_position, return_offset_in_module);
USE(expected_breakpoint_position);
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_;
int32_t locals_;
};
class WasmEvaluatorBuilder {
public:
explicit WasmEvaluatorBuilder(TestExecutionTier 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"));
get_local_function_index =
AddImport<void, uint32_t, uint32_t>(CStrVector("__getLocal"));
get_global_function_index =
AddImport<void, uint32_t, uint32_t>(CStrVector("__getGlobal"));
get_operand_function_index =
AddImport<void, uint32_t, uint32_t>(CStrVector("__getOperand"));
sbrk_function_index = AddImport<uint32_t, uint32_t>(CStrVector("__sbrk"));
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_),
CStrVector("env"));
}
void push_back(std::initializer_list<byte> code) {
wasm_format_function->EmitCode(code.begin(),
static_cast<uint32_t>(code.size()));
}
void CallSbrk(std::initializer_list<byte> args) {
push_back(args);
push_back({WASM_CALL_FUNCTION0(sbrk_function_index)});
}
void CallGetOperand(std::initializer_list<byte> args) {
push_back(args);
push_back({WASM_CALL_FUNCTION0(get_operand_function_index)});
}
void CallGetGlobal(std::initializer_list<byte> args) {
push_back(args);
push_back({WASM_CALL_FUNCTION0(get_global_function_index)});
}
void CallGetLocal(std::initializer_list<byte> args) {
push_back(args);
push_back({WASM_CALL_FUNCTION0(get_local_function_index)});
}
void CallGetMemory(std::initializer_list<byte> args) {
push_back(args);
push_back({WASM_CALL_FUNCTION0(get_memory_function_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;
uint32_t get_local_function_index = 0;
uint32_t get_global_function_index = 0;
uint32_t get_operand_function_index = 0;
uint32_t sbrk_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_);
WasmFrame* frame = WasmFrame::cast(frame_it.frame());
Handle<WasmInstanceObject> instance{frame->wasm_instance(), isolate_};
MaybeHandle<String> result_handle = v8::internal::wasm::DebugEvaluate(
{evaluator_bytes_.begin(), evaluator_bytes_.size()}, instance,
frame_it.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});
}
};
class WasmJSBreakHandler : public debug::DebugDelegate {
public:
struct EvaluationResult {
Maybe<std::string> result = Nothing<std::string>();
Maybe<std::string> error = Nothing<std::string>();
};
WasmJSBreakHandler(Isolate* isolate, std::vector<Handle<String>> snippets)
: isolate_(isolate), snippets_(std::move(snippets)) {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
}
WasmJSBreakHandler(Isolate* isolate, Handle<String> snippet)
: WasmJSBreakHandler(isolate, std::vector<Handle<String>>({snippet})) {}
~WasmJSBreakHandler() override {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
nullptr);
}
const std::vector<EvaluationResult>& results() const { return results_; }
private:
Isolate* isolate_;
std::vector<Handle<String>> snippets_;
std::vector<EvaluationResult> results_;
Maybe<std::string> GetPendingExceptionAsString() const {
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());
}
Maybe<std::string> GetResultAsString(MaybeHandle<Object> result) const {
Handle<Object> just_result;
if (!result.ToHandle(&just_result)) return Nothing<std::string>();
MaybeHandle<String> maybe_string = Object::ToString(isolate_, just_result);
Handle<String> just_string;
if (!maybe_string.ToHandle(&just_string)) return Nothing<std::string>();
return Just<std::string>(just_string->ToCString().get());
}
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<int>&) override {
StackTraceFrameIterator frame_it(isolate_);
WasmFrame* frame = WasmFrame::cast(frame_it.frame());
Handle<WasmInstanceObject> instance{frame->wasm_instance(), isolate_};
for (Handle<String> snippet : snippets_) {
MaybeHandle<Object> result_handle = DebugEvaluate::WebAssembly(
instance, frame_it.frame()->id(), snippet, false);
Maybe<std::string> error_message = GetPendingExceptionAsString();
Maybe<std::string> result_message = GetResultAsString(result_handle);
isolate_->clear_pending_exception();
results_.emplace_back(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))});
// Use a max memory size of 2 here to verify the precondition for the
// GrowMemory test below.
WasmEvaluatorBuilder evaluator(execution_tier, 1, 2);
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_GrowMemory) {
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, 1, 2);
// Grow the memory.
evaluator.CallSbrk({WASM_I32V_1(1)});
// Load 1 byte from an address that's too high for the default memory.
evaluator.CallGetMemory(
{WASM_I32V_1(32), WASM_I32V_1(1), WASM_I32V_3((1 << 16) + 1)});
evaluator.push_back({WASM_RETURN1(WASM_I32V_3((1 << 16) + 1)), 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");
}
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");
}
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_Locals) {
WasmRunner<int> runner(execution_tier);
runner.builder().AddMemoryElems<int32_t>(64);
TestCode<int> code(
&runner,
{WASM_LOCAL_SET(0, WASM_I32V_2('A')), WASM_RETURN1(WASM_LOCAL_GET(0))},
{ValueType::kI32});
code.BreakOnReturn(&runner);
WasmEvaluatorBuilder evaluator(execution_tier);
evaluator.CallGetLocal({WASM_I32V_1(0), 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");
}
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_Globals) {
WasmRunner<int> runner(execution_tier);
runner.builder().AddMemoryElems<int32_t>(64);
runner.builder().AddGlobal<int32_t>();
runner.builder().AddGlobal<int32_t>();
TestCode<void> code(&runner,
{WASM_GLOBAL_SET(0, WASM_I32V_1('4')),
WASM_GLOBAL_SET(1, WASM_I32V_1('5')), WASM_RETURN0},
{});
code.BreakOnReturn(&runner);
WasmEvaluatorBuilder evaluator(execution_tier);
evaluator.CallGetGlobal({WASM_I32V_1(0), WASM_I32V_1(33)});
evaluator.CallGetGlobal({WASM_I32V_1(1), WASM_I32V_1(34)});
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(), "45");
}
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_Operands) {
WasmRunner<int> runner(execution_tier);
runner.builder().AddMemoryElems<int32_t>(64);
TestCode<int> code(&runner,
{WASM_LOCAL_SET(0, WASM_I32V_1('4')), WASM_LOCAL_GET(0),
WASM_RETURN1(WASM_I32V_1('5'))},
{ValueType::kI32});
code.BreakOnReturn(&runner);
WasmEvaluatorBuilder evaluator(execution_tier);
evaluator.CallGetOperand({WASM_I32V_1(0), WASM_I32V_1(33)});
evaluator.CallGetOperand({WASM_I32V_1(1), WASM_I32V_1(34)});
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(), "45");
}
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_JavaScript) {
WasmRunner<int> runner(execution_tier);
runner.builder().AddGlobal<int32_t>();
runner.builder().AddMemoryElems<int32_t>(64);
uint16_t index = 0;
runner.builder().AddIndirectFunctionTable(&index, 1);
TestCode<int64_t> code(
&runner,
{WASM_GLOBAL_SET(0, WASM_I32V_2('B')),
WASM_LOCAL_SET(0, WASM_I64V_2('A')), WASM_RETURN1(WASM_LOCAL_GET(0))},
{ValueType::kI64});
code.BreakOnReturn(&runner);
Isolate* isolate = runner.main_isolate();
Handle<String> snippet =
V8String(isolate,
"JSON.stringify(["
"$global0, "
"$table0, "
"$var0, "
"$main, "
"$memory0, "
"globals.$nope, "
"globals[0], "
"globals[1], "
"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());
WasmJSBreakHandler::EvaluationResult result = break_handler.results().front();
CHECK_WITH_MSG(result.error.IsNothing(), result.error.ToChecked().c_str());
CHECK_EQ(result.result.ToChecked(),
"[\"66\",{},\"65\",\"function 0() { [native code] "
"}\",{},null,\"66\",null,{},\"65\",\"function 0() { [native code] "
"}\",{},{},{},{\"0\":\"65\"},{},{},{},{},{}]");
}
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_JavaScript_Caching) {
WasmRunner<int> runner(execution_tier);
runner.builder().AddGlobal<int32_t>();
TestCode<int32_t> code(
&runner,
{WASM_GLOBAL_SET(0, WASM_I32V_2('B')), WASM_RETURN1(WASM_GLOBAL_GET(0))},
{});
code.BreakOnReturn(&runner);
Isolate* isolate = runner.main_isolate();
Handle<String> snippet = V8String(isolate, "JSON.stringify($global0)");
WasmJSBreakHandler break_handler(isolate, {snippet, snippet});
CHECK(!code.Run(&runner).is_null());
auto results = break_handler.results();
CHECK_EQ(results.size(), 2);
CHECK_WITH_MSG(results[0].error.IsNothing(),
results[0].error.ToChecked().c_str());
CHECK_EQ(results[0].result.ToChecked(), "66");
CHECK_WITH_MSG(results[1].error.IsNothing(),
results[1].error.ToChecked().c_str());
CHECK_EQ(results[1].result.ToChecked(), "66");
}
} // namespace
} // namespace wasm
} // namespace internal
} // namespace v8
Tests wasm debug-evaluate
Test: TestGetMemory
Result: 2
Expected: 2
Finished!
// 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.
// Flags: --expose-wasm --wasm-expose-debug-eval
utils.load('test/inspector/wasm-inspector-test.js');
const {session, contextGroup, Protocol} =
InspectorTest.start('Tests wasm debug-evaluate');
function printFailure(message) {
if (!message.result) {
InspectorTest.logMessage(message);
}
return message;
}
async function getWasmScript() {
while (true) {
const script = await Protocol.Debugger.onceScriptParsed();
if (script.params.url.startsWith('wasm://')) return script.params;
}
}
async function handleDebuggerPaused(data, messageObject) {
const topFrameId = messageObject.params.callFrames[0].callFrameId;
const params = {callFrameId: topFrameId, evaluator: data};
try {
const evalResult = await Protocol.Debugger.executeWasmEvaluator(params);
InspectorTest.log('Result: ' + evalResult.result.result.value);
} catch (err) {
InspectorTest.log(
'Eval failed: ' + err + '\nGot: ' + JSON.stringify(evalResult));
}
await Protocol.Debugger.resume();
}
async function runTest(testName, breakLine, debuggeeBytes, snippetBytes) {
try {
await Protocol.Debugger.onPaused(
handleDebuggerPaused.bind(null, snippetBytes));
InspectorTest.log('Test: ' + testName);
const scriptListener = getWasmScript();
await WasmInspectorTest.instantiate(debuggeeBytes);
const script = await scriptListener;
const msg = await Protocol.Debugger.setBreakpoint({
'location': {
'scriptId': script.scriptId,
'lineNumber': 0,
'columnNumber': breakLine
}
});
printFailure(msg);
const eval = await Protocol.Runtime.evaluate(
{'expression': 'instance.exports.main()'});
InspectorTest.log(
'Expected: ' + String.fromCharCode(eval.result.result.value));
InspectorTest.log('Finished!');
} catch (err) {
InspectorTest.log(err.message);
}
}
// copied from v8
function encode64(data) {
const BASE =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const PAD = '=';
var ret = '';
var leftchar = 0;
var leftbits = 0;
for (var i = 0; i < data.length; i++) {
leftchar = (leftchar << 8) | data[i];
leftbits += 8;
while (leftbits >= 6) {
const curr = (leftchar >> (leftbits - 6)) & 0x3f;
leftbits -= 6;
ret += BASE[curr];
}
}
if (leftbits == 2) {
ret += BASE[(leftchar & 3) << 4];
ret += PAD + PAD;
} else if (leftbits == 4) {
ret += BASE[(leftchar & 0xf) << 2];
ret += PAD;
}
return ret;
}
(async () => {
try {
await Protocol.Debugger.enable();
await (async function TestGetMemory() {
const debuggee_builder = new WasmModuleBuilder();
debuggee_builder.addMemory(256, 256);
const mainFunc =
debuggee_builder.addFunction('main', kSig_i_v)
.addBody([
// clang-format off
kExprI32Const, 32,
kExprI32Const, 50,
kExprI32StoreMem, 0, 0,
kExprI32Const, 32,
kExprI32LoadMem, 0, 0,
kExprReturn
// clang-format on
])
.exportAs('main');
const snippet_builder = new WasmModuleBuilder();
snippet_builder.addMemory(1, 1);
const getMemoryIdx = snippet_builder.addImport(
'env', '__getMemory', makeSig([kWasmI32, kWasmI32, kWasmI32], []));
const heapBase = 32; // Just pick some position in memory
snippet_builder.addFunction('wasm_format', kSig_i_v)
.addBody([
// clang-format off
// __getMemory(32, 4, heapBase)
kExprI32Const, 32, kExprI32Const, 4, kExprI32Const, heapBase,
kExprCallFunction, getMemoryIdx,
// return heapBase
kExprI32Const, heapBase,
kExprReturn
// clang-format on
])
.exportAs('wasm_format');
const debuggeeModule = debuggee_builder.toArray();
await runTest(
'TestGetMemory', mainFunc.body_offset + 9, debuggeeModule,
encode64(snippet_builder.toArray()));
})();
} catch (err) {
InspectorTest.log(err)
}
InspectorTest.completeTest();
})();
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