/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
using ::google::protobuf::io::ArrayInputStream; using ::google::protobuf::io::CodedInputStream; using ::google::protobuf::io::GzipInputStream; using ::google::protobuf::io::ZeroCopyInputStream;
using JS::ubi::AtomOrTwoByteChars; using JS::ubi::ShortestPaths;
MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf() { auto ccjscx = CycleCollectedJSContext::Get();
MOZ_ASSERT(ccjscx); auto cx = ccjscx->Context();
MOZ_ASSERT(cx); auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(cx);
MOZ_ASSERT(mallocSizeOf); return mallocSizeOf;
}
template <typename MessageType> staticbool parseMessage(ZeroCopyInputStream& stream, uint32_t sizeOfMessage,
MessageType& message) { // We need to create a new `CodedInputStream` for each message so that the // 64MB limit is applied per-message rather than to the whole stream.
CodedInputStream codedStream(&stream);
// The protobuf message nesting that core dumps exhibit is dominated by // allocation stacks' frames. In the most deeply nested case, each frame has // two messages: a StackFrame message and a StackFrame::Data message. These // frames are on top of a small constant of other messages. There are a // MAX_STACK_DEPTH number of frames, so we multiply this by 3 to make room for // the two messages per frame plus some head room for the constant number of // non-dominating messages.
codedStream.SetRecursionLimit(HeapSnapshot::MAX_STACK_DEPTH * 3);
auto limit = codedStream.PushLimit(sizeOfMessage); if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
NS_WARN_IF(!codedStream.ConsumedEntireMessage()) ||
NS_WARN_IF(codedStream.BytesUntilLimit() != 0)) { returnfalse;
}
template < // Either char or char16_t. typename CharT, // A reference to either `internedOneByteStrings` or // `internedTwoByteStrings` if CharT is char or char16_t respectively. typename InternedStringSet> const CharT* HeapSnapshot::getOrInternString(
InternedStringSet& internedStrings, Maybe<StringOrRef>& maybeStrOrRef) { // Incomplete message: has neither a string nor a reference to an already // interned string. if (MOZ_UNLIKELY(maybeStrOrRef.isNothing())) return nullptr;
// Get a de-duplicated string as a Maybe<StringOrRef> from the given `msg`. #define GET_STRING_OR_REF_WITH_PROP_NAMES(msg, strPropertyName, \
refPropertyName) \
(msg.has_##refPropertyName() ? Some(StringOrRef(msg.refPropertyName())) \
: msg.has_##strPropertyName() ? Some(StringOrRef(&msg.strPropertyName())) \
: Nothing())
bool HeapSnapshot::saveNode(const protobuf::Node& node,
NodeIdSet& edgeReferents) { // NB: de-duplicated string properties must be read back and interned in the // same order here as they are written and serialized in // `CoreDumpWriter::writeNode` or else indices in references to already // serialized strings will be off.
if (NS_WARN_IF(!node.has_id())) returnfalse;
NodeId id = node.id();
// NodeIds are derived from pointers (at most 48 bits) and we rely on them // fitting into JS numbers (IEEE 754 doubles, can precisely store 53 bit // integers) despite storing them on disk as 64 bit integers. if (NS_WARN_IF(!JS::Value::isNumberRepresentable(id))) returnfalse;
// Should only deserialize each node once. if (NS_WARN_IF(nodes.has(id))) returnfalse;
if (NS_WARN_IF(!JS::ubi::Uint32IsValidCoarseType(node.coarsetype()))) returnfalse; auto coarseType = JS::ubi::Uint32ToCoarseType(node.coarsetype());
if (NS_WARN_IF(!node.has_size())) returnfalse;
uint64_t size = node.size();
auto edgesLength = node.edges_size();
DeserializedNode::EdgeVector edges; if (NS_WARN_IF(!edges.reserve(edgesLength))) returnfalse; for (decltype(edgesLength) i = 0; i < edgesLength; i++) { auto& protoEdge = node.edges(i);
if (NS_WARN_IF(!protoEdge.has_referent())) returnfalse;
NodeId referent = protoEdge.referent();
if (NS_WARN_IF(!edgeReferents.put(referent))) returnfalse;
bool HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame,
StackFrameId& outFrameId) { // NB: de-duplicated string properties must be read in the same order here as // they are written in `CoreDumpWriter::getProtobufStackFrame` or else indices // in references to already serialized strings will be off.
if (frame.has_ref()) { // We should only get a reference to the previous frame if we have already // seen the previous frame. if (!frames.has(frame.ref())) returnfalse;
outFrameId = frame.ref(); returntrue;
}
// Incomplete message. if (!frame.has_data()) returnfalse;
auto data = frame.data();
if (!data.has_id()) returnfalse;
StackFrameId id = data.id();
// This should be the first and only time we see this frame. if (frames.has(id)) returnfalse;
if (!data.has_line()) returnfalse;
uint32_t line = data.line();
if (!data.has_column()) returnfalse;
JS::TaggedColumnNumberOneOrigin column(
JS::LimitedColumnNumberOneOrigin(data.column()));
if (!data.has_issystem()) returnfalse; bool isSystem = data.issystem();
if (!data.has_isselfhosted()) returnfalse; bool isSelfHosted = data.isselfhosted();
Maybe<StringOrRef> sourceOrRef = GET_STRING_OR_REF(data, source); auto source =
getOrInternString<char16_t>(internedTwoByteStrings, sourceOrRef); if (!source) returnfalse;
// Because protobuf messages aren't self-delimiting, we serialize each message // preceded by its size in bytes. When deserializing, we read this size and then // limit reading from the stream to the given byte size. If we didn't, then the // first message would consume the entire stream. staticbool readSizeOfNextMessage(ZeroCopyInputStream& stream,
uint32_t* sizep) {
MOZ_ASSERT(sizep);
CodedInputStream codedStream(&stream); return codedStream.ReadVarint32(sizep) && *sizep > 0;
}
protobuf::Metadata metadata; if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage))) returnfalse; if (!parseMessage(gzipStream, sizeOfMessage, metadata)) returnfalse; if (metadata.has_timestamp()) timestamp.emplace(metadata.timestamp());
// Next is the root node.
protobuf::Node root; if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage))) returnfalse; if (!parseMessage(gzipStream, sizeOfMessage, root)) returnfalse;
// Although the id is optional in the protobuf format for future proofing, we // can't currently do anything without it. if (NS_WARN_IF(!root.has_id())) returnfalse;
rootId = root.id();
// The set of all node ids we've found edges pointing to.
NodeIdSet edgeReferents(cx);
if (NS_WARN_IF(!saveNode(root, edgeReferents))) returnfalse;
// Finally, the rest of the nodes in the core dump.
// Test for the end of the stream. The protobuf library gives no way to tell // the difference between an underlying read error and the stream being // done. All we can do is attempt to read the size of the next message and // extrapolate guestimations from the result of that operation. while (readSizeOfNextMessage(gzipStream, &sizeOfMessage)) {
protobuf::Node node; if (!parseMessage(gzipStream, sizeOfMessage, node)) returnfalse; if (NS_WARN_IF(!saveNode(node, edgeReferents))) returnfalse;
}
// Check the set of node ids referred to by edges we found and ensure that we // have the node corresponding to each id. If we don't have all of them, it is // unsafe to perform analyses of this heap snapshot. for (auto iter = edgeReferents.iter(); !iter.done(); iter.next()) { if (NS_WARN_IF(!nodes.has(iter.get()))) returnfalse;
}
// If we are only taking a snapshot of the heap affected by the given set of // globals, find the set of compartments the globals are allocated // within. Returns false on OOM failure. staticbool PopulateCompartmentsWithGlobals(
CompartmentSet& compartments, JS::HandleVector<JSObject*> globals) { unsigned length = globals.length(); for (unsigned i = 0; i < length; i++) { if (!compartments.put(JS::GetCompartment(globals[i]))) returnfalse;
}
returntrue;
}
// Add the given set of globals as explicit roots in the given roots // list. Returns false on OOM failure. staticbool AddGlobalsAsRoots(JS::HandleVector<JSObject*> globals,
ubi::RootList& roots) { unsigned length = globals.length(); for (unsigned i = 0; i < length; i++) { if (!roots.addRoot(ubi::Node(globals[i].get()), u"heap snapshot global")) { returnfalse;
}
} returntrue;
}
// Choose roots and limits for a traversal, given `boundaries`. Set `roots` to // the set of nodes within the boundaries that are referred to by nodes // outside. If `boundaries` does not include all JS compartments, initialize // `compartments` to the set of included compartments; otherwise, leave // `compartments` uninitialized. (You can use compartments.initialized() to // check.) // // If `boundaries` is incoherent, or we encounter an error while trying to // handle it, or we run out of memory, set `rv` appropriately and return // `false`. // // Return value is a pair of the status and an AutoCheckCannotGC token, // forwarded from ubi::RootList::init(), to ensure that the caller does // not GC while the RootList is live and initialized. static std::pair<bool, AutoCheckCannotGC> EstablishBoundaries(
JSContext* cx, ErrorResult& rv, const HeapSnapshotBoundaries& boundaries,
ubi::RootList& roots, CompartmentSet& compartments) {
MOZ_ASSERT(!roots.initialized());
MOZ_ASSERT(compartments.empty());
bool foundBoundaryProperty = false;
if (boundaries.mRuntime.WasPassed()) {
foundBoundaryProperty = true;
if (!boundaries.mRuntime.Value()) {
rv.Throw(NS_ERROR_INVALID_ARG); return {false, AutoCheckCannotGC(cx)};
}
auto [ok, nogc] = roots.init(); if (!ok) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY); return {false, nogc};
}
}
if (boundaries.mDebugger.WasPassed()) { if (foundBoundaryProperty) {
rv.Throw(NS_ERROR_INVALID_ARG); return {false, AutoCheckCannotGC(cx)};
}
foundBoundaryProperty = true;
// A variant covering all the various two-byte strings that we can get from the // ubi::Node API. class TwoByteString
: public Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName> { using Base = Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>;
// Rewrap the inner value of a JS::ubi::AtomOrTwoByteChars as a TwoByteString. static TwoByteString from(JS::ubi::AtomOrTwoByteChars&& s) { return s.match([](auto* a) { return TwoByteString(a); });
}
// Returns true if the given TwoByteString is non-null, false otherwise. bool isNonNull() const { return match([](auto& t) { return t != nullptr; });
}
// Return the length of the string, 0 if it is null.
size_t length() const { return match(
[](JSAtom* atom) -> size_t {
MOZ_ASSERT(atom);
JS::ubi::AtomOrTwoByteChars s(atom); return s.length();
},
[](const char16_t* chars) -> size_t {
MOZ_ASSERT(chars); return NS_strlen(chars);
},
[](const JS::ubi::EdgeName& ptr) -> size_t {
MOZ_ASSERT(ptr); return NS_strlen(ptr.get());
});
}
// Copy the contents of a TwoByteString into the provided buffer. The buffer // is NOT null terminated. The number of characters written is returned.
size_t copyToBuffer(RangedPtr<char16_t> destination, size_t maxLength) {
CopyToBufferMatcher m(destination, maxLength); return match(m);
}
struct HashPolicy;
};
// A hashing policy for TwoByteString. // // Atoms are pointer hashed and use pointer equality, which means that we // tolerate some duplication across atoms and the other two types of two-byte // strings. In practice, we expect the amount of this duplication to be very low // because each type is generally a different semantic thing in addition to // having a slightly different representation. For example, the set of edge // names and the set stack frames' source names naturally tend not to overlap // very much if at all. struct TwoByteString::HashPolicy { using Lookup = TwoByteString;
// Returns whether `edge` should be included in a heap snapshot of // `compartments`. The optional `policy` out-param is set to INCLUDE_EDGES // if we want to include the referent's edges, or EXCLUDE_EDGES if we don't // want to include them. staticbool ShouldIncludeEdge(JS::CompartmentSet* compartments, const ubi::Node& origin, const ubi::Edge& edge,
CoreDumpWriter::EdgePolicy* policy = nullptr) { if (policy) {
*policy = CoreDumpWriter::INCLUDE_EDGES;
}
if (!compartments) { // We aren't targeting a particular set of compartments, so serialize all // the things! returntrue;
}
// We are targeting a particular set of compartments. If this node is in our // target set, serialize it and all of its edges. If this node is _not_ in our // target set, we also serialize under the assumption that it is a shared // resource being used by something in our target compartments since we // reached it by traversing the heap graph. However, we do not serialize its // outgoing edges and we abandon further traversal from this node. // // If the node does not belong to any compartment, we also serialize its // outgoing edges. This case is relevant for Shapes: they don't belong to a // specific compartment and contain edges to parent/kids Shapes we want to // include. Note that these Shapes may contain pointers into our target // compartment (the Shape's getter/setter JSObjects). However, we do not // serialize nodes in other compartments that are reachable from these // non-compartment nodes.
if (!compartment || compartments->has(compartment)) { returntrue;
}
if (policy) {
*policy = CoreDumpWriter::EXCLUDE_EDGES;
}
return !!origin.compartment();
}
// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to the // given `ZeroCopyOutputStream`. class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter { using FrameSet = js::HashSet<uint64_t>; using TwoByteStringMap =
js::HashMap<TwoByteString, uint64_t, TwoByteString::HashPolicy>; using OneByteStringMap = js::HashMap<constchar*, uint64_t>;
JSContext* cx; bool wantNames; // The set of |JS::ubi::StackFrame::identifier()|s that have already been // serialized and written to the core dump.
FrameSet framesAlreadySerialized; // The set of two-byte strings that have already been serialized and written // to the core dump.
TwoByteStringMap twoByteStringsAlreadySerialized; // The set of one-byte strings that have already been serialized and written // to the core dump.
OneByteStringMap oneByteStringsAlreadySerialized;
bool writeMessage(const ::google::protobuf::MessageLite& message) { // We have to create a new CodedOutputStream when writing each message so // that the 64MB size limit used by Coded{Output,Input}Stream to prevent // integer overflow is enforced per message rather than on the whole stream.
::google::protobuf::io::CodedOutputStream codedStream(&stream);
codedStream.WriteVarint32(message.ByteSizeLong());
message.SerializeWithCachedSizes(&codedStream); return !codedStream.HadError();
}
// Attach the full two-byte string or a reference to a two-byte string that // has already been serialized to a protobuf message. template <typename SetStringFunction, typename SetRefFunction> bool attachTwoByteString(TwoByteString& string, SetStringFunction setString,
SetRefFunction setRef) { auto ptr = twoByteStringsAlreadySerialized.lookupForAdd(string); if (ptr) {
setRef(ptr->value()); returntrue;
}
auto length = string.length(); auto stringData = MakeUnique<std::string>(length * sizeof(char16_t), '\0'); if (!stringData) returnfalse;
auto buf = const_cast<char16_t*>( reinterpret_cast<const char16_t*>(stringData->data()));
string.copyToBuffer(RangedPtr<char16_t>(buf, length), length);
uint64_t ref = twoByteStringsAlreadySerialized.count(); if (!twoByteStringsAlreadySerialized.add(ptr, std::move(string), ref)) returnfalse;
setString(stringData.release()); returntrue;
}
// Attach the full one-byte string or a reference to a one-byte string that // has already been serialized to a protobuf message. template <typename SetStringFunction, typename SetRefFunction> bool attachOneByteString(constchar* string, SetStringFunction setString,
SetRefFunction setRef) { auto ptr = oneByteStringsAlreadySerialized.lookupForAdd(string); if (ptr) {
setRef(ptr->value()); returntrue;
}
auto length = strlen(string); auto stringData = MakeUnique<std::string>(string, length); if (!stringData) returnfalse;
uint64_t ref = oneByteStringsAlreadySerialized.count(); if (!oneByteStringsAlreadySerialized.add(ptr, string, ref)) returnfalse;
setString(stringData.release()); returntrue;
}
protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame,
size_t depth = 1) { // NB: de-duplicated string properties must be written in the same order // here as they are read in `HeapSnapshot::saveStackFrame` or else indices // in references to already serialized strings will be off.
MOZ_ASSERT(frame, "null frames should be represented as the lack of a serialized " "stack frame");
auto id = frame.identifier(); auto protobufStackFrame = MakeUnique<protobuf::StackFrame>(); if (!protobufStackFrame) return nullptr;
if (framesAlreadySerialized.has(id)) {
protobufStackFrame->set_ref(id); return protobufStackFrame.release();
}
auto data = MakeUnique<protobuf::StackFrame_Data>(); if (!data) return nullptr;
bool writeNode(const JS::ubi::Node& ubiNode, EdgePolicy includeEdges) final { // NB: de-duplicated string properties must be written in the same order // here as they are read in `HeapSnapshot::saveNode` or else indices in // references to already serialized strings will be off.
if (ubiNode.hasAllocationStack()) { auto ubiStackFrame = ubiNode.allocationStack(); auto protoStackFrame = getProtobufStackFrame(ubiStackFrame); if (NS_WARN_IF(!protoStackFrame)) returnfalse;
protobufNode.set_allocated_allocationstack(protoStackFrame);
}
if (ubiNode.descriptiveTypeName()) { auto descriptiveTypeName = TwoByteString(ubiNode.descriptiveTypeName()); if (NS_WARN_IF(!attachTwoByteString(
descriptiveTypeName,
[&](std::string* name) {
protobufNode.set_allocated_descriptivetypename(name);
},
[&](uint64_t ref) {
protobufNode.set_descriptivetypenameref(ref);
}))) { returnfalse;
}
}
return writeMessage(protobufNode);
}
};
// A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a // core dump. class MOZ_STACK_CLASS HeapSnapshotHandler {
CoreDumpWriter& writer;
JS::CompartmentSet* compartments;
public: // For telemetry.
uint32_t nodeCount;
uint32_t edgeCount;
// We're only interested in the first time we reach edge.referent, not in // every edge arriving at that node. "But, don't we want to serialize every // edge in the heap graph?" you ask. Don't worry! This edge is still // serialized into the core dump. Serializing a node also serializes each of // its edges, and if we are traversing a given edge, we must have already // visited and serialized the origin node and its edges. if (!first) returntrue;
CoreDumpWriter::EdgePolicy policy; if (!ShouldIncludeEdge(compartments, origin, edge, &policy)) { // Because ShouldIncludeEdge considers the |origin| node as well, we don't // want to consider this node 'visited' until we write it to the core // dump.
traversal.doNotMarkReferentAsVisited(); returntrue;
}
nodeCount++;
if (policy == CoreDumpWriter::EXCLUDE_EDGES) traversal.abandonReferent();
nsAutoString tempPath;
rv = file->GetPath(tempPath); if (NS_WARN_IF(rv.Failed())) return nullptr;
auto ms = msSinceProcessCreation(now);
rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms)); if (NS_WARN_IF(rv.Failed())) return nullptr;
rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666); if (NS_WARN_IF(rv.Failed())) return nullptr;
rv = file->GetPath(outFilePath); if (NS_WARN_IF(rv.Failed())) return nullptr;
// The snapshot ID must be computed in the process that created the // temp file, because TmpD may not be the same in all processes.
outSnapshotId.Assign(Substring(
outFilePath, tempPath.Length() + 1,
outFilePath.Length() - tempPath.Length() - sizeof(".fxsnapshot")));
return file.forget();
}
// Deletion policy for cleaning up PHeapSnapshotTempFileHelperChild pointers. class DeleteHeapSnapshotTempFileHelperChild { public:
constexpr DeleteHeapSnapshotTempFileHelperChild() {}
// A UniquePtr alias to automatically manage PHeapSnapshotTempFileHelperChild // pointers. using UniqueHeapSnapshotTempFileHelperChild =
UniquePtr<PHeapSnapshotTempFileHelperChild,
DeleteHeapSnapshotTempFileHelperChild>;
// Get an nsIOutputStream that we can write the heap snapshot to. In non-e10s // and in the e10s parent process, open a file directly and create an output // stream for it. In e10s child processes, we are sandboxed without access to // the filesystem. Use IPDL to request a file descriptor from the parent // process. static already_AddRefed<nsIOutputStream> getCoreDumpOutputStream(
ErrorResult& rv, TimeStamp& start, nsAString& outFilePath,
nsAString& outSnapshotId) { if (XRE_IsParentProcess()) { // Create the file and open the output stream directly.
if (!rv.Failed())
Telemetry::AccumulateTimeDelta(Telemetry::DEVTOOLS_READ_HEAP_SNAPSHOT_MS,
start);
return snapshot.forget();
}
} // namespace dom
} // namespace mozilla
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.38Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.