diff options
Diffstat (limited to 'src/wasm/wasm-debug.cc')
-rw-r--r-- | src/wasm/wasm-debug.cc | 530 |
1 files changed, 416 insertions, 114 deletions
diff --git a/src/wasm/wasm-debug.cc b/src/wasm/wasm-debug.cc index 11c2ef8a..c00a4f1d 100644 --- a/src/wasm/wasm-debug.cc +++ b/src/wasm/wasm-debug.cc @@ -2,144 +2,446 @@ // 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 { -enum { - kWasmDebugInfoWasmObj, - kWasmDebugInfoWasmBytesHash, - kWasmDebugInfoAsmJsOffsets, - kWasmDebugInfoNumEntries -}; +// 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<byte*>(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<const byte> 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<int>(sig->parameter_count()); + ScopedVector<WasmVal> 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<ctype*>(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<ctype*>(arg_buffer) = ret_val.to<ctype>(); \ + 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<WasmInstanceObject> GetInstanceObject() { + StackTraceFrameIterator it(isolate_); + WasmInterpreterEntryFrame* frame = + WasmInterpreterEntryFrame::cast(it.frame()); + Handle<WasmInstanceObject> 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<WasmCompiledModule> compiled_module( + GetInstanceObject()->compiled_module(), isolate_); + int position = GetTopPosition(compiled_module); + Handle<FixedArray> breakpoints; + if (compiled_module->CheckBreakPoints(position).ToHandle(&breakpoints)) { + // We hit one or several breakpoints. Clear stepping, notify the + // listeners and return. + ClearStepping(); + Handle<Object> 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()); + } -// TODO(clemensh): Move asm.js offset tables to the compiled module. -FixedArray *GetAsmJsOffsetTables(Handle<WasmDebugInfo> debug_info, - Isolate *isolate) { - Object *offset_tables = debug_info->get(kWasmDebugInfoAsmJsOffsets); - if (!offset_tables->IsUndefined(isolate)) { - return FixedArray::cast(offset_tables); - } - - Handle<JSObject> wasm_instance(debug_info->wasm_instance(), isolate); - Handle<WasmCompiledModule> compiled_module(GetCompiledModule(*wasm_instance), - isolate); - DCHECK(compiled_module->has_asm_js_offset_tables()); - - AsmJsOffsetsResult asm_offsets; - { - Handle<ByteArray> asm_offset_tables = - compiled_module->asm_js_offset_tables(); - DisallowHeapAllocation no_gc; - const byte *bytes_start = asm_offset_tables->GetDataStartAddress(); - const byte *bytes_end = bytes_start + asm_offset_tables->length(); - asm_offsets = wasm::DecodeAsmJsOffsets(bytes_start, bytes_end); - } - // Wasm bytes must be valid and must contain asm.js offset table. - DCHECK(asm_offsets.ok()); - DCHECK_GE(static_cast<size_t>(kMaxInt), asm_offsets.val.size()); - int num_functions = static_cast<int>(asm_offsets.val.size()); - DCHECK_EQ( - wasm::GetNumberOfFunctions(handle(debug_info->wasm_instance())), - static_cast<int>(num_functions + - compiled_module->module()->num_imported_functions)); - Handle<FixedArray> all_tables = - isolate->factory()->NewFixedArray(num_functions); - debug_info->set(kWasmDebugInfoAsmJsOffsets, *all_tables); - for (int func = 0; func < num_functions; ++func) { - std::vector<std::pair<int, int>> &func_asm_offsets = asm_offsets.val[func]; - if (func_asm_offsets.empty()) continue; - size_t array_size = 2 * kIntSize * func_asm_offsets.size(); - CHECK_LE(array_size, static_cast<size_t>(kMaxInt)); - ByteArray *arr = - *isolate->factory()->NewByteArray(static_cast<int>(array_size)); - all_tables->set(func, arr); - int idx = 0; - for (std::pair<int, int> p : func_asm_offsets) { - // Byte offsets must be strictly monotonously increasing: - DCHECK(idx == 0 || p.first > arr->get_int(idx - 2)); - arr->set_int(idx++, p.first); - arr->set_int(idx++, p.second); + int GetTopPosition(Handle<WasmCompiledModule> 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<std::pair<uint32_t, int>> 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<std::pair<uint32_t, int>> 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()}; } - DCHECK_EQ(arr->length(), idx * kIntSize); + return stack; + } + + std::unique_ptr<wasm::InterpretedFrame> 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<wasm::InterpretedFrame>( + new wasm::InterpretedFrame(thread->GetMutableFrame(idx))); + } + + uint64_t NumInterpretedCalls() { + DCHECK_EQ(1, interpreter()->GetThreadCount()); + return interpreter()->GetThread(0)->NumInterpretedCalls(); + } +}; + +InterpreterHandle* GetOrCreateInterpreterHandle( + Isolate* isolate, Handle<WasmDebugInfo> debug_info) { + Handle<Object> handle(debug_info->get(WasmDebugInfo::kInterpreterHandle), + isolate); + if (handle->IsUndefined(isolate)) { + InterpreterHandle* cpp_handle = new InterpreterHandle(isolate, *debug_info); + handle = Managed<InterpreterHandle>::New(isolate, cpp_handle); + debug_info->set(WasmDebugInfo::kInterpreterHandle, *handle); + } + + return Handle<Managed<InterpreterHandle>>::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<InterpreterHandle>::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<InterpreterHandle>::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<int>(num_functions); +} + +Handle<FixedArray> GetOrCreateInterpretedFunctions( + Isolate* isolate, Handle<WasmDebugInfo> debug_info) { + Handle<Object> obj(debug_info->get(WasmDebugInfo::kInterpretedFunctions), + isolate); + if (!obj->IsUndefined(isolate)) return Handle<FixedArray>::cast(obj); + + Handle<FixedArray> 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()); } - return *all_tables; } -} // namespace -Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<JSObject> wasm) { - Isolate *isolate = wasm->GetIsolate(); - Factory *factory = isolate->factory(); - Handle<FixedArray> arr = - factory->NewFixedArray(kWasmDebugInfoNumEntries, TENURED); - arr->set(kWasmDebugInfoWasmObj, *wasm); - int hash = 0; - Handle<SeqOneByteString> wasm_bytes = GetWasmBytes(wasm); - { - DisallowHeapAllocation no_gc; - hash = StringHasher::HashSequentialString( - wasm_bytes->GetChars(), wasm_bytes->length(), kZeroHashSeed); - } - Handle<Object> hash_obj = factory->NewNumberFromInt(hash, TENURED); - arr->set(kWasmDebugInfoWasmBytesHash, *hash_obj); +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> WasmDebugInfo::New(Handle<WasmInstanceObject> instance) { + Isolate* isolate = instance->GetIsolate(); + Factory* factory = isolate->factory(); + Handle<FixedArray> arr = factory->NewFixedArray(kFieldCount, TENURED); + arr->set(kInstance, *instance); return Handle<WasmDebugInfo>::cast(arr); } -bool WasmDebugInfo::IsDebugInfo(Object *object) { +bool WasmDebugInfo::IsDebugInfo(Object* object) { if (!object->IsFixedArray()) return false; - FixedArray *arr = FixedArray::cast(object); - return arr->length() == kWasmDebugInfoNumEntries && - IsWasmInstance(arr->get(kWasmDebugInfoWasmObj)) && - arr->get(kWasmDebugInfoWasmBytesHash)->IsNumber(); + 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) { +WasmDebugInfo* WasmDebugInfo::cast(Object* object) { DCHECK(IsDebugInfo(object)); - return reinterpret_cast<WasmDebugInfo *>(object); -} - -JSObject *WasmDebugInfo::wasm_instance() { - return JSObject::cast(get(kWasmDebugInfoWasmObj)); -} - -int WasmDebugInfo::GetAsmJsSourcePosition(Handle<WasmDebugInfo> debug_info, - int func_index, int byte_offset) { - Isolate *isolate = debug_info->GetIsolate(); - Handle<JSObject> instance(debug_info->wasm_instance(), isolate); - FixedArray *offset_tables = GetAsmJsOffsetTables(debug_info, isolate); - - WasmCompiledModule *compiled_module = wasm::GetCompiledModule(*instance); - int num_imported_functions = - compiled_module->module()->num_imported_functions; - DCHECK_LE(num_imported_functions, func_index); - func_index -= num_imported_functions; - DCHECK_LT(func_index, offset_tables->length()); - ByteArray *offset_table = ByteArray::cast(offset_tables->get(func_index)); - - // Binary search for the current byte offset. - int left = 0; // inclusive - int right = offset_table->length() / kIntSize / 2; // exclusive - DCHECK_LT(left, right); - while (right - left > 1) { - int mid = left + (right - left) / 2; - if (offset_table->get_int(2 * mid) <= byte_offset) { - left = mid; - } else { - right = mid; - } - } - // There should be an entry for each position that could show up on the stack - // trace: - DCHECK_EQ(byte_offset, offset_table->get_int(2 * left)); - return offset_table->get_int(2 * left + 1); + return reinterpret_cast<WasmDebugInfo*>(object); +} + +WasmInstanceObject* WasmDebugInfo::wasm_instance() { + return WasmInstanceObject::cast(get(kInstance)); +} + +void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> 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<WasmDebugInfo> 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<FixedArray> 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<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate); + Handle<Code> new_code = compiler::CompileWasmInterpreterEntry( + isolate, func_index, + instance->compiled_module()->module()->functions[func_index].sig, + instance); + + Handle<FixedArray> code_table = instance->compiled_module()->code_table(); + Handle<Code> 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<uint32_t>(func_index), + arg_buffer); +} + +std::vector<std::pair<uint32_t, int>> WasmDebugInfo::GetInterpretedStack( + Address frame_pointer) { + return GetInterpreterHandle(this)->GetInterpretedStack(frame_pointer); +} + +std::unique_ptr<wasm::InterpretedFrame> 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; } |