// Copyright 2019 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. #if !V8_ENABLE_WEBASSEMBLY #error This header should only be included if WebAssembly is enabled. #endif // !V8_ENABLE_WEBASSEMBLY #ifndef V8_WASM_WASM_DEBUG_H_ #define V8_WASM_WASM_DEBUG_H_ #include <algorithm> #include <memory> #include <vector> #include "include/v8-internal.h" #include "src/base/iterator.h" #include "src/base/logging.h" #include "src/base/macros.h" #include "src/base/vector.h" #include "src/wasm/value-type.h" namespace v8 { namespace internal { template <typename T> class Handle; class WasmFrame; namespace wasm { class DebugInfoImpl; class IndirectNameMap; class NativeModule; class WasmCode; class WireBytesRef; class WasmValue; struct WasmFunction; // Side table storing information used to inspect Liftoff frames at runtime. // This table is only created on demand for debugging, so it is not optimized // for memory size. class DebugSideTable { public: class Entry { public: enum Storage : int8_t { kConstant, kRegister, kStack }; struct Value { int index; ValueType type; Storage storage; union { int32_t i32_const; // if kind == kConstant int reg_code; // if kind == kRegister int stack_offset; // if kind == kStack }; bool operator==(const Value& other) const { if (index != other.index) return false; if (type != other.type) return false; if (storage != other.storage) return false; switch (storage) { case kConstant: return i32_const == other.i32_const; case kRegister: return reg_code == other.reg_code; case kStack: return stack_offset == other.stack_offset; } } bool operator!=(const Value& other) const { return !(*this == other); } bool is_constant() const { return storage == kConstant; } bool is_register() const { return storage == kRegister; } }; Entry(int pc_offset, int stack_height, std::vector<Value> changed_values) : pc_offset_(pc_offset), stack_height_(stack_height), changed_values_(std::move(changed_values)) {} // Constructor for map lookups (only initializes the {pc_offset_}). explicit Entry(int pc_offset) : pc_offset_(pc_offset) {} int pc_offset() const { return pc_offset_; } // Stack height, including locals. int stack_height() const { return stack_height_; } base::Vector<const Value> changed_values() const { return base::VectorOf(changed_values_); } const Value* FindChangedValue(int stack_index) const { DCHECK_GT(stack_height_, stack_index); auto it = std::lower_bound( changed_values_.begin(), changed_values_.end(), stack_index, [](const Value& changed_value, int stack_index) { return changed_value.index < stack_index; }); return it != changed_values_.end() && it->index == stack_index ? &*it : nullptr; } void Print(std::ostream&) const; private: int pc_offset_; int stack_height_; // Only store differences from the last entry, to keep the table small. std::vector<Value> changed_values_; }; // Technically it would be fine to copy this class, but there should not be a // reason to do so, hence mark it move only. MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(DebugSideTable); explicit DebugSideTable(int num_locals, std::vector<Entry> entries) : num_locals_(num_locals), entries_(std::move(entries)) { DCHECK( std::is_sorted(entries_.begin(), entries_.end(), EntryPositionLess{})); } const Entry* GetEntry(int pc_offset) const { auto it = std::lower_bound(entries_.begin(), entries_.end(), Entry{pc_offset}, EntryPositionLess{}); if (it == entries_.end() || it->pc_offset() != pc_offset) return nullptr; DCHECK_LE(num_locals_, it->stack_height()); return &*it; } const Entry::Value* FindValue(const Entry* entry, int stack_index) const { while (true) { if (auto* value = entry->FindChangedValue(stack_index)) { // Check that the table was correctly minimized: If the previous stack // also had an entry for {stack_index}, it must be different. DCHECK(entry == &entries_.front() || (entry - 1)->stack_height() <= stack_index || *FindValue(entry - 1, stack_index) != *value); return value; } DCHECK_NE(&entries_.front(), entry); --entry; } } auto entries() const { return base::make_iterator_range(entries_.begin(), entries_.end()); } int num_locals() const { return num_locals_; } void Print(std::ostream&) const; private: struct EntryPositionLess { bool operator()(const Entry& a, const Entry& b) const { return a.pc_offset() < b.pc_offset(); } }; int num_locals_; std::vector<Entry> entries_; }; // Debug info per NativeModule, created lazily on demand. // Implementation in {wasm-debug.cc} using PIMPL. class V8_EXPORT_PRIVATE DebugInfo { public: explicit DebugInfo(NativeModule*); ~DebugInfo(); // For the frame inspection methods below: // {fp} is the frame pointer of the Liftoff frame, {debug_break_fp} that of // the {WasmDebugBreak} frame (if any). int GetNumLocals(Address pc); WasmValue GetLocalValue(int local, Address pc, Address fp, Address debug_break_fp, Isolate* isolate); int GetStackDepth(Address pc); const wasm::WasmFunction& GetFunctionAtAddress(Address pc); WasmValue GetStackValue(int index, Address pc, Address fp, Address debug_break_fp, Isolate* isolate); // Returns the name of the entity (with the given |index| and |kind|) derived // from the exports table. If the entity is not exported, an empty reference // will be returned instead. WireBytesRef GetExportName(ImportExportKindCode kind, uint32_t index); // Returns the module and field name of the entity (with the given |index| // and |kind|) derived from the imports table. If the entity is not imported, // a pair of empty references will be returned instead. std::pair<WireBytesRef, WireBytesRef> GetImportName(ImportExportKindCode kind, uint32_t index); WireBytesRef GetTypeName(int type_index); WireBytesRef GetLocalName(int func_index, int local_index); WireBytesRef GetFieldName(int struct_index, int field_index); void SetBreakpoint(int func_index, int offset, Isolate* current_isolate); // Returns true if we stay inside the passed frame (or a called frame) after // the step. False if the frame will return after the step. bool PrepareStep(WasmFrame*); void PrepareStepOutTo(WasmFrame*); void ClearStepping(Isolate*); // Remove stepping code from a single frame; this is a performance // optimization only, hitting debug breaks while not stepping and not at a set // breakpoint would be unobservable otherwise. void ClearStepping(WasmFrame*); bool IsStepping(WasmFrame*); void RemoveBreakpoint(int func_index, int offset, Isolate* current_isolate); void RemoveDebugSideTables(base::Vector<WasmCode* const>); // Return the debug side table for the given code object, but only if it has // already been created. This will never trigger generation of the table. DebugSideTable* GetDebugSideTableIfExists(const WasmCode*) const; void RemoveIsolate(Isolate*); private: std::unique_ptr<DebugInfoImpl> impl_; }; } // namespace wasm } // namespace internal } // namespace v8 #endif // V8_WASM_WASM_DEBUG_H_