Commit 1d3c4975 authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

[tools] Use instance types of known Maps in v8_debug_helper

If we can read an object's Map pointer but not any data from the Map
itself, we may still be able to accurately describe the object's type if
the Map pointer matches one of the known Maps from the snapshot.
GetObjectProperties uses that data in one of two ways:
- If it is sure that the Map pointer matches a known Map, then it uses
  the type from that Map and continues as if it read the type normally.
- If the Map pointer is at the right offset within a heap page to match
  a known Map, but the caller didn't provide the addresses of the first
  pages in Map space or read-only space, then the type of that Map is
  just a guess and gets returned in a separate array. This gives the
  caller the opportunity to present guessed types to the user, and
  perhaps call again using the guessed type as the type hint.

Bug: v8:9376
Change-Id: I187f67b77e76699863a14534a9d635b79f654124
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1787986
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarMichael Achenbach <machenbach@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63908}
parent 0336a33c
......@@ -213,9 +213,17 @@ template("v8_executable") {
template("v8_component") {
component(target_name) {
forward_variables_from(invoker, "*", [ "configs" ])
forward_variables_from(invoker,
"*",
[
"configs",
"remove_configs",
])
configs -= v8_remove_configs
configs += v8_add_configs
if (defined(invoker.remove_configs)) {
configs -= invoker.remove_configs
}
configs += invoker.configs
}
}
......
......@@ -10,6 +10,14 @@ namespace v8 {
namespace internal {
namespace torque {
const char* tq_object_override_decls =
R"( std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
d::MemoryAccessor accessor) const override;
const char* GetName() const override;
void Visit(TqObjectVisitor* visitor) const override;
bool IsSuperclassOf(const TqObject* other) const override;
)";
namespace {
void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
std::ostream& cc_contents, std::ostream& visitor,
......@@ -32,10 +40,7 @@ void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
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) const override;\n";
h_contents << " const char* GetName() const override;\n";
h_contents << " void Visit(TqObjectVisitor* visitor) const override;\n";
h_contents << tq_object_override_decls;
cc_contents << "\nconst char* Tq" << name << "::GetName() const {\n";
cc_contents << " return \"v8::internal::" << name << "\";\n";
......@@ -46,6 +51,13 @@ void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
cc_contents << " visitor->Visit" << name << "(this);\n";
cc_contents << "}\n";
cc_contents << "\nbool Tq" << name
<< "::IsSuperclassOf(const TqObject* other) const {\n";
cc_contents
<< " return GetName() != other->GetName() && dynamic_cast<const Tq"
<< name << "*>(other) != nullptr;\n";
cc_contents << "}\n";
visitor << " virtual void Visit" << name << "(const Tq" << name
<< "* object) {\n";
visitor << " Visit" << super_name << "(object);\n";
......@@ -157,7 +169,8 @@ void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
<< address_getter << "()" << index_offset
<< ", reinterpret_cast<uint8_t*>(&value), sizeof(value));\n";
cc_contents << " return {validity, "
<< (is_field_tagged ? "Decompress(value, address_)" : "value")
<< (is_field_tagged ? "EnsureDecompressed(value, address_)"
: "value")
<< "};\n";
cc_contents << "}\n";
}
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "src/api/api-inl.h"
#include "src/flags/flags.h"
#include "src/heap/spaces.h"
#include "test/cctest/cctest.h"
#include "tools/debug_helper/debug-helper.h"
......@@ -123,8 +124,9 @@ TEST(GetObjectProperties) {
// object is on the right page. This response can only happen in builds
// without pointer compression, because otherwise heap addresses would be at
// deterministic locations within the heap reservation.
CHECK(StartsWith(props->brief, "maybe EmptyFixedArray") ||
StartsWith(props->brief, "EmptyFixedArray"));
CHECK(COMPRESS_POINTERS_BOOL
? StartsWith(props->brief, "EmptyFixedArray")
: StartsWith(props->brief, "maybe EmptyFixedArray"));
// Provide a heap first page so the API can be more sure.
heap_addresses.read_only_space_first_page =
......@@ -153,8 +155,8 @@ TEST(GetObjectProperties) {
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));
i::Tagged_t second_string_address =
reinterpret_cast<i::Tagged_t*>(props->properties[2]->address)[1];
props = d::GetObjectProperties(second_string_address, &ReadMemory,
heap_addresses);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
......@@ -174,6 +176,7 @@ TEST(GetObjectProperties) {
// its properties should match what we read last time.
d::ObjectPropertiesResultPtr props2;
{
heap_addresses.read_only_space_first_page = 0;
uintptr_t map_address =
d::GetObjectProperties(
*reinterpret_cast<i::Tagged_t*>(props->properties[0]->address),
......@@ -183,9 +186,28 @@ TEST(GetObjectProperties) {
MemoryFailureRegion failure(map_address, map_address + i::Map::kSize);
props2 = d::GetObjectProperties(second_string_address, &ReadMemory,
heap_addresses, "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);
if (COMPRESS_POINTERS_BOOL) {
// The first page of each heap space can be automatically detected when
// pointer compression is active, so we expect to use known maps instead
// of the type hint.
CHECK_EQ(props2->type_check_result, d::TypeCheckResult::kKnownMapPointer);
CHECK(props2->type == std::string("v8::internal::SeqOneByteString"));
CHECK_EQ(props2->num_properties, 4);
CheckProp(*props2->properties[3], "char", "chars",
d::PropertyKind::kArrayOfKnownSize, 2);
CHECK_EQ(props2->num_guessed_types, 0);
} else {
CHECK_EQ(props2->type_check_result, d::TypeCheckResult::kUsedTypeHint);
CHECK(props2->type == std::string("v8::internal::String"));
CHECK_EQ(props2->num_properties, 3);
// The type hint we provided was the abstract class String, but
// GetObjectProperties should have recognized that the Map pointer looked
// like the right value for a SeqOneByteString.
CHECK_EQ(props2->num_guessed_types, 1);
CHECK(std::string(props2->guessed_types[0]) ==
std::string("v8::internal::SeqOneByteString"));
}
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",
......
......@@ -100,5 +100,8 @@ v8_component("v8_debug_helper") {
configs += [ "//third_party/icu:icu_config" ]
}
remove_configs = [ "//build/config/compiler:no_rtti" ]
configs += [ "//build/config/compiler:rtti" ]
public_configs = [ ":external_config" ]
}
......@@ -20,7 +20,8 @@ bool IsPointerCompressed(uintptr_t address) {
#endif
}
uintptr_t Decompress(uintptr_t address, uintptr_t any_uncompressed_ptr) {
uintptr_t EnsureDecompressed(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));
......@@ -55,4 +56,8 @@ void TqObject::Visit(TqObjectVisitor* visitor) const {
visitor->VisitObject(this);
}
bool TqObject::IsSuperclassOf(const TqObject* other) const {
return GetName() != other->GetName();
}
} // namespace v8_debug_helper_internal
......@@ -28,6 +28,7 @@ struct Value {
TValue value;
};
// Internal version of API class v8::debug_helper::ObjectProperty.
class ObjectProperty {
public:
inline ObjectProperty(std::string name, std::string type,
......@@ -69,15 +70,20 @@ struct ObjectPropertiesResultExtended : public d::ObjectPropertiesResult {
ObjectPropertiesResultInternal* base; // Back reference for cleanup
};
// Internal version of API class v8::debug_helper::ObjectPropertiesResult.
class ObjectPropertiesResult {
public:
inline ObjectPropertiesResult(
ObjectPropertiesResult(d::TypeCheckResult type_check_result,
std::string brief, std::string type)
: type_check_result_(type_check_result), brief_(brief), type_(type) {}
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)) {}
std::vector<std::unique_ptr<ObjectProperty>> properties,
std::vector<std::string> guessed_types)
: ObjectPropertiesResult(type_check_result, brief, type) {
properties_ = std::move(properties);
guessed_types_ = std::move(guessed_types);
}
inline void Prepend(const char* prefix) { brief_ = prefix + brief_; }
......@@ -86,11 +92,17 @@ class ObjectPropertiesResult {
public_view_.brief = brief_.c_str();
public_view_.type = type_.c_str();
public_view_.num_properties = properties_.size();
properties_raw_.resize(0);
properties_raw_.clear();
for (const auto& property : properties_) {
properties_raw_.push_back(property->GetPublicView());
}
public_view_.properties = properties_raw_.data();
public_view_.num_guessed_types = guessed_types_.size();
guessed_types_raw_.clear();
for (const auto& guess : guessed_types_) {
guessed_types_raw_.push_back(guess.c_str());
}
public_view_.guessed_types = guessed_types_raw_.data();
public_view_.base = this;
return &public_view_;
}
......@@ -100,9 +112,11 @@ class ObjectPropertiesResult {
std::string brief_;
std::string type_;
std::vector<std::unique_ptr<ObjectProperty>> properties_;
std::vector<std::string> guessed_types_;
ObjectPropertiesResultExtended public_view_;
std::vector<d::ObjectProperty*> properties_raw_;
std::vector<const char*> guessed_types_raw_;
};
class TqObjectVisitor;
......@@ -117,13 +131,24 @@ class TqObject {
d::MemoryAccessor accessor) const;
virtual const char* GetName() const;
virtual void Visit(TqObjectVisitor* visitor) const;
virtual bool IsSuperclassOf(const TqObject* other) const;
protected:
uintptr_t address_;
};
// In ptr-compr builds, returns whether the address looks like a compressed
// pointer (sign-extended from 32 bits). Otherwise returns false because no
// pointers can be compressed.
bool IsPointerCompressed(uintptr_t address);
uintptr_t Decompress(uintptr_t address, uintptr_t any_uncompressed_address);
// If the given address looks like a compressed pointer, returns a decompressed
// representation of it. Otherwise returns the address unmodified.
uintptr_t EnsureDecompressed(uintptr_t address,
uintptr_t any_uncompressed_address);
// Converts the MemoryAccessResult from attempting to read an array's length
// into the corresponding PropertyKind for the array.
d::PropertyKind GetArrayKind(d::MemoryAccessResult mem_result);
} // namespace v8_debug_helper_internal
......
......@@ -46,6 +46,7 @@ enum class TypeCheckResult {
kSmi,
kWeakRef,
kUsedMap,
kKnownMapPointer,
kUsedTypeHint,
// Failure cases:
......@@ -98,6 +99,16 @@ struct ObjectPropertiesResult {
const char* type; // Runtime type of the object.
size_t num_properties;
ObjectProperty** properties;
// If not all relevant memory is available, GetObjectProperties may respond
// with a technically correct but uninteresting type such as HeapObject, and
// use other heuristics to make reasonable guesses about what specific type
// the object actually is. You may request data about the same object again
// using any of these guesses as the type hint, but the results should be
// formatted to the user in a way that clearly indicates that they're only
// guesses.
size_t num_guessed_types;
const char** guessed_types;
};
// Copies byte_count bytes of memory from the given address in the debuggee to
......
This diff is collapsed.
......@@ -49,4 +49,37 @@ std::string FindKnownObject(uintptr_t address,
return result;
}
KnownInstanceType FindKnownMapInstanceType(
uintptr_t address, const d::HeapAddresses& heap_addresses) {
uintptr_t containing_page = address & ~i::kPageAlignmentMask;
uintptr_t offset_in_page = address & i::kPageAlignmentMask;
// If there's a match with a known page, then search only that page.
if (containing_page == heap_addresses.map_space_first_page) {
return KnownInstanceType(
FindKnownMapInstanceTypeInMapSpace(offset_in_page));
}
if (containing_page == heap_addresses.read_only_space_first_page) {
return KnownInstanceType(
FindKnownMapInstanceTypeInReadOnlySpace(offset_in_page));
}
// For any unknown pages, compile a list of things this object might be.
KnownInstanceType result;
if (heap_addresses.map_space_first_page == 0) {
int sub_result = FindKnownMapInstanceTypeInMapSpace(offset_in_page);
if (sub_result >= 0) {
result.types.push_back(static_cast<i::InstanceType>(sub_result));
}
}
if (heap_addresses.read_only_space_first_page == 0) {
int sub_result = FindKnownMapInstanceTypeInReadOnlySpace(offset_in_page);
if (sub_result >= 0) {
result.types.push_back(static_cast<i::InstanceType>(sub_result));
}
}
return result;
}
} // namespace v8_debug_helper_internal
......@@ -7,25 +7,61 @@
#include <cstdint>
#include <string>
#include <vector>
#include "debug-helper.h"
#include "src/objects/instance-type.h"
namespace d = v8::debug_helper;
namespace v8_debug_helper_internal {
// Functions generated by gen-heap-constants.py, based on data from mkgrokdump:
// ===== Functions generated by gen-heap-constants.py: =========================
// Returns the name of a known object, given its offset within the first page of
// the space, or empty string on failure.
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);
// In builds with pointer compression enabled, sets the *_first_page members in
// the HeapAddresses object. In other builds, does nothing.
void FillInUnknownHeapAddresses(d::HeapAddresses* heap_addresses,
uintptr_t any_uncompressed_ptr);
// Returns the instance type for the known Map, given its offset within the
// first page of the space, or empty string on failure.
int FindKnownMapInstanceTypeInMapSpace(uintptr_t offset);
int FindKnownMapInstanceTypeInReadOnlySpace(uintptr_t offset);
// ===== End of generated functions. ===========================================
// Returns a descriptive string if the given address matches a known object, or
// an empty string otherwise.
std::string FindKnownObject(uintptr_t address,
const d::HeapAddresses& heap_addresses);
struct KnownInstanceType {
enum class Confidence {
kLow,
kHigh,
};
KnownInstanceType() : confidence(Confidence::kLow) {}
KnownInstanceType(int type) : KnownInstanceType() {
if (type >= 0) {
confidence = Confidence::kHigh;
types.push_back(static_cast<v8::internal::InstanceType>(type));
}
}
Confidence confidence;
std::vector<v8::internal::InstanceType> types;
};
// Returns information about the instance type of the Map at the given address,
// based on the list of known Maps.
KnownInstanceType FindKnownMapInstanceType(
uintptr_t address, const d::HeapAddresses& heap_addresses);
} // namespace v8_debug_helper_internal
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment