wasm-debug.h 7.63 KB
Newer Older
1 2 3 4
// 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.

5 6 7 8
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif  // !V8_ENABLE_WEBASSEMBLY

9 10 11
#ifndef V8_WASM_WASM_DEBUG_H_
#define V8_WASM_WASM_DEBUG_H_

12
#include <algorithm>
13
#include <memory>
14 15
#include <vector>

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 24 25 26 27
namespace v8 {
namespace internal {

template <typename T>
class Handle;
28
class WasmFrame;
29 30 31

namespace wasm {

32
class DebugInfoImpl;
33
class IndirectNameMap;
34
class NativeModule;
35
class WasmCode;
36
class WireBytesRef;
37
class WasmValue;
38
struct WasmFunction;
39

40 41 42 43 44 45 46
// 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:
47
    enum Storage : int8_t { kConstant, kRegister, kStack };
48
    struct Value {
49
      int index;
50
      ValueType type;
51
      Storage storage;
52 53
      union {
        int32_t i32_const;  // if kind == kConstant
54
        int reg_code;       // if kind == kRegister
55 56
        int stack_offset;   // if kind == kStack
      };
57 58 59

      bool operator==(const Value& other) const {
        if (index != other.index) return false;
60
        if (type != other.type) return false;
61 62
        if (storage != other.storage) return false;
        switch (storage) {
63 64 65 66 67 68 69 70 71 72
          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); }

73 74
      bool is_constant() const { return storage == kConstant; }
      bool is_register() const { return storage == kRegister; }
75 76
    };

77 78 79 80
    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)) {}
81

82 83 84
    // Constructor for map lookups (only initializes the {pc_offset_}).
    explicit Entry(int pc_offset) : pc_offset_(pc_offset) {}

85
    int pc_offset() const { return pc_offset_; }
86

87 88
    // Stack height, including locals.
    int stack_height() const { return stack_height_; }
89

90 91
    base::Vector<const Value> changed_values() const {
      return base::VectorOf(changed_values_);
92
    }
93

94 95 96 97 98 99 100 101 102
    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;
103 104
    }

105 106
    void Print(std::ostream&) const;

107 108
   private:
    int pc_offset_;
109 110 111
    int stack_height_;
    // Only store differences from the last entry, to keep the table small.
    std::vector<Value> changed_values_;
112 113
  };

114 115 116 117
  // 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);

118 119
  explicit DebugSideTable(int num_locals, std::vector<Entry> entries)
      : num_locals_(num_locals), entries_(std::move(entries)) {
120 121 122 123 124 125
    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(),
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 130 131
    return &*it;
  }

132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
  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;
    }
  }

147 148 149 150
  auto entries() const {
    return base::make_iterator_range(entries_.begin(), entries_.end());
  }

151
  int num_locals() const { return num_locals_; }
152

153 154
  void Print(std::ostream&) const;

155 156 157 158 159 160 161
 private:
  struct EntryPositionLess {
    bool operator()(const Entry& a, const Entry& b) const {
      return a.pc_offset() < b.pc_offset();
    }
  };

162
  int num_locals_;
163 164 165
  std::vector<Entry> entries_;
};

166 167
// Debug info per NativeModule, created lazily on demand.
// Implementation in {wasm-debug.cc} using PIMPL.
168
class V8_EXPORT_PRIVATE DebugInfo {
169 170 171 172
 public:
  explicit DebugInfo(NativeModule*);
  ~DebugInfo();

173
  // For the frame inspection methods below:
174 175
  // {fp} is the frame pointer of the Liftoff frame, {debug_break_fp} that of
  // the {WasmDebugBreak} frame (if any).
176 177
  int GetNumLocals(Address pc);
  WasmValue GetLocalValue(int local, Address pc, Address fp,
178
                          Address debug_break_fp, Isolate* isolate);
179
  int GetStackDepth(Address pc);
180 181 182

  const wasm::WasmFunction& GetFunctionAtAddress(Address pc);

183
  WasmValue GetStackValue(int index, Address pc, Address fp,
184
                          Address debug_break_fp, Isolate* isolate);
185

186 187 188 189 190 191 192 193 194 195 196
  // 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);

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 204 205 206 207
  // 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*);
208

209
  void ClearStepping(Isolate*);
210

211 212 213 214 215
  // 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*);

216
  bool IsStepping(WasmFrame*);
217

218 219
  void RemoveBreakpoint(int func_index, int offset, Isolate* current_isolate);

220
  void RemoveDebugSideTables(base::Vector<WasmCode* const>);
221

222 223 224 225
  // 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;

226 227
  void RemoveIsolate(Isolate*);

228 229 230 231
 private:
  std::unique_ptr<DebugInfoImpl> impl_;
};

232 233 234 235 236
}  // namespace wasm
}  // namespace internal
}  // namespace v8

#endif  // V8_WASM_WASM_DEBUG_H_