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:
parent
df8e617772
commit
0921e8f28b
13
BUILD.gn
13
BUILD.gn
@ -192,15 +192,6 @@ declare_args() {
|
|||||||
v8_enable_single_generation = false
|
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.
|
# Derived defaults.
|
||||||
if (v8_enable_verify_heap == "") {
|
if (v8_enable_verify_heap == "") {
|
||||||
v8_enable_verify_heap = v8_enable_debugging_features
|
v8_enable_verify_heap = v8_enable_debugging_features
|
||||||
@ -1009,6 +1000,7 @@ if (!v8_enable_i18n_support) {
|
|||||||
action("run_torque") {
|
action("run_torque") {
|
||||||
visibility = [
|
visibility = [
|
||||||
":*",
|
":*",
|
||||||
|
"tools/debug_helper/:*",
|
||||||
"tools/gcmole/:*",
|
"tools/gcmole/:*",
|
||||||
"test/cctest/:*",
|
"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.cc",
|
||||||
"$target_gen_dir/torque-generated/class-definitions-tq-inl.h",
|
"$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-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.cc",
|
||||||
"$target_gen_dir/torque-generated/exported-macros-assembler-tq.h",
|
"$target_gen_dir/torque-generated/exported-macros-assembler-tq.h",
|
||||||
"$target_gen_dir/torque-generated/csa-types-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/ast.h",
|
||||||
"src/torque/cfg.cc",
|
"src/torque/cfg.cc",
|
||||||
"src/torque/cfg.h",
|
"src/torque/cfg.h",
|
||||||
|
"src/torque/class-debug-reader-generator.cc",
|
||||||
"src/torque/constants.h",
|
"src/torque/constants.h",
|
||||||
"src/torque/contextual.h",
|
"src/torque/contextual.h",
|
||||||
"src/torque/csa-generator.cc",
|
"src/torque/csa-generator.cc",
|
||||||
|
@ -107,3 +107,12 @@ if (v8_snapshot_toolchain == "") {
|
|||||||
assert(v8_snapshot_toolchain != "",
|
assert(v8_snapshot_toolchain != "",
|
||||||
"Do not know how to build a snapshot for $current_toolchain " +
|
"Do not know how to build a snapshot for $current_toolchain " +
|
||||||
"on $host_os $host_cpu")
|
"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
|
type TransitionArray extends WeakFixedArray
|
||||||
generates 'TNode<TransitionArray>';
|
generates 'TNode<TransitionArray>';
|
||||||
|
|
||||||
type InstanceType extends uint16 constexpr 'InstanceType';
|
type InstanceType extends uint16 constexpr 'v8::internal::InstanceType';
|
||||||
|
|
||||||
extern class Map extends HeapObject {
|
extern class Map extends HeapObject {
|
||||||
instance_size_in_words: uint8;
|
instance_size_in_words: uint8;
|
||||||
|
175
src/torque/class-debug-reader-generator.cc
Normal file
175
src/torque/class-debug-reader-generator.cc
Normal 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
|
@ -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(
|
void ImplementationVisitor::GenerateBuiltinDefinitions(
|
||||||
const std::string& output_directory) {
|
const std::string& output_directory) {
|
||||||
std::stringstream new_contents_stream;
|
std::stringstream new_contents_stream;
|
||||||
|
@ -357,6 +357,7 @@ class ImplementationVisitor {
|
|||||||
void GenerateClassDefinitions(const std::string& output_directory);
|
void GenerateClassDefinitions(const std::string& output_directory);
|
||||||
void GenerateInstanceTypes(const std::string& output_directory);
|
void GenerateInstanceTypes(const std::string& output_directory);
|
||||||
void GenerateClassVerifiers(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 GenerateExportedMacrosAssembler(const std::string& output_directory);
|
||||||
void GenerateCSATypes(const std::string& output_directory);
|
void GenerateCSATypes(const std::string& output_directory);
|
||||||
void GenerateCppForInternalClasses(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.GeneratePrintDefinitions(output_directory);
|
||||||
implementation_visitor.GenerateClassDefinitions(output_directory);
|
implementation_visitor.GenerateClassDefinitions(output_directory);
|
||||||
implementation_visitor.GenerateClassVerifiers(output_directory);
|
implementation_visitor.GenerateClassVerifiers(output_directory);
|
||||||
|
implementation_visitor.GenerateClassDebugReaders(output_directory);
|
||||||
implementation_visitor.GenerateExportedMacrosAssembler(output_directory);
|
implementation_visitor.GenerateExportedMacrosAssembler(output_directory);
|
||||||
implementation_visitor.GenerateCSATypes(output_directory);
|
implementation_visitor.GenerateCSATypes(output_directory);
|
||||||
implementation_visitor.GenerateInstanceTypes(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 torque
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace v8
|
} // 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;
|
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 torque
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
@ -898,7 +898,8 @@ class BailoutId {
|
|||||||
|
|
||||||
// Our version of printf().
|
// Our version of printf().
|
||||||
V8_EXPORT_PRIVATE void PRINTF_FORMAT(1, 2) PrintF(const char* format, ...);
|
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.
|
// Prepends the current process ID to the output.
|
||||||
void PRINTF_FORMAT(1, 2) PrintPID(const char* format, ...);
|
void PRINTF_FORMAT(1, 2) PrintPID(const char* format, ...);
|
||||||
|
@ -190,6 +190,7 @@ v8_source_set("cctest_sources") {
|
|||||||
"test-conversions.cc",
|
"test-conversions.cc",
|
||||||
"test-cpu-profiler.cc",
|
"test-cpu-profiler.cc",
|
||||||
"test-date.cc",
|
"test-date.cc",
|
||||||
|
"test-debug-helper.cc",
|
||||||
"test-debug.cc",
|
"test-debug.cc",
|
||||||
"test-decls.cc",
|
"test-decls.cc",
|
||||||
"test-deoptimization.cc",
|
"test-deoptimization.cc",
|
||||||
@ -389,6 +390,7 @@ v8_source_set("cctest_sources") {
|
|||||||
"../..:v8_libbase",
|
"../..:v8_libbase",
|
||||||
"../..:v8_libplatform",
|
"../..:v8_libplatform",
|
||||||
"../..:wasm_module_runner",
|
"../..:wasm_module_runner",
|
||||||
|
"../../tools/debug_helper:v8_debug_helper",
|
||||||
"//build/win:default_exe_manifest",
|
"//build/win:default_exe_manifest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
include_rules = [
|
include_rules = [
|
||||||
"+src",
|
"+src",
|
||||||
|
"+tools",
|
||||||
"+torque-generated",
|
"+torque-generated",
|
||||||
"+perfetto/tracing.h"
|
"+perfetto/tracing.h"
|
||||||
]
|
]
|
||||||
|
192
test/cctest/test-debug-helper.cc
Normal file
192
test/cctest/test-debug-helper.cc
Normal 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
|
@ -42,7 +42,7 @@ class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
|
|||||||
void Free(void* p, size_t) override {}
|
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) {
|
i::HeapObject object) {
|
||||||
#define RO_ROOT_LIST_CASE(type, name, CamelName) \
|
#define RO_ROOT_LIST_CASE(type, name, CamelName) \
|
||||||
if (root_name == nullptr && object == roots.name()) root_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)
|
MUTABLE_ROOT_LIST(MUTABLE_ROOT_LIST_CASE)
|
||||||
|
|
||||||
if (root_name == nullptr) return;
|
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);
|
root_ptr, map.instance_type(), root_name);
|
||||||
|
|
||||||
#undef MUTABLE_ROOT_LIST_CASE
|
#undef MUTABLE_ROOT_LIST_CASE
|
||||||
#undef RO_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) {
|
i::HeapObject object) {
|
||||||
#define RO_ROOT_LIST_CASE(type, name, CamelName) \
|
#define RO_ROOT_LIST_CASE(type, name, CamelName) \
|
||||||
if (root_name == nullptr && object == roots.name()) { \
|
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 (root_name == nullptr) return;
|
||||||
if (!i::RootsTable::IsImmortalImmovable(root_index)) return;
|
if (!i::RootsTable::IsImmortalImmovable(root_index)) return;
|
||||||
|
|
||||||
i::PrintF(" (\"%s\", 0x%05" V8PRIxPTR "): \"%s\",\n", space_name, root_ptr,
|
i::PrintF(out, " (\"%s\", 0x%05" V8PRIxPTR "): \"%s\",\n", space_name,
|
||||||
root_name);
|
root_ptr, root_name);
|
||||||
|
|
||||||
#undef ROOT_LIST_CASE
|
#undef ROOT_LIST_CASE
|
||||||
#undef RO_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.
|
// Start up V8.
|
||||||
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
|
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
|
||||||
v8::V8::InitializePlatform(platform.get());
|
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::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
|
||||||
i::ReadOnlyHeap* read_only_heap =
|
i::ReadOnlyHeap* read_only_heap =
|
||||||
reinterpret_cast<i::Isolate*>(isolate)->read_only_heap();
|
reinterpret_cast<i::Isolate*>(isolate)->read_only_heap();
|
||||||
i::PrintF("%s", kHeader);
|
i::PrintF(out, "%s", kHeader);
|
||||||
#define DUMP_TYPE(T) i::PrintF(" %d: \"%s\",\n", i::T, #T);
|
#define DUMP_TYPE(T) i::PrintF(out, " %d: \"%s\",\n", i::T, #T);
|
||||||
i::PrintF("INSTANCE_TYPES = {\n");
|
i::PrintF(out, "INSTANCE_TYPES = {\n");
|
||||||
INSTANCE_TYPE_LIST(DUMP_TYPE)
|
INSTANCE_TYPE_LIST(DUMP_TYPE)
|
||||||
i::PrintF("}\n");
|
i::PrintF(out, "}\n");
|
||||||
#undef DUMP_TYPE
|
#undef DUMP_TYPE
|
||||||
|
|
||||||
{
|
{
|
||||||
// Dump the KNOWN_MAP table to the console.
|
// Dump the KNOWN_MAP table to the console.
|
||||||
i::PrintF("\n# List of known V8 maps.\n");
|
i::PrintF(out, "\n# List of known V8 maps.\n");
|
||||||
i::PrintF("KNOWN_MAPS = {\n");
|
i::PrintF(out, "KNOWN_MAPS = {\n");
|
||||||
i::ReadOnlyHeapObjectIterator ro_iterator(read_only_heap);
|
i::ReadOnlyHeapObjectIterator ro_iterator(read_only_heap);
|
||||||
for (i::HeapObject object = ro_iterator.Next(); !object.is_null();
|
for (i::HeapObject object = ro_iterator.Next(); !object.is_null();
|
||||||
object = ro_iterator.Next()) {
|
object = ro_iterator.Next()) {
|
||||||
if (!object.IsMap()) continue;
|
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());
|
i::PagedSpaceObjectIterator iterator(heap->map_space());
|
||||||
for (i::HeapObject object = iterator.Next(); !object.is_null();
|
for (i::HeapObject object = iterator.Next(); !object.is_null();
|
||||||
object = iterator.Next()) {
|
object = iterator.Next()) {
|
||||||
if (!object.IsMap()) continue;
|
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.
|
// Dump the KNOWN_OBJECTS table to the console.
|
||||||
i::PrintF("\n# List of known V8 objects.\n");
|
i::PrintF(out, "\n# List of known V8 objects.\n");
|
||||||
i::PrintF("KNOWN_OBJECTS = {\n");
|
i::PrintF(out, "KNOWN_OBJECTS = {\n");
|
||||||
i::ReadOnlyHeapObjectIterator ro_iterator(read_only_heap);
|
i::ReadOnlyHeapObjectIterator ro_iterator(read_only_heap);
|
||||||
for (i::HeapObject object = ro_iterator.Next(); !object.is_null();
|
for (i::HeapObject object = ro_iterator.Next(); !object.is_null();
|
||||||
object = ro_iterator.Next()) {
|
object = ro_iterator.Next()) {
|
||||||
// Skip read-only heap maps, they will be reported elsewhere.
|
// Skip read-only heap maps, they will be reported elsewhere.
|
||||||
if (object.IsMap()) continue;
|
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);
|
i::PagedSpaceIterator spit(heap);
|
||||||
@ -158,22 +158,22 @@ static int DumpHeapConstants(const char* argv0) {
|
|||||||
continue;
|
continue;
|
||||||
const char* sname = s->name();
|
const char* sname = s->name();
|
||||||
for (i::HeapObject o = it.Next(); !o.is_null(); o = it.Next()) {
|
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
|
// Dump frame markers
|
||||||
i::PrintF("\n# List of known V8 Frame Markers.\n");
|
i::PrintF(out, "\n# List of known V8 Frame Markers.\n");
|
||||||
#define DUMP_MARKER(T, class) i::PrintF(" \"%s\",\n", #T);
|
#define DUMP_MARKER(T, class) i::PrintF(out, " \"%s\",\n", #T);
|
||||||
i::PrintF("FRAME_MARKERS = (\n");
|
i::PrintF(out, "FRAME_MARKERS = (\n");
|
||||||
STACK_FRAME_TYPE_LIST(DUMP_MARKER)
|
STACK_FRAME_TYPE_LIST(DUMP_MARKER)
|
||||||
i::PrintF(")\n");
|
i::PrintF(out, ")\n");
|
||||||
#undef DUMP_MARKER
|
#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);
|
kBuild);
|
||||||
|
|
||||||
// Teardown.
|
// Teardown.
|
||||||
@ -184,4 +184,10 @@ static int DumpHeapConstants(const char* argv0) {
|
|||||||
|
|
||||||
} // namespace v8
|
} // 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 = [
|
data_deps = [
|
||||||
":v8_check_static_initializers",
|
":v8_check_static_initializers",
|
||||||
|
"debug_helper:v8_debug_helper",
|
||||||
"gcmole:v8_run_gcmole",
|
"gcmole:v8_run_gcmole",
|
||||||
"jsfunfuzz:v8_jsfunfuzz",
|
"jsfunfuzz:v8_jsfunfuzz",
|
||||||
]
|
]
|
||||||
|
104
tools/debug_helper/BUILD.gn
Normal file
104
tools/debug_helper/BUILD.gn
Normal 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
3
tools/debug_helper/DEPS
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
include_rules = [
|
||||||
|
"+torque-generated"
|
||||||
|
]
|
6
tools/debug_helper/README.md
Normal file
6
tools/debug_helper/README.md
Normal 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.
|
51
tools/debug_helper/debug-helper-internal.cc
Normal file
51
tools/debug_helper/debug-helper-internal.cc
Normal 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
|
125
tools/debug_helper/debug-helper-internal.h
Normal file
125
tools/debug_helper/debug-helper-internal.h
Normal 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
|
177
tools/debug_helper/debug-helper.h
Normal file
177
tools/debug_helper/debug-helper.h
Normal 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
|
63
tools/debug_helper/gen-heap-constants.py
Normal file
63
tools/debug_helper/gen-heap-constants.py
Normal 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)
|
331
tools/debug_helper/get-object-properties.cc
Normal file
331
tools/debug_helper/get-object-properties.cc
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
51
tools/debug_helper/heap-constants.cc
Normal file
51
tools/debug_helper/heap-constants.cc
Normal 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
|
28
tools/debug_helper/heap-constants.h
Normal file
28
tools/debug_helper/heap-constants.h
Normal 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
|
@ -15,6 +15,7 @@ group("v8_run_gcmole") {
|
|||||||
"run-gcmole.py",
|
"run-gcmole.py",
|
||||||
|
|
||||||
# The following contains all relevant source and build files.
|
# The following contains all relevant source and build files.
|
||||||
|
"../debug_helper/debug-helper.h",
|
||||||
"../../BUILD.gn",
|
"../../BUILD.gn",
|
||||||
"../../base/",
|
"../../base/",
|
||||||
"../../include/",
|
"../../include/",
|
||||||
|
Loading…
Reference in New Issue
Block a user