1 // Copyright 2019 the V8 project authors. All rights reserved. Use of 2 // this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #if !V8_ENABLE_WEBASSEMBLY 6 #error This header should only be included if WebAssembly is enabled. 7 #endif // !V8_ENABLE_WEBASSEMBLY 8 9 #ifndef V8_WASM_WASM_DEBUG_H_ 10 #define V8_WASM_WASM_DEBUG_H_ 11 12 #include <algorithm> 13 #include <memory> 14 #include <vector> 15 16 #include "include/v8-internal.h" 17 #include "src/base/iterator.h" 18 #include "src/base/logging.h" 19 #include "src/base/macros.h" 20 #include "src/base/vector.h" 21 #include "src/wasm/value-type.h" 22 23 namespace v8 { 24 namespace internal { 25 26 template <typename T> 27 class Handle; 28 class WasmFrame; 29 30 namespace wasm { 31 32 class DebugInfoImpl; 33 class IndirectNameMap; 34 class NativeModule; 35 class WasmCode; 36 class WireBytesRef; 37 class WasmValue; 38 struct WasmFunction; 39 40 // Side table storing information used to inspect Liftoff frames at runtime. 41 // This table is only created on demand for debugging, so it is not optimized 42 // for memory size. 43 class DebugSideTable { 44 public: 45 class Entry { 46 public: 47 enum Storage : int8_t { kConstant, kRegister, kStack }; 48 struct Value { 49 int index; 50 ValueType type; 51 Storage storage; 52 union { 53 int32_t i32_const; // if kind == kConstant 54 int reg_code; // if kind == kRegister 55 int stack_offset; // if kind == kStack 56 }; 57 operator ==v8::internal::wasm::DebugSideTable::Entry::Value58 bool operator==(const Value& other) const { 59 if (index != other.index) return false; 60 if (type != other.type) return false; 61 if (storage != other.storage) return false; 62 switch (storage) { 63 case kConstant: 64 return i32_const == other.i32_const; 65 case kRegister: 66 return reg_code == other.reg_code; 67 case kStack: 68 return stack_offset == other.stack_offset; 69 } 70 } operator !=v8::internal::wasm::DebugSideTable::Entry::Value71 bool operator!=(const Value& other) const { return !(*this == other); } 72 is_constantv8::internal::wasm::DebugSideTable::Entry::Value73 bool is_constant() const { return storage == kConstant; } is_registerv8::internal::wasm::DebugSideTable::Entry::Value74 bool is_register() const { return storage == kRegister; } 75 }; 76 Entry(int pc_offset, int stack_height, std::vector<Value> changed_values)77 Entry(int pc_offset, int stack_height, std::vector<Value> changed_values) 78 : pc_offset_(pc_offset), 79 stack_height_(stack_height), 80 changed_values_(std::move(changed_values)) {} 81 82 // Constructor for map lookups (only initializes the {pc_offset_}). Entry(int pc_offset)83 explicit Entry(int pc_offset) : pc_offset_(pc_offset) {} 84 pc_offset() const85 int pc_offset() const { return pc_offset_; } 86 87 // Stack height, including locals. stack_height() const88 int stack_height() const { return stack_height_; } 89 changed_values() const90 base::Vector<const Value> changed_values() const { 91 return base::VectorOf(changed_values_); 92 } 93 FindChangedValue(int stack_index) const94 const Value* FindChangedValue(int stack_index) const { 95 DCHECK_GT(stack_height_, stack_index); 96 auto it = std::lower_bound( 97 changed_values_.begin(), changed_values_.end(), stack_index, 98 [](const Value& changed_value, int stack_index) { 99 return changed_value.index < stack_index; 100 }); 101 return it != changed_values_.end() && it->index == stack_index ? &*it 102 : nullptr; 103 } 104 105 void Print(std::ostream&) const; 106 107 private: 108 int pc_offset_; 109 int stack_height_; 110 // Only store differences from the last entry, to keep the table small. 111 std::vector<Value> changed_values_; 112 }; 113 114 // Technically it would be fine to copy this class, but there should not be a 115 // reason to do so, hence mark it move only. 116 MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(DebugSideTable); 117 DebugSideTable(int num_locals, std::vector<Entry> entries)118 explicit DebugSideTable(int num_locals, std::vector<Entry> entries) 119 : num_locals_(num_locals), entries_(std::move(entries)) { 120 DCHECK( 121 std::is_sorted(entries_.begin(), entries_.end(), EntryPositionLess{})); 122 } 123 GetEntry(int pc_offset) const124 const Entry* GetEntry(int pc_offset) const { 125 auto it = std::lower_bound(entries_.begin(), entries_.end(), 126 Entry{pc_offset}, EntryPositionLess{}); 127 if (it == entries_.end() || it->pc_offset() != pc_offset) return nullptr; 128 DCHECK_LE(num_locals_, it->stack_height()); 129 return &*it; 130 } 131 FindValue(const Entry* entry, int stack_index) const132 const Entry::Value* FindValue(const Entry* entry, int stack_index) const { 133 while (true) { 134 if (auto* value = entry->FindChangedValue(stack_index)) { 135 // Check that the table was correctly minimized: If the previous stack 136 // also had an entry for {stack_index}, it must be different. 137 DCHECK(entry == &entries_.front() || 138 (entry - 1)->stack_height() <= stack_index || 139 *FindValue(entry - 1, stack_index) != *value); 140 return value; 141 } 142 DCHECK_NE(&entries_.front(), entry); 143 --entry; 144 } 145 } 146 entries() const147 auto entries() const { 148 return base::make_iterator_range(entries_.begin(), entries_.end()); 149 } 150 num_locals() const151 int num_locals() const { return num_locals_; } 152 153 void Print(std::ostream&) const; 154 155 private: 156 struct EntryPositionLess { operator ()v8::internal::wasm::DebugSideTable::EntryPositionLess157 bool operator()(const Entry& a, const Entry& b) const { 158 return a.pc_offset() < b.pc_offset(); 159 } 160 }; 161 162 int num_locals_; 163 std::vector<Entry> entries_; 164 }; 165 166 // Debug info per NativeModule, created lazily on demand. 167 // Implementation in {wasm-debug.cc} using PIMPL. 168 class V8_EXPORT_PRIVATE DebugInfo { 169 public: 170 explicit DebugInfo(NativeModule*); 171 ~DebugInfo(); 172 173 // For the frame inspection methods below: 174 // {fp} is the frame pointer of the Liftoff frame, {debug_break_fp} that of 175 // the {WasmDebugBreak} frame (if any). 176 int GetNumLocals(Address pc); 177 WasmValue GetLocalValue(int local, Address pc, Address fp, 178 Address debug_break_fp, Isolate* isolate); 179 int GetStackDepth(Address pc); 180 181 const wasm::WasmFunction& GetFunctionAtAddress(Address pc); 182 183 WasmValue GetStackValue(int index, Address pc, Address fp, 184 Address debug_break_fp, Isolate* isolate); 185 186 // Returns the name of the entity (with the given |index| and |kind|) derived 187 // from the exports table. If the entity is not exported, an empty reference 188 // will be returned instead. 189 WireBytesRef GetExportName(ImportExportKindCode kind, uint32_t index); 190 191 // Returns the module and field name of the entity (with the given |index| 192 // and |kind|) derived from the imports table. If the entity is not imported, 193 // a pair of empty references will be returned instead. 194 std::pair<WireBytesRef, WireBytesRef> GetImportName(ImportExportKindCode kind, 195 uint32_t index); 196 197 WireBytesRef GetTypeName(int type_index); 198 WireBytesRef GetLocalName(int func_index, int local_index); 199 WireBytesRef GetFieldName(int struct_index, int field_index); 200 201 void SetBreakpoint(int func_index, int offset, Isolate* current_isolate); 202 203 // Returns true if we stay inside the passed frame (or a called frame) after 204 // the step. False if the frame will return after the step. 205 bool PrepareStep(WasmFrame*); 206 207 void PrepareStepOutTo(WasmFrame*); 208 209 void ClearStepping(Isolate*); 210 211 // Remove stepping code from a single frame; this is a performance 212 // optimization only, hitting debug breaks while not stepping and not at a set 213 // breakpoint would be unobservable otherwise. 214 void ClearStepping(WasmFrame*); 215 216 bool IsStepping(WasmFrame*); 217 218 void RemoveBreakpoint(int func_index, int offset, Isolate* current_isolate); 219 220 void RemoveDebugSideTables(base::Vector<WasmCode* const>); 221 222 // Return the debug side table for the given code object, but only if it has 223 // already been created. This will never trigger generation of the table. 224 DebugSideTable* GetDebugSideTableIfExists(const WasmCode*) const; 225 226 void RemoveIsolate(Isolate*); 227 228 private: 229 std::unique_ptr<DebugInfoImpl> impl_; 230 }; 231 232 } // namespace wasm 233 } // namespace internal 234 } // namespace v8 235 236 #endif // V8_WASM_WASM_DEBUG_H_ 237