diff --git a/pkg/native_stack_traces/CHANGELOG.md b/pkg/native_stack_traces/CHANGELOG.md index ae7d875fff1..7c60f285a93 100644 --- a/pkg/native_stack_traces/CHANGELOG.md +++ b/pkg/native_stack_traces/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.3 + +- Exported some more of the ELF utilities for use in Dart tests. + ## 0.4.2 - When decoding a stack trace, frames corresponding to the functions diff --git a/pkg/native_stack_traces/lib/elf.dart b/pkg/native_stack_traces/lib/elf.dart index de32cd3b4f9..3b1624bd132 100644 --- a/pkg/native_stack_traces/lib/elf.dart +++ b/pkg/native_stack_traces/lib/elf.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -export 'src/elf.dart' show Elf, Symbol; +export 'src/elf.dart' show Elf, Section, Symbol; export 'src/constants.dart' show isolateDataSymbolName, diff --git a/pkg/native_stack_traces/pubspec.yaml b/pkg/native_stack_traces/pubspec.yaml index d3987084af4..bdf1f94874f 100644 --- a/pkg/native_stack_traces/pubspec.yaml +++ b/pkg/native_stack_traces/pubspec.yaml @@ -1,6 +1,6 @@ name: native_stack_traces description: Utilities for working with non-symbolic stack traces. -version: 0.4.2 +version: 0.4.3 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/native_stack_traces diff --git a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart index 8719b765991..212571aef7b 100644 --- a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart +++ b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart @@ -189,12 +189,17 @@ Future testAOT(String dillPath, final expectedSize = profile.nodes.fold(0, (size, n) => size + n.selfSize); + // May not be ELF, but another format. + final elf = Elf.fromFile(snapshotPath); + var checkedSize = false; if (!useAsm) { - // Verify that the total size of the snapshot text and data sections is - // the same as the sum of the shallow sizes of all objects in the profile. - // This ensures that all bytes are accounted for in some way. - final elf = Elf.fromFile(snapshotPath); + // Verify that the total size of the snapshot text and data section + // symbols is the same as the sum of the shallow sizes of all objects in + // the profile. This ensures that all bytes are accounted for in some way. + // + // We only check this when generating ELF directly because that's when + // we're guaranteed the symbols will have non-zero size. Expect.isNotNull(elf); elf!; // To refine type to non-nullable version. @@ -215,10 +220,54 @@ Future testAOT(String dillPath, Expect.equals( expectedSize, actualSize, "failed on $description snapshot"); + Expect.equals(expectedSize, actualSize, + "symbol size check failed on $description snapshot"); + checkedSize = true; + } + + // See Elf::kPages in runtime/vm/elf.h, which is also used for assembly + // padding. + final segmentAlignment = 16 * 1024; + + if (elf != null) { + // Verify that the total size of the snapshot text and data sections is + // approximately the sum of the shallow sizes of all objects in the + // profile. As sections might be merged by the assembler when useAsm is + // true, we need to account for possible padding. + final textSections = elf.namedSections(".text"); + Expect.isNotEmpty(textSections); + Expect.isTrue( + textSections.length <= 2, "More text sections than expected"); + final dataSections = elf.namedSections(".rodata"); + Expect.isNotEmpty(dataSections); + Expect.isTrue( + dataSections.length <= 2, "More data sections than expected"); + + var actualSize = 0; + for (final section in textSections) { + actualSize += section.length; + } + for (final section in dataSections) { + actualSize += section.length; + } + + final mergedCount = (2 - textSections.length) + (2 - dataSections.length); + final possiblePadding = mergedCount * segmentAlignment; + + Expect.approxEquals( + expectedSize, + actualSize, + possiblePadding, + "section size failed on $description snapshot" + + (!useAsm ? ", but symbol size test passed" : "")); checkedSize = true; } if (stripUtil || stripFlag) { + // Verify that the actual size of the stripped snapshot is close to the + // sum of the shallow sizes of all objects in the profile. They will not + // be exactly equal because of global headers, padding, and non-text/data + // sections. var strippedSnapshotPath = snapshotPath; if (stripUtil) { strippedSnapshotPath = snapshotPath + '.stripped'; @@ -227,20 +276,18 @@ Future testAOT(String dillPath, print("Stripped snapshot generated at $strippedSnapshotPath."); } - // Verify that the actual size of the stripped snapshot is close to the - // sum of the shallow sizes of all objects in the profile. They will not - // be exactly equal because of global headers and padding. final actualSize = await File(strippedSnapshotPath).length(); - // See Elf::kPages in runtime/vm/elf.h, which is also used for assembly - // padding. - final segmentAlignment = 16 * 1024; // Not every byte is accounted for by the snapshot profile, and data and // instruction segments are padded to an alignment boundary. final tolerance = 0.03 * actualSize + 2 * segmentAlignment; - Expect.approxEquals(expectedSize, actualSize, tolerance, - "failed on $description snapshot"); + Expect.approxEquals( + expectedSize, + actualSize, + tolerance, + "total size check failed on $description snapshot" + + (elf != null ? ", but section size checks passed" : "")); checkedSize = true; } diff --git a/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart b/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart index 0628aa0b6fc..9b7c95da980 100644 --- a/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart +++ b/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart @@ -197,12 +197,17 @@ Future testAOT(String dillPath, final expectedSize = profile.nodes.fold(0, (size, n) => size + n.selfSize); + // May not be ELF, but another format. + final elf = Elf.fromFile(snapshotPath); + var checkedSize = false; if (!useAsm) { - // Verify that the total size of the snapshot text and data sections is - // the same as the sum of the shallow sizes of all objects in the profile. - // This ensures that all bytes are accounted for in some way. - final elf = Elf.fromFile(snapshotPath); + // Verify that the total size of the snapshot text and data section + // symbols is the same as the sum of the shallow sizes of all objects in + // the profile. This ensures that all bytes are accounted for in some way. + // + // We only check this when generating ELF directly because that's when + // we're guaranteed the symbols will have non-zero size. Expect.isNotNull(elf); final vmTextSectionSymbol = elf.dynamicSymbolFor(vmSymbolName); @@ -220,12 +225,54 @@ Future testAOT(String dillPath, isolateTextSectionSymbol.size + isolateDataSectionSymbol.size; - Expect.equals( - expectedSize, actualSize, "failed on $description snapshot"); + Expect.equals(expectedSize, actualSize, + "symbol size check failed on $description snapshot"); + checkedSize = true; + } + + // See Elf::kPages in runtime/vm/elf.h, which is also used for assembly + // padding. + final segmentAlignment = 16 * 1024; + + if (elf != null) { + // Verify that the total size of the snapshot text and data sections is + // approximately the sum of the shallow sizes of all objects in the + // profile. As sections might be merged by the assembler when useAsm is + // true, we need to account for possible padding. + final textSections = elf.namedSections(".text"); + Expect.isNotEmpty(textSections); + Expect.isTrue( + textSections.length <= 2, "More text sections than expected"); + final dataSections = elf.namedSections(".rodata"); + Expect.isNotEmpty(dataSections); + Expect.isTrue( + dataSections.length <= 2, "More data sections than expected"); + + var actualSize = 0; + for (final section in textSections) { + actualSize += section.length; + } + for (final section in dataSections) { + actualSize += section.length; + } + + final mergedCount = (2 - textSections.length) + (2 - dataSections.length); + final possiblePadding = mergedCount * segmentAlignment; + + Expect.approxEquals( + expectedSize, + actualSize, + possiblePadding, + "section size failed on $description snapshot" + + (!useAsm ? ", but symbol size test passed" : "")); checkedSize = true; } if (stripUtil || stripFlag) { + // Verify that the actual size of the stripped snapshot is close to the + // sum of the shallow sizes of all objects in the profile. They will not + // be exactly equal because of global headers, padding, and non-text/data + // sections. var strippedSnapshotPath = snapshotPath; if (stripUtil) { strippedSnapshotPath = snapshotPath + '.stripped'; @@ -234,20 +281,18 @@ Future testAOT(String dillPath, print("Stripped snapshot generated at $strippedSnapshotPath."); } - // Verify that the actual size of the stripped snapshot is close to the - // sum of the shallow sizes of all objects in the profile. They will not - // be exactly equal because of global headers and padding. final actualSize = await File(strippedSnapshotPath).length(); - // See Elf::kPages in runtime/vm/elf.h, which is also used for assembly - // padding. - final segmentAlignment = 16 * 1024; // Not every byte is accounted for by the snapshot profile, and data and // instruction segments are padded to an alignment boundary. final tolerance = 0.03 * actualSize + 2 * segmentAlignment; - Expect.approxEquals(expectedSize, actualSize, tolerance, - "failed on $description snapshot"); + Expect.approxEquals( + expectedSize, + actualSize, + tolerance, + "total size check failed on $description snapshot" + + (elf != null ? ", but section size checks passed" : "")); checkedSize = true; } diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc index b99bc8afbd1..6802b928ab2 100644 --- a/runtime/vm/clustered_snapshot.cc +++ b/runtime/vm/clustered_snapshot.cc @@ -2481,6 +2481,13 @@ class WeakSerializationReferenceSerializationCluster intptr_t Count(Serializer* s) { return objects_.length(); } + void CreateArtificialTargetNodesIfNeeded(Serializer* s) { + for (intptr_t i = 0; i < objects_.length(); i++) { + WeakSerializationReferencePtr weak = objects_[i]; + s->CreateArtificialNodeIfNeeded(weak->untag()->target()); + } + } + void WriteAlloc(Serializer* s) { UNREACHABLE(); // No WSRs are serialized, and so this cluster is not added. } @@ -6268,34 +6275,29 @@ void Serializer::AttributeReference( ObjectPtr object, const V8SnapshotProfileWriter::Reference& reference) { if (profile_writer_ == nullptr) return; - + const auto& object_id = GetProfileId(object); #if defined(DART_PRECOMPILER) - // Make artificial nodes for dropped targets in WSRs. if (object->IsHeapObject() && object->IsWeakSerializationReference()) { - const auto& wsr = WeakSerializationReference::RawCast(object); - const auto& target = wsr->untag()->target(); - const bool wsr_reachable = !CreateArtificialNodeIfNeeded(wsr); - if (wsr_reachable && HasArtificialRef(target)) { - // The target has artificial information used for snapshot analysis and - // the replacement is part of the snapshot, so write information for both. - const auto& replacement = wsr->untag()->replacement(); + auto const wsr = WeakSerializationReference::RawCast(object); + auto const target = wsr->untag()->target(); + const auto& target_id = GetProfileId(target); + if (object_id != target_id) { + const auto& replacement_id = GetProfileId(wsr->untag()->replacement()); + ASSERT(object_id == replacement_id); + // The target of the WSR will be replaced in the snapshot, so write + // attributions for both the dropped target and for the replacement. profile_writer_->AttributeDroppedReferenceTo( - object_currently_writing_.id_, reference, GetProfileId(target), - GetProfileId(replacement)); + object_currently_writing_.id_, reference, target_id, replacement_id); return; } - // The replacement isn't used, as either the target is strongly referenced - // or the WSR itself is unreachable, so fall through to attributing a - // reference to the WSR (which shares a profile ID with the target). - ASSERT(GetProfileId(wsr) == GetProfileId(target)); - } else if (object_currently_writing_.id_.IsArtificial()) { - // We may need to recur when writing members of artificial nodes in - // CreateArtificialNodeIfNeeded. - CreateArtificialNodeIfNeeded(object); + // The replacement isn't used for this WSR in the snapshot, as either the + // target is strongly referenced or the WSR itself is unreachable, so fall + // through to attributing a reference to the WSR (which shares the profile + // ID of the target). } #endif profile_writer_->AttributeReferenceTo(object_currently_writing_.id_, - reference, GetProfileId(object)); + reference, object_id); } Serializer::WritingObjectScope::WritingObjectScope( @@ -6364,33 +6366,19 @@ bool Serializer::CreateArtificialNodeIfNeeded(ObjectPtr obj) { // UnsafeRefId will do lazy reference allocation for WSRs. intptr_t id = UnsafeRefId(obj); ASSERT(id != kUnallocatedReference); - if (IsArtificialReference(id)) { - return true; + if (id != kUnreachableReference) { + return IsArtificialReference(id); } if (obj->IsHeapObject() && obj->IsWeakSerializationReference()) { - // The object ID for the WSR may need lazy resolution. - if (id == kUnallocatedReference) { - id = UnsafeRefId(obj); - } - ASSERT(id != kUnallocatedReference); - // Create an artificial node for an unreachable target at this point, - // whether or not the WSR itself is reachable. - const auto& target = + auto const target = WeakSerializationReference::RawCast(obj)->untag()->target(); CreateArtificialNodeIfNeeded(target); - if (id == kUnreachableReference) { - ASSERT(HasArtificialRef(target)); - // We can safely set the WSR's object ID to the target's artificial one, - // as that won't make it look reachable. - heap_->SetObjectId(obj, heap_->GetObjectId(target)); - return true; - } - // The WSR is reachable, so continue to the IsAllocatedReference behavior. + // Since the WSR is unreachable, we can replace its id with whatever the + // ID of the target is, whether real or artificial. + id = heap_->GetObjectId(target); + heap_->SetObjectId(obj, id); + return IsArtificialReference(id); } - if (IsAllocatedReference(id)) { - return false; - } - ASSERT_EQUAL(id, kUnreachableReference); const char* type = nullptr; const char* name = nullptr; @@ -6490,6 +6478,7 @@ bool Serializer::CreateArtificialNodeIfNeeded(ObjectPtr obj) { id = AssignArtificialRef(obj); Serializer::WritingObjectScope scope(this, type, obj, name); for (const auto& link : links) { + CreateArtificialNodeIfNeeded(link.first); AttributeReference(link.first, link.second); } return true; @@ -7045,10 +7034,11 @@ ZoneGrowableArray* Serializer::Serialize(SerializationRoots* roots) { } #if defined(DART_PRECOMPILER) - if (auto const cluster = CID_CLUSTER(WeakSerializationReference)) { + auto const wsr_cluster = CID_CLUSTER(WeakSerializationReference); + if (wsr_cluster != nullptr) { // Now that we have computed the reachability fixpoint, we remove the // count of now-reachable WSRs as they are not actually serialized. - num_written_objects_ -= cluster->Count(this); + num_written_objects_ -= wsr_cluster->Count(this); // We don't need to write this cluster, so remove it from consideration. clusters_by_cid_[kWeakSerializationReferenceCid] = nullptr; } @@ -7137,6 +7127,19 @@ ZoneGrowableArray* Serializer::Serialize(SerializationRoots* roots) { // And recorded them all in [objects_]. ASSERT(objects_->length() == num_objects); +#if defined(DART_PRECOMPILER) + if (profile_writer_ != nullptr && wsr_cluster != nullptr) { + // Post-WriteAlloc, we eagerly create artificial nodes for any unreachable + // targets in reachable WSRs if writing a v8 snapshot profile, since they + // will be used in AttributeReference(). + // + // Unreachable WSRs may also need artifical nodes, as they may be members + // of other unreachable objects that have artificial nodes in the profile, + // but they are instead lazily handled in CreateArtificialNodeIfNeeded(). + wsr_cluster->CreateArtificialTargetNodesIfNeeded(this); + } +#endif + for (SerializationCluster* cluster : clusters) { cluster->WriteAndMeasureFill(this); #if defined(DEBUG)