Commit 0921e8f2 authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

Reland "Add postmortem debugging helper library"

This is a reland of 517ab73f

Updates since original: now compressed pointers passed to the function
GetObjectProperties are required to be sign-extended. Previously, the
function allowed zero-extended values, but that led to ambiguity on
pointers like 0x88044919: is it compressed or is the heap range actually
centered on 0x100000000?

Original change's description:
> 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: Yang Guo <yangguo@chromium.org>
> Reviewed-by: Michael Stanton <mvstanton@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#62882}

Bug: v8:9376
Change-Id: I866a1cc9d4c34bfe10c7b98462451fe69763cf3f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1717090Reviewed-by: 's avatarMichael Achenbach <machenbach@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#63008}
parent df8e6177
......@@ -192,15 +192,6 @@ declare_args() {
v8_enable_single_generation = false
}
# 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
......@@ -1009,6 +1000,7 @@ if (!v8_enable_i18n_support) {
action("run_torque") {
visibility = [
":*",
"tools/debug_helper/:*",
"tools/gcmole/:*",
"test/cctest/:*",
]
......@@ -1030,6 +1022,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",
......@@ -3338,6 +3332,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;
......
// 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
......@@ -2691,70 +2691,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;
......
......@@ -357,6 +357,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
......
......@@ -898,7 +898,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, ...);
......
......@@ -190,6 +190,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",
......@@ -389,6 +390,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 signed_address = static_cast<intptr_t>(address);
return signed_address >= INT32_MIN && signed_address <= INT32_MAX;
#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, which could be:
// - A tagged pointer, strong or weak
// - A cleared weak pointer
// - A compressed tagged pointer, sign-extended to 64 bits
// - A tagged small integer
// 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