blob: bbd75f6b188f11e286725b66505226c1fccbe9d5 [file] [log] [blame]
// 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/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;
}
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