blob: 7e5e60ceda3a58ea91984aa265c91914fc6d1b3c [file] [log] [blame]
// 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_