Commit 517ab73f authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

Add postmortem debugging helper library

This change begins to implement the functionality described in
https://docs.google.com/document/d/1evHnb1uLlSbvHAAsmOXyc25x3uh1DjgNa8u1RHvwVhk/edit#
for investigating V8 state in crash dumps.

This change adds a new library, v8_debug_helper, for providing platform-
agnostic assistance with postmortem debugging. This library can be used
by extensions built for debuggers such as WinDbg or lldb. Its public API
is described by debug-helper.h; currently the only method it exposes is
GetObjectProperties, but we'd like to add more functionality over time.
The API surface is restricted to plain C-style structs and pointers, so
that it's easy to link from a debugger extension built with a different
toolchain.

This change also adds a new cctest file to exercise some basic
interaction with the new library.

The API function GetObjectProperties takes an object pointer (which
could be compressed, or weak, or a SMI), and returns a string
description of the object and a list of properties the object contains.
For now, the list of properties is entirely based on Torque object
definitions, but we expect to add custom properties in future updates so
that it can be easier to make sense of complex data structures such as
dictionaries.

GetObjectProperties does several things that are intended to generate
somewhat useful results even in cases where memory may be corrupt or
unavailable:
- The caller may optionally provide a type string which will be used if
  the memory for the object's Map is inaccessible.
- All object pointers are compared against the list of known objects
  generated by mkgrokdump. The caller may optionally provide the
  pointers for the first pages of various heap spaces, to avoid spurious
  matches. If those pointers are not provided, then any matches are
  prefixed with "maybe" in the resulting description string, such as
  "maybe UndefinedValue (0x4288000341 <Oddball>)".

Bug: v8:9376

Change-Id: Iebf3cc2dea3133c7811bcefcdf38d9458b02fded
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1628012
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62882}
parent 1320c917
......@@ -189,15 +189,6 @@ declare_args() {
v8_enable_shared_ro_heap = ""
}
# We reuse the snapshot toolchain for building torque and other generators to
# avoid building v8_libbase on the host more than once. On mips with big endian,
# the snapshot toolchain is the target toolchain and, hence, can't be used.
v8_generator_toolchain = v8_snapshot_toolchain
if (host_cpu == "x64" &&
(v8_current_cpu == "mips" || v8_current_cpu == "mips64")) {
v8_generator_toolchain = "//build/toolchain/linux:clang_x64"
}
# Derived defaults.
if (v8_enable_verify_heap == "") {
v8_enable_verify_heap = v8_enable_debugging_features
......@@ -1002,6 +993,7 @@ if (!v8_enable_i18n_support) {
action("run_torque") {
visibility = [
":*",
"tools/debug_helper/:*",
"tools/gcmole/:*",
"test/cctest/:*",
]
......@@ -1023,6 +1015,8 @@ action("run_torque") {
"$target_gen_dir/torque-generated/class-definitions-tq.cc",
"$target_gen_dir/torque-generated/class-definitions-tq-inl.h",
"$target_gen_dir/torque-generated/class-definitions-tq.h",
"$target_gen_dir/torque-generated/class-debug-readers-tq.cc",
"$target_gen_dir/torque-generated/class-debug-readers-tq.h",
"$target_gen_dir/torque-generated/exported-macros-assembler-tq.cc",
"$target_gen_dir/torque-generated/exported-macros-assembler-tq.h",
"$target_gen_dir/torque-generated/csa-types-tq.h",
......@@ -3325,6 +3319,7 @@ v8_source_set("torque_base") {
"src/torque/ast.h",
"src/torque/cfg.cc",
"src/torque/cfg.h",
"src/torque/class-debug-reader-generator.cc",
"src/torque/constants.h",
"src/torque/contextual.h",
"src/torque/csa-generator.cc",
......
......@@ -107,3 +107,12 @@ if (v8_snapshot_toolchain == "") {
assert(v8_snapshot_toolchain != "",
"Do not know how to build a snapshot for $current_toolchain " +
"on $host_os $host_cpu")
# We reuse the snapshot toolchain for building torque and other generators to
# avoid building v8_libbase on the host more than once. On mips with big endian,
# the snapshot toolchain is the target toolchain and, hence, can't be used.
v8_generator_toolchain = v8_snapshot_toolchain
if (host_cpu == "x64" &&
(v8_current_cpu == "mips" || v8_current_cpu == "mips64")) {
v8_generator_toolchain = "//build/toolchain/linux:clang_x64"
}
......@@ -205,7 +205,7 @@ type LayoutDescriptor extends ByteArray
type TransitionArray extends WeakFixedArray
generates 'TNode<TransitionArray>';
type InstanceType extends uint16 constexpr 'InstanceType';
type InstanceType extends uint16 constexpr 'v8::internal::InstanceType';
extern class Map extends HeapObject {
instance_size_in_words: uint8;
......@@ -1424,6 +1424,10 @@ extern class JSFinalizationGroupCleanupIterator extends JSObject {
finalization_group: JSFinalizationGroup;
}
extern class FinalizationGroupCleanupJobTask extends Microtask {
finalization_group: JSFinalizationGroup;
}
extern class WeakCell extends HeapObject {
finalization_group: Undefined | JSFinalizationGroup;
target: Undefined | JSReceiver;
......
......@@ -1301,11 +1301,7 @@ void JSFinalizationGroupCleanupIterator::
VerifyHeapPointer(isolate, finalization_group());
}
void FinalizationGroupCleanupJobTask::FinalizationGroupCleanupJobTaskVerify(
Isolate* isolate) {
CHECK(IsFinalizationGroupCleanupJobTask());
CHECK(finalization_group().IsJSFinalizationGroup());
}
USE_TORQUE_VERIFIER(FinalizationGroupCleanupJobTask)
void JSWeakMap::JSWeakMapVerify(Isolate* isolate) {
TorqueGeneratedClassVerifiers::JSWeakMapVerify(*this, isolate);
......
......@@ -141,15 +141,10 @@ class FinalizationGroupCleanupJobTask : public Microtask {
DECL_VERIFIER(FinalizationGroupCleanupJobTask)
DECL_PRINTER(FinalizationGroupCleanupJobTask)
// Layout description.
#define FINALIZATION_GROUP_CLEANUP_JOB_TASK_FIELDS(V) \
V(kFinalizationGroupOffset, kTaggedSize) \
/* Total size. */ \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(Microtask::kHeaderSize,
FINALIZATION_GROUP_CLEANUP_JOB_TASK_FIELDS)
#undef FINALIZATION_GROUP_CLEANUP_JOB_TASK_FIELDS
// Layout description.
DEFINE_FIELD_OFFSET_CONSTANTS(
Microtask::kHeaderSize,
TORQUE_GENERATED_FINALIZATION_GROUP_CLEANUP_JOB_TASK_FIELDS)
OBJECT_CONSTRUCTORS(FinalizationGroupCleanupJobTask, Microtask);
};
......
// 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.
#include "src/flags/flags.h"
#include "src/torque/implementation-visitor.h"
#include "src/torque/type-oracle.h"
namespace v8 {
namespace internal {
namespace torque {
namespace {
void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
std::ostream& cc_contents,
std::unordered_set<const ClassType*>* done) {
// Make sure each class only gets generated once.
if (!type.IsExtern() || !done->insert(&type).second) return;
const ClassType* super_type = type.GetSuperClass();
// We must emit the classes in dependency order. If the super class hasn't
// been emitted yet, go handle it first.
if (super_type != nullptr) {
GenerateClassDebugReader(*super_type, h_contents, cc_contents, done);
}
const std::string name = type.name();
const std::string super_name =
super_type == nullptr ? "Object" : super_type->name();
h_contents << "\nclass Tq" << name << " : public Tq" << super_name << " {\n";
h_contents << " public:\n";
h_contents << " inline Tq" << name << "(uintptr_t address) : Tq"
<< super_name << "(address) {}\n";
h_contents << " std::vector<std::unique_ptr<ObjectProperty>> "
"GetProperties(d::MemoryAccessor accessor);\n";
std::stringstream get_props_impl;
for (const Field& field : type.fields()) {
const Type* field_type = field.name_and_type.type;
if (field_type == TypeOracle::GetVoidType()) continue;
const std::string& field_name = field.name_and_type.name;
bool is_field_tagged = field_type->IsSubtypeOf(TypeOracle::GetTaggedType());
base::Optional<const ClassType*> field_class_type =
field_type->ClassSupertype();
size_t field_size = 0;
std::string field_size_string;
std::tie(field_size, field_size_string) = field.GetFieldSizeInformation();
std::string field_value_type;
std::string field_value_type_compressed;
std::string field_cc_type;
std::string field_cc_type_compressed;
if (is_field_tagged) {
field_value_type = "uintptr_t";
field_value_type_compressed = "i::Tagged_t";
field_cc_type = "v8::internal::" + (field_class_type.has_value()
? (*field_class_type)->name()
: "Object");
field_cc_type_compressed =
COMPRESS_POINTERS_BOOL ? "v8::internal::TaggedValue" : field_cc_type;
} else {
const Type* constexpr_version = field_type->ConstexprVersion();
if (constexpr_version == nullptr) {
Error("Type '", field_type->ToString(),
"' requires a constexpr representation");
continue;
}
field_cc_type = constexpr_version->GetGeneratedTypeName();
field_cc_type_compressed = field_cc_type;
// Note that we need constexpr names to resolve correctly in the global
// namespace, because we're passing them as strings to a debugging
// extension. We can verify this during build of the debug helper, because
// we use this type for a local variable below, and generate this code in
// a disjoint namespace. However, we can't emit a useful error at this
// point. Instead we'll emit a comment that might be helpful.
field_value_type =
field_cc_type +
" /*Failing? Ensure constexpr type name is fully qualified and "
"necessary #includes are in debug-helper-internal.h*/";
field_value_type_compressed = field_value_type;
}
const std::string field_getter =
"Get" + CamelifyString(field_name) + "Value";
const std::string address_getter =
"Get" + CamelifyString(field_name) + "Address";
std::string indexed_field_info;
if (field.index) {
if ((*field.index)->name_and_type.type != TypeOracle::GetSmiType()) {
Error("Non-SMI values are not (yet) supported as indexes.");
continue;
}
get_props_impl << " Value<uintptr_t> indexed_field_count = Get"
<< CamelifyString((*field.index)->name_and_type.name)
<< "Value(accessor);\n";
indexed_field_info =
", i::PlatformSmiTagging::SmiToInt(indexed_field_count.value), "
"GetArrayKind(indexed_field_count.validity)";
}
get_props_impl
<< " result.push_back(v8::base::make_unique<ObjectProperty>(\""
<< field_name << "\", \"" << field_cc_type_compressed << "\", \""
<< field_cc_type << "\", " << address_getter << "()"
<< indexed_field_info << "));\n";
h_contents << " uintptr_t " << address_getter << "();\n";
h_contents << " Value<" << field_value_type << "> " << field_getter
<< "(d::MemoryAccessor accessor);\n";
cc_contents << "\nuintptr_t Tq" << name << "::" << address_getter
<< "() {\n";
cc_contents << " return address_ - i::kHeapObjectTag + " << field.offset
<< ";\n";
cc_contents << "}\n";
cc_contents << "\nValue<" << field_value_type << "> Tq" << name
<< "::" << field_getter << "(d::MemoryAccessor accessor) {\n";
cc_contents << " " << field_value_type_compressed << " value{};\n";
cc_contents << " d::MemoryAccessResult validity = accessor("
<< address_getter
<< "(), reinterpret_cast<uint8_t*>(&value), sizeof(value));\n";
cc_contents << " return {validity, "
<< (is_field_tagged ? "Decompress(value, address_)" : "value")
<< "};\n";
cc_contents << "}\n";
}
h_contents << "};\n";
cc_contents << "\nstd::vector<std::unique_ptr<ObjectProperty>> Tq" << name
<< "::GetProperties(d::MemoryAccessor accessor) {\n";
cc_contents << " std::vector<std::unique_ptr<ObjectProperty>> result = Tq"
<< super_name << "::GetProperties(accessor);\n";
cc_contents << get_props_impl.str();
cc_contents << " return result;\n";
cc_contents << "}\n";
}
} // namespace
void ImplementationVisitor::GenerateClassDebugReaders(
const std::string& output_directory) {
const std::string file_name = "class-debug-readers-tq";
std::stringstream h_contents;
std::stringstream cc_contents;
h_contents << "// Provides the ability to read object properties in\n";
h_contents << "// postmortem or remote scenarios, where the debuggee's\n";
h_contents << "// memory is not part of the current process's address\n";
h_contents << "// space and must be read using a callback function.\n\n";
{
IncludeGuardScope include_guard(h_contents, file_name + ".h");
h_contents << "#include <cstdint>\n";
h_contents << "#include <vector>\n";
h_contents
<< "\n#include \"tools/debug_helper/debug-helper-internal.h\"\n\n";
cc_contents << "#include \"torque-generated/" << file_name << ".h\"\n";
cc_contents << "#include \"include/v8-internal.h\"\n\n";
cc_contents << "namespace i = v8::internal;\n\n";
NamespaceScope h_namespaces(h_contents, {"v8_debug_helper_internal"});
NamespaceScope cc_namespaces(cc_contents, {"v8_debug_helper_internal"});
std::unordered_set<const ClassType*> done;
for (const TypeAlias* alias : GlobalContext::GetClasses()) {
const ClassType* type = ClassType::DynamicCast(alias->type());
GenerateClassDebugReader(*type, h_contents, cc_contents, &done);
}
}
WriteFile(output_directory + "/" + file_name + ".h", h_contents.str());
WriteFile(output_directory + "/" + file_name + ".cc", cc_contents.str());
}
} // namespace torque
} // namespace internal
} // namespace v8
......@@ -2663,70 +2663,6 @@ void ImplementationVisitor::Visit(Declarable* declarable) {
}
}
namespace {
class IfDefScope {
public:
IfDefScope(std::ostream& os, std::string d) : os_(os), d_(std::move(d)) {
os_ << "#ifdef " << d_ << "\n";
}
~IfDefScope() { os_ << "#endif // " << d_ << "\n"; }
private:
std::ostream& os_;
std::string d_;
};
class NamespaceScope {
public:
NamespaceScope(std::ostream& os,
std::initializer_list<std::string> namespaces)
: os_(os), d_(std::move(namespaces)) {
for (const std::string& s : d_) {
os_ << "namespace " << s << " {\n";
}
}
~NamespaceScope() {
for (auto i = d_.rbegin(); i != d_.rend(); ++i) {
os_ << "} // namespace " << *i << "\n";
}
}
private:
std::ostream& os_;
std::vector<std::string> d_;
};
class IncludeGuardScope {
public:
IncludeGuardScope(std::ostream& os, std::string file_name)
: os_(os),
d_("V8_GEN_TORQUE_GENERATED_" + CapifyStringWithUnderscores(file_name) +
"_") {
os_ << "#ifndef " << d_ << "\n";
os_ << "#define " << d_ << "\n\n";
}
~IncludeGuardScope() { os_ << "#endif // " << d_ << "\n"; }
private:
std::ostream& os_;
std::string d_;
};
class IncludeObjectMacrosScope {
public:
explicit IncludeObjectMacrosScope(std::ostream& os) : os_(os) {
os_ << "\n// Has to be the last include (doesn't have include guards):\n"
"#include \"src/objects/object-macros.h\"\n";
}
~IncludeObjectMacrosScope() {
os_ << "\n#include \"src/objects/object-macros-undef.h\"\n";
}
private:
std::ostream& os_;
};
} // namespace
void ImplementationVisitor::GenerateBuiltinDefinitions(
const std::string& output_directory) {
std::stringstream new_contents_stream;
......
......@@ -354,6 +354,7 @@ class ImplementationVisitor {
void GenerateClassDefinitions(const std::string& output_directory);
void GenerateInstanceTypes(const std::string& output_directory);
void GenerateClassVerifiers(const std::string& output_directory);
void GenerateClassDebugReaders(const std::string& output_directory);
void GenerateExportedMacrosAssembler(const std::string& output_directory);
void GenerateCSATypes(const std::string& output_directory);
void GenerateCppForInternalClasses(const std::string& output_directory);
......
......@@ -83,6 +83,7 @@ void CompileCurrentAst(TorqueCompilerOptions options) {
implementation_visitor.GeneratePrintDefinitions(output_directory);
implementation_visitor.GenerateClassDefinitions(output_directory);
implementation_visitor.GenerateClassVerifiers(output_directory);
implementation_visitor.GenerateClassDebugReaders(output_directory);
implementation_visitor.GenerateExportedMacrosAssembler(output_directory);
implementation_visitor.GenerateCSATypes(output_directory);
implementation_visitor.GenerateInstanceTypes(output_directory);
......
......@@ -292,6 +292,42 @@ void ReplaceFileContentsIfDifferent(const std::string& file_path,
}
}
IfDefScope::IfDefScope(std::ostream& os, std::string d)
: os_(os), d_(std::move(d)) {
os_ << "#ifdef " << d_ << "\n";
}
IfDefScope::~IfDefScope() { os_ << "#endif // " << d_ << "\n"; }
NamespaceScope::NamespaceScope(std::ostream& os,
std::initializer_list<std::string> namespaces)
: os_(os), d_(std::move(namespaces)) {
for (const std::string& s : d_) {
os_ << "namespace " << s << " {\n";
}
}
NamespaceScope::~NamespaceScope() {
for (auto i = d_.rbegin(); i != d_.rend(); ++i) {
os_ << "} // namespace " << *i << "\n";
}
}
IncludeGuardScope::IncludeGuardScope(std::ostream& os, std::string file_name)
: os_(os),
d_("V8_GEN_TORQUE_GENERATED_" + CapifyStringWithUnderscores(file_name) +
"_") {
os_ << "#ifndef " << d_ << "\n";
os_ << "#define " << d_ << "\n\n";
}
IncludeGuardScope::~IncludeGuardScope() { os_ << "#endif // " << d_ << "\n"; }
IncludeObjectMacrosScope::IncludeObjectMacrosScope(std::ostream& os) : os_(os) {
os_ << "\n// Has to be the last include (doesn't have include guards):\n"
"#include \"src/objects/object-macros.h\"\n";
}
IncludeObjectMacrosScope::~IncludeObjectMacrosScope() {
os_ << "\n#include \"src/objects/object-macros-undef.h\"\n";
}
} // namespace torque
} // namespace internal
} // namespace v8
......@@ -356,6 +356,54 @@ inline bool StringEndsWith(const std::string& s, const std::string& suffix) {
return s.substr(s.size() - suffix.size()) == suffix;
}
class IfDefScope {
public:
IfDefScope(std::ostream& os, std::string d);
~IfDefScope();
private:
IfDefScope(const IfDefScope&) = delete;
IfDefScope& operator=(const IfDefScope&) = delete;
std::ostream& os_;
std::string d_;
};
class NamespaceScope {
public:
NamespaceScope(std::ostream& os,
std::initializer_list<std::string> namespaces);
~NamespaceScope();
private:
NamespaceScope(const NamespaceScope&) = delete;
NamespaceScope& operator=(const NamespaceScope&) = delete;
std::ostream& os_;
std::vector<std::string> d_;
};
class IncludeGuardScope {
public:
IncludeGuardScope(std::ostream& os, std::string file_name);
~IncludeGuardScope();
private:
IncludeGuardScope(const IncludeGuardScope&) = delete;
IncludeGuardScope& operator=(const IncludeGuardScope&) = delete;
std::ostream& os_;
std::string d_;
};
class IncludeObjectMacrosScope {
public:
explicit IncludeObjectMacrosScope(std::ostream& os);
~IncludeObjectMacrosScope();
private:
IncludeObjectMacrosScope(const IncludeObjectMacrosScope&) = delete;
IncludeObjectMacrosScope& operator=(const IncludeObjectMacrosScope&) = delete;
std::ostream& os_;
};
} // namespace torque
} // namespace internal
} // namespace v8
......
......@@ -892,7 +892,8 @@ class BailoutId {
// Our version of printf().
V8_EXPORT_PRIVATE void PRINTF_FORMAT(1, 2) PrintF(const char* format, ...);
void PRINTF_FORMAT(2, 3) PrintF(FILE* out, const char* format, ...);
V8_EXPORT_PRIVATE void PRINTF_FORMAT(2, 3)
PrintF(FILE* out, const char* format, ...);
// Prepends the current process ID to the output.
void PRINTF_FORMAT(1, 2) PrintPID(const char* format, ...);
......
......@@ -188,6 +188,7 @@ v8_source_set("cctest_sources") {
"test-conversions.cc",
"test-cpu-profiler.cc",
"test-date.cc",
"test-debug-helper.cc",
"test-debug.cc",
"test-decls.cc",
"test-deoptimization.cc",
......@@ -382,6 +383,7 @@ v8_source_set("cctest_sources") {
"../..:v8_libbase",
"../..:v8_libplatform",
"../..:wasm_module_runner",
"../../tools/debug_helper:v8_debug_helper",
"//build/win:default_exe_manifest",
]
......
include_rules = [
"+src",
"+tools",
"+torque-generated",
"+perfetto/tracing.h"
]
// Copyright 2018 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.
#include "src/api/api-inl.h"
#include "src/heap/spaces.h"
#include "test/cctest/cctest.h"
#include "tools/debug_helper/debug-helper.h"
namespace v8 {
namespace internal {
namespace {
namespace d = v8::debug_helper;
uintptr_t memory_fail_start = 0;
uintptr_t memory_fail_end = 0;
class MemoryFailureRegion {
public:
MemoryFailureRegion(uintptr_t start, uintptr_t end) {
memory_fail_start = start;
memory_fail_end = end;
}
~MemoryFailureRegion() {
memory_fail_start = 0;
memory_fail_end = 0;
}
};
// Implement the memory-reading callback. This one just fetches memory from the
// current process, but a real implementation for a debugging extension would
// fetch memory from the debuggee process or crash dump.
d::MemoryAccessResult ReadMemory(uintptr_t address, uint8_t* destination,
size_t byte_count) {
if (address >= memory_fail_start && address <= memory_fail_end) {
// Simulate failure to read debuggee memory.
return d::MemoryAccessResult::kAddressValidButInaccessible;
}
memcpy(destination, reinterpret_cast<void*>(address), byte_count);
return d::MemoryAccessResult::kOk;
}
void CheckProp(const d::ObjectProperty& property, const char* expected_type,
const char* expected_name,
d::PropertyKind expected_kind = d::PropertyKind::kSingle,
size_t expected_num_values = 1) {
CHECK_EQ(property.num_values, expected_num_values);
CHECK(property.type == std::string("v8::internal::TaggedValue") ||
property.type == std::string(expected_type));
CHECK(property.decompressed_type == std::string(expected_type));
CHECK(property.kind == expected_kind);
CHECK(property.name == std::string(expected_name));
}
template <typename TValue>
void CheckProp(const d::ObjectProperty& property, const char* expected_type,
const char* expected_name, TValue expected_value) {
CheckProp(property, expected_type, expected_name);
CHECK(*reinterpret_cast<TValue*>(property.address) == expected_value);
}
} // namespace
TEST(GetObjectProperties) {
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
LocalContext context;
d::Roots roots{0, 0, 0, 0}; // We don't know the heap roots.
v8::Local<v8::Value> v = CompileRun("42");
Handle<Object> o = v8::Utils::OpenHandle(*v);
d::ObjectPropertiesResultPtr props =
d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
CHECK(props->type_check_result == d::TypeCheckResult::kSmi);
CHECK(props->brief == std::string("42 (0x2a)"));
CHECK(props->type == std::string("v8::internal::Smi"));
CHECK_EQ(props->num_properties, 0);
v = CompileRun("[\"a\", \"b\"]");
o = v8::Utils::OpenHandle(*v);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props->type == std::string("v8::internal::JSArray"));
CHECK_EQ(props->num_properties, 4);
CheckProp(*props->properties[0], "v8::internal::Map", "map");
CheckProp(*props->properties[1], "v8::internal::Object",
"properties_or_hash");
CheckProp(*props->properties[2], "v8::internal::FixedArrayBase", "elements");
CheckProp(*props->properties[3], "v8::internal::Object", "length",
static_cast<i::Tagged_t>(IntToSmi(2)));
// We need to supply a root address for decompression before reading the
// elements from the JSArray.
roots.any_heap_pointer = o->ptr();
i::Tagged_t properties_or_hash =
*reinterpret_cast<i::Tagged_t*>(props->properties[1]->address);
i::Tagged_t elements =
*reinterpret_cast<i::Tagged_t*>(props->properties[2]->address);
// The properties_or_hash_code field should be an empty fixed array. Since
// that is at a known offset, we should be able to detect it even without
// any ability to read memory.
{
MemoryFailureRegion failure(0, UINTPTR_MAX);
props = d::GetObjectProperties(properties_or_hash, &ReadMemory, roots);
CHECK(props->type_check_result ==
d::TypeCheckResult::kObjectPointerValidButInaccessible);
CHECK(props->type == std::string("v8::internal::Object"));
CHECK_EQ(props->num_properties, 0);
CHECK(std::string(props->brief).substr(0, 21) ==
std::string("maybe EmptyFixedArray"));
// Provide a heap root so the API can be more sure.
roots.read_only_space =
reinterpret_cast<uintptr_t>(reinterpret_cast<i::Isolate*>(isolate)
->heap()
->read_only_space()
->first_page());
props = d::GetObjectProperties(properties_or_hash, &ReadMemory, roots);
CHECK(props->type_check_result ==
d::TypeCheckResult::kObjectPointerValidButInaccessible);
CHECK(props->type == std::string("v8::internal::Object"));
CHECK_EQ(props->num_properties, 0);
CHECK(std::string(props->brief).substr(0, 15) ==
std::string("EmptyFixedArray"));
}
props = d::GetObjectProperties(elements, &ReadMemory, roots);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props->type == std::string("v8::internal::FixedArray"));
CHECK_EQ(props->num_properties, 3);
CheckProp(*props->properties[0], "v8::internal::Map", "map");
CheckProp(*props->properties[1], "v8::internal::Object", "length",
static_cast<i::Tagged_t>(IntToSmi(2)));
CheckProp(*props->properties[2], "v8::internal::Object", "objects",
d::PropertyKind::kArrayOfKnownSize, 2);
// Get the second string value from the FixedArray.
i::Tagged_t second_string_address = *reinterpret_cast<i::Tagged_t*>(
props->properties[2]->address + sizeof(i::Tagged_t));
props = d::GetObjectProperties(second_string_address, &ReadMemory, roots);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props->type == std::string("v8::internal::String"));
CHECK_EQ(props->num_properties, 3);
CheckProp(*props->properties[0], "v8::internal::Map", "map");
CheckProp(*props->properties[1], "uint32_t", "hash_field");
CheckProp(*props->properties[2], "int32_t", "length", 1);
// Read the second string again, using a type hint instead of the map. All of
// its properties should match what we read last time.
d::ObjectPropertiesResultPtr props2;
{
uintptr_t map_address =
d::GetObjectProperties(
*reinterpret_cast<i::Tagged_t*>(props->properties[0]->address),
&ReadMemory, roots)
->properties[0]
->address;
MemoryFailureRegion failure(map_address, map_address + i::Map::kSize);
props2 = d::GetObjectProperties(second_string_address, &ReadMemory, roots,
"v8::internal::String");
CHECK(props2->type_check_result == d::TypeCheckResult::kUsedTypeHint);
CHECK(props2->type == std::string("v8::internal::String"));
CHECK_EQ(props2->num_properties, 3);
CheckProp(*props2->properties[0], "v8::internal::Map", "map",
*reinterpret_cast<i::Tagged_t*>(props->properties[0]->address));
CheckProp(*props2->properties[1], "uint32_t", "hash_field",
*reinterpret_cast<int32_t*>(props->properties[1]->address));
CheckProp(*props2->properties[2], "int32_t", "length", 1);
}
// Try a weak reference.
props2 = d::GetObjectProperties(second_string_address | kWeakHeapObjectMask,
&ReadMemory, roots);
std::string weak_ref_prefix = "weak ref to ";
CHECK(weak_ref_prefix + props->brief == props2->brief);
CHECK(props2->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props2->type == std::string("v8::internal::String"));
CHECK_EQ(props2->num_properties, 3);
CheckProp(*props2->properties[0], "v8::internal::Map", "map",
*reinterpret_cast<i::Tagged_t*>(props->properties[0]->address));
CheckProp(*props2->properties[1], "uint32_t", "hash_field",
*reinterpret_cast<i::Tagged_t*>(props->properties[1]->address));
CheckProp(*props2->properties[2], "int32_t", "length", 1);
}
} // namespace internal
} // namespace v8
......@@ -42,7 +42,7 @@ class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
void Free(void* p, size_t) override {}
};
static void DumpKnownMap(i::Heap* heap, const char* space_name,
static void DumpKnownMap(FILE* out, i::Heap* heap, const char* space_name,
i::HeapObject object) {
#define RO_ROOT_LIST_CASE(type, name, CamelName) \
if (root_name == nullptr && object == roots.name()) root_name = #CamelName;
......@@ -59,14 +59,14 @@ static void DumpKnownMap(i::Heap* heap, const char* space_name,
MUTABLE_ROOT_LIST(MUTABLE_ROOT_LIST_CASE)
if (root_name == nullptr) return;
i::PrintF(" (\"%s\", 0x%05" V8PRIxPTR "): (%d, \"%s\"),\n", space_name,
i::PrintF(out, " (\"%s\", 0x%05" V8PRIxPTR "): (%d, \"%s\"),\n", space_name,
root_ptr, map.instance_type(), root_name);
#undef MUTABLE_ROOT_LIST_CASE
#undef RO_ROOT_LIST_CASE
}
static void DumpKnownObject(i::Heap* heap, const char* space_name,
static void DumpKnownObject(FILE* out, i::Heap* heap, const char* space_name,
i::HeapObject object) {
#define RO_ROOT_LIST_CASE(type, name, CamelName) \
if (root_name == nullptr && object == roots.name()) { \
......@@ -90,14 +90,14 @@ static void DumpKnownObject(i::Heap* heap, const char* space_name,
if (root_name == nullptr) return;
if (!i::RootsTable::IsImmortalImmovable(root_index)) return;
i::PrintF(" (\"%s\", 0x%05" V8PRIxPTR "): \"%s\",\n", space_name, root_ptr,
root_name);
i::PrintF(out, " (\"%s\", 0x%05" V8PRIxPTR "): \"%s\",\n", space_name,
root_ptr, root_name);
#undef ROOT_LIST_CASE
#undef RO_ROOT_LIST_CASE
}
static int DumpHeapConstants(const char* argv0) {
static int DumpHeapConstants(FILE* out, const char* argv0) {
// Start up V8.
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
......@@ -112,42 +112,42 @@ static int DumpHeapConstants(const char* argv0) {
i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
i::ReadOnlyHeap* read_only_heap =
reinterpret_cast<i::Isolate*>(isolate)->read_only_heap();
i::PrintF("%s", kHeader);
#define DUMP_TYPE(T) i::PrintF(" %d: \"%s\",\n", i::T, #T);
i::PrintF("INSTANCE_TYPES = {\n");
i::PrintF(out, "%s", kHeader);
#define DUMP_TYPE(T) i::PrintF(out, " %d: \"%s\",\n", i::T, #T);
i::PrintF(out, "INSTANCE_TYPES = {\n");
INSTANCE_TYPE_LIST(DUMP_TYPE)
i::PrintF("}\n");
i::PrintF(out, "}\n");
#undef DUMP_TYPE
{
// Dump the KNOWN_MAP table to the console.
i::PrintF("\n# List of known V8 maps.\n");
i::PrintF("KNOWN_MAPS = {\n");
i::PrintF(out, "\n# List of known V8 maps.\n");
i::PrintF(out, "KNOWN_MAPS = {\n");
i::ReadOnlyHeapObjectIterator ro_iterator(read_only_heap);
for (i::HeapObject object = ro_iterator.Next(); !object.is_null();
object = ro_iterator.Next()) {
if (!object.IsMap()) continue;
DumpKnownMap(heap, i::Heap::GetSpaceName(i::RO_SPACE), object);
DumpKnownMap(out, heap, i::Heap::GetSpaceName(i::RO_SPACE), object);
}
i::PagedSpaceObjectIterator iterator(heap->map_space());
for (i::HeapObject object = iterator.Next(); !object.is_null();
object = iterator.Next()) {
if (!object.IsMap()) continue;
DumpKnownMap(heap, i::Heap::GetSpaceName(i::MAP_SPACE), object);
DumpKnownMap(out, heap, i::Heap::GetSpaceName(i::MAP_SPACE), object);
}
i::PrintF("}\n");
i::PrintF(out, "}\n");
}
{
// Dump the KNOWN_OBJECTS table to the console.
i::PrintF("\n# List of known V8 objects.\n");
i::PrintF("KNOWN_OBJECTS = {\n");
i::PrintF(out, "\n# List of known V8 objects.\n");
i::PrintF(out, "KNOWN_OBJECTS = {\n");
i::ReadOnlyHeapObjectIterator ro_iterator(read_only_heap);
for (i::HeapObject object = ro_iterator.Next(); !object.is_null();
object = ro_iterator.Next()) {
// Skip read-only heap maps, they will be reported elsewhere.
if (object.IsMap()) continue;
DumpKnownObject(heap, i::Heap::GetSpaceName(i::RO_SPACE), object);
DumpKnownObject(out, heap, i::Heap::GetSpaceName(i::RO_SPACE), object);
}
i::PagedSpaceIterator spit(heap);
......@@ -158,22 +158,22 @@ static int DumpHeapConstants(const char* argv0) {
continue;
const char* sname = s->name();
for (i::HeapObject o = it.Next(); !o.is_null(); o = it.Next()) {
DumpKnownObject(heap, sname, o);
DumpKnownObject(out, heap, sname, o);
}
}
i::PrintF("}\n");
i::PrintF(out, "}\n");
}
// Dump frame markers
i::PrintF("\n# List of known V8 Frame Markers.\n");
#define DUMP_MARKER(T, class) i::PrintF(" \"%s\",\n", #T);
i::PrintF("FRAME_MARKERS = (\n");
i::PrintF(out, "\n# List of known V8 Frame Markers.\n");
#define DUMP_MARKER(T, class) i::PrintF(out, " \"%s\",\n", #T);
i::PrintF(out, "FRAME_MARKERS = (\n");
STACK_FRAME_TYPE_LIST(DUMP_MARKER)
i::PrintF(")\n");
i::PrintF(out, ")\n");
#undef DUMP_MARKER
}
i::PrintF("\n# This set of constants is generated from a %s build.\n",
i::PrintF(out, "\n# This set of constants is generated from a %s build.\n",
kBuild);
// Teardown.
......@@ -184,4 +184,10 @@ static int DumpHeapConstants(const char* argv0) {
} // namespace v8
int main(int argc, char* argv[]) { return v8::DumpHeapConstants(argv[0]); }
int main(int argc, char* argv[]) {
FILE* out = stdout;
if (argc > 2 && strcmp(argv[1], "--outfile") == 0) {
out = fopen(argv[2], "wb");
}
return v8::DumpHeapConstants(out, argv[0]);
}
......@@ -10,6 +10,7 @@ group("gn_all") {
data_deps = [
":v8_check_static_initializers",
"debug_helper:v8_debug_helper",
"gcmole:v8_run_gcmole",
"jsfunfuzz:v8_jsfunfuzz",
]
......
# 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.
import("../../gni/snapshot_toolchain.gni")
import("../../gni/v8.gni")
config("internal_config") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
if (is_component_build) {
defines = [ "BUILDING_V8_DEBUG_HELPER" ]
}
include_dirs = [
".",
"../..",
"$target_gen_dir",
"$target_gen_dir/../..",
]
}
# This config should be applied to code using v8_debug_helper.
config("external_config") {
if (is_component_build) {
defines = [ "USING_V8_DEBUG_HELPER" ]
}
include_dirs = [ "." ]
}
action("run_mkgrokdump") {
testonly = true
visibility = [ ":*" ]
deps = [
"../../test/mkgrokdump:mkgrokdump($v8_generator_toolchain)",
]
script = "../run.py"
outputs = [
"$target_gen_dir/v8heapconst.py",
]
args = [
"./" + rebase_path(
get_label_info(
"../../test/mkgrokdump:mkgrokdump($v8_generator_toolchain)",
"root_out_dir") + "/mkgrokdump",
root_build_dir),
"--outfile",
rebase_path("$target_gen_dir/v8heapconst.py", root_build_dir),
]
}
action("gen_heap_constants") {
testonly = true
visibility = [ ":*" ]
deps = [
":run_mkgrokdump",
]
script = "gen-heap-constants.py"
outputs = [
"$target_gen_dir/heap-constants-gen.cc",
]
args = [
rebase_path(target_gen_dir, root_build_dir),
rebase_path("$target_gen_dir/heap-constants-gen.cc", root_build_dir),
]
}
v8_component("v8_debug_helper") {
testonly = true
public = [
"debug-helper.h",
]
sources = [
"$target_gen_dir/../../torque-generated/class-debug-readers-tq.cc",
"$target_gen_dir/../../torque-generated/class-debug-readers-tq.h",
"$target_gen_dir/heap-constants-gen.cc",
"debug-helper-internal.cc",
"debug-helper-internal.h",
"debug-helper.h",
"get-object-properties.cc",
"heap-constants.cc",
"heap-constants.h",
]
deps = [
":gen_heap_constants",
"../..:run_torque",
"../..:v8_headers",
"../..:v8_libbase",
]
configs = [ ":internal_config" ]
if (v8_enable_i18n_support) {
configs += [ "//third_party/icu:icu_config" ]
}
public_configs = [ ":external_config" ]
}
include_rules = [
"+torque-generated"
]
# V8 debug helper
This library is for debugging V8 itself, not debugging JavaScript running within
V8. It is designed to be called from a debugger extension running within a
native debugger such as WinDbg or LLDB. It can be used on live processes or
crash dumps, and cannot assume that all memory is available in a dump.
// 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.
#include "debug-helper-internal.h"
#include "src/common/ptr-compr-inl.h"
namespace i = v8::internal;
namespace v8_debug_helper_internal {
bool IsPointerCompressed(uintptr_t address) {
#if COMPRESS_POINTERS_BOOL
STATIC_ASSERT(i::kPtrComprHeapReservationSize == uintptr_t{1} << 32);
intptr_t upper_half = static_cast<intptr_t>(address) >> 32;
// Allow compressed pointers to be either zero-extended or sign-extended by
// the caller.
return upper_half == 0 || upper_half == -1;
#else
return false;
#endif
}
uintptr_t Decompress(uintptr_t address, uintptr_t any_uncompressed_ptr) {
if (!COMPRESS_POINTERS_BOOL || !IsPointerCompressed(address)) return address;
return i::DecompressTaggedAny(any_uncompressed_ptr,
static_cast<i::Tagged_t>(address));
}
d::PropertyKind GetArrayKind(d::MemoryAccessResult mem_result) {
d::PropertyKind indexed_field_kind{};
switch (mem_result) {
case d::MemoryAccessResult::kOk:
indexed_field_kind = d::PropertyKind::kArrayOfKnownSize;
break;
case d::MemoryAccessResult::kAddressNotValid:
indexed_field_kind =
d::PropertyKind::kArrayOfUnknownSizeDueToInvalidMemory;
break;
default:
indexed_field_kind =
d::PropertyKind::kArrayOfUnknownSizeDueToValidButInaccessibleMemory;
break;
}
return indexed_field_kind;
}
std::vector<std::unique_ptr<ObjectProperty>> TqObject::GetProperties(
d::MemoryAccessor accessor) {
return std::vector<std::unique_ptr<ObjectProperty>>();
}
} // namespace v8_debug_helper_internal
// 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.
// This file defines internal versions of the public API structs. These should
// all be tidy and simple classes which maintain proper ownership (unique_ptr)
// of each other. Each contains an instance of its corresponding public type,
// which can be filled out with GetPublicView.
#ifndef V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_INTERNAL_H_
#define V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_INTERNAL_H_
#include <string>
#include <vector>
#include "debug-helper.h"
#include "src/objects/instance-type.h"
namespace d = v8::debug_helper;
namespace v8_debug_helper_internal {
// A value that was read from the debuggee's memory.
template <typename TValue>
struct Value {
d::MemoryAccessResult validity;
TValue value;
};
class ObjectProperty {
public:
inline ObjectProperty(std::string name, std::string type,
std::string decompressed_type, uintptr_t address,
size_t num_values = 1,
d::PropertyKind kind = d::PropertyKind::kSingle)
: name_(name),
type_(type),
decompressed_type_(decompressed_type),
address_(address),
num_values_(num_values),
kind_(kind) {}
inline d::ObjectProperty* GetPublicView() {
public_view_.name = name_.c_str();
public_view_.type = type_.c_str();
public_view_.decompressed_type = decompressed_type_.c_str();
public_view_.address = address_;
public_view_.num_values = num_values_;
public_view_.kind = kind_;
return &public_view_;
}
private:
std::string name_;
std::string type_;
std::string decompressed_type_;
uintptr_t address_;
size_t num_values_;
d::PropertyKind kind_;
d::ObjectProperty public_view_;
};
class ObjectPropertiesResult;
using ObjectPropertiesResultInternal = ObjectPropertiesResult;
struct ObjectPropertiesResultExtended : public d::ObjectPropertiesResult {
ObjectPropertiesResultInternal* base; // Back reference for cleanup
};
class ObjectPropertiesResult {
public:
inline ObjectPropertiesResult(
d::TypeCheckResult type_check_result, std::string brief, std::string type,
std::vector<std::unique_ptr<ObjectProperty>> properties)
: type_check_result_(type_check_result),
brief_(brief),
type_(type),
properties_(std::move(properties)) {}
inline void Prepend(const char* prefix) { brief_ = prefix + brief_; }
inline d::ObjectPropertiesResult* GetPublicView() {
public_view_.type_check_result = type_check_result_;
public_view_.brief = brief_.c_str();
public_view_.type = type_.c_str();
public_view_.num_properties = properties_.size();
properties_raw_.resize(0);
for (const auto& property : properties_) {
properties_raw_.push_back(property->GetPublicView());
}
public_view_.properties = properties_raw_.data();
public_view_.base = this;
return &public_view_;
}
private:
d::TypeCheckResult type_check_result_;
std::string brief_;
std::string type_;
std::vector<std::unique_ptr<ObjectProperty>> properties_;
ObjectPropertiesResultExtended public_view_;
std::vector<d::ObjectProperty*> properties_raw_;
};
// Base class representing a V8 object in the debuggee's address space.
// Subclasses for specific object types are generated by the Torque compiler.
class TqObject {
public:
inline TqObject(uintptr_t address) : address_(address) {}
std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
d::MemoryAccessor accessor);
protected:
uintptr_t address_;
};
bool IsPointerCompressed(uintptr_t address);
uintptr_t Decompress(uintptr_t address, uintptr_t any_uncompressed_address);
d::PropertyKind GetArrayKind(d::MemoryAccessResult mem_result);
} // namespace v8_debug_helper_internal
#endif
// 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.
// This file defines the public interface to v8_debug_helper.
#ifndef V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_H_
#define V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_H_
#include <cstdint>
#include <memory>
#if defined(_WIN32)
#ifdef BUILDING_V8_DEBUG_HELPER
#define V8_DEBUG_HELPER_EXPORT __declspec(dllexport)
#elif USING_V8_DEBUG_HELPER
#define V8_DEBUG_HELPER_EXPORT __declspec(dllimport)
#else
#define V8_DEBUG_HELPER_EXPORT
#endif
#else // defined(_WIN32)
#ifdef BUILDING_V8_DEBUG_HELPER
#define V8_DEBUG_HELPER_EXPORT __attribute__((visibility("default")))
#else
#define V8_DEBUG_HELPER_EXPORT
#endif
#endif // defined(_WIN32)
namespace v8 {
namespace debug_helper {
// Possible results when attempting to fetch memory from the debuggee.
enum class MemoryAccessResult {
kOk,
kAddressNotValid,
kAddressValidButInaccessible, // Possible in incomplete dump.
};
// Information about how this tool discovered the type of the object.
enum class TypeCheckResult {
// Success cases:
kSmi,
kWeakRef,
kUsedMap,
kUsedTypeHint,
// Failure cases:
kUnableToDecompress, // Caller must provide the heap range somehow.
kObjectPointerInvalid,
kObjectPointerValidButInaccessible, // Possible in incomplete dump.
kMapPointerInvalid,
kMapPointerValidButInaccessible, // Possible in incomplete dump.
kUnknownInstanceType,
kUnknownTypeHint,
};
enum class PropertyKind {
kSingle,
kArrayOfKnownSize,
kArrayOfUnknownSizeDueToInvalidMemory,
kArrayOfUnknownSizeDueToValidButInaccessibleMemory,
};
struct ObjectProperty {
const char* name;
// Statically-determined type, such as from .tq definition.
const char* type;
// In some cases, |type| may be a simple type representing a compressed
// pointer such as v8::internal::TaggedValue. In those cases,
// |decompressed_type| will contain the type of the object when decompressed.
// Otherwise, |decompressed_type| will match |type|. In any case, it is safe
// to pass the |decompressed_type| value as the type_hint on a subsequent call
// to GetObjectProperties.
const char* decompressed_type;
// The address where the property value can be found in the debuggee's address
// space, or the address of the first value for an array.
uintptr_t address;
// If kind indicates an array of unknown size, num_values will be 0 and debug
// tools should display this property as a raw pointer. Note that there is a
// semantic difference between num_values=1 and kind=kSingle (normal property)
// versus num_values=1 and kind=kArrayOfKnownSize (one-element array).
size_t num_values;
PropertyKind kind;
};
struct ObjectPropertiesResult {
TypeCheckResult type_check_result;
const char* brief;
const char* type; // Runtime type of the object.
size_t num_properties;
ObjectProperty** properties;
};
// Copies byte_count bytes of memory from the given address in the debuggee to
// the destination buffer.
typedef MemoryAccessResult (*MemoryAccessor)(uintptr_t address,
uint8_t* destination,
size_t byte_count);
// Additional data that can help GetObjectProperties to be more accurate. Any
// fields you don't know can be set to zero and this library will do the best it
// can with the information available.
struct Roots {
// Beginning of allocated space for various kinds of data. These can help us
// to detect certain common objects that are placed in memory during startup.
// These values might be provided via name-value pairs in CrashPad dumps.
// Otherwise, they can be obtained as follows:
// 1. Get the Isolate pointer for the current thread. It might be somewhere on
// the stack, or it might be accessible from thread-local storage with the
// key stored in v8::internal::Isolate::isolate_key_.
// 2. Get isolate->heap_.map_space_->memory_chunk_list_.front_ and similar for
// old_space_ and read_only_space_.
uintptr_t map_space;
uintptr_t old_space;
uintptr_t read_only_space;
// Any valid heap pointer address. On platforms where pointer compression is
// enabled, this can allow us to get data from compressed pointers even if the
// other data above is not provided. The Isolate pointer is valid for this
// purpose if you have it.
uintptr_t any_heap_pointer;
};
} // namespace debug_helper
} // namespace v8
extern "C" {
// Raw library interface. If possible, use functions in v8::debug_helper
// namespace instead because they use smart pointers to prevent leaks.
V8_DEBUG_HELPER_EXPORT v8::debug_helper::ObjectPropertiesResult*
_v8_debug_helper_GetObjectProperties(
uintptr_t object, v8::debug_helper::MemoryAccessor memory_accessor,
const v8::debug_helper::Roots& heap_roots, const char* type_hint);
V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_ObjectPropertiesResult(
v8::debug_helper::ObjectPropertiesResult* result);
}
namespace v8 {
namespace debug_helper {
struct DebugHelperObjectPropertiesResultDeleter {
void operator()(v8::debug_helper::ObjectPropertiesResult* ptr) {
_v8_debug_helper_Free_ObjectPropertiesResult(ptr);
}
};
using ObjectPropertiesResultPtr =
std::unique_ptr<ObjectPropertiesResult,
DebugHelperObjectPropertiesResultDeleter>;
// Get information about the given object pointer (either a tagged pointer
// (compressed or uncompressed), or a SMI). The type hint is only used if the
// object's Map is missing or corrupt. It should be the fully-qualified name of
// a class that inherits from v8::internal::Object.
inline ObjectPropertiesResultPtr GetObjectProperties(
uintptr_t object, v8::debug_helper::MemoryAccessor memory_accessor,
const Roots& heap_roots, const char* type_hint = nullptr) {
return ObjectPropertiesResultPtr(_v8_debug_helper_GetObjectProperties(
object, memory_accessor, heap_roots, type_hint));
}
} // namespace debug_helper
} // namespace v8
#endif
#!/usr/bin/env python
# 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.
"""This program writes a C++ file that can be used to look up whether a given
address matches known object locations. The first argument is the directory
containing the file v8heapconst.py; the second argument is the output .cc file.
"""
import sys
sys.path.insert(0, sys.argv[1])
import v8heapconst
out = """
#include <cstdint>
#include <string>
namespace v8_debug_helper_internal {
"""
def iterate_objects(target_space, camel_space_name):
global out
result = []
for (space, offset), (instance_type, name) in v8heapconst.KNOWN_MAPS.items():
if space == target_space:
result.append((offset, name))
for (space, offset), name in v8heapconst.KNOWN_OBJECTS.items():
if space == target_space:
result.append((offset, name))
out = out + '\nstd::string FindKnownObjectIn' + camel_space_name \
+ '(uintptr_t offset) {\n switch (offset) {\n'
for offset, name in result:
out = out + ' case ' + str(offset) + ': return "' + name + '";\n'
out = out + ' default: return "";\n }\n}\n'
iterate_objects('map_space', 'MapSpace')
iterate_objects('read_only_space', 'ReadOnlySpace')
iterate_objects('old_space', 'OldSpace')
def iterate_maps(target_space, camel_space_name):
global out
out = out + '\nint FindKnownMapInstanceTypeIn' + camel_space_name \
+ '(uintptr_t offset) {\n switch (offset) {\n'
for (space, offset), (instance_type, name) in v8heapconst.KNOWN_MAPS.items():
if space == target_space:
out = out + ' case ' + str(offset) + ': return ' + str(instance_type) \
+ ';\n'
out = out + ' default: return -1;\n }\n}\n'
iterate_maps('map_space', 'MapSpace')
iterate_maps('read_only_space', 'ReadOnlySpace')
out = out + '\n}\n'
try:
with open(sys.argv[2], "r") as out_file:
if out == out_file.read():
sys.exit(0) # No modification needed.
except:
pass # File probably doesn't exist; write it.
with open(sys.argv[2], "w") as out_file:
out_file.write(out)
This diff is collapsed.
// 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.
#include "heap-constants.h"
#include "src/common/globals.h"
namespace d = v8::debug_helper;
namespace v8_debug_helper_internal {
std::string FindKnownObject(uintptr_t address, const d::Roots& roots) {
uintptr_t containing_page = address & ~i::kPageAlignmentMask;
uintptr_t offset_in_page = address & i::kPageAlignmentMask;
// If there's a match with a known root, then search only that page.
if (containing_page == roots.map_space) {
return FindKnownObjectInMapSpace(offset_in_page);
}
if (containing_page == roots.old_space) {
return FindKnownObjectInOldSpace(offset_in_page);
}
if (containing_page == roots.read_only_space) {
return FindKnownObjectInReadOnlySpace(offset_in_page);
}
// For any unknown roots, compile a list of things this object might be.
std::string result;
if (roots.map_space == 0) {
std::string sub_result = FindKnownObjectInMapSpace(offset_in_page);
if (!sub_result.empty()) {
result += "maybe " + sub_result;
}
}
if (roots.old_space == 0) {
std::string sub_result = FindKnownObjectInOldSpace(offset_in_page);
if (!sub_result.empty()) {
result = (result.empty() ? "" : result + ", ") + "maybe " + sub_result;
}
}
if (roots.read_only_space == 0) {
std::string sub_result = FindKnownObjectInReadOnlySpace(offset_in_page);
if (!sub_result.empty()) {
result = (result.empty() ? "" : result + ", ") + "maybe " + sub_result;
}
}
return result;
}
} // namespace v8_debug_helper_internal
// 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.
#ifndef V8_TOOLS_DEBUG_HELPER_HEAP_CONSTANTS_H_
#define V8_TOOLS_DEBUG_HELPER_HEAP_CONSTANTS_H_
#include <cstdint>
#include <string>
#include "debug-helper.h"
namespace d = v8::debug_helper;
namespace v8_debug_helper_internal {
// Functions generated by mkgrokdump:
std::string FindKnownObjectInOldSpace(uintptr_t offset);
std::string FindKnownObjectInReadOnlySpace(uintptr_t offset);
std::string FindKnownObjectInMapSpace(uintptr_t offset);
std::string FindKnownMapInstanceTypeInMapSpace(uintptr_t offset);
std::string FindKnownMapInstanceTypeInReadOnlySpace(uintptr_t offset);
std::string FindKnownObject(uintptr_t address, const d::Roots& roots);
} // namespace v8_debug_helper_internal
#endif
......@@ -15,6 +15,7 @@ group("v8_run_gcmole") {
"run-gcmole.py",
# The following contains all relevant source and build files.
"../debug_helper/debug-helper.h",
"../../BUILD.gn",
"../../base/",
"../../include/",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment