Version 2.14.3

* Cherry-pick 06bb7992d201993496f9b5cb617e5d4f7f6417dc to stable
* Cherry-pick 6207d1da351fcbe385d09625bf4cbe31ff16aaa0 to stable
* Cherry-pick d16ad3d64bee94ffa7de95e78d65d953e67a1a34 to stable
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4df1b37..1dbd93d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 2.14.3 - 2021-09-30
+
+This is a patch release that fixes:
+
+- a code completion performance regression [flutter/flutter-intellij#5761][].
+- debug information emitted by the Dart VM [#47289][].
+
+[flutter/flutter-intellij#5761]:
+  https://github.com/flutter/flutter-intellij/issues/5761
+[#47289]: https://github.com/dart-lang/sdk/issues/47289
+
 ## 2.14.2 - 2021-09-16
 
 This is a patch release that fixes:
diff --git a/pkg/analysis_server/lib/src/cider/completion.dart b/pkg/analysis_server/lib/src/cider/completion.dart
index 458ce08..8591b99 100644
--- a/pkg/analysis_server/lib/src/cider/completion.dart
+++ b/pkg/analysis_server/lib/src/cider/completion.dart
@@ -99,7 +99,6 @@
             return await manager.computeSuggestions(
               performance,
               completionRequest,
-              enableImportedReferenceContributor: false,
               enableOverrideContributor: false,
               enableUriContributor: false,
             );
diff --git a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
index 54ffabf..dce10de 100644
--- a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
+++ b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
@@ -38,9 +38,8 @@
   ) {
     int relevance;
     if (importedUriSet.contains(library.uri)) {
-      return;
-    }
-    if (library.isDeprecated) {
+      relevance = importedRelevance;
+    } else if (library.isDeprecated) {
       relevance = deprecatedRelevance;
     } else {
       relevance = otherwiseRelevance;
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index b71fa3e..8ee5426 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -92,7 +92,6 @@
   Future<List<CompletionSuggestion>> computeSuggestions(
     OperationPerformanceImpl performance,
     CompletionRequest request, {
-    bool enableImportedReferenceContributor = true,
     bool enableOverrideContributor = true,
     bool enableUriContributor = true,
     CompletionPreference? completionPreference,
@@ -131,7 +130,6 @@
       CombinatorContributor(),
       ExtensionMemberContributor(),
       FieldFormalContributor(),
-      if (enableImportedReferenceContributor) ImportedReferenceContributor(),
       KeywordContributor(),
       LabelContributor(),
       LibraryMemberContributor(),
@@ -150,6 +148,8 @@
     if (includedElementKinds != null) {
       _addIncludedElementKinds(dartRequest);
       _addIncludedSuggestionRelevanceTags(dartRequest);
+    } else {
+      contributors.add(ImportedReferenceContributor());
     }
 
     try {
diff --git a/pkg/analysis_server/test/client/completion_driver_test.dart b/pkg/analysis_server/test/client/completion_driver_test.dart
index 13d807c..657cc19 100644
--- a/pkg/analysis_server/test/client/completion_driver_test.dart
+++ b/pkg/analysis_server/test/client/completion_driver_test.dart
@@ -71,7 +71,6 @@
   }
 
   void assertSuggestion({
-    bool allowMultiple = false,
     required String completion,
     ElementKind? element,
     CompletionSuggestionKind? kind,
@@ -79,7 +78,6 @@
   }) {
     expect(
         suggestionWith(
-          allowMultiple: allowMultiple,
           completion: completion,
           element: element,
           kind: kind,
@@ -184,7 +182,6 @@
           completion: completion, element: element, kind: kind, file: file));
 
   CompletionSuggestion suggestionWith({
-    bool allowMultiple = false,
     required String completion,
     ElementKind? element,
     CompletionSuggestionKind? kind,
@@ -192,9 +189,7 @@
   }) {
     final matches = suggestionsWith(
         completion: completion, element: element, kind: kind, file: file);
-    if (!allowMultiple) {
-      expect(matches, hasLength(1));
-    }
+    expect(matches, hasLength(1));
     return matches.first;
   }
 
@@ -269,10 +264,7 @@
 }
 ''');
 
-    // TODO(brianwilkerson) There should be a single suggestion here after we
-    //  figure out how to stop the duplication.
     assertSuggestion(
-        allowMultiple: true,
         completion: 'A',
         element: ElementKind.CONSTRUCTOR,
         kind: CompletionSuggestionKind.INVOCATION);
@@ -296,11 +288,7 @@
   ^
 }
 ''');
-
-    // TODO(brianwilkerson) There should be a single suggestion here after we
-    //  figure out how to stop the duplication.
     assertSuggestion(
-        allowMultiple: true,
         completion: 'E.e',
         element: ElementKind.ENUM_CONSTANT,
         kind: CompletionSuggestionKind.INVOCATION);
@@ -325,10 +313,7 @@
 }
 ''');
 
-    // TODO(brianwilkerson) There should be a single suggestion here after we
-    //  figure out how to stop the duplication.
     assertSuggestion(
-        allowMultiple: true,
         completion: 'A.a',
         element: ElementKind.CONSTRUCTOR,
         kind: CompletionSuggestionKind.INVOCATION);
@@ -656,15 +641,13 @@
 }
 ''');
 
-    // TODO(brianwilkerson) There should be a single suggestion here after we
-    //  figure out how to stop the duplication.
     expect(
         suggestionsWith(
             completion: 'Future.value',
             file: '/sdk/lib/async/async.dart',
             element: ElementKind.CONSTRUCTOR,
             kind: CompletionSuggestionKind.INVOCATION),
-        hasLength(2));
+        hasLength(1));
   }
 
   Future<void> test_sdk_lib_suggestions() async {
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 0328fa0..9ccf020 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -1560,7 +1560,7 @@
     expect(completions, hasLength(1));
     final resolved = await resolveCompletion(completions.first);
     // It should not include auto-import text since it's already imported.
-    expect(resolved.detail, isNot(contains('Auto import from')));
+    expect(resolved.detail, isNull);
   }
 
   Future<void> test_suggestionSets_filtersOutAlreadyImportedSymbols() async {
diff --git a/pkg/analysis_server/test/services/completion/dart/relevance/bool_assignment_test.dart b/pkg/analysis_server/test/services/completion/dart/relevance/bool_assignment_test.dart
index b796a44..039c915 100644
--- a/pkg/analysis_server/test/services/completion/dart/relevance/bool_assignment_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/relevance/bool_assignment_test.dart
@@ -15,13 +15,7 @@
 
 @reflectiveTest
 class BoolAssignmentTest extends CompletionRelevanceTest {
-  @failingTest
   Future<void> test_boolLiterals_imported() async {
-    // TODO(brianwilkerson) This test is arguably invalid given that there's no
-    //  data to suggest that the boolean keywords should appear before a
-    //  constructor in the list, but it does seem likely to be valid in this
-    //  case, so we should investigate features that might cause this test to
-    //  start passing again.
     await addTestFile('''
 foo() {
   bool b;
diff --git a/pkg/native_stack_traces/CHANGELOG.md b/pkg/native_stack_traces/CHANGELOG.md
index 7c60f28..7a0f2b5 100644
--- a/pkg/native_stack_traces/CHANGELOG.md
+++ b/pkg/native_stack_traces/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 0.4.4
+
+- Added handling of dynamic tables for testing.
+
 ## 0.4.3
 
 - Exported some more of the ELF utilities for use in Dart tests.
diff --git a/pkg/native_stack_traces/lib/elf.dart b/pkg/native_stack_traces/lib/elf.dart
index 3b1624b..3316655 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, Section, Symbol;
+export 'src/elf.dart' show DynamicTable, DynamicTableTag, Elf, Section, Symbol;
 export 'src/constants.dart'
     show
         isolateDataSymbolName,
diff --git a/pkg/native_stack_traces/lib/src/elf.dart b/pkg/native_stack_traces/lib/src/elf.dart
index 548b5ac..268d5d80 100644
--- a/pkg/native_stack_traces/lib/src/elf.dart
+++ b/pkg/native_stack_traces/lib/src/elf.dart
@@ -214,7 +214,10 @@
   static const _ELFDATA2MSB = 0x02;
 
   void writeToStringBuffer(StringBuffer buffer) {
-    buffer..write('Format is ')..write(wordSize * 8)..write(' bits');
+    buffer
+      ..write('Format is ')
+      ..write(wordSize * 8)
+      ..write(' bits');
     switch (endian) {
       case Endian.little:
         buffer..writeln(' and little-endian');
@@ -342,6 +345,15 @@
   int get length => _entries.length;
   ProgramHeaderEntry operator [](int index) => _entries[index];
 
+  ProgramHeaderEntry? loadSegmentFor(int address) {
+    for (final entry in _entries) {
+      if (entry.vaddr <= address && address <= entry.vaddr + entry.memsz) {
+        return entry;
+      }
+    }
+    return null;
+  }
+
   static ProgramHeader fromReader(Reader reader, ElfHeader header) {
     final programReader = reader.refocusedCopy(
         header.programHeaderOffset, header.programHeaderSize);
@@ -352,7 +364,10 @@
 
   void writeToStringBuffer(StringBuffer buffer) {
     for (var i = 0; i < length; i++) {
-      if (i != 0) buffer..writeln()..writeln();
+      if (i != 0)
+        buffer
+          ..writeln()
+          ..writeln();
       buffer
         ..write('Entry ')
         ..write(i)
@@ -422,6 +437,17 @@
   static const _SHT_NOBITS = 8;
   static const _SHT_DYNSYM = 11;
 
+  // sh_flags constants from ELF specification.
+  static const _SHF_WRITE = 0x1;
+  static const _SHF_ALLOC = 0x2;
+  static const _SHF_EXECINSTR = 0x4;
+
+  bool get isWritable => flags & _SHF_WRITE != 0;
+  bool get isAllocated => flags & _SHF_ALLOC != 0;
+  bool get isExecutable => flags & _SHF_EXECINSTR != 0;
+
+  bool get hasBits => type != _SHT_NOBITS;
+
   void setName(StringTable nameTable) {
     name = nameTable[nameIndex]!;
   }
@@ -495,7 +521,10 @@
 
   void writeToStringBuffer(StringBuffer buffer) {
     for (var i = 0; i < entries.length; i++) {
-      if (i != 0) buffer..writeln()..writeln();
+      if (i != 0)
+        buffer
+          ..writeln()
+          ..writeln();
       buffer
         ..write('Entry ')
         ..write(i)
@@ -536,6 +565,8 @@
         return SymbolTable.fromReader(reader, entry);
       case SectionHeaderEntry._SHT_NOTE:
         return Note.fromReader(reader, entry);
+      case SectionHeaderEntry._SHT_DYNAMIC:
+        return DynamicTable.fromReader(reader, entry);
       default:
         return Section._(entry);
     }
@@ -713,7 +744,10 @@
   SymbolVisibility get visibility => SymbolVisibility.values[other & 0x03];
 
   void writeToStringBuffer(StringBuffer buffer) {
-    buffer..write('"')..write(name)..write('" =>');
+    buffer
+      ..write('"')
+      ..write(name)
+      ..write('" =>');
     switch (bind) {
       case SymbolBinding.STB_GLOBAL:
         buffer..write(' a global');
@@ -794,6 +828,109 @@
   }
 }
 
+/// Represents d_tag constants from ELF specification.
+enum DynamicTableTag {
+  DT_NULL,
+  DT_NEEDED,
+  DT_PLTRELSZ,
+  DT_PLTGOT,
+  DT_HASH,
+  DT_STRTAB,
+  DT_SYMTAB,
+  DT_RELA,
+  DT_RELASZ,
+  DT_RELAENT,
+  DT_STRSZ,
+  DT_SYMENT,
+  // Later d_tag values are not currently used in Dart ELF files.
+}
+
+/// The dynamic table, which contains entries pointing to various relocated
+/// addresses.
+class DynamicTable extends Section {
+  // We don't use DynamicTableTag for the key so that we can handle ELF files
+  // that may use unknown (to us) tags.
+  final Map<int, int> _entries;
+  final _wordSize;
+
+  DynamicTable._(SectionHeaderEntry entry, this._entries, this._wordSize)
+      : super._(entry);
+
+  static DynamicTable fromReader(Reader reader, SectionHeaderEntry entry) {
+    final sectionReader = reader.refocusedCopy(entry.offset, entry.size);
+    final entries = <int, int>{};
+    while (true) {
+      // Each entry is a tag and a value, both native word sized.
+      final tag = _readElfNative(sectionReader);
+      final value = _readElfNative(sectionReader);
+      // A DT_NULL entry signfies the end of entries.
+      if (tag == DynamicTableTag.DT_NULL.index) break;
+      entries[tag] = value;
+    }
+    return DynamicTable._(entry, entries, sectionReader.wordSize);
+  }
+
+  int? operator [](DynamicTableTag tag) => _entries[tag.index];
+  bool containsKey(DynamicTableTag tag) => _entries.containsKey(tag.index);
+
+  // To avoid depending on EnumName.name from 2.15.
+  static const _tagStrings = {
+    DynamicTableTag.DT_NULL: 'DT_NULL',
+    DynamicTableTag.DT_NEEDED: 'DT_NEEDED',
+    DynamicTableTag.DT_PLTRELSZ: 'DT_PLTRELSZ',
+    DynamicTableTag.DT_PLTGOT: 'DT_PLTGOT',
+    DynamicTableTag.DT_HASH: 'DT_HASH',
+    DynamicTableTag.DT_STRTAB: 'DT_STRTAB',
+    DynamicTableTag.DT_SYMTAB: 'DT_SYMTAB',
+    DynamicTableTag.DT_RELA: 'DT_RELA',
+    DynamicTableTag.DT_RELASZ: 'DT_RELASZ',
+    DynamicTableTag.DT_STRSZ: 'DT_STRSZ',
+    DynamicTableTag.DT_SYMENT: 'DT_SYMENT',
+  };
+  static final _maxTagStringLength = (_tagStrings.values.toList()
+        ..sort((s1, s2) => s2.length - s1.length))
+      .first
+      .length;
+
+  @override
+  void writeToStringBuffer(StringBuffer buffer) {
+    buffer
+      ..write('Section "')
+      ..write(headerEntry.name)
+      ..writeln('" is a dynamic table:');
+    for (var kv in _entries.entries) {
+      buffer.write(' ');
+      if (kv.key < DynamicTableTag.values.length) {
+        final tag = DynamicTableTag.values[kv.key];
+        buffer
+          ..write(_tagStrings[tag]?.padRight(_maxTagStringLength))
+          ..write(' => ');
+        switch (tag) {
+          // These are relocated addresses.
+          case DynamicTableTag.DT_HASH:
+          case DynamicTableTag.DT_PLTGOT:
+          case DynamicTableTag.DT_SYMTAB:
+          case DynamicTableTag.DT_STRTAB:
+          case DynamicTableTag.DT_RELA:
+            buffer
+              ..write('0x')
+              ..writeln(paddedHex(kv.value, _wordSize));
+            break;
+          // Other entries are just values or offsets.
+          default:
+            buffer.writeln(kv.value);
+        }
+      } else {
+        buffer
+          ..write("Unknown tag ")
+          ..write(kv.key)
+          ..write(' => ')
+          ..writeln(kv.value);
+      }
+    }
+  }
+}
+
 /// Information parsed from an Executable and Linking Format (ELF) file.
 class Elf {
   final ElfHeader _header;
@@ -819,6 +956,21 @@
   Iterable<Section> namedSections(String name) =>
       _sectionsByName[name] ?? <Section>[];
 
+  /// Checks that the contents of a given section have valid addresses when the
+  /// file contents for the corresponding segment is loaded into memory.
+  ///
+  /// Returns false for sections that are not allocated or where the address
+  /// does not correspond to file contents (i.e., NOBITS sections).
+  bool sectionHasValidSegmentAddresses(Section section) {
+    final headerEntry = section.headerEntry;
+    if (!headerEntry.isAllocated || !headerEntry.hasBits) return false;
+    final segment = _programHeader.loadSegmentFor(headerEntry.addr);
+    if (segment == null) return false;
+    return (headerEntry.addr < (segment.vaddr + segment.filesz)) &&
+        (headerEntry.addr + headerEntry.size) <=
+            (segment.vaddr + segment.filesz);
+  }
+
   /// Lookup of a dynamic symbol by name.
   ///
   /// Returns -1 if there is no dynamic symbol that matches [name].
diff --git a/pkg/native_stack_traces/pubspec.yaml b/pkg/native_stack_traces/pubspec.yaml
index bdf1f94..7dbc3b2 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.3
+version: 0.4.4
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/native_stack_traces
 
diff --git a/runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart b/runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart
index 091087d..2bd55a2 100644
--- a/runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart
+++ b/runtime/tests/vm/dart/use_save_debugging_info_flag_test.dart
@@ -13,6 +13,7 @@
 import "dart:typed_data";
 
 import 'package:expect/expect.dart';
+import 'package:native_stack_traces/elf.dart';
 import 'package:native_stack_traces/native_stack_traces.dart';
 import 'package:path/path.dart' as path;
 
@@ -63,6 +64,7 @@
       '--elf=$scriptWholeSnapshot',
       scriptDill,
     ]);
+    checkElf(scriptWholeSnapshot);
 
     final scriptStrippedOnlySnapshot = path.join(tempDir, 'stripped_only.so');
     await run(genSnapshot, <String>[
@@ -72,6 +74,7 @@
       '--strip',
       scriptDill,
     ]);
+    checkElf(scriptStrippedOnlySnapshot);
 
     final scriptStrippedSnapshot = path.join(tempDir, 'stripped.so');
     final scriptDebuggingInfo = path.join(tempDir, 'debug.so');
@@ -83,6 +86,8 @@
       '--save-debugging-info=$scriptDebuggingInfo',
       scriptDill,
     ]);
+    checkElf(scriptStrippedSnapshot);
+    checkElf(scriptDebuggingInfo);
 
     // Run the resulting scripts, saving the stack traces.
     final wholeTrace = await runError(aotRuntime, <String>[
@@ -184,3 +189,46 @@
     }
   }
 }
+
+void checkElf(String filename) {
+  print("Checking ELF file $filename:");
+  final elf = Elf.fromFile(filename);
+  Expect.isNotNull(elf);
+  final dynamic = elf!.namedSections(".dynamic").single as DynamicTable;
+
+  // Check the dynamic string table information.
+  Expect.isTrue(
+      dynamic.containsKey(DynamicTableTag.DT_STRTAB), "no string table entry");
+  final dynstr = elf.namedSections(".dynstr").single;
+  print(".dynstr address = ${dynamic[DynamicTableTag.DT_STRTAB]}");
+  Expect.isTrue(elf.sectionHasValidSegmentAddresses(dynstr),
+      "string table addresses are invalid");
+  Expect.equals(dynamic[DynamicTableTag.DT_STRTAB], dynstr.headerEntry.addr);
+  Expect.isTrue(dynamic.containsKey(DynamicTableTag.DT_STRSZ),
+      "no string table size entry");
+  print(".dynstr size = ${dynamic[DynamicTableTag.DT_STRSZ]}");
+  Expect.equals(dynamic[DynamicTableTag.DT_STRSZ], dynstr.headerEntry.size);
+
+  // Check the dynamic symbol table information.
+  Expect.isTrue(
+      dynamic.containsKey(DynamicTableTag.DT_SYMTAB), "no symbol table entry");
+  print(".dynsym address = ${dynamic[DynamicTableTag.DT_SYMTAB]}");
+  final dynsym = elf.namedSections(".dynsym").single;
+  Expect.isTrue(elf.sectionHasValidSegmentAddresses(dynsym),
+      "string table addresses are invalid");
+  Expect.equals(dynamic[DynamicTableTag.DT_SYMTAB], dynsym.headerEntry.addr);
+  Expect.isTrue(dynamic.containsKey(DynamicTableTag.DT_SYMENT),
+      "no symbol table entry size entry");
+  print(".dynsym entry size = ${dynamic[DynamicTableTag.DT_SYMENT]}");
+  Expect.equals(
+      dynamic[DynamicTableTag.DT_SYMENT], dynsym.headerEntry.entrySize);
+
+  // Check the hash table information.
+  Expect.isTrue(
+      dynamic.containsKey(DynamicTableTag.DT_HASH), "no hash table entry");
+  print(".hash address = ${dynamic[DynamicTableTag.DT_HASH]}");
+  final hash = elf.namedSections(".hash").single;
+  Expect.isTrue(elf.sectionHasValidSegmentAddresses(hash),
+      "hash table addresses are invalid");
+  Expect.equals(dynamic[DynamicTableTag.DT_HASH], hash.headerEntry.addr);
+}
diff --git a/runtime/tests/vm/dart_2/use_save_debugging_info_flag_test.dart b/runtime/tests/vm/dart_2/use_save_debugging_info_flag_test.dart
index 1a21971..0af1e8c 100644
--- a/runtime/tests/vm/dart_2/use_save_debugging_info_flag_test.dart
+++ b/runtime/tests/vm/dart_2/use_save_debugging_info_flag_test.dart
@@ -13,6 +13,7 @@
 import "dart:typed_data";
 
 import 'package:expect/expect.dart';
+import 'package:native_stack_traces/elf.dart';
 import 'package:native_stack_traces/native_stack_traces.dart';
 import 'package:path/path.dart' as path;
 
@@ -63,6 +64,7 @@
       '--elf=$scriptWholeSnapshot',
       scriptDill,
     ]);
+    checkElf(scriptWholeSnapshot);
 
     final scriptStrippedOnlySnapshot = path.join(tempDir, 'stripped_only.so');
     await run(genSnapshot, <String>[
@@ -72,6 +74,7 @@
       '--strip',
       scriptDill,
     ]);
+    checkElf(scriptStrippedOnlySnapshot);
 
     final scriptStrippedSnapshot = path.join(tempDir, 'stripped.so');
     final scriptDebuggingInfo = path.join(tempDir, 'debug.so');
@@ -83,6 +86,8 @@
       '--save-debugging-info=$scriptDebuggingInfo',
       scriptDill,
     ]);
+    checkElf(scriptStrippedSnapshot);
+    checkElf(scriptDebuggingInfo);
 
     // Run the resulting scripts, saving the stack traces.
     final wholeTrace = await runError(aotRuntime, <String>[
@@ -184,3 +189,46 @@
     }
   }
 }
+
+void checkElf(String filename) {
+  print("Checking ELF file $filename:");
+  final elf = Elf.fromFile(filename);
+  Expect.isNotNull(elf);
+  final dynamic = elf.namedSections(".dynamic").single as DynamicTable;
+
+  // Check the dynamic string table information.
+  Expect.isTrue(
+      dynamic.containsKey(DynamicTableTag.DT_STRTAB), "no string table entry");
+  final dynstr = elf.namedSections(".dynstr").single;
+  print(".dynstr address = ${dynamic[DynamicTableTag.DT_STRTAB]}");
+  Expect.isTrue(elf.sectionHasValidSegmentAddresses(dynstr),
+      "string table addresses are invalid");
+  Expect.equals(dynamic[DynamicTableTag.DT_STRTAB], dynstr.headerEntry.addr);
+  Expect.isTrue(dynamic.containsKey(DynamicTableTag.DT_STRSZ),
+      "no string table size entry");
+  print(".dynstr size = ${dynamic[DynamicTableTag.DT_STRSZ]}");
+  Expect.equals(dynamic[DynamicTableTag.DT_STRSZ], dynstr.headerEntry.size);
+
+  // Check the dynamic symbol table information.
+  Expect.isTrue(
+      dynamic.containsKey(DynamicTableTag.DT_SYMTAB), "no symbol table entry");
+  print(".dynsym address = ${dynamic[DynamicTableTag.DT_SYMTAB]}");
+  final dynsym = elf.namedSections(".dynsym").single;
+  Expect.isTrue(elf.sectionHasValidSegmentAddresses(dynsym),
+      "string table addresses are invalid");
+  Expect.equals(dynamic[DynamicTableTag.DT_SYMTAB], dynsym.headerEntry.addr);
+  Expect.isTrue(dynamic.containsKey(DynamicTableTag.DT_SYMENT),
+      "no symbol table entry size entry");
+  print(".dynsym entry size = ${dynamic[DynamicTableTag.DT_SYMENT]}");
+  Expect.equals(
+      dynamic[DynamicTableTag.DT_SYMENT], dynsym.headerEntry.entrySize);
+
+  // Check the hash table information.
+  Expect.isTrue(
+      dynamic.containsKey(DynamicTableTag.DT_HASH), "no hash table entry");
+  print(".hash address = ${dynamic[DynamicTableTag.DT_HASH]}");
+  final hash = elf.namedSections(".hash").single;
+  Expect.isTrue(elf.sectionHasValidSegmentAddresses(hash),
+      "hash table addresses are invalid");
+  Expect.equals(dynamic[DynamicTableTag.DT_HASH], hash.headerEntry.addr);
+}
diff --git a/runtime/vm/elf.cc b/runtime/vm/elf.cc
index a4f550f..28e8224 100644
--- a/runtime/vm/elf.cc
+++ b/runtime/vm/elf.cc
@@ -173,6 +173,8 @@
   }
   bool IsWritable() const { return (flags & elf::SHF_WRITE) == elf::SHF_WRITE; }
 
+  bool HasBits() const { return type != elf::SectionHeaderType::SHT_NOBITS; }
+
   // Returns whether the size of a section can change.
   bool HasBeenFinalized() const {
     // Sections can grow or shrink up until Elf::ComputeOffsets has been run,
@@ -1559,8 +1561,27 @@
   // We now do several passes over the collected sections to reorder them in
   // a way that minimizes segments (and thus padding) in the resulting snapshot.
 
-  // If a build ID was created, we put it after the program table so it can
-  // be read with a minimum number of bytes from the ELF file.
+  auto add_sections_matching =
+      [&](const std::function<bool(Section*)>& should_add) {
+        // We order the sections in a segment so all non-NOBITS sections come
+        // before NOBITS sections, since the former sections correspond to the
+        // file contents for the segment.
+        for (auto* const section : sections_) {
+          if (!section->HasBits()) continue;
+          if (should_add(section)) {
+            add_to_reordered_sections(section);
+          }
+        }
+        for (auto* const section : sections_) {
+          if (section->HasBits()) continue;
+          if (should_add(section)) {
+            add_to_reordered_sections(section);
+          }
+        }
+      };
+
+  // If a build ID was created, we put it right after the program table so it
+  // can be read with a minimum number of bytes from the ELF file.
   auto* const build_id = Find(Elf::kBuildIdNoteName);
   if (build_id != nullptr) {
     ASSERT(build_id->type == elf::SectionHeaderType::SHT_NOTE);
@@ -1568,38 +1589,31 @@
   }
 
   // Now add the other non-writable, non-executable allocated sections.
-  for (auto* const section : sections_) {
-    if (section == build_id) continue;  // Already added.
-    if (section->IsAllocated() && !section->IsWritable() &&
-        !section->IsExecutable()) {
-      add_to_reordered_sections(section);
-    }
-  }
+  add_sections_matching([&](Section* section) -> bool {
+    if (section == build_id) return false;  // Already added.
+    return section->IsAllocated() && !section->IsWritable() &&
+           !section->IsExecutable();
+  });
 
   // Now add the executable sections in a new segment.
-  for (auto* const section : sections_) {
-    if (section->IsExecutable()) {  // Implies IsAllocated() && !IsWritable()
-      add_to_reordered_sections(section);
-    }
-  }
+  add_sections_matching([](Section* section) -> bool {
+    return section->IsExecutable();  // Implies IsAllocated() && !IsWritable()
+  });
 
   // Now add all the writable sections.
-  for (auto* const section : sections_) {
-    if (section->IsWritable()) {  // Implies IsAllocated() && !IsExecutable()
-      add_to_reordered_sections(section);
-    }
-  }
+  add_sections_matching([](Section* section) -> bool {
+    return section->IsWritable();  // Implies IsAllocated() && !IsExecutable()
+  });
 
   // We put all non-reserved unallocated sections last. Otherwise, they would
   // affect the file offset but not the memory offset of any following allocated
   // sections. Doing it in this order makes it easier to keep file and memory
   // offsets page-aligned with respect to each other, which is required for
   // some loaders.
-  for (intptr_t i = 1; i < num_sections; i++) {
-    auto* const section = sections_[i];
-    if (section->IsAllocated()) continue;
-    add_to_reordered_sections(section);
-  }
+  add_sections_matching([](Section* section) -> bool {
+    // Don't re-add the initial reserved section.
+    return !section->IsReservedSection() && !section->IsAllocated();
+  });
 
   // All sections should have been accounted for in the loops above.
   ASSERT_EQUAL(sections_.length(), reordered_sections.length());
diff --git a/tools/VERSION b/tools/VERSION
index 4444ab4..d26e6ca 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -26,6 +26,6 @@
 CHANNEL stable
 MAJOR 2
 MINOR 14
-PATCH 2
+PATCH 3
 PRERELEASE 0
 PRERELEASE_PATCH 0
\ No newline at end of file