// 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 "src/assembler-inl.h" #include "src/assert-scope.h" #include "src/compiler/wasm-compiler.h" #include "src/debug/debug.h" #include "src/factory.h" #include "src/frames-inl.h" #include "src/isolate.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-interpreter.h" #include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects.h" #include "src/zone/accounting-allocator.h" using namespace v8::internal; using namespace v8::internal::wasm; namespace { // Forward declaration. class InterpreterHandle; InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info); class InterpreterHandle { AccountingAllocator allocator_; WasmInstance instance_; WasmInterpreter interpreter_; Isolate* isolate_; StepAction next_step_action_ = StepNone; int last_step_stack_depth_ = 0; public: // Initialize in the right order, using helper methods to make this possible. // WasmInterpreter has to be allocated in place, since it is not movable. InterpreterHandle(Isolate* isolate, WasmDebugInfo* debug_info) : instance_(debug_info->wasm_instance()->compiled_module()->module()), interpreter_(GetBytesEnv(&instance_, debug_info), &allocator_), isolate_(isolate) { if (debug_info->wasm_instance()->has_memory_buffer()) { JSArrayBuffer* mem_buffer = debug_info->wasm_instance()->memory_buffer(); instance_.mem_start = reinterpret_cast(mem_buffer->backing_store()); CHECK(mem_buffer->byte_length()->ToUint32(&instance_.mem_size)); } else { DCHECK_EQ(0, instance_.module->min_mem_pages); instance_.mem_start = nullptr; instance_.mem_size = 0; } } static ModuleBytesEnv GetBytesEnv(WasmInstance* instance, 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. SeqOneByteString* bytes_str = debug_info->wasm_instance()->compiled_module()->module_bytes(); Vector bytes(bytes_str->GetChars(), bytes_str->length()); return ModuleBytesEnv(instance->module, instance, bytes); } WasmInterpreter* interpreter() { return &interpreter_; } const WasmModule* module() { return instance_.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(); } void Execute(uint32_t func_index, uint8_t* arg_buffer) { DCHECK_GE(module()->functions.size(), func_index); FunctionSig* sig = module()->functions[func_index].sig; DCHECK_GE(kMaxInt, sig->parameter_count()); int num_params = static_cast(sig->parameter_count()); ScopedVector wasm_args(num_params); uint8_t* arg_buf_ptr = arg_buffer; for (int i = 0; i < num_params; ++i) { int param_size = 1 << ElementSizeLog2Of(sig->GetParam(i)); #define CASE_ARG_TYPE(type, ctype) \ case type: \ DCHECK_EQ(param_size, sizeof(ctype)); \ wasm_args[i] = WasmVal(*reinterpret_cast(arg_buf_ptr)); \ break; switch (sig->GetParam(i)) { CASE_ARG_TYPE(kWasmI32, uint32_t) CASE_ARG_TYPE(kWasmI64, uint64_t) CASE_ARG_TYPE(kWasmF32, float) CASE_ARG_TYPE(kWasmF64, double) #undef CASE_ARG_TYPE default: UNREACHABLE(); } arg_buf_ptr += RoundUpToMultipleOfPowOf2(param_size, 8); } WasmInterpreter::Thread* thread = interpreter_.GetThread(0); // We do not support reentering an already running interpreter at the moment // (like INTERPRETER -> JS -> WASM -> INTERPRETER). DCHECK(thread->state() == WasmInterpreter::STOPPED || thread->state() == WasmInterpreter::FINISHED); thread->Reset(); thread->PushFrame(&module()->functions[func_index], wasm_args.start()); 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: // TODO(clemensh): Generate appropriate JS exception. UNIMPLEMENTED(); break; // STOPPED and RUNNING should never occur here. case WasmInterpreter::State::STOPPED: 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()) { WasmVal ret_val = thread->GetReturnValue(0); #define CASE_RET_TYPE(type, ctype) \ case type: \ DCHECK_EQ(1 << ElementSizeLog2Of(sig->GetReturn(0)), sizeof(ctype)); \ *reinterpret_cast(arg_buffer) = ret_val.to(); \ break; switch (sig->GetReturn(0)) { CASE_RET_TYPE(kWasmI32, uint32_t) CASE_RET_TYPE(kWasmI64, uint64_t) CASE_RET_TYPE(kWasmF32, float) CASE_RET_TYPE(kWasmF64, double) #undef CASE_RET_TYPE default: UNREACHABLE(); } } } 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(); return WasmInterpreter::STOPPED; } } Handle GetInstanceObject() { StackTraceFrameIterator it(isolate_); WasmInterpreterEntryFrame* frame = WasmInterpreterEntryFrame::cast(it.frame()); Handle instance_obj(frame->wasm_instance(), isolate_); DCHECK_EQ(this, GetInterpreterHandle(instance_obj->debug_info())); return instance_obj; } void NotifyDebugEventListeners(WasmInterpreter::Thread* thread) { // Enter the debugger. DebugScope debug_scope(isolate_->debug()); if (debug_scope.failed()) return; // Postpone interrupt during breakpoint processing. PostponeInterruptsScope postpone(isolate_); // Check whether we hit a breakpoint. if (isolate_->debug()->break_points_active()) { Handle compiled_module( GetInstanceObject()->compiled_module(), isolate_); int position = GetTopPosition(compiled_module); Handle breakpoints; if (compiled_module->CheckBreakPoints(position).ToHandle(&breakpoints)) { // We hit one or several breakpoints. Clear stepping, notify the // listeners and return. ClearStepping(); Handle hit_breakpoints_js = isolate_->factory()->NewJSArrayWithElements(breakpoints); isolate_->debug()->OnDebugBreak(hit_breakpoints_js); 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()->undefined_value()); } int GetTopPosition(Handle compiled_module) { DCHECK_EQ(1, interpreter()->GetThreadCount()); WasmInterpreter::Thread* thread = interpreter()->GetThread(0); DCHECK_LT(0, thread->GetFrameCount()); wasm::InterpretedFrame frame = thread->GetFrame(thread->GetFrameCount() - 1); return compiled_module->GetFunctionOffset(frame.function()->func_index) + frame.pc(); } std::vector> GetInterpretedStack( Address frame_pointer) { // TODO(clemensh): Use frame_pointer. USE(frame_pointer); DCHECK_EQ(1, interpreter()->GetThreadCount()); WasmInterpreter::Thread* thread = interpreter()->GetThread(0); std::vector> stack(thread->GetFrameCount()); for (int i = 0, e = thread->GetFrameCount(); i < e; ++i) { wasm::InterpretedFrame frame = thread->GetFrame(i); stack[i] = {frame.function()->func_index, frame.pc()}; } return stack; } std::unique_ptr GetInterpretedFrame( Address frame_pointer, int idx) { // TODO(clemensh): Use frame_pointer. USE(frame_pointer); DCHECK_EQ(1, interpreter()->GetThreadCount()); WasmInterpreter::Thread* thread = interpreter()->GetThread(0); return std::unique_ptr( new wasm::InterpretedFrame(thread->GetMutableFrame(idx))); } uint64_t NumInterpretedCalls() { DCHECK_EQ(1, interpreter()->GetThreadCount()); return interpreter()->GetThread(0)->NumInterpretedCalls(); } }; InterpreterHandle* GetOrCreateInterpreterHandle( Isolate* isolate, Handle debug_info) { Handle handle(debug_info->get(WasmDebugInfo::kInterpreterHandle), isolate); if (handle->IsUndefined(isolate)) { InterpreterHandle* cpp_handle = new InterpreterHandle(isolate, *debug_info); handle = Managed::New(isolate, cpp_handle); debug_info->set(WasmDebugInfo::kInterpreterHandle, *handle); } return Handle>::cast(handle)->get(); } InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info) { Object* handle_obj = debug_info->get(WasmDebugInfo::kInterpreterHandle); DCHECK(!handle_obj->IsUndefined(debug_info->GetIsolate())); return Managed::cast(handle_obj)->get(); } InterpreterHandle* GetInterpreterHandleOrNull(WasmDebugInfo* debug_info) { Object* handle_obj = debug_info->get(WasmDebugInfo::kInterpreterHandle); if (handle_obj->IsUndefined(debug_info->GetIsolate())) return nullptr; return Managed::cast(handle_obj)->get(); } int GetNumFunctions(WasmInstanceObject* instance) { size_t num_functions = instance->compiled_module()->module()->functions.size(); DCHECK_GE(kMaxInt, num_functions); return static_cast(num_functions); } Handle GetOrCreateInterpretedFunctions( Isolate* isolate, Handle debug_info) { Handle obj(debug_info->get(WasmDebugInfo::kInterpretedFunctions), isolate); if (!obj->IsUndefined(isolate)) return Handle::cast(obj); Handle new_arr = isolate->factory()->NewFixedArray( GetNumFunctions(debug_info->wasm_instance())); debug_info->set(WasmDebugInfo::kInterpretedFunctions, *new_arr); return new_arr; } void RedirectCallsitesInCode(Code* code, Code* old_target, Code* new_target) { DisallowHeapAllocation no_gc; for (RelocIterator it(code, RelocInfo::kCodeTargetMask); !it.done(); it.next()) { DCHECK(RelocInfo::IsCodeTarget(it.rinfo()->rmode())); Code* target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); if (target != old_target) continue; it.rinfo()->set_target_address(new_target->instruction_start()); } } void RedirectCallsitesInInstance(Isolate* isolate, WasmInstanceObject* instance, Code* old_target, Code* new_target) { DisallowHeapAllocation no_gc; // Redirect all calls in wasm functions. FixedArray* code_table = instance->compiled_module()->ptr_to_code_table(); for (int i = 0, e = GetNumFunctions(instance); i < e; ++i) { RedirectCallsitesInCode(Code::cast(code_table->get(i)), old_target, new_target); } // Redirect all calls in exported functions. FixedArray* weak_exported_functions = instance->compiled_module()->ptr_to_weak_exported_functions(); for (int i = 0, e = weak_exported_functions->length(); i != e; ++i) { WeakCell* weak_function = WeakCell::cast(weak_exported_functions->get(i)); if (weak_function->cleared()) continue; Code* code = JSFunction::cast(weak_function->value())->code(); RedirectCallsitesInCode(code, old_target, new_target); } } } // namespace Handle WasmDebugInfo::New(Handle instance) { Isolate* isolate = instance->GetIsolate(); Factory* factory = isolate->factory(); Handle arr = factory->NewFixedArray(kFieldCount, TENURED); arr->set(kInstance, *instance); return Handle::cast(arr); } bool WasmDebugInfo::IsDebugInfo(Object* object) { if (!object->IsFixedArray()) return false; FixedArray* arr = FixedArray::cast(object); if (arr->length() != kFieldCount) return false; if (!IsWasmInstance(arr->get(kInstance))) return false; Isolate* isolate = arr->GetIsolate(); if (!arr->get(kInterpreterHandle)->IsUndefined(isolate) && !arr->get(kInterpreterHandle)->IsForeign()) return false; return true; } WasmDebugInfo* WasmDebugInfo::cast(Object* object) { DCHECK(IsDebugInfo(object)); return reinterpret_cast(object); } WasmInstanceObject* WasmDebugInfo::wasm_instance() { return WasmInstanceObject::cast(get(kInstance)); } void WasmDebugInfo::SetBreakpoint(Handle debug_info, int func_index, int offset) { Isolate* isolate = debug_info->GetIsolate(); InterpreterHandle* handle = GetOrCreateInterpreterHandle(isolate, debug_info); RedirectToInterpreter(debug_info, func_index); const WasmFunction* func = &handle->module()->functions[func_index]; handle->interpreter()->SetBreakpoint(func, offset, true); } void WasmDebugInfo::RedirectToInterpreter(Handle debug_info, int func_index) { Isolate* isolate = debug_info->GetIsolate(); DCHECK_LE(0, func_index); DCHECK_GT(debug_info->wasm_instance()->module()->functions.size(), func_index); Handle interpreted_functions = GetOrCreateInterpretedFunctions(isolate, debug_info); if (!interpreted_functions->get(func_index)->IsUndefined(isolate)) return; // Ensure that the interpreter is instantiated. GetOrCreateInterpreterHandle(isolate, debug_info); Handle instance(debug_info->wasm_instance(), isolate); Handle new_code = compiler::CompileWasmInterpreterEntry( isolate, func_index, instance->compiled_module()->module()->functions[func_index].sig, instance); Handle code_table = instance->compiled_module()->code_table(); Handle old_code(Code::cast(code_table->get(func_index)), isolate); interpreted_functions->set(func_index, *new_code); RedirectCallsitesInInstance(isolate, *instance, *old_code, *new_code); } void WasmDebugInfo::PrepareStep(StepAction step_action) { GetInterpreterHandle(this)->PrepareStep(step_action); } void WasmDebugInfo::RunInterpreter(int func_index, uint8_t* arg_buffer) { DCHECK_LE(0, func_index); GetInterpreterHandle(this)->Execute(static_cast(func_index), arg_buffer); } std::vector> WasmDebugInfo::GetInterpretedStack( Address frame_pointer) { return GetInterpreterHandle(this)->GetInterpretedStack(frame_pointer); } std::unique_ptr 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; }