// Copyright 2016 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 #include "src/base/optional.h" #include "src/codegen/assembler-inl.h" #include "src/common/assert-scope.h" #include "src/compiler/wasm-compiler.h" #include "src/debug/debug-scopes.h" #include "src/debug/debug.h" #include "src/execution/frames-inl.h" #include "src/execution/isolate.h" #include "src/heap/factory.h" #include "src/utils/identity-map.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-interpreter.h" #include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" #include "src/zone/accounting-allocator.h" namespace v8 { namespace internal { namespace wasm { namespace { template Handle PrintFToOneByteString(Isolate* isolate, const char* format, Args... args) { // Maximum length of a formatted value name ("arg#%d", "local#%d", // "global#%d", i32 constants, i64 constants), including null character. static constexpr int kMaxStrLen = 21; EmbeddedVector value; int len = SNPrintF(value, format, args...); CHECK(len > 0 && len < value.length()); Vector name = Vector::cast(value.SubVector(0, len)); return internal ? isolate->factory()->InternalizeString(name) : isolate->factory()->NewStringFromOneByte(name).ToHandleChecked(); } Handle WasmValueToValueObject(Isolate* isolate, WasmValue value) { switch (value.type()) { case kWasmI32: if (Smi::IsValid(value.to())) return handle(Smi::FromInt(value.to()), isolate); return PrintFToOneByteString(isolate, "%d", value.to()); case kWasmI64: { int64_t i64 = value.to(); int32_t i32 = static_cast(i64); if (i32 == i64 && Smi::IsValid(i32)) return handle(Smi::FromIntptr(i32), isolate); return PrintFToOneByteString(isolate, "%" PRId64, i64); } case kWasmF32: return isolate->factory()->NewNumber(value.to()); case kWasmF64: return isolate->factory()->NewNumber(value.to()); case kWasmAnyRef: return value.to_anyref(); default: UNIMPLEMENTED(); return isolate->factory()->undefined_value(); } } MaybeHandle GetLocalName(Isolate* isolate, Handle debug_info, int func_index, int local_index) { DCHECK_LE(0, func_index); DCHECK_LE(0, local_index); if (!debug_info->has_locals_names()) { Handle module_object( debug_info->wasm_instance().module_object(), isolate); Handle locals_names = DecodeLocalNames(isolate, module_object); debug_info->set_locals_names(*locals_names); } Handle locals_names(debug_info->locals_names(), isolate); if (func_index >= locals_names->length() || locals_names->get(func_index).IsUndefined(isolate)) { return {}; } Handle func_locals_names( FixedArray::cast(locals_names->get(func_index)), isolate); if (local_index >= func_locals_names->length() || func_locals_names->get(local_index).IsUndefined(isolate)) { return {}; } return handle(String::cast(func_locals_names->get(local_index)), isolate); } class InterpreterHandle { MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(InterpreterHandle); Isolate* isolate_; const WasmModule* module_; WasmInterpreter interpreter_; StepAction next_step_action_ = StepNone; int last_step_stack_depth_ = 0; std::unordered_map activations_; uint32_t StartActivation(Address frame_pointer) { WasmInterpreter::Thread* thread = interpreter_.GetThread(0); uint32_t activation_id = thread->StartActivation(); DCHECK_EQ(0, activations_.count(frame_pointer)); activations_.insert(std::make_pair(frame_pointer, activation_id)); return activation_id; } void FinishActivation(Address frame_pointer, uint32_t activation_id) { WasmInterpreter::Thread* thread = interpreter_.GetThread(0); thread->FinishActivation(activation_id); DCHECK_EQ(1, activations_.count(frame_pointer)); activations_.erase(frame_pointer); } std::pair GetActivationFrameRange( WasmInterpreter::Thread* thread, Address frame_pointer) { DCHECK_EQ(1, activations_.count(frame_pointer)); uint32_t activation_id = activations_.find(frame_pointer)->second; uint32_t num_activations = static_cast(activations_.size() - 1); uint32_t frame_base = thread->ActivationFrameBase(activation_id); uint32_t frame_limit = activation_id == num_activations ? thread->GetFrameCount() : thread->ActivationFrameBase(activation_id + 1); DCHECK_LE(frame_base, frame_limit); DCHECK_LE(frame_limit, thread->GetFrameCount()); return {frame_base, frame_limit}; } static ModuleWireBytes GetBytes(WasmDebugInfo debug_info) { // Return raw pointer into heap. The WasmInterpreter will make its own copy // of this data anyway, and there is no heap allocation in-between. NativeModule* native_module = debug_info.wasm_instance().module_object().native_module(); return ModuleWireBytes{native_module->wire_bytes()}; } public: InterpreterHandle(Isolate* isolate, Handle debug_info) : isolate_(isolate), module_(debug_info->wasm_instance().module_object().module()), interpreter_(isolate, module_, GetBytes(*debug_info), handle(debug_info->wasm_instance(), isolate)) {} WasmInterpreter* interpreter() { return &interpreter_; } const WasmModule* module() const { return module_; } void PrepareStep(StepAction step_action) { next_step_action_ = step_action; last_step_stack_depth_ = CurrentStackDepth(); } void ClearStepping() { next_step_action_ = StepNone; } int CurrentStackDepth() { DCHECK_EQ(1, interpreter()->GetThreadCount()); return interpreter()->GetThread(0)->GetFrameCount(); } // Returns true if exited regularly, false if a trap/exception occurred and // was not handled inside this activation. In the latter case, a pending // exception will have been set on the isolate. bool Execute(Handle instance_object, Address frame_pointer, uint32_t func_index, Vector argument_values, Vector return_values) { DCHECK_GE(module()->functions.size(), func_index); FunctionSig* sig = module()->functions[func_index].sig; DCHECK_EQ(sig->parameter_count(), argument_values.size()); DCHECK_EQ(sig->return_count(), return_values.size()); uint32_t activation_id = StartActivation(frame_pointer); WasmCodeRefScope code_ref_scope; WasmInterpreter::Thread* thread = interpreter_.GetThread(0); thread->InitFrame(&module()->functions[func_index], argument_values.begin()); bool finished = false; while (!finished) { // TODO(clemensh): Add occasional StackChecks. WasmInterpreter::State state = ContinueExecution(thread); switch (state) { case WasmInterpreter::State::PAUSED: NotifyDebugEventListeners(thread); break; case WasmInterpreter::State::FINISHED: // Perfect, just break the switch and exit the loop. finished = true; break; case WasmInterpreter::State::TRAPPED: { MessageTemplate message_id = WasmOpcodes::TrapReasonToMessageId(thread->GetTrapReason()); Handle exception = isolate_->factory()->NewWasmRuntimeError(message_id); auto result = thread->RaiseException(isolate_, exception); if (result == WasmInterpreter::Thread::HANDLED) break; // If no local handler was found, we fall-thru to {STOPPED}. DCHECK_EQ(WasmInterpreter::State::STOPPED, thread->state()); V8_FALLTHROUGH; } case WasmInterpreter::State::STOPPED: // An exception happened, and the current activation was unwound // without hitting a local exception handler. All that remains to be // done is finish the activation and let the exception propagate. DCHECK_EQ(thread->ActivationFrameBase(activation_id), thread->GetFrameCount()); DCHECK(isolate_->has_pending_exception()); FinishActivation(frame_pointer, activation_id); return false; // RUNNING should never occur here. case WasmInterpreter::State::RUNNING: default: UNREACHABLE(); } } // Copy back the return value. DCHECK_GE(kV8MaxWasmFunctionReturns, sig->return_count()); // TODO(wasm): Handle multi-value returns. DCHECK_EQ(1, kV8MaxWasmFunctionReturns); if (sig->return_count()) { return_values[0] = thread->GetReturnValue(0); } FinishActivation(frame_pointer, activation_id); return true; } WasmInterpreter::State ContinueExecution(WasmInterpreter::Thread* thread) { switch (next_step_action_) { case StepNone: return thread->Run(); case StepIn: return thread->Step(); case StepOut: thread->AddBreakFlags(WasmInterpreter::BreakFlag::AfterReturn); return thread->Run(); case StepNext: { int stack_depth = thread->GetFrameCount(); if (stack_depth == last_step_stack_depth_) return thread->Step(); thread->AddBreakFlags(stack_depth > last_step_stack_depth_ ? WasmInterpreter::BreakFlag::AfterReturn : WasmInterpreter::BreakFlag::AfterCall); return thread->Run(); } default: UNREACHABLE(); } } Handle GetInstanceObject() { StackTraceFrameIterator it(isolate_); WasmInterpreterEntryFrame* frame = WasmInterpreterEntryFrame::cast(it.frame()); Handle instance_obj(frame->wasm_instance(), isolate_); // Check that this is indeed the instance which is connected to this // interpreter. DCHECK_EQ(this, Managed::cast( instance_obj->debug_info().interpreter_handle()) .raw()); return instance_obj; } void NotifyDebugEventListeners(WasmInterpreter::Thread* thread) { // Enter the debugger. DebugScope debug_scope(isolate_->debug()); // Check whether we hit a breakpoint. if (isolate_->debug()->break_points_active()) { Handle module_object( GetInstanceObject()->module_object(), isolate_); int position = GetTopPosition(module_object); Handle breakpoints; if (WasmModuleObject::CheckBreakPoints(isolate_, module_object, position) .ToHandle(&breakpoints)) { // We hit one or several breakpoints. Clear stepping, notify the // listeners and return. ClearStepping(); isolate_->debug()->OnDebugBreak(breakpoints); return; } } // We did not hit a breakpoint, so maybe this pause is related to stepping. bool hit_step = false; switch (next_step_action_) { case StepNone: break; case StepIn: hit_step = true; break; case StepOut: hit_step = thread->GetFrameCount() < last_step_stack_depth_; break; case StepNext: { hit_step = thread->GetFrameCount() == last_step_stack_depth_; break; } default: UNREACHABLE(); } if (!hit_step) return; ClearStepping(); isolate_->debug()->OnDebugBreak(isolate_->factory()->empty_fixed_array()); } int GetTopPosition(Handle module_object) { DCHECK_EQ(1, interpreter()->GetThreadCount()); WasmInterpreter::Thread* thread = interpreter()->GetThread(0); DCHECK_LT(0, thread->GetFrameCount()); auto frame = thread->GetFrame(thread->GetFrameCount() - 1); return module_object->GetFunctionOffset(frame->function()->func_index) + frame->pc(); } std::vector> GetInterpretedStack( Address frame_pointer) { DCHECK_EQ(1, interpreter()->GetThreadCount()); WasmInterpreter::Thread* thread = interpreter()->GetThread(0); std::pair frame_range = GetActivationFrameRange(thread, frame_pointer); std::vector> stack; stack.reserve(frame_range.second - frame_range.first); for (uint32_t fp = frame_range.first; fp < frame_range.second; ++fp) { auto frame = thread->GetFrame(fp); stack.emplace_back(frame->function()->func_index, frame->pc()); } return stack; } WasmInterpreter::FramePtr GetInterpretedFrame(Address frame_pointer, int idx) { DCHECK_EQ(1, interpreter()->GetThreadCount()); WasmInterpreter::Thread* thread = interpreter()->GetThread(0); std::pair frame_range = GetActivationFrameRange(thread, frame_pointer); DCHECK_LE(0, idx); DCHECK_GT(frame_range.second - frame_range.first, idx); return thread->GetFrame(frame_range.first + idx); } uint64_t NumInterpretedCalls() { DCHECK_EQ(1, interpreter()->GetThreadCount()); return interpreter()->GetThread(0)->NumInterpretedCalls(); } Handle GetGlobalScopeObject(InterpretedFrame* frame, Handle debug_info) { Isolate* isolate = isolate_; Handle instance(debug_info->wasm_instance(), isolate); Handle global_scope_object = isolate_->factory()->NewJSObjectWithNullProto(); if (instance->has_memory_object()) { Handle name = isolate_->factory()->InternalizeString(StaticCharVector("memory")); Handle memory_buffer( instance->memory_object().array_buffer(), isolate_); Handle uint8_array = isolate_->factory()->NewJSTypedArray( kExternalUint8Array, memory_buffer, 0, memory_buffer->byte_length()); JSObject::SetOwnPropertyIgnoreAttributes(global_scope_object, name, uint8_array, NONE) .Assert(); } DCHECK_EQ(1, interpreter()->GetThreadCount()); WasmInterpreter::Thread* thread = interpreter()->GetThread(0); uint32_t global_count = thread->GetGlobalCount(); if (global_count > 0) { Handle globals_obj = isolate_->factory()->NewJSObjectWithNullProto(); Handle globals_name = isolate_->factory()->InternalizeString(StaticCharVector("globals")); JSObject::SetOwnPropertyIgnoreAttributes(global_scope_object, globals_name, globals_obj, NONE) .Assert(); for (uint32_t i = 0; i < global_count; ++i) { const char* label = "global#%d"; Handle name = PrintFToOneByteString(isolate_, label, i); WasmValue value = thread->GetGlobalValue(i); Handle value_obj = WasmValueToValueObject(isolate_, value); JSObject::SetOwnPropertyIgnoreAttributes(globals_obj, name, value_obj, NONE) .Assert(); } } return global_scope_object; } Handle GetLocalScopeObject(InterpretedFrame* frame, Handle debug_info) { Isolate* isolate = isolate_; Handle local_scope_object = isolate_->factory()->NewJSObjectWithNullProto(); // Fill parameters and locals. int num_params = frame->GetParameterCount(); int num_locals = frame->GetLocalCount(); DCHECK_LE(num_params, num_locals); if (num_locals > 0) { Handle locals_obj = isolate_->factory()->NewJSObjectWithNullProto(); Handle locals_name = isolate_->factory()->InternalizeString(StaticCharVector("locals")); JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, locals_name, locals_obj, NONE) .Assert(); for (int i = 0; i < num_locals; ++i) { MaybeHandle name = GetLocalName(isolate, debug_info, frame->function()->func_index, i); if (name.is_null()) { // Parameters should come before locals in alphabetical ordering, so // we name them "args" here. const char* label = i < num_params ? "arg#%d" : "local#%d"; name = PrintFToOneByteString(isolate_, label, i); } WasmValue value = frame->GetLocalValue(i); Handle value_obj = WasmValueToValueObject(isolate_, value); JSObject::SetOwnPropertyIgnoreAttributes( locals_obj, name.ToHandleChecked(), value_obj, NONE) .Assert(); } } // Fill stack values. int stack_count = frame->GetStackHeight(); // Use an object without prototype instead of an Array, for nicer displaying // in DevTools. For Arrays, the length field and prototype is displayed, // which does not make too much sense here. Handle stack_obj = isolate_->factory()->NewJSObjectWithNullProto(); Handle stack_name = isolate_->factory()->InternalizeString(StaticCharVector("stack")); JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, stack_name, stack_obj, NONE) .Assert(); for (int i = 0; i < stack_count; ++i) { WasmValue value = frame->GetStackValue(i); Handle value_obj = WasmValueToValueObject(isolate_, value); JSObject::SetOwnElementIgnoreAttributes( stack_obj, static_cast(i), value_obj, NONE) .Assert(); } return local_scope_object; } }; } // namespace } // namespace wasm namespace { wasm::InterpreterHandle* GetOrCreateInterpreterHandle( Isolate* isolate, Handle debug_info) { Handle handle(debug_info->interpreter_handle(), isolate); if (handle->IsUndefined(isolate)) { // Use the maximum stack size to estimate the maximum size of the // interpreter. The interpreter keeps its own stack internally, and the size // of the stack should dominate the overall size of the interpreter. We // multiply by '2' to account for the growing strategy for the backing store // of the stack. size_t interpreter_size = FLAG_stack_size * KB * 2; handle = Managed::Allocate( isolate, interpreter_size, isolate, debug_info); debug_info->set_interpreter_handle(*handle); } return Handle>::cast(handle)->raw(); } wasm::InterpreterHandle* GetInterpreterHandle(WasmDebugInfo debug_info) { Object handle_obj = debug_info.interpreter_handle(); DCHECK(!handle_obj.IsUndefined()); return Managed::cast(handle_obj).raw(); } wasm::InterpreterHandle* GetInterpreterHandleOrNull(WasmDebugInfo debug_info) { Object handle_obj = debug_info.interpreter_handle(); if (handle_obj.IsUndefined()) return nullptr; return Managed::cast(handle_obj).raw(); } } // namespace Handle WasmDebugInfo::New(Handle instance) { DCHECK(!instance->has_debug_info()); Factory* factory = instance->GetIsolate()->factory(); Handle debug_info = Handle::cast( factory->NewStruct(WASM_DEBUG_INFO_TYPE, AllocationType::kOld)); debug_info->set_wasm_instance(*instance); instance->set_debug_info(*debug_info); return debug_info; } wasm::WasmInterpreter* WasmDebugInfo::SetupForTesting( Handle instance_obj) { Handle debug_info = WasmDebugInfo::New(instance_obj); Isolate* isolate = instance_obj->GetIsolate(); // Use the maximum stack size to estimate the maximum size of the interpreter. // The interpreter keeps its own stack internally, and the size of the stack // should dominate the overall size of the interpreter. We multiply by '2' to // account for the growing strategy for the backing store of the stack. size_t interpreter_size = FLAG_stack_size * KB * 2; auto interp_handle = Managed::Allocate( isolate, interpreter_size, isolate, debug_info); debug_info->set_interpreter_handle(*interp_handle); return interp_handle->raw()->interpreter(); } void WasmDebugInfo::SetBreakpoint(Handle debug_info, int func_index, int offset) { Isolate* isolate = debug_info->GetIsolate(); auto* handle = GetOrCreateInterpreterHandle(isolate, debug_info); RedirectToInterpreter(debug_info, Vector(&func_index, 1)); const wasm::WasmFunction* func = &handle->module()->functions[func_index]; handle->interpreter()->SetBreakpoint(func, offset, true); } void WasmDebugInfo::RedirectToInterpreter(Handle debug_info, Vector func_indexes) { Isolate* isolate = debug_info->GetIsolate(); // Ensure that the interpreter is instantiated. GetOrCreateInterpreterHandle(isolate, debug_info); Handle instance(debug_info->wasm_instance(), isolate); wasm::NativeModule* native_module = instance->module_object().native_module(); const wasm::WasmModule* module = instance->module(); // We may modify the wasm jump table. wasm::NativeModuleModificationScope native_module_modification_scope( native_module); for (int func_index : func_indexes) { DCHECK_LE(0, func_index); DCHECK_GT(module->functions.size(), func_index); // Note that this is just a best effort check. Multiple threads can still // race at redirecting the same function to the interpreter, which is OK. if (native_module->IsRedirectedToInterpreter(func_index)) continue; wasm::WasmCodeRefScope code_ref_scope; wasm::WasmCompilationResult result = compiler::CompileWasmInterpreterEntry( isolate->wasm_engine(), native_module->enabled_features(), func_index, module->functions[func_index].sig); std::unique_ptr wasm_code = native_module->AddCode( func_index, result.code_desc, result.frame_slot_count, result.tagged_parameter_slots, std::move(result.protected_instructions), std::move(result.source_positions), wasm::WasmCode::kInterpreterEntry, wasm::ExecutionTier::kInterpreter); native_module->PublishCode(std::move(wasm_code)); DCHECK(native_module->IsRedirectedToInterpreter(func_index)); } } void WasmDebugInfo::PrepareStep(StepAction step_action) { GetInterpreterHandle(*this)->PrepareStep(step_action); } // static bool WasmDebugInfo::RunInterpreter(Isolate* isolate, Handle debug_info, Address frame_pointer, int func_index, Vector argument_values, Vector return_values) { DCHECK_LE(0, func_index); auto* handle = GetOrCreateInterpreterHandle(isolate, debug_info); Handle instance(debug_info->wasm_instance(), isolate); return handle->Execute(instance, frame_pointer, static_cast(func_index), argument_values, return_values); } std::vector> WasmDebugInfo::GetInterpretedStack( Address frame_pointer) { return GetInterpreterHandle(*this)->GetInterpretedStack(frame_pointer); } wasm::WasmInterpreter::FramePtr WasmDebugInfo::GetInterpretedFrame( Address frame_pointer, int idx) { return GetInterpreterHandle(*this)->GetInterpretedFrame(frame_pointer, idx); } uint64_t WasmDebugInfo::NumInterpretedCalls() { auto* handle = GetInterpreterHandleOrNull(*this); return handle ? handle->NumInterpretedCalls() : 0; } // static Handle WasmDebugInfo::GetGlobalScopeObject( Handle debug_info, Address frame_pointer, int frame_index) { auto* interp_handle = GetInterpreterHandle(*debug_info); auto frame = interp_handle->GetInterpretedFrame(frame_pointer, frame_index); return interp_handle->GetGlobalScopeObject(frame.get(), debug_info); } // static Handle WasmDebugInfo::GetLocalScopeObject( Handle debug_info, Address frame_pointer, int frame_index) { auto* interp_handle = GetInterpreterHandle(*debug_info); auto frame = interp_handle->GetInterpretedFrame(frame_pointer, frame_index); return interp_handle->GetLocalScopeObject(frame.get(), debug_info); } // static Handle WasmDebugInfo::GetCWasmEntry(Handle debug_info, wasm::FunctionSig* sig) { Isolate* isolate = debug_info->GetIsolate(); DCHECK_EQ(debug_info->has_c_wasm_entries(), debug_info->has_c_wasm_entry_map()); if (!debug_info->has_c_wasm_entries()) { auto entries = isolate->factory()->NewFixedArray(4, AllocationType::kOld); debug_info->set_c_wasm_entries(*entries); size_t map_size = 0; // size estimate not so important here. auto managed_map = Managed::Allocate(isolate, map_size); debug_info->set_c_wasm_entry_map(*managed_map); } Handle entries(debug_info->c_wasm_entries(), isolate); wasm::SignatureMap* map = debug_info->c_wasm_entry_map().raw(); int32_t index = map->Find(*sig); if (index == -1) { index = static_cast(map->FindOrInsert(*sig)); if (index == entries->length()) { entries = isolate->factory()->CopyFixedArrayAndGrow( entries, entries->length(), AllocationType::kOld); debug_info->set_c_wasm_entries(*entries); } DCHECK(entries->get(index).IsUndefined(isolate)); Handle new_entry_code = compiler::CompileCWasmEntry(isolate, sig).ToHandleChecked(); entries->set(index, *new_entry_code); } return handle(Code::cast(entries->get(index)), isolate); } } // namespace internal } // namespace v8