Commit 0a31d508 authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

[tools] Teach v8_debug_helper where heap spaces start in ptr-compr mode

v8_debug_helper attempts to flag known object pointers when it can
recognize them, even if the memory pointed to is not available in the
crash dump. In ptr-compr builds, the first pages of the map space,
read-only space, and old space are always at the same offsets within the
heap reservation region, so we can more easily detect known objects.

Bug: v8:9376
Change-Id: I04e0d2357143d753f575f556e94f8fd42ce9d811
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1783729
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63624}
parent 2f8361d4
......@@ -61,6 +61,10 @@ void CheckProp(const d::ObjectProperty& property, const char* expected_type,
CHECK(*reinterpret_cast<TValue*>(property.address) == expected_value);
}
bool StartsWith(std::string full_string, std::string prefix) {
return full_string.substr(0, prefix.size()) == prefix;
}
} // namespace
TEST(GetObjectProperties) {
......@@ -68,12 +72,13 @@ TEST(GetObjectProperties) {
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.
// Claim we don't know anything about the heap layout.
d::HeapAddresses heap_addresses{0, 0, 0, 0};
v8::Local<v8::Value> v = CompileRun("42");
Handle<Object> o = v8::Utils::OpenHandle(*v);
d::ObjectPropertiesResultPtr props =
d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK(props->type_check_result == d::TypeCheckResult::kSmi);
CHECK(props->brief == std::string("42 (0x2a)"));
CHECK(props->type == std::string("v8::internal::Smi"));
......@@ -81,7 +86,7 @@ TEST(GetObjectProperties) {
v = CompileRun("[\"a\", \"bc\"]");
o = v8::Utils::OpenHandle(*v);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props->type == std::string("v8::internal::JSArray"));
CHECK_EQ(props->num_properties, 4);
......@@ -92,9 +97,9 @@ TEST(GetObjectProperties) {
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
// We need to supply some valid address for decompression before reading the
// elements from the JSArray.
roots.any_heap_pointer = o->ptr();
heap_addresses.any_heap_pointer = o->ptr();
i::Tagged_t properties_or_hash =
*reinterpret_cast<i::Tagged_t*>(props->properties[1]->address);
......@@ -106,32 +111,38 @@ TEST(GetObjectProperties) {
// any ability to read memory.
{
MemoryFailureRegion failure(0, UINTPTR_MAX);
props = d::GetObjectProperties(properties_or_hash, &ReadMemory, roots);
props =
d::GetObjectProperties(properties_or_hash, &ReadMemory, heap_addresses);
CHECK(props->type_check_result ==
d::TypeCheckResult::kObjectPointerValidButInaccessible);
CHECK(props->type == std::string("v8::internal::HeapObject"));
CHECK_EQ(props->num_properties, 1);
CheckProp(*props->properties[0], "v8::internal::Map", "map");
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 =
// "maybe" prefix indicates that GetObjectProperties recognized the offset
// within the page as matching a known object, but didn't know whether the
// 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"));
// Provide a heap first page so the API can be more sure.
heap_addresses.read_only_space_first_page =
reinterpret_cast<uintptr_t>(reinterpret_cast<i::Isolate*>(isolate)
->heap()
->read_only_space()
->first_page());
props = d::GetObjectProperties(properties_or_hash, &ReadMemory, roots);
props =
d::GetObjectProperties(properties_or_hash, &ReadMemory, heap_addresses);
CHECK(props->type_check_result ==
d::TypeCheckResult::kObjectPointerValidButInaccessible);
CHECK(props->type == std::string("v8::internal::HeapObject"));
CHECK_EQ(props->num_properties, 1);
CheckProp(*props->properties[0], "v8::internal::Map", "map");
CHECK(std::string(props->brief).substr(0, 15) ==
std::string("EmptyFixedArray"));
CHECK(StartsWith(props->brief, "EmptyFixedArray"));
}
props = d::GetObjectProperties(elements, &ReadMemory, roots);
props = d::GetObjectProperties(elements, &ReadMemory, heap_addresses);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props->type == std::string("v8::internal::FixedArray"));
CHECK_EQ(props->num_properties, 3);
......@@ -144,7 +155,8 @@ TEST(GetObjectProperties) {
// 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);
props = d::GetObjectProperties(second_string_address, &ReadMemory,
heap_addresses);
CHECK(props->type_check_result == d::TypeCheckResult::kUsedMap);
CHECK(props->type == std::string("v8::internal::SeqOneByteString"));
CHECK_EQ(props->num_properties, 4);
......@@ -165,12 +177,12 @@ TEST(GetObjectProperties) {
uintptr_t map_address =
d::GetObjectProperties(
*reinterpret_cast<i::Tagged_t*>(props->properties[0]->address),
&ReadMemory, roots)
&ReadMemory, heap_addresses)
->properties[0]
->address;
MemoryFailureRegion failure(map_address, map_address + i::Map::kSize);
props2 = d::GetObjectProperties(second_string_address, &ReadMemory, roots,
"v8::internal::String");
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);
......@@ -183,7 +195,7 @@ TEST(GetObjectProperties) {
// Try a weak reference.
props2 = d::GetObjectProperties(second_string_address | kWeakHeapObjectMask,
&ReadMemory, roots);
&ReadMemory, heap_addresses);
std::string weak_ref_prefix = "weak ref to ";
CHECK(weak_ref_prefix + props->brief == props2->brief);
CHECK(props2->type_check_result == d::TypeCheckResult::kUsedMap);
......@@ -201,9 +213,8 @@ TEST(GetObjectProperties) {
const alphabet = "abcdefghijklmnopqrstuvwxyz";
alphabet.substr(3,20) + alphabet.toUpperCase().substr(5,15) + "7")");
o = v8::Utils::OpenHandle(*v);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
CHECK(std::string(props->brief).substr(0, 38) ==
std::string("\"defghijklmnopqrstuvwFGHIJKLMNOPQRST7\""));
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK(StartsWith(props->brief, "\"defghijklmnopqrstuvwFGHIJKLMNOPQRST7\""));
// Cause a failure when reading the "second" pointer within the top-level
// ConsString.
......@@ -211,15 +222,15 @@ TEST(GetObjectProperties) {
CheckProp(*props->properties[4], "v8::internal::String", "second");
uintptr_t second_address = props->properties[4]->address;
MemoryFailureRegion failure(second_address, second_address + 4);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
CHECK(std::string(props->brief).substr(0, 40) ==
std::string("\"defghijklmnopqrstuvwFGHIJKLMNOPQRST...\""));
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK(
StartsWith(props->brief, "\"defghijklmnopqrstuvwFGHIJKLMNOPQRST...\""));
}
// Build a very long string.
v = CompileRun("'a'.repeat(1000)");
o = v8::Utils::OpenHandle(*v);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, roots);
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK(std::string(props->brief).substr(79, 7) == std::string("aa...\" "));
}
......
......@@ -97,6 +97,14 @@ static void DumpKnownObject(FILE* out, i::Heap* heap, const char* space_name,
#undef RO_ROOT_LIST_CASE
}
static void DumpSpaceFirstPageAddress(FILE* out, i::PagedSpace* space) {
const char* name = space->name();
i::Address first_page = reinterpret_cast<i::Address>(space->first_page());
i::Tagged_t compressed = i::CompressTagged(first_page);
uintptr_t unsigned_compressed = static_cast<uint32_t>(compressed);
i::PrintF(out, " 0x%08" V8PRIxPTR ": \"%s\",\n", unsigned_compressed, name);
}
static int DumpHeapConstants(FILE* out, const char* argv0) {
// Start up V8.
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
......@@ -164,6 +172,25 @@ static int DumpHeapConstants(FILE* out, const char* argv0) {
i::PrintF(out, "}\n");
}
if (COMPRESS_POINTERS_BOOL) {
// Dump a list of addresses for the first page of each space that contains
// objects in the other tables above. This is only useful if two
// assumptions hold:
// 1. Those pages are positioned deterministically within the heap
// reservation block during snapshot deserialization.
// 2. Those pages cannot ever be moved (such as by compaction).
i::PrintF(out,
"\n# Lower 32 bits of first page addresses for various heap "
"spaces.\n");
i::PrintF(out, "HEAP_FIRST_PAGES = {\n");
i::PagedSpaceIterator it(heap);
for (i::PagedSpace* s = it.Next(); s != nullptr; s = it.Next()) {
DumpSpaceFirstPageAddress(out, s);
}
DumpSpaceFirstPageAddress(out, read_only_heap->read_only_space());
i::PrintF(out, "}\n");
}
// Dump frame markers
i::PrintF(out, "\n# List of known V8 Frame Markers.\n");
#define DUMP_MARKER(T, class) i::PrintF(out, " \"%s\",\n", #T);
......
......@@ -109,7 +109,7 @@ typedef MemoryAccessResult (*MemoryAccessor)(uintptr_t address,
// 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 {
struct HeapAddresses {
// 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.
......@@ -119,9 +119,9 @@ struct Roots {
// 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;
uintptr_t map_space_first_page;
uintptr_t old_space_first_page;
uintptr_t read_only_space_first_page;
// 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
......@@ -139,7 +139,8 @@ extern "C" {
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);
const v8::debug_helper::HeapAddresses& heap_addresses,
const char* type_hint);
V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_ObjectPropertiesResult(
v8::debug_helper::ObjectPropertiesResult* result);
}
......@@ -166,9 +167,9 @@ using ObjectPropertiesResultPtr =
// v8::internal::Object.
inline ObjectPropertiesResultPtr GetObjectProperties(
uintptr_t object, v8::debug_helper::MemoryAccessor memory_accessor,
const Roots& heap_roots, const char* type_hint = nullptr) {
const HeapAddresses& heap_addresses, const char* type_hint = nullptr) {
return ObjectPropertiesResultPtr(_v8_debug_helper_GetObjectProperties(
object, memory_accessor, heap_roots, type_hint));
object, memory_accessor, heap_addresses, type_hint));
}
} // namespace debug_helper
......
......@@ -16,6 +16,9 @@ out = """
#include <cstdint>
#include <string>
#include "src/common/ptr-compr-inl.h"
#include "tools/debug_helper/debug-helper-internal.h"
namespace v8_debug_helper_internal {
"""
......@@ -51,6 +54,25 @@ def iterate_maps(target_space, camel_space_name):
iterate_maps('map_space', 'MapSpace')
iterate_maps('read_only_space', 'ReadOnlySpace')
out = out + '\nvoid FillInUnknownHeapAddresses(' + \
'd::HeapAddresses* heap_addresses, uintptr_t any_uncompressed_ptr) {\n'
if (hasattr(v8heapconst, 'HEAP_FIRST_PAGES')): # Only exists in ptr-compr builds.
out = out + ' if (heap_addresses->any_heap_pointer == 0) {\n'
out = out + ' heap_addresses->any_heap_pointer = any_uncompressed_ptr;\n'
out = out + ' }\n'
expected_spaces = set(['map_space', 'read_only_space', 'old_space'])
for offset, space_name in v8heapconst.HEAP_FIRST_PAGES.items():
# Turn 32-bit unsigned value into signed.
if offset >= 0x80000000:
offset -= 0x100000000
if (space_name in expected_spaces):
out = out + ' if (heap_addresses->' + space_name + '_first_page == 0) {\n'
out = out + ' heap_addresses->' + space_name + \
'_first_page = i::DecompressTaggedPointer(any_uncompressed_ptr, ' + \
str(offset) + ');\n'
out = out + ' }\n'
out = out + '}\n'
out = out + '\n}\n'
try:
......
......@@ -241,10 +241,10 @@ TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor,
return GetTypedObjectByHint(address, type_hint);
} 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.
// heap page addresses 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.
return {type.validity == d::MemoryAccessResult::kAddressNotValid
? d::TypeCheckResult::kMapPointerInvalid
: d::TypeCheckResult::kMapPointerValidButInaccessible,
......@@ -447,19 +447,24 @@ std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
}
std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
uintptr_t address, d::MemoryAccessor memory_accessor, const d::Roots& roots,
const char* type_hint) {
uintptr_t address, d::MemoryAccessor memory_accessor,
d::HeapAddresses heap_addresses, 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)
any_uncompressed_ptr = heap_addresses.any_heap_pointer;
if (any_uncompressed_ptr == 0)
any_uncompressed_ptr = heap_addresses.map_space_first_page;
if (any_uncompressed_ptr == 0)
any_uncompressed_ptr = heap_addresses.old_space_first_page;
if (any_uncompressed_ptr == 0)
any_uncompressed_ptr = heap_addresses.read_only_space_first_page;
FillInUnknownHeapAddresses(&heap_addresses, any_uncompressed_ptr);
if (any_uncompressed_ptr == 0) {
// We can't figure out the heap range. Just check for known objects.
std::string brief = FindKnownObject(address, roots);
std::string brief = FindKnownObject(address, heap_addresses);
brief = AppendAddressAndType(brief, address, "v8::internal::TaggedValue");
return v8::base::make_unique<ObjectPropertiesResult>(
d::TypeCheckResult::kUnableToDecompress, brief,
......@@ -467,22 +472,18 @@ std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
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);
std::string brief = FindKnownObject(address, heap_addresses);
return GetHeapObjectProperties(address, memory_accessor, type_hint, brief);
}
std::unique_ptr<ObjectPropertiesResult> GetObjectPropertiesImpl(
uintptr_t address, d::MemoryAccessor memory_accessor, const d::Roots& roots,
const char* type_hint) {
uintptr_t address, d::MemoryAccessor memory_accessor,
const d::HeapAddresses& heap_addresses, 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>(
......@@ -494,8 +495,8 @@ std::unique_ptr<ObjectPropertiesResult> GetObjectPropertiesImpl(
address &= ~i::kWeakHeapObjectMask;
}
if (i::Internals::HasHeapObjectTag(address)) {
std::unique_ptr<ObjectPropertiesResult> result =
GetHeapObjectProperties(address, memory_accessor, roots, type_hint);
std::unique_ptr<ObjectPropertiesResult> result = GetHeapObjectProperties(
address, memory_accessor, heap_addresses, type_hint);
if (is_weak) {
result->Prepend("weak ref to ");
}
......@@ -520,9 +521,9 @@ 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 d::HeapAddresses& heap_addresses,
const char* type_hint) {
return di::GetObjectPropertiesImpl(object, memory_accessor, heap_roots,
return di::GetObjectPropertiesImpl(object, memory_accessor, heap_addresses,
type_hint)
.release()
->GetPublicView();
......
......@@ -9,36 +9,37 @@ namespace d = v8::debug_helper;
namespace v8_debug_helper_internal {
std::string FindKnownObject(uintptr_t address, const d::Roots& roots) {
std::string FindKnownObject(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 root, then search only that page.
if (containing_page == roots.map_space) {
// If there's a match with a known page, then search only that page.
if (containing_page == heap_addresses.map_space_first_page) {
return FindKnownObjectInMapSpace(offset_in_page);
}
if (containing_page == roots.old_space) {
if (containing_page == heap_addresses.old_space_first_page) {
return FindKnownObjectInOldSpace(offset_in_page);
}
if (containing_page == roots.read_only_space) {
if (containing_page == heap_addresses.read_only_space_first_page) {
return FindKnownObjectInReadOnlySpace(offset_in_page);
}
// For any unknown roots, compile a list of things this object might be.
// For any unknown pages, compile a list of things this object might be.
std::string result;
if (roots.map_space == 0) {
if (heap_addresses.map_space_first_page == 0) {
std::string sub_result = FindKnownObjectInMapSpace(offset_in_page);
if (!sub_result.empty()) {
result += "maybe " + sub_result;
}
}
if (roots.old_space == 0) {
if (heap_addresses.old_space_first_page == 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) {
if (heap_addresses.read_only_space_first_page == 0) {
std::string sub_result = FindKnownObjectInReadOnlySpace(offset_in_page);
if (!sub_result.empty()) {
result = (result.empty() ? "" : result + ", ") + "maybe " + sub_result;
......
......@@ -14,14 +14,17 @@ namespace d = v8::debug_helper;
namespace v8_debug_helper_internal {
// Functions generated by mkgrokdump:
// Functions generated by gen-heap-constants.py, based on data from 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);
void FillInUnknownHeapAddresses(d::HeapAddresses* heap_addresses,
uintptr_t any_uncompressed_ptr);
std::string FindKnownObject(uintptr_t address, const d::Roots& roots);
std::string FindKnownObject(uintptr_t address,
const d::HeapAddresses& heap_addresses);
} // namespace v8_debug_helper_internal
......
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