// 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 #include #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 V8String(Isolate* isolate, const char* str) { return isolate->factory()->NewStringFromAsciiChecked(str); } static bool CheckSignature(ValueType return_type, std::initializer_list 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().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).type_name().c_str(), return_type.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).type_name().c_str(), argument_type.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::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, StandardFrame* frame) : isolate_(isolate), frame_(frame) {} static void GetMemoryTrampoline( const v8::FunctionCallbackInfo& 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::max() - increment) { Handle memory(evaluator_->memory_object(), isolate_); uint32_t new_pages = (increment - 1 + wasm::kWasmPageSize) / wasm::kWasmPageSize; WasmMemoryObject::Grow(isolate_, memory, new_pages); } return static_cast(evaluator_->memory_size()); } static void SbrkTrampoline(const v8::FunctionCallbackInfo& args) { auto& proxy = GetProxy(args); uint32_t size = proxy.GetArgAsUInt32(args, 0); uint32_t result = proxy.Sbrk(size); args.GetReturnValue().Set(result); } template void write_result(const WasmValue& result, uint32_t result_offset) { wasm::ScheduledErrorThrower thrower(isolate_, "debug evaluate proxy"); T val = result.to(); static_assert(static_cast(sizeof(T)) == sizeof(T), "Unexpected size"); if (CheckRangeOutOfBounds(result_offset, sizeof(T), evaluator_->memory_size(), &thrower)) { return; } memcpy(&evaluator_->memory_start()[result_offset], &val, sizeof(T)); } // void __getLocal(uint32_t local, void* result); void GetLocal(uint32_t local, uint32_t result_offset) { WasmValue result = LoadLocalValue(local); switch (result.type().kind()) { case ValueType::kI32: write_result(result, result_offset); break; case ValueType::kI64: write_result(result, result_offset); break; case ValueType::kF32: write_result(result, result_offset); break; case ValueType::kF64: write_result(result, result_offset); break; default: UNIMPLEMENTED(); } } static void GetLocalTrampoline( const v8::FunctionCallbackInfo& args) { auto& proxy = GetProxy(args); uint32_t local = proxy.GetArgAsUInt32(args, 0); uint32_t result = proxy.GetArgAsUInt32(args, 1); proxy.GetLocal(local, result); } Handle CreateImports() { Handle imports_obj = isolate_->factory()->NewJSObject(isolate_->object_function()); Handle 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, "__getLocal", DebugEvaluatorProxy::GetLocalTrampoline); AddImport(import_module_obj, "__getMemory", DebugEvaluatorProxy::GetMemoryTrampoline); AddImport(import_module_obj, "__sbrk", DebugEvaluatorProxy::SbrkTrampoline); return imports_obj; } void SetInstances(Handle evaluator, Handle debuggee) { evaluator_ = evaluator; debuggee_ = debuggee; } private: WasmValue LoadLocalValue(uint32_t local) { DCHECK(frame_->is_wasm()); wasm::DebugInfo* debug_info = WasmFrame::cast(frame_)->native_module()->GetDebugInfo(); return debug_info->GetLocalValue(local, frame_->pc(), frame_->fp(), frame_->callee_fp()); } uint32_t GetArgAsUInt32(const v8::FunctionCallbackInfo& 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(number); } static DebugEvaluatorProxy& GetProxy( const v8::FunctionCallbackInfo& args) { return *reinterpret_cast( args.Data().As()->Value()); } template void AddImport(Handle import_module_obj, const char* function_name, CallableT callback) { v8::Isolate* api_isolate = reinterpret_cast(isolate_); v8::Local context = api_isolate->GetCurrentContext(); std::string data; v8::Local v8_function = v8::Function::New(context, callback, v8::External::New(api_isolate, this)) .ToLocalChecked(); auto wrapped_function = Utils::OpenHandle(*v8_function); Object::SetProperty(isolate_, import_module_obj, V8String(isolate_, function_name), wrapped_function) .Assert(); } Isolate* isolate_; StandardFrame* frame_; Handle evaluator_; Handle 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 == "__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 DebugEvaluateImpl( Vector snippet, Handle debuggee_instance, StandardFrame* 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 evaluator_module; if (!engine->SyncCompile(isolate, features, &thrower, bytes) .ToHandle(&evaluator_module)) { return Nothing(); } // Verify interface. const WasmModule* raw_module = evaluator_module->module(); if (!VerifyEvaluatorInterface(raw_module, bytes, &thrower)) { return Nothing(); } // Set up imports. DebugEvaluatorProxy proxy(isolate, frame); Handle imports = proxy.CreateImports(); // Instantiate Module. Handle evaluator_instance; if (!engine->SyncInstantiate(isolate, &thrower, evaluator_module, imports, {}) .ToHandle(&evaluator_instance)) { return Nothing(); } proxy.SetInstances(evaluator_instance, debuggee_instance); Handle exports_obj(evaluator_instance->exports_object(), isolate); Handle 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(); } Handle entry_point = Handle::cast(entry_point_obj); // TODO(wasm): Cache this code. Handle wasm_entry = compiler::CompileCWasmEntry(isolate, 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(); uint32_t offset = packer.Pop(); if (CheckRangeOutOfBounds(offset, 0, evaluator_instance->memory_size(), &thrower)) { return Nothing(); } // 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(); } MaybeHandle DebugEvaluate(Vector snippet, Handle debuggee_instance, StandardFrame* frame) { Maybe 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