Commit 534482b3 authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

[tools] Show contents of cached external strings from crash dumps

This change adds support for the postmortem inspection library to show
the content of cached external strings if that content is available. It
also fixes a minor annoyance where strings with unavailable data would
show up as "...". Now, if fetching the very first character fails, we
omit the literal value from the output.

Bug: v8:9376
Change-Id: Id694a774c231ab3467fb59b1c149284729acfb20
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1987922Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#65961}
parent f3a5c36a
......@@ -102,6 +102,21 @@ TValue ReadProp(const d::ObjectPropertiesResult& props, std::string name) {
return *reinterpret_cast<TValue*>(prop.address);
}
// A simple implementation of ExternalStringResource that lets us control the
// result of IsCacheable().
class StringResource : public v8::String::ExternalStringResource {
public:
explicit StringResource(bool cacheable) : cacheable_(cacheable) {}
const uint16_t* data() const override {
return reinterpret_cast<const uint16_t*>(u"abcde");
}
size_t length() const override { return 5; }
bool IsCacheable() const override { return cacheable_; }
private:
bool cacheable_;
};
} // namespace
TEST(GetObjectProperties) {
......@@ -290,6 +305,19 @@ TEST(GetObjectProperties) {
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK(Contains(props->brief, "\"" + std::string(80, 'a') + "...\""));
// GetObjectProperties can read cacheable external strings.
auto external_string =
v8::String::NewExternalTwoByte(isolate, new StringResource(true));
o = v8::Utils::OpenHandle(*external_string.ToLocalChecked());
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK(Contains(props->brief, "\"abcde\""));
// GetObjectProperties cannot read uncacheable external strings.
external_string =
v8::String::NewExternalTwoByte(isolate, new StringResource(false));
o = v8::Utils::OpenHandle(*external_string.ToLocalChecked());
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
CHECK_EQ(std::string(props->brief).find("\""), std::string::npos);
// Build a basic JS object and get its properties.
v = CompileRun("({a: 1, b: 2})");
o = v8::Utils::OpenHandle(*v);
......
......@@ -153,7 +153,7 @@ TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor,
// We can't read the Map, so check whether it is in the list of known Maps,
// as another way to get its instance type.
KnownInstanceType known_map_type =
FindKnownMapInstanceType(map_ptr.value, heap_addresses);
FindKnownMapInstanceTypes(map_ptr.value, heap_addresses);
if (known_map_type.confidence == KnownInstanceType::Confidence::kHigh) {
DCHECK_EQ(known_map_type.types.size(), 1);
return GetTypedObjectByInstanceType(address, known_map_type.types[0],
......@@ -195,16 +195,17 @@ TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor,
// An object visitor that accumulates the first few characters of a string.
class ReadStringVisitor : public TqObjectVisitor {
public:
ReadStringVisitor(d::MemoryAccessor accessor,
const d::HeapAddresses& heap_addresses)
: accessor_(accessor),
heap_addresses_(heap_addresses),
index_(0),
limit_(INT32_MAX),
done_(false) {}
static v8::base::Optional<std::string> Visit(
d::MemoryAccessor accessor, const d::HeapAddresses& heap_addresses,
const TqString* object) {
ReadStringVisitor visitor(accessor, heap_addresses);
object->Visit(&visitor);
return visitor.GetString();
}
// Returns the result as UTF-8 once visiting is complete.
std::string GetString() {
v8::base::Optional<std::string> GetString() {
if (failed_) return {};
std::vector<char> result(
string_.size() * unibrow::Utf16::kMaxExtraUtf8BytesForOneUtf16CodeUnit);
unsigned write_index = 0;
......@@ -216,25 +217,40 @@ class ReadStringVisitor : public TqObjectVisitor {
prev_char, /*replace_invalid=*/true);
prev_char = character;
}
return {result.data(), write_index};
return std::string(result.data(), write_index);
}
template <typename T>
void ReadSeqString(const T* object) {
template <typename TChar>
Value<TChar> ReadCharacter(uintptr_t data_address, int32_t index) {
TChar value{};
d::MemoryAccessResult validity =
accessor_(data_address + index * sizeof(TChar),
reinterpret_cast<uint8_t*>(&value), sizeof(value));
return {validity, value};
}
template <typename TChar>
void ReadStringCharacters(const TqString* object, uintptr_t data_address) {
int32_t length = GetOrFinish(object->GetLengthValue(accessor_));
for (; index_ < length && index_ < limit_ && !done_; ++index_) {
STATIC_ASSERT(sizeof(TChar) <= sizeof(char16_t));
char16_t c = static_cast<char16_t>(
GetOrFinish(object->GetCharsValue(accessor_, index_)));
GetOrFinish(ReadCharacter<TChar>(data_address, index_)));
if (!done_) AddCharacter(c);
}
}
template <typename TChar, typename TString>
void ReadSeqString(const TString* object) {
ReadStringCharacters<TChar>(object, object->GetCharsAddress());
}
void VisitSeqOneByteString(const TqSeqOneByteString* object) override {
ReadSeqString(object);
ReadSeqString<char>(object);
}
void VisitSeqTwoByteString(const TqSeqTwoByteString* object) override {
ReadSeqString(object);
ReadSeqString<char16_t>(object);
}
void VisitConsString(const TqConsString* object) override {
......@@ -273,13 +289,57 @@ class ReadStringVisitor : public TqObjectVisitor {
.object->Visit(this);
}
void VisitExternalString(const TqExternalString* object) override {
// TODO(v8:9376): External strings are very common and important when
// attempting to print the source of a function in the browser. For now
// we're just ignoring them, but eventually we'll want some kind of
// mechanism where the user of this library can provide a callback function
// that fetches data from external strings.
AddEllipsisAndFinish();
bool IsExternalStringCached(const TqExternalString* object) {
// The safest way to get the instance type is to use known map pointers, in
// case the map data is not available.
uintptr_t map = GetOrFinish(object->GetMapValue(accessor_));
if (done_) return false;
auto instance_types = FindKnownMapInstanceTypes(map, heap_addresses_);
// Exactly one of the matched instance types should be a string type,
// because all maps for string types are in the same space (read-only
// space). The "uncached" flag on that instance type tells us whether it's
// safe to read the cached data.
for (const auto& type : instance_types.types) {
if ((type & i::kIsNotStringMask) == i::kStringTag &&
(type & i::kStringRepresentationMask) == i::kExternalStringTag) {
return (type & i::kUncachedExternalStringMask) !=
i::kUncachedExternalStringTag;
}
}
// If for some reason we can't find an external string type here (maybe the
// caller provided an external string type as the type hint, but it doesn't
// actually match the in-memory map pointer), then we can't safely use the
// cached data.
return false;
}
template <typename TChar>
void ReadExternalString(const TqExternalString* object) {
// Cached external strings are easy to read; uncached external strings
// require knowledge of the embedder. For now, we only read cached external
// strings.
if (IsExternalStringCached(object)) {
uintptr_t data_address = reinterpret_cast<uintptr_t>(
GetOrFinish(object->GetResourceDataValue(accessor_)));
if (done_) return;
ReadStringCharacters<TChar>(object, data_address);
} else {
// TODO(v8:9376): Come up with some way that a caller with full knowledge
// of a particular embedder could provide a callback function for getting
// uncached string data.
AddEllipsisAndFinish();
}
}
void VisitExternalOneByteString(
const TqExternalOneByteString* object) override {
ReadExternalString<char>(object);
}
void VisitExternalTwoByteString(
const TqExternalTwoByteString* object) override {
ReadExternalString<char16_t>(object);
}
void VisitObject(const TqObject* object) override {
......@@ -289,6 +349,15 @@ class ReadStringVisitor : public TqObjectVisitor {
}
private:
ReadStringVisitor(d::MemoryAccessor accessor,
const d::HeapAddresses& heap_addresses)
: accessor_(accessor),
heap_addresses_(heap_addresses),
index_(0),
limit_(INT32_MAX),
done_(false),
failed_(false) {}
// Unpacks a value that was fetched from the debuggee. If the value indicates
// that it couldn't successfully fetch memory, then prevents further work.
template <typename T>
......@@ -301,8 +370,12 @@ class ReadStringVisitor : public TqObjectVisitor {
void AddEllipsisAndFinish() {
if (!done_) {
string_ += u"...";
done_ = true;
if (string_.empty()) {
failed_ = true;
} else {
string_ += u"...";
}
}
}
......@@ -346,6 +419,7 @@ class ReadStringVisitor : public TqObjectVisitor {
int32_t index_; // Index of next char to read.
int32_t limit_; // Don't read past this index (set by SlicedString).
bool done_; // Whether to stop further work.
bool failed_; // Whether an error was encountered before any valid data.
};
// An object visitor that supplies extra information for some types.
......@@ -363,9 +437,10 @@ class AddInfoVisitor : public TqObjectVisitor {
}
void VisitString(const TqString* object) override {
ReadStringVisitor visitor(accessor_, heap_addresses_);
object->Visit(&visitor);
brief_ = "\"" + visitor.GetString() + "\"";
auto str = ReadStringVisitor::Visit(accessor_, heap_addresses_, object);
if (str.has_value()) {
brief_ = "\"" + *str + "\"";
}
}
void VisitJSObject(const TqJSObject* object) override {
......
......@@ -49,7 +49,7 @@ std::string FindKnownObject(uintptr_t address,
return result;
}
KnownInstanceType FindKnownMapInstanceType(
KnownInstanceType FindKnownMapInstanceTypes(
uintptr_t address, const d::HeapAddresses& heap_addresses) {
uintptr_t containing_page = address & ~i::kPageAlignmentMask;
uintptr_t offset_in_page = address & i::kPageAlignmentMask;
......
......@@ -59,7 +59,7 @@ struct KnownInstanceType {
// Returns information about the instance type of the Map at the given address,
// based on the list of known Maps.
KnownInstanceType FindKnownMapInstanceType(
KnownInstanceType FindKnownMapInstanceTypes(
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