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