Reland "Add postmortem debugging helper library"

This is a reland of 517ab73fd7

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/+/1717090
Reviewed-by: Michael Achenbach <machenbach@chromium.org>
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#63008}
This commit is contained in:
Seth Brenith 2019-07-30 07:38:15 -07:00 committed by Commit Bot
parent df8e617772
commit 0921e8f28b
26 changed files with 1445 additions and 101 deletions

View File

@ -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",

View File

@ -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"
}

View File

@ -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;

View File

@ -0,0 +1,175 @@
// 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

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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, ...);

View File

@ -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",
]

View File

@ -1,5 +1,6 @@
include_rules = [
"+src",
"+tools",
"+torque-generated",
"+perfetto/tracing.h"
]

View File

@ -0,0 +1,192 @@
// 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

View File

@ -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]);
}

View File

@ -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",
]

104
tools/debug_helper/BUILD.gn Normal file
View File

@ -0,0 +1,104 @@
# 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" ]
}

3
tools/debug_helper/DEPS Normal file
View File

@ -0,0 +1,3 @@
include_rules = [
"+torque-generated"
]

View File

@ -0,0 +1,6 @@
# 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.

View File

@ -0,0 +1,51 @@
// 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

View File

@ -0,0 +1,125 @@
// 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

View File

@ -0,0 +1,177 @@
// 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

View File

@ -0,0 +1,63 @@
#!/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)

View File

@ -0,0 +1,331 @@
// 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 <sstream>
#include "debug-helper-internal.h"
#include "heap-constants.h"
#include "include/v8-internal.h"
#include "src/common/ptr-compr-inl.h"
#include "torque-generated/class-debug-readers-tq.h"
namespace i = v8::internal;
namespace v8_debug_helper_internal {
// INSTANCE_TYPE_CHECKERS_SINGLE_BASE, trimmed down to only classes that have
// layouts defined in .tq files (this subset relationship is asserted below).
// For now, this is a hand-maintained list.
// TODO(v8:7793): Torque should know enough about instance types to generate
// this list.
#define TQ_INSTANCE_TYPES_SINGLE_BASE(V) \
V(ByteArray, BYTE_ARRAY_TYPE) \
V(BytecodeArray, BYTECODE_ARRAY_TYPE) \
V(CallHandlerInfo, CALL_HANDLER_INFO_TYPE) \
V(Cell, CELL_TYPE) \
V(DescriptorArray, DESCRIPTOR_ARRAY_TYPE) \
V(EmbedderDataArray, EMBEDDER_DATA_ARRAY_TYPE) \
V(FeedbackCell, FEEDBACK_CELL_TYPE) \
V(FeedbackVector, FEEDBACK_VECTOR_TYPE) \
V(FixedDoubleArray, FIXED_DOUBLE_ARRAY_TYPE) \
V(Foreign, FOREIGN_TYPE) \
V(FreeSpace, FREE_SPACE_TYPE) \
V(HeapNumber, HEAP_NUMBER_TYPE) \
V(JSArgumentsObject, JS_ARGUMENTS_TYPE) \
V(JSArray, JS_ARRAY_TYPE) \
V(JSArrayBuffer, JS_ARRAY_BUFFER_TYPE) \
V(JSArrayIterator, JS_ARRAY_ITERATOR_TYPE) \
V(JSAsyncFromSyncIterator, JS_ASYNC_FROM_SYNC_ITERATOR_TYPE) \
V(JSAsyncFunctionObject, JS_ASYNC_FUNCTION_OBJECT_TYPE) \
V(JSAsyncGeneratorObject, JS_ASYNC_GENERATOR_OBJECT_TYPE) \
V(JSBoundFunction, JS_BOUND_FUNCTION_TYPE) \
V(JSDataView, JS_DATA_VIEW_TYPE) \
V(JSDate, JS_DATE_TYPE) \
V(JSFunction, JS_FUNCTION_TYPE) \
V(JSGlobalObject, JS_GLOBAL_OBJECT_TYPE) \
V(JSGlobalProxy, JS_GLOBAL_PROXY_TYPE) \
V(JSMap, JS_MAP_TYPE) \
V(JSMessageObject, JS_MESSAGE_OBJECT_TYPE) \
V(JSModuleNamespace, JS_MODULE_NAMESPACE_TYPE) \
V(JSPromise, JS_PROMISE_TYPE) \
V(JSProxy, JS_PROXY_TYPE) \
V(JSRegExp, JS_REGEXP_TYPE) \
V(JSRegExpStringIterator, JS_REGEXP_STRING_ITERATOR_TYPE) \
V(JSSet, JS_SET_TYPE) \
V(JSStringIterator, JS_STRING_ITERATOR_TYPE) \
V(JSTypedArray, JS_TYPED_ARRAY_TYPE) \
V(JSPrimitiveWrapper, JS_PRIMITIVE_WRAPPER_TYPE) \
V(JSFinalizationGroup, JS_FINALIZATION_GROUP_TYPE) \
V(JSFinalizationGroupCleanupIterator, \
JS_FINALIZATION_GROUP_CLEANUP_ITERATOR_TYPE) \
V(JSWeakMap, JS_WEAK_MAP_TYPE) \
V(JSWeakRef, JS_WEAK_REF_TYPE) \
V(JSWeakSet, JS_WEAK_SET_TYPE) \
V(Map, MAP_TYPE) \
V(Oddball, ODDBALL_TYPE) \
V(PreparseData, PREPARSE_DATA_TYPE) \
V(PropertyArray, PROPERTY_ARRAY_TYPE) \
V(PropertyCell, PROPERTY_CELL_TYPE) \
V(SharedFunctionInfo, SHARED_FUNCTION_INFO_TYPE) \
V(Symbol, SYMBOL_TYPE) \
V(WasmExceptionObject, WASM_EXCEPTION_TYPE) \
V(WasmGlobalObject, WASM_GLOBAL_TYPE) \
V(WasmMemoryObject, WASM_MEMORY_TYPE) \
V(WasmModuleObject, WASM_MODULE_TYPE) \
V(WasmTableObject, WASM_TABLE_TYPE) \
V(WeakArrayList, WEAK_ARRAY_LIST_TYPE) \
V(WeakCell, WEAK_CELL_TYPE)
#ifdef V8_INTL_SUPPORT
#define TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) \
TQ_INSTANCE_TYPES_SINGLE_BASE(V) \
V(JSV8BreakIterator, JS_INTL_V8_BREAK_ITERATOR_TYPE) \
V(JSCollator, JS_INTL_COLLATOR_TYPE) \
V(JSDateTimeFormat, JS_INTL_DATE_TIME_FORMAT_TYPE) \
V(JSListFormat, JS_INTL_LIST_FORMAT_TYPE) \
V(JSLocale, JS_INTL_LOCALE_TYPE) \
V(JSNumberFormat, JS_INTL_NUMBER_FORMAT_TYPE) \
V(JSPluralRules, JS_INTL_PLURAL_RULES_TYPE) \
V(JSRelativeTimeFormat, JS_INTL_RELATIVE_TIME_FORMAT_TYPE) \
V(JSSegmentIterator, JS_INTL_SEGMENT_ITERATOR_TYPE) \
V(JSSegmenter, JS_INTL_SEGMENTER_TYPE)
#else
#define TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) TQ_INSTANCE_TYPES_SINGLE_BASE(V)
#endif // V8_INTL_SUPPORT
enum class InstanceTypeCheckersSingle {
#define ENUM_VALUE(ClassName, INSTANCE_TYPE) k##ClassName = i::INSTANCE_TYPE,
INSTANCE_TYPE_CHECKERS_SINGLE(ENUM_VALUE)
#undef ENUM_VALUE
};
#define CHECK_VALUE(ClassName, INSTANCE_TYPE) \
static_assert( \
static_cast<i::InstanceType>( \
InstanceTypeCheckersSingle::k##ClassName) == i::INSTANCE_TYPE, \
"TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS must be subset of " \
"INSTANCE_TYPE_CHECKERS_SINGLE. Invalid class: " #ClassName);
TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(CHECK_VALUE)
#undef CHECK_VALUE
// Adapts one STRUCT_LIST_GENERATOR entry to (Name, NAME) format.
#define STRUCT_INSTANCE_TYPE_ADAPTER(V, NAME, Name, name) V(Name, NAME)
#define TQ_INSTANCE_TYPES_SINGLE(V) \
TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) \
STRUCT_LIST_GENERATOR(STRUCT_INSTANCE_TYPE_ADAPTER, V)
// Likewise, these are the subset of INSTANCE_TYPE_CHECKERS_RANGE that have
// definitions in .tq files, rearranged with more specific things first. Also
// includes JSObject and JSReceiver, which in the runtime are optimized to use
// a one-sided check.
#define TQ_INSTANCE_TYPES_RANGE(V) \
V(Context, FIRST_CONTEXT_TYPE, LAST_CONTEXT_TYPE) \
V(FixedArray, FIRST_FIXED_ARRAY_TYPE, LAST_FIXED_ARRAY_TYPE) \
V(Microtask, FIRST_MICROTASK_TYPE, LAST_MICROTASK_TYPE) \
V(String, FIRST_STRING_TYPE, LAST_STRING_TYPE) \
V(Name, FIRST_NAME_TYPE, LAST_NAME_TYPE) \
V(WeakFixedArray, FIRST_WEAK_FIXED_ARRAY_TYPE, LAST_WEAK_FIXED_ARRAY_TYPE) \
V(JSObject, FIRST_JS_OBJECT_TYPE, LAST_JS_OBJECT_TYPE) \
V(JSReceiver, FIRST_JS_RECEIVER_TYPE, LAST_JS_RECEIVER_TYPE)
std::string AppendAddressAndType(const std::string& brief, uintptr_t address,
const char* type) {
std::stringstream brief_stream;
brief_stream << "0x" << std::hex << address << " <" << type << ">";
return brief.empty() ? brief_stream.str()
: brief + " (" + brief_stream.str() + ")";
}
std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
uintptr_t address, d::MemoryAccessor accessor, Value<i::InstanceType> type,
const char* type_hint, std::string brief) {
std::vector<std::unique_ptr<ObjectProperty>> props;
std::string type_name;
d::TypeCheckResult type_check_result = d::TypeCheckResult::kUsedMap;
if (type.validity == d::MemoryAccessResult::kOk) {
// Dispatch to the appropriate method for each instance type. After calling
// the generated method to fetch properties, we can add custom properties.
switch (type.value) {
#define INSTANCE_TYPE_CASE(ClassName, INSTANCE_TYPE) \
case i::INSTANCE_TYPE: \
type_name = #ClassName; \
props = Tq##ClassName(address).GetProperties(accessor); \
break;
TQ_INSTANCE_TYPES_SINGLE(INSTANCE_TYPE_CASE)
#undef INSTANCE_TYPE_CASE
default:
#define INSTANCE_RANGE_CASE(ClassName, FIRST_TYPE, LAST_TYPE) \
if (type.value >= i::FIRST_TYPE && type.value <= i::LAST_TYPE) { \
type_name = #ClassName; \
props = Tq##ClassName(address).GetProperties(accessor); \
break; \
}
TQ_INSTANCE_TYPES_RANGE(INSTANCE_RANGE_CASE)
#undef INSTANCE_RANGE_CASE
type_check_result = d::TypeCheckResult::kUnknownInstanceType;
break;
}
} else if (type_hint != nullptr) {
// Try to use the provided type hint, since the real instance type is
// unavailable.
std::string type_hint_string(type_hint);
type_check_result = d::TypeCheckResult::kUsedTypeHint;
#define TYPE_NAME_CASE(ClassName, ...) \
if (type_hint_string == "v8::internal::" #ClassName) { \
type_name = #ClassName; \
props = Tq##ClassName(address).GetProperties(accessor); \
} else
TQ_INSTANCE_TYPES_SINGLE(TYPE_NAME_CASE)
TQ_INSTANCE_TYPES_RANGE(TYPE_NAME_CASE)
/*else*/ {
type_check_result = d::TypeCheckResult::kUnknownTypeHint;
}
#undef TYPE_NAME_CASE
} else {
// TODO(v8:9376): Use known maps here. If known map is just a guess (because
// root pointers weren't provided), then create a synthetic property with
// the more specific type. Then the caller could presumably ask us again
// with the type hint we provided. Otherwise, just go ahead and use it to
// generate properties.
type_check_result =
type.validity == d::MemoryAccessResult::kAddressNotValid
? d::TypeCheckResult::kMapPointerInvalid
: d::TypeCheckResult::kMapPointerValidButInaccessible;
}
if (type_name.empty()) {
type_name = "Object";
}
// TODO(v8:9376): Many object types need additional data that is not included
// in their Torque layout definitions. For example, JSObject has an array of
// in-object properties after its Torque-defined fields, which at a minimum
// should be represented as an array in this response. If the relevant memory
// is available, we should instead represent those properties (and any out-of-
// object properties) using their JavaScript property names.
brief = AppendAddressAndType(brief, address, type_name.c_str());
return v8::base::make_unique<ObjectPropertiesResult>(
type_check_result, brief, "v8::internal::" + type_name, std::move(props));
}
#undef STRUCT_INSTANCE_TYPE_ADAPTER
#undef TQ_INSTANCE_TYPES_SINGLE_BASE
#undef TQ_INSTANCE_TYPES_SINGLE
#undef TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS
#undef TQ_INSTANCE_TYPES_RANGE
std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
uintptr_t address, d::MemoryAccessor memory_accessor, const d::Roots& roots,
const char* type_hint) {
// Try to figure out the heap range, for pointer compression (this is unused
// if pointer compression is disabled).
uintptr_t any_uncompressed_ptr = 0;
if (!IsPointerCompressed(address)) any_uncompressed_ptr = address;
if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.any_heap_pointer;
if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.map_space;
if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.old_space;
if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.read_only_space;
if (any_uncompressed_ptr == 0) {
// We can't figure out the heap range. Just check for known objects.
std::string brief = FindKnownObject(address, roots);
brief = AppendAddressAndType(brief, address, "v8::internal::TaggedValue");
return v8::base::make_unique<ObjectPropertiesResult>(
d::TypeCheckResult::kUnableToDecompress, brief,
"v8::internal::TaggedValue",
std::vector<std::unique_ptr<ObjectProperty>>());
}
// TODO(v8:9376): It seems that the space roots are at predictable offsets
// within the heap reservation block when pointer compression is enabled, so
// we should be able to set those here.
address = Decompress(address, any_uncompressed_ptr);
// From here on all addresses should be decompressed.
// Regardless of whether we can read the object itself, maybe we can find its
// pointer in the list of known objects.
std::string brief = FindKnownObject(address, roots);
TqHeapObject heap_object(address);
Value<uintptr_t> map_ptr = heap_object.GetMapValue(memory_accessor);
if (map_ptr.validity != d::MemoryAccessResult::kOk) {
brief = AppendAddressAndType(brief, address, "v8::internal::Object");
return v8::base::make_unique<ObjectPropertiesResult>(
map_ptr.validity == d::MemoryAccessResult::kAddressNotValid
? d::TypeCheckResult::kObjectPointerInvalid
: d::TypeCheckResult::kObjectPointerValidButInaccessible,
brief, "v8::internal::Object",
std::vector<std::unique_ptr<ObjectProperty>>());
}
Value<i::InstanceType> instance_type =
TqMap(map_ptr.value).GetInstanceTypeValue(memory_accessor);
return GetHeapObjectProperties(address, memory_accessor, instance_type,
type_hint, brief);
}
std::unique_ptr<ObjectPropertiesResult> GetObjectPropertiesImpl(
uintptr_t address, d::MemoryAccessor memory_accessor, const d::Roots& roots,
const char* type_hint) {
std::vector<std::unique_ptr<ObjectProperty>> props;
if (static_cast<uint32_t>(address) == i::kClearedWeakHeapObjectLower32) {
return v8::base::make_unique<ObjectPropertiesResult>(
d::TypeCheckResult::kWeakRef, "cleared weak ref",
"v8::internal::HeapObject", std::move(props));
}
bool is_weak = (address & i::kHeapObjectTagMask) == i::kWeakHeapObjectTag;
if (is_weak) {
address &= ~i::kWeakHeapObjectMask;
}
if (i::Internals::HasHeapObjectTag(address)) {
std::unique_ptr<ObjectPropertiesResult> result =
GetHeapObjectProperties(address, memory_accessor, roots, type_hint);
if (is_weak) {
result->Prepend("weak ref to ");
}
return result;
}
// For smi values, construct a response with a description representing the
// untagged value.
int32_t value = i::PlatformSmiTagging::SmiToInt(address);
std::stringstream stream;
stream << value << " (0x" << std::hex << value << ")";
return v8::base::make_unique<ObjectPropertiesResult>(
d::TypeCheckResult::kSmi, stream.str(), "v8::internal::Smi",
std::move(props));
}
} // namespace v8_debug_helper_internal
namespace di = v8_debug_helper_internal;
extern "C" {
V8_DEBUG_HELPER_EXPORT d::ObjectPropertiesResult*
_v8_debug_helper_GetObjectProperties(uintptr_t object,
d::MemoryAccessor memory_accessor,
const d::Roots& heap_roots,
const char* type_hint) {
return di::GetObjectPropertiesImpl(object, memory_accessor, heap_roots,
type_hint)
.release()
->GetPublicView();
}
V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_ObjectPropertiesResult(
d::ObjectPropertiesResult* result) {
std::unique_ptr<di::ObjectPropertiesResult> ptr(
static_cast<di::ObjectPropertiesResultExtended*>(result)->base);
}
}

View File

@ -0,0 +1,51 @@
// 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

View File

@ -0,0 +1,28 @@
// 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

View File

@ -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/",