0921e8f28b
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}
193 lines
8.1 KiB
C++
193 lines
8.1 KiB
C++
// 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
|