[vm/aot] Add a GNU build ID to direct-to-ELF snapshots.
For proper crashpad integration, we need to generate a build ID, as the
build ID generated by crashpad if there is not one will be a simple XOR
of the first text page, which rarely changes for Dart snapshots.
Assembly snapshots already have a build ID included by the assembler, so
we currently only do this for ELF snapshots.
Currently the build ID is a 128-bit hash value that is four separate
32-bit hash values concatenated together. Those hash values come from
the contents of the VM and isolate .text and .rodata sections.
This change also contains work to separate out the concepts of sections
and segments in the ELF builder. Now, consecutive allocated sections
with the same write and execute flags are combined into a single PT_LOAD
segment when possible, which reduces the padding needed to ensure that
segments start on page boundaries in ELF snapshots.
Bug: https://github.com/dart-lang/sdk/issues/42020
Change-Id: I42a837dae665a3902d881b8d151b49ede87d6c67
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-release-simarm_x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150625
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/native_stack_traces/CHANGELOG.md b/pkg/native_stack_traces/CHANGELOG.md
index 9e00323..10e8638 100644
--- a/pkg/native_stack_traces/CHANGELOG.md
+++ b/pkg/native_stack_traces/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## 0.3.7
+
+- Added buildId accessor for retrieving GNU build IDs from DWARF files that
+ include them.
+
## 0.3.6
- Adjusts RegExp for stack trace header line to be more flexible in what it
diff --git a/pkg/native_stack_traces/lib/src/constants.dart b/pkg/native_stack_traces/lib/src/constants.dart
index 980e8e0..c44d615 100644
--- a/pkg/native_stack_traces/lib/src/constants.dart
+++ b/pkg/native_stack_traces/lib/src/constants.dart
@@ -2,6 +2,13 @@
// 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.
+// The section name in which the build ID is stored as a note.
+const String buildIdSectionName = ".note.gnu.build-id";
+// The type of a build ID note.
+const int buildIdNoteType = 3;
+// The name of a build ID note.
+const String buildIdNoteName = "GNU";
+
// The dynamic symbol name for the VM instructions section.
const String vmSymbolName = "_kDartVmSnapshotInstructions";
diff --git a/pkg/native_stack_traces/lib/src/dwarf.dart b/pkg/native_stack_traces/lib/src/dwarf.dart
index 96b93c9..72f7623 100644
--- a/pkg/native_stack_traces/lib/src/dwarf.dart
+++ b/pkg/native_stack_traces/lib/src/dwarf.dart
@@ -1214,6 +1214,18 @@
vmStartAddress, isolateStartAddress);
}
+ /// The build ID for the debugging information.
+ ///
+ /// Returns null if there is no build ID information recorded.
+ String get buildId {
+ final sections = _elf.namedSections(constants.buildIdSectionName);
+ if (sections.isEmpty) return null;
+ final Note note = sections.single;
+ if (note.type != constants.buildIdNoteType) return null;
+ if (note.name != constants.buildIdNoteName) return null;
+ return note.description.map((i) => i.toRadixString(16)).join();
+ }
+
/// The call information for the given virtual address. There may be
/// multiple [CallInfo] objects returned for a single virtual address when
/// code has been inlined.
diff --git a/pkg/native_stack_traces/lib/src/elf.dart b/pkg/native_stack_traces/lib/src/elf.dart
index cee46f0..b0c7c54 100644
--- a/pkg/native_stack_traces/lib/src/elf.dart
+++ b/pkg/native_stack_traces/lib/src/elf.dart
@@ -322,7 +322,7 @@
..writeln(paddedHex(paddr, wordSize))
..write('Size in file: ')
..writeln(filesz)
- ..write('Size in memory')
+ ..write('Size in memory: ')
..writeln(memsz)
..write('Alignment: 0x')
..write(paddedHex(align, wordSize));
@@ -421,6 +421,7 @@
static const _SHT_STRTAB = 3;
static const _SHT_HASH = 5;
static const _SHT_DYNAMIC = 6;
+ static const _SHT_NOTE = 7;
static const _SHT_NOBITS = 8;
static const _SHT_DYNSYM = 11;
@@ -437,6 +438,7 @@
_SHT_STRTAB: "SHT_STRTAB",
_SHT_HASH: "SHT_HASH",
_SHT_DYNAMIC: "SHT_DYNAMIC",
+ _SHT_NOTE: "SHT_NOTE",
_SHT_NOBITS: "SHT_NOBITS",
_SHT_DYNSYM: "SHT_DYNSYM",
};
@@ -545,6 +547,8 @@
return SymbolTable.fromReader(reader, entry);
case SectionHeaderEntry._SHT_DYNSYM:
return SymbolTable.fromReader(reader, entry);
+ case SectionHeaderEntry._SHT_NOTE:
+ return Note.fromReader(reader, entry);
default:
return Section._(entry);
}
@@ -574,6 +578,55 @@
}
}
+/// A section that contains a single note.
+class Note extends Section {
+ final int type;
+ final String name;
+ final Uint8List description;
+
+ Note._(entry, this.type, this.name, this.description) : super._(entry);
+
+ static Note fromReader(Reader originalReader, SectionHeaderEntry entry) {
+ final reader = originalReader.refocusedCopy(entry.offset, entry.size);
+ final nameLength = reader.readBytes(4);
+ final descriptionLength = reader.readBytes(4);
+ final type = reader.readBytes(4);
+ final nameEnd = reader.offset + nameLength;
+ final name = reader.readNullTerminatedString();
+ assert(reader.offset == nameEnd);
+ assert(reader.length - reader.offset == descriptionLength);
+ final descriptionStart = reader.offset;
+ final descriptionEnd = descriptionStart + descriptionLength;
+ final description =
+ Uint8List.sublistView(reader.bdata, descriptionStart, descriptionEnd);
+ return Note._(entry, type, name, description);
+ }
+
+ void writeToStringBuffer(StringBuffer buffer) {
+ buffer
+ ..write('Section "')
+ ..write(headerEntry.name)
+ ..writeln('" is a note:');
+ buffer
+ ..write(' Type: ')
+ ..writeln(type);
+ buffer
+ ..write(' Name: "')
+ ..write(name)
+ ..writeln('"');
+ buffer
+ ..write(' Description: ')
+ ..writeln(description);
+ }
+
+ @override
+ String toString() {
+ final buffer = StringBuffer();
+ writeToStringBuffer(buffer);
+ return buffer.toString();
+ }
+}
+
/// A map from table offsets to strings, used to store names of ELF objects.
class StringTable extends Section {
final _entries;
diff --git a/pkg/native_stack_traces/pubspec.yaml b/pkg/native_stack_traces/pubspec.yaml
index 3b0ad2d..10a0ef8 100644
--- a/pkg/native_stack_traces/pubspec.yaml
+++ b/pkg/native_stack_traces/pubspec.yaml
@@ -1,11 +1,11 @@
name: native_stack_traces
description: Utilities for working with non-symbolic stack traces.
-version: 0.3.6
+version: 0.3.7
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/native_stack_traces
environment:
- sdk: '>=2.7.1 <3.0.0'
+ sdk: '>=2.8 <3.0.0'
executables:
decode:
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index 0e43ad9..56120e7 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -3524,17 +3524,20 @@
// Use the '...CSymbol' definitions for resolving through 'dlsym'. The actual
// symbol names in the objects are given by the '...AsmSymbol' definitions.
#if defined(__APPLE__)
+#define kSnapshotBuildIdCSymbol "kDartSnapshotBuildId"
#define kVmSnapshotDataCSymbol "kDartVmSnapshotData"
#define kVmSnapshotInstructionsCSymbol "kDartVmSnapshotInstructions"
#define kIsolateSnapshotDataCSymbol "kDartIsolateSnapshotData"
#define kIsolateSnapshotInstructionsCSymbol "kDartIsolateSnapshotInstructions"
#else
+#define kSnapshotBuildIdCSymbol "_kDartSnapshotBuildId"
#define kVmSnapshotDataCSymbol "_kDartVmSnapshotData"
#define kVmSnapshotInstructionsCSymbol "_kDartVmSnapshotInstructions"
#define kIsolateSnapshotDataCSymbol "_kDartIsolateSnapshotData"
#define kIsolateSnapshotInstructionsCSymbol "_kDartIsolateSnapshotInstructions"
#endif
+#define kSnapshotBuildIdAsmSymbol "_kDartSnapshotBuildId"
#define kVmSnapshotDataAsmSymbol "_kDartVmSnapshotData"
#define kVmSnapshotInstructionsAsmSymbol "_kDartVmSnapshotInstructions"
#define kIsolateSnapshotDataAsmSymbol "_kDartIsolateSnapshotData"
diff --git a/runtime/platform/elf.h b/runtime/platform/elf.h
index 39b8e81..42c2f87 100644
--- a/runtime/platform/elf.h
+++ b/runtime/platform/elf.h
@@ -36,8 +36,10 @@
};
enum class ProgramHeaderType : uint32_t {
+ PT_NULL = 0,
PT_LOAD = 1,
PT_DYNAMIC = 2,
+ PT_NOTE = 4,
PT_PHDR = 6,
};
@@ -63,10 +65,22 @@
#endif
};
+enum class SectionHeaderType : uint32_t {
+ SHT_NULL = 0,
+ SHT_PROGBITS = 1,
+ SHT_SYMTAB = 2,
+ SHT_STRTAB = 3,
+ SHT_HASH = 5,
+ SHT_NOTE = 7,
+ SHT_NOBITS = 8,
+ SHT_DYNAMIC = 6,
+ SHT_DYNSYM = 11,
+};
+
struct SectionHeader {
#if defined(TARGET_ARCH_IS_32_BIT)
uint32_t name;
- uint32_t type;
+ SectionHeaderType type;
uint32_t flags;
uint32_t memory_offset;
uint32_t file_offset;
@@ -77,7 +91,7 @@
uint32_t entry_size;
#else
uint32_t name;
- uint32_t type;
+ SectionHeaderType type;
uint64_t flags;
uint64_t memory_offset;
uint64_t file_offset;
@@ -107,6 +121,36 @@
#endif
};
+enum class DynamicEntryType : uint32_t {
+ DT_NULL = 0,
+ DT_HASH = 4,
+ DT_STRTAB = 5,
+ DT_SYMTAB = 6,
+ DT_STRSZ = 10,
+ DT_SYMENT = 11,
+};
+
+struct DynamicEntry {
+#if defined(TARGET_ARCH_IS_32_BIT)
+ uint32_t tag;
+ uint32_t value;
+#else
+ uint64_t tag;
+ uint64_t value;
+#endif
+};
+
+enum class NoteType : uint32_t {
+ NT_GNU_BUILD_ID = 3,
+};
+
+struct Note {
+ uint32_t name_size;
+ uint32_t description_size;
+ NoteType type;
+ uint8_t data[];
+};
+
#pragma pack(pop)
static constexpr intptr_t ELFCLASS32 = 1;
@@ -134,15 +178,6 @@
static const intptr_t PF_W = 2;
static const intptr_t PF_R = 4;
-static const intptr_t SHT_NULL = 0;
-static const intptr_t SHT_PROGBITS = 1;
-static const intptr_t SHT_SYMTAB = 2;
-static const intptr_t SHT_STRTAB = 3;
-static const intptr_t SHT_HASH = 5;
-static const intptr_t SHT_NOBITS = 8;
-static const intptr_t SHT_DYNAMIC = 6;
-static const intptr_t SHT_DYNSYM = 11;
-
static const intptr_t SHF_WRITE = 0x1;
static const intptr_t SHF_ALLOC = 0x2;
static const intptr_t SHF_EXECINSTR = 0x4;
@@ -151,11 +186,6 @@
static const intptr_t STN_UNDEF = 0;
-static const intptr_t PT_NULL = 0;
-static const intptr_t PT_LOAD = 1;
-static const intptr_t PT_DYNAMIC = 2;
-static const intptr_t PT_PHDR = 6;
-
static const intptr_t STB_LOCAL = 0;
static const intptr_t STB_GLOBAL = 1;
@@ -163,12 +193,7 @@
static const intptr_t STT_FUNC = 2;
static const intptr_t STT_SECTION = 3;
-static const intptr_t DT_NULL = 0;
-static const intptr_t DT_HASH = 4;
-static const intptr_t DT_STRTAB = 5;
-static const intptr_t DT_SYMTAB = 6;
-static const intptr_t DT_STRSZ = 10;
-static const intptr_t DT_SYMENT = 11;
+static constexpr const char* ELF_NOTE_GNU = "GNU";
} // namespace elf
} // namespace dart
diff --git a/runtime/vm/elf.cc b/runtime/vm/elf.cc
index 0fd4084..2ce45e3 100644
--- a/runtime/vm/elf.cc
+++ b/runtime/vm/elf.cc
@@ -14,24 +14,6 @@
namespace dart {
-#if defined(TARGET_ARCH_IS_32_BIT)
-static const intptr_t kElfHeaderSize = 52;
-static const intptr_t kElfSectionTableAlignment = 4;
-static const intptr_t kElfSectionTableEntrySize = 40;
-static const intptr_t kElfProgramTableEntrySize = 32;
-static const intptr_t kElfSymbolTableEntrySize = 16;
-static const intptr_t kElfDynamicTableEntrySize = 8;
-static const intptr_t kElfSymbolHashTableEntrySize = 4;
-#else
-static const intptr_t kElfHeaderSize = 64;
-static const intptr_t kElfSectionTableAlignment = 8;
-static const intptr_t kElfSectionTableEntrySize = 64;
-static const intptr_t kElfProgramTableEntrySize = 56;
-static const intptr_t kElfSymbolTableEntrySize = 24;
-static const intptr_t kElfDynamicTableEntrySize = 16;
-static const intptr_t kElfSymbolHashTableEntrySize = 4;
-#endif
-
// A wrapper around StreamingWriteStream that provides methods useful for
// writing ELF files (e.g., using ELF definitions of data sizes).
class ElfWriteStream : public ValueObject {
@@ -72,164 +54,139 @@
StreamingWriteStream* const stream_;
};
-#define DEFINE_LINEAR_FIELD_METHODS(name, type, init) \
- type name() const { \
- ASSERT(name##_ != init); \
+static constexpr intptr_t kLinearInitValue = -1;
+
+#define DEFINE_LINEAR_FIELD_METHODS(name) \
+ intptr_t name() const { \
+ ASSERT(name##_ != kLinearInitValue); \
return name##_; \
} \
- void set_##name(type value) { \
- ASSERT_EQUAL(name##_, init); \
+ bool name##_is_set() const { return name##_ != kLinearInitValue; } \
+ void set_##name(intptr_t value) { \
+ ASSERT(value != kLinearInitValue); \
+ ASSERT_EQUAL(name##_, kLinearInitValue); \
name##_ = value; \
}
-#define DEFINE_LINEAR_FIELD(name, type, init) type name##_ = init;
+#define DEFINE_LINEAR_FIELD(name) intptr_t name##_ = kLinearInitValue;
-// Used for segments that should not be used as sections.
-static constexpr intptr_t kInvalidSection = -1;
+class BitsContainer;
+class Segment;
+
+static constexpr intptr_t kDefaultAlignment = -1;
+// Align note sections and segments to 4 byte boundries.
+static constexpr intptr_t kNoteAlignment = 4;
class Section : public ZoneAllocated {
public:
- Section(intptr_t type,
- intptr_t segment_type,
+ Section(elf::SectionHeaderType t,
bool allocate,
bool executable,
bool writable,
- intptr_t alignment = 1)
- : section_type(type),
- section_flags(EncodeSectionFlags(allocate, executable, writable)),
- alignment(allocate ? SegmentAlignment(alignment) : alignment),
- segment_type(segment_type),
- segment_flags(EncodeSegmentFlags(allocate, executable, writable)),
+ intptr_t align = kDefaultAlignment)
+ : type(t),
+ flags(EncodeFlags(allocate, executable, writable)),
+ alignment(align == kDefaultAlignment ? DefaultAlignment(t) : align),
// Non-segments will never have a memory offset, here represented by 0.
- memory_offset_(allocate ? -1 : 0) {
- // Only the reserved section (type 0) has (and must have) an alignment of 0.
- ASSERT(type == elf::SHT_NULL || alignment > 0);
- ASSERT(alignment == 0 || type != elf::SHT_NULL);
- // Segment-only entries should have a set segment_type.
- ASSERT(type != kInvalidSection || segment_type != elf::PT_NULL);
+ memory_offset_(allocate ? kLinearInitValue : 0) {
+ // Only sections with type SHT_NULL are allowed to have an alignment of 0.
+ ASSERT(type == elf::SectionHeaderType::SHT_NULL || alignment > 0);
+ // Non-zero alignments must be a power of 2.
+ ASSERT(alignment == 0 || Utils::IsPowerOfTwo(alignment));
}
- // The constructor that most subclasses will use.
- Section(intptr_t type,
- bool allocate,
- bool executable,
- bool writable,
- intptr_t alignment = 1)
- : Section(type,
- /*segment_type=*/allocate ? elf::PT_LOAD : elf::PT_NULL,
- allocate,
- executable,
- writable,
- alignment) {}
-
virtual ~Section() {}
// Linker view.
- const intptr_t section_type;
- const intptr_t section_flags;
+ const elf::SectionHeaderType type;
+ const intptr_t flags;
const intptr_t alignment;
// These are fields that only are not set for most kinds of sections and so we
// set them to a reasonable default.
- intptr_t section_link = elf::SHN_UNDEF;
- intptr_t section_info = 0;
- intptr_t section_entry_size = 0;
+ intptr_t link = elf::SHN_UNDEF;
+ intptr_t info = 0;
+ intptr_t entry_size = 0;
- static constexpr intptr_t kInitValue = -1;
+ // Stores the name for the symbol that should be created in the dynamic (and
+ // static, if unstripped) tables for this section.
+ const char* symbol_name = nullptr;
#define FOR_EACH_SECTION_LINEAR_FIELD(M) \
- M(section_name, intptr_t, kInitValue) \
- M(section_index, intptr_t, kInitValue) \
- M(file_offset, intptr_t, kInitValue)
+ M(name) \
+ M(index) \
+ M(file_offset)
FOR_EACH_SECTION_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
virtual intptr_t FileSize() const = 0;
// Loader view.
- const intptr_t segment_type;
- const intptr_t segment_flags;
-
-#define FOR_EACH_SEGMENT_LINEAR_FIELD(M) M(memory_offset, intptr_t, kInitValue)
+#define FOR_EACH_SEGMENT_LINEAR_FIELD(M) M(memory_offset)
FOR_EACH_SEGMENT_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
+ // Each section belongs to at most one PT_LOAD segment.
+ const Segment* load_segment = nullptr;
+
virtual intptr_t MemorySize() const = 0;
// Other methods.
+ bool IsAllocated() const {
+ return (flags & elf::SHF_ALLOC) == elf::SHF_ALLOC;
+ }
+ bool IsExecutable() const {
+ return (flags & elf::SHF_EXECINSTR) == elf::SHF_EXECINSTR;
+ }
+ bool IsWritable() const { return (flags & elf::SHF_WRITE) == elf::SHF_WRITE; }
+
// Returns whether new content can be added to a section.
bool HasBeenFinalized() const {
- if (segment_type != elf::PT_NULL) {
- // The contents of a segment must not change after the segment is added
- // (when its memory offset is calculated).
- return memory_offset_ != kInitValue;
+ if (IsAllocated()) {
+ // The contents of a section that is allocated (part of a segment) must
+ // not change after the section is added.
+ return memory_offset_is_set();
} else {
- // Sections with an in-memory segment can have new content added until
- // we calculate file offsets.
- return file_offset_ != kInitValue;
+ // Unallocated sections can have new content added until we calculate
+ // file offsets.
+ return file_offset_is_set();
}
}
+ virtual const BitsContainer* AsBitsContainer() const { return nullptr; }
+
+ // Writes the file contents of the section.
virtual void Write(ElfWriteStream* stream) = 0;
- void WriteSegmentEntry(ElfWriteStream* stream) {
- // This should never be used on sections without a segment.
- ASSERT(segment_type != elf::PT_NULL);
- ASSERT(MemorySize() > 0);
+ virtual void WriteSectionHeader(ElfWriteStream* stream) {
#if defined(TARGET_ARCH_IS_32_BIT)
- stream->WriteWord(segment_type);
- stream->WriteOff(file_offset());
- stream->WriteAddr(memory_offset()); // Virtual address.
- stream->WriteAddr(memory_offset()); // Physical address, not used.
- stream->WriteWord(FileSize());
- stream->WriteWord(MemorySize());
- stream->WriteWord(segment_flags);
- stream->WriteWord(alignment);
-#else
- stream->WriteWord(segment_type);
- stream->WriteWord(segment_flags);
- stream->WriteOff(file_offset());
- stream->WriteAddr(memory_offset()); // Virtual address.
- stream->WriteAddr(memory_offset()); // Physical address, not used.
- stream->WriteXWord(FileSize());
- stream->WriteXWord(MemorySize());
- stream->WriteXWord(alignment);
-#endif
- }
-
- void WriteSectionEntry(ElfWriteStream* stream) {
- // Segment-only entries should not be in sections_.
- ASSERT(section_type != kInvalidSection);
-#if defined(TARGET_ARCH_IS_32_BIT)
- stream->WriteWord(section_name());
- stream->WriteWord(section_type);
- stream->WriteWord(section_flags);
+ stream->WriteWord(name());
+ stream->WriteWord(static_cast<uint32_t>(type));
+ stream->WriteWord(flags);
stream->WriteAddr(memory_offset());
stream->WriteOff(file_offset());
stream->WriteWord(FileSize()); // Has different meaning for BSS.
- stream->WriteWord(section_link);
- stream->WriteWord(section_info);
+ stream->WriteWord(link);
+ stream->WriteWord(info);
stream->WriteWord(alignment);
- stream->WriteWord(section_entry_size);
+ stream->WriteWord(entry_size);
#else
- stream->WriteWord(section_name());
- stream->WriteWord(section_type);
- stream->WriteXWord(section_flags);
+ stream->WriteWord(name());
+ stream->WriteWord(static_cast<uint32_t>(type));
+ stream->WriteXWord(flags);
stream->WriteAddr(memory_offset());
stream->WriteOff(file_offset());
stream->WriteXWord(FileSize()); // Has different meaning for BSS.
- stream->WriteWord(section_link);
- stream->WriteWord(section_info);
+ stream->WriteWord(link);
+ stream->WriteWord(info);
stream->WriteXWord(alignment);
- stream->WriteXWord(section_entry_size);
+ stream->WriteXWord(entry_size);
#endif
}
private:
- static intptr_t EncodeSectionFlags(bool allocate,
- bool executable,
- bool writable) {
+ static intptr_t EncodeFlags(bool allocate, bool executable, bool writable) {
if (!allocate) return 0;
intptr_t flags = elf::SHF_ALLOC;
if (executable) flags |= elf::SHF_EXECINSTR;
@@ -237,18 +194,16 @@
return flags;
}
- static intptr_t EncodeSegmentFlags(bool allocate,
- bool executable,
- bool writable) {
- if (!allocate) return 0;
- intptr_t flags = elf::PF_R;
- if (executable) flags |= elf::PF_X;
- if (writable) flags |= elf::PF_W;
- return flags;
- }
-
- static intptr_t SegmentAlignment(intptr_t alignment) {
- return alignment < Elf::kPageSize ? Elf::kPageSize : alignment;
+ static intptr_t DefaultAlignment(elf::SectionHeaderType type) {
+ switch (type) {
+ case elf::SectionHeaderType::SHT_SYMTAB:
+ case elf::SectionHeaderType::SHT_DYNSYM:
+ case elf::SectionHeaderType::SHT_HASH:
+ case elf::SectionHeaderType::SHT_DYNAMIC:
+ return compiler::target::kWordSize;
+ default:
+ return 1;
+ }
}
FOR_EACH_SECTION_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
@@ -261,18 +216,158 @@
#undef DEFINE_LINEAR_FIELD
#undef DEFINE_LINEAR_FIELD_METHODS
+class Segment : public ZoneAllocated {
+ public:
+ Segment(Zone* zone,
+ Section* initial_section,
+ elf::ProgramHeaderType segment_type)
+ : type(segment_type),
+ // Flags for the segment are the same as the initial section.
+ flags(EncodeFlags(ASSERT_NOTNULL(initial_section)->IsExecutable(),
+ ASSERT_NOTNULL(initial_section)->IsWritable())),
+ sections_(zone, 0) {
+ // Unlike sections, we don't have a reserved segment with the null type,
+ // so we never should pass this value.
+ ASSERT(segment_type != elf::ProgramHeaderType::PT_NULL);
+ // All segments should have at least one section. The first one is added
+ // during initialization. Unlike others added later, it should already have
+ // a memory offset since we use it to determine the segment memory offset.
+ ASSERT(initial_section->IsAllocated());
+ ASSERT(initial_section->memory_offset_is_set());
+ sections_.Add(initial_section);
+ if (type == elf::ProgramHeaderType::PT_LOAD) {
+ ASSERT(initial_section->load_segment == nullptr);
+ initial_section->load_segment = this;
+ }
+ }
+
+ virtual ~Segment() {}
+
+ static intptr_t Alignment(elf::ProgramHeaderType segment_type) {
+ switch (segment_type) {
+ case elf::ProgramHeaderType::PT_DYNAMIC:
+ return compiler::target::kWordSize;
+ case elf::ProgramHeaderType::PT_NOTE:
+ return kNoteAlignment;
+ default:
+ return Elf::kPageSize;
+ }
+ }
+
+ bool IsExecutable() const { return (flags & elf::PF_X) == elf::PF_X; }
+ bool IsWritable() const { return (flags & elf::PF_W) == elf::PF_W; }
+
+ void WriteProgramHeader(ElfWriteStream* stream) {
+#if defined(TARGET_ARCH_IS_32_BIT)
+ stream->WriteWord(static_cast<uint32_t>(type));
+ stream->WriteOff(FileOffset());
+ stream->WriteAddr(MemoryOffset()); // Virtual address.
+ stream->WriteAddr(MemoryOffset()); // Physical address, not used.
+ stream->WriteWord(FileSize());
+ stream->WriteWord(MemorySize());
+ stream->WriteWord(flags);
+ stream->WriteWord(Alignment(type));
+#else
+ stream->WriteWord(static_cast<uint32_t>(type));
+ stream->WriteWord(flags);
+ stream->WriteOff(FileOffset());
+ stream->WriteAddr(MemoryOffset()); // Virtual address.
+ stream->WriteAddr(MemoryOffset()); // Physical address, not used.
+ stream->WriteXWord(FileSize());
+ stream->WriteXWord(MemorySize());
+ stream->WriteXWord(Alignment(type));
+#endif
+ }
+
+ // Adds the given section to this segment.
+ //
+ // Returns whether the Section could be added to the segment. If not, a
+ // new segment will need to be created for this section.
+ //
+ // Sets the memory offset of the section if added.
+ bool Add(Section* section) {
+ // We only add additional sections to load segments.
+ ASSERT(type == elf::ProgramHeaderType::PT_LOAD);
+ ASSERT(section != nullptr);
+ // Only sections with the allocate flag set should be added to segments,
+ // and sections with already-set memory offsets cannot be added.
+ ASSERT(section->IsAllocated());
+ ASSERT(!section->memory_offset_is_set());
+ ASSERT(section->load_segment == nullptr);
+ switch (sections_.Last()->type) {
+ // We only use SHT_NULL sections as pseudo sections that will not appear
+ // in the final ELF file. Don't pack sections into these segments, as we
+ // may remove/replace the segments during finalization.
+ case elf::SectionHeaderType::SHT_NULL:
+ // If the last section in the segments is NOBITS, then we don't add it,
+ // as otherwise we'll be guaranteed the file offset and memory offset
+ // won't be page aligned without padding.
+ case elf::SectionHeaderType::SHT_NOBITS:
+ return false;
+ default:
+ break;
+ }
+ // We don't add if the W or X bits don't match.
+ if (IsExecutable() != section->IsExecutable() ||
+ IsWritable() != section->IsWritable()) {
+ return false;
+ }
+ auto const start_address = Utils::RoundUp(MemoryEnd(), section->alignment);
+ section->set_memory_offset(start_address);
+ sections_.Add(section);
+ section->load_segment = this;
+ return true;
+ }
+
+ intptr_t FileOffset() const { return sections_[0]->file_offset(); }
+
+ intptr_t FileSize() const {
+ auto const last = sections_.Last();
+ const intptr_t end = last->file_offset() + last->FileSize();
+ return end - FileOffset();
+ }
+
+ intptr_t MemoryOffset() const { return sections_[0]->memory_offset(); }
+
+ intptr_t MemorySize() const {
+ auto const last = sections_.Last();
+ const intptr_t end = last->memory_offset() + last->MemorySize();
+ return end - MemoryOffset();
+ }
+
+ intptr_t MemoryEnd() const { return MemoryOffset() + MemorySize(); }
+
+ private:
+ static constexpr intptr_t kInitValue = -1;
+ static_assert(kInitValue < 0, "init value must be negative");
+
+ static intptr_t EncodeFlags(bool executable, bool writable) {
+ intptr_t flags = elf::PF_R;
+ if (executable) flags |= elf::PF_X;
+ if (writable) flags |= elf::PF_W;
+ return flags;
+ }
+
+ public:
+ const elf::ProgramHeaderType type;
+ const intptr_t flags;
+
+ private:
+ GrowableArray<const Section*> sections_;
+};
+
// Represents the first entry in the section table, which should only contain
// zero values and does not correspond to a memory segment.
class ReservedSection : public Section {
public:
ReservedSection()
- : Section(/*type=*/elf::SHT_NULL,
+ : Section(elf::SectionHeaderType::SHT_NULL,
/*allocate=*/false,
/*executable=*/false,
/*writable=*/false,
/*alignment=*/0) {
- set_section_name(0);
- set_section_index(0);
+ set_name(0);
+ set_index(0);
set_file_offset(0);
}
@@ -281,40 +376,31 @@
void Write(ElfWriteStream* stream) {}
};
-class BlobSection : public Section {
+// Represents portions of the file/memory space which do not correspond to
+// actual sections. Should never be added to sections_.
+class PseudoSection : public Section {
public:
- BlobSection(intptr_t type,
- intptr_t segment_type,
- bool allocate,
- bool executable,
- bool writable,
- intptr_t filesz,
- intptr_t memsz,
- int alignment = 1)
- : Section(type, segment_type, allocate, executable, writable, alignment),
- file_size_(filesz),
- memory_size_(allocate ? memsz : 0) {}
-
- BlobSection(intptr_t type,
- bool allocate,
- bool executable,
- bool writable,
- intptr_t filesz,
- intptr_t memsz,
- int alignment = 1)
- : BlobSection(type,
- /*segment_type=*/allocate ? elf::PT_LOAD : 0,
- allocate,
- executable,
- writable,
- filesz,
- memsz,
- alignment) {}
+ PseudoSection(bool executable,
+ bool writable,
+ intptr_t file_offset,
+ intptr_t file_size,
+ intptr_t memory_offset,
+ intptr_t memory_size)
+ : Section(elf::SectionHeaderType::SHT_NULL,
+ /*allocate=*/true,
+ executable,
+ writable,
+ /*alignment=*/0),
+ file_size_(file_size),
+ memory_size_(memory_size) {
+ set_file_offset(file_offset);
+ set_memory_offset(memory_offset);
+ }
intptr_t FileSize() const { return file_size_; }
intptr_t MemorySize() const { return memory_size_; }
-
- virtual void Write(ElfWriteStream* stream) = 0;
+ void WriteSectionHeader(ElfWriteStream* stream) { UNREACHABLE(); }
+ void Write(ElfWriteStream* stream) { UNREACHABLE(); }
private:
const intptr_t file_size_;
@@ -322,27 +408,23 @@
};
// A segment for representing the program header table self-reference in the
-// program header table. There is no corresponding section for this segment.
-class ProgramTableSelfSegment : public BlobSection {
+// program header table.
+class ProgramTableSelfSegment : public Segment {
public:
- ProgramTableSelfSegment(intptr_t offset, intptr_t size)
- : BlobSection(/*type=*/kInvalidSection,
- /*segment_type=*/elf::PT_PHDR,
- /*allocate=*/true,
- /*executable=*/false,
- /*writable=*/false,
- /*filesz=*/size,
- /*memsz=*/size) {
- set_file_offset(offset);
- set_memory_offset(offset);
- }
-
- void Write(ElfWriteStream* stream) { UNREACHABLE(); }
+ ProgramTableSelfSegment(Zone* zone, intptr_t offset, intptr_t size)
+ : Segment(zone,
+ new (zone) PseudoSection(/*executable=*/false,
+ /*writable=*/false,
+ offset,
+ size,
+ offset,
+ size),
+ elf::ProgramHeaderType::PT_PHDR) {}
};
// A segment for representing the program header table load segment in the
-// program header table. There is no corresponding section for this segment.
-class ProgramTableLoadSegment : public BlobSection {
+// program header table.
+class ProgramTableLoadSegment : public Segment {
public:
// The Android dynamic linker in Jelly Bean incorrectly assumes that all
// non-writable segments are continguous. Since the BSS segment comes directly
@@ -352,64 +434,95 @@
//
// The bug is here:
// https://github.com/aosp-mirror/platform_bionic/blob/94963af28e445384e19775a838a29e6a71708179/linker/linker.c#L1991-L2001
- explicit ProgramTableLoadSegment(intptr_t size)
- : BlobSection(/*type=*/kInvalidSection,
- /*allocate=*/true,
- /*executable=*/false,
- /*writable=*/true,
- /*filesz=*/size,
- /*memsz=*/size) {
- set_file_offset(0);
- set_memory_offset(0);
+ explicit ProgramTableLoadSegment(Zone* zone, intptr_t size)
+ : Segment(zone,
+ // This segment should always start at address 0.
+ new (zone) PseudoSection(/*executable=*/false,
+ /*writable=*/true,
+ 0,
+ size,
+ 0,
+ size),
+ elf::ProgramHeaderType::PT_LOAD) {}
+};
+
+class BitsContainer : public Section {
+ public:
+ // Fully specified BitsContainer information.
+ BitsContainer(elf::SectionHeaderType type,
+ bool allocate,
+ bool executable,
+ bool writable,
+ intptr_t size,
+ const uint8_t* bytes,
+ int alignment = kDefaultAlignment)
+ : Section(type, allocate, executable, writable, alignment),
+ file_size_(type == elf::SectionHeaderType::SHT_NOBITS ? 0 : size),
+ memory_size_(allocate ? size : 0),
+ bytes_(bytes) {
+ ASSERT(type == elf::SectionHeaderType::SHT_NOBITS || bytes != nullptr);
}
- void Write(ElfWriteStream* stream) { UNREACHABLE(); }
-};
+ // For BitsContainers used only as sections.
+ BitsContainer(elf::SectionHeaderType type,
+ intptr_t size,
+ const uint8_t* bytes,
+ intptr_t alignment = kDefaultAlignment)
+ : BitsContainer(type,
+ /*allocate=*/false,
+ /*executable=*/false,
+ /*writable=*/false,
+ size,
+ bytes,
+ alignment) {}
-class ProgramBits : public BlobSection {
- public:
- ProgramBits(bool allocate,
- bool executable,
- bool writable,
- const uint8_t* bytes,
- intptr_t filesz,
- intptr_t memsz = -1)
- : BlobSection(elf::SHT_PROGBITS,
- allocate,
- executable,
- writable,
- filesz,
- memsz != -1 ? memsz : filesz),
- bytes_(ASSERT_NOTNULL(bytes)) {}
+ // For BitsContainers used as segments whose type differ on the type of the
+ // ELF file. Creates an elf::SHT_NOBITS section if type is DebugInfo,
+ // otherwise creates an elf::SHT_PROGBITS section.
+ BitsContainer(Elf::Type t,
+ bool executable,
+ bool writable,
+ intptr_t size,
+ const uint8_t* bytes,
+ intptr_t alignment = kDefaultAlignment)
+ : BitsContainer(t == Elf::Type::DebugInfo
+ ? elf::SectionHeaderType::SHT_NOBITS
+ : elf::SectionHeaderType::SHT_PROGBITS,
+ /*allocate=*/true,
+ executable,
+ writable,
+ size,
+ bytes,
+ alignment) {}
- void Write(ElfWriteStream* stream) { stream->WriteBytes(bytes_, FileSize()); }
+ const BitsContainer* AsBitsContainer() const { return this; }
- const uint8_t* bytes_;
-};
+ void Write(ElfWriteStream* stream) {
+ if (type != elf::SectionHeaderType::SHT_NOBITS) {
+ stream->WriteBytes(bytes_, FileSize());
+ }
+ }
-class NoBits : public BlobSection {
- public:
- NoBits(bool allocate, bool executable, bool writable, intptr_t memsz)
- : BlobSection(elf::SHT_NOBITS,
- allocate,
- executable,
- writable,
- /*filesz=*/0,
- memsz) {}
+ intptr_t FileSize() const { return file_size_; }
+ intptr_t MemorySize() const { return memory_size_; }
+ const uint8_t* bytes() const { return bytes_; }
- void Write(ElfWriteStream* stream) {}
+ private:
+ const intptr_t file_size_;
+ const intptr_t memory_size_;
+ const uint8_t* const bytes_;
};
class StringTable : public Section {
public:
- explicit StringTable(bool allocate)
- : Section(elf::SHT_STRTAB,
+ explicit StringTable(Zone* zone, bool allocate)
+ : Section(elf::SectionHeaderType::SHT_STRTAB,
allocate,
/*executable=*/false,
/*writable=*/false),
dynamic_(allocate),
- text_(Thread::Current()->zone(), 128),
- text_indices_() {
+ text_(zone, 128),
+ text_indices_(zone) {
text_.AddChar('\0');
text_indices_.Insert({"", 1});
}
@@ -461,6 +574,7 @@
cstr_(cstr) {}
void Write(ElfWriteStream* stream) const {
+ const intptr_t start = stream->position();
stream->WriteWord(name_index);
#if defined(TARGET_ARCH_IS_32_BIT)
stream->WriteAddr(offset);
@@ -475,6 +589,7 @@
stream->WriteAddr(offset);
stream->WriteXWord(size);
#endif
+ ASSERT_EQUAL(stream->position() - start, sizeof(elf::Symbol));
}
const intptr_t name_index;
@@ -491,21 +606,22 @@
class SymbolTable : public Section {
public:
- explicit SymbolTable(bool dynamic)
- : Section(dynamic ? elf::SHT_DYNSYM : elf::SHT_SYMTAB,
+ SymbolTable(Zone* zone, bool dynamic)
+ : Section(dynamic ? elf::SectionHeaderType::SHT_DYNSYM
+ : elf::SectionHeaderType::SHT_SYMTAB,
dynamic,
/*executable=*/false,
- /*writable=*/false,
- compiler::target::kWordSize),
+ /*writable=*/false),
dynamic_(dynamic),
- reserved_("", 0, 0, 0, 0, 0) {
- section_entry_size = kElfSymbolTableEntrySize;
+ reserved_("", 0, 0, 0, 0, 0),
+ symbols_(zone, 1) {
+ entry_size = sizeof(elf::Symbol);
// The first symbol table entry is reserved and must be all zeros.
symbols_.Add(&reserved_);
- section_info = 1; // One "local" symbol, the reserved first entry.
+ info = 1; // One "local" symbol, the reserved first entry.
}
- intptr_t FileSize() const { return Length() * kElfSymbolTableEntrySize; }
+ intptr_t FileSize() const { return Length() * entry_size; }
intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; }
void Write(ElfWriteStream* stream) {
@@ -513,8 +629,7 @@
auto const symbol = At(i);
const intptr_t start = stream->position();
symbol->Write(stream);
- const intptr_t end = stream->position();
- ASSERT_EQUAL(end - start, kElfSymbolTableEntrySize);
+ ASSERT_EQUAL(stream->position() - start, entry_size);
}
}
@@ -549,19 +664,17 @@
class SymbolHashTable : public Section {
public:
- SymbolHashTable(StringTable* strtab, SymbolTable* symtab)
- : Section(elf::SHT_HASH,
+ SymbolHashTable(Zone* zone, StringTable* strtab, SymbolTable* symtab)
+ : Section(elf::SectionHeaderType::SHT_HASH,
/*allocate=*/true,
/*executable=*/false,
- /*writable=*/false,
- compiler::target::kWordSize) {
- section_link = symtab->section_index();
- section_entry_size = kElfSymbolHashTableEntrySize;
+ /*writable=*/false) {
+ link = symtab->index();
+ entry_size = sizeof(int32_t);
nchain_ = symtab->Length();
nbucket_ = symtab->Length();
- auto zone = Thread::Current()->zone();
bucket_ = zone->Alloc<int32_t>(nbucket_);
for (intptr_t i = 0; i < nbucket_; i++) {
bucket_[i] = elf::STN_UNDEF;
@@ -581,7 +694,7 @@
}
}
- intptr_t FileSize() const { return 4 * (nbucket_ + nchain_ + 2); }
+ intptr_t FileSize() const { return entry_size * (nbucket_ + nchain_ + 2); }
intptr_t MemorySize() const { return FileSize(); }
void Write(ElfWriteStream* stream) {
@@ -604,53 +717,55 @@
class DynamicTable : public Section {
public:
- DynamicTable(StringTable* strtab, SymbolTable* symtab, SymbolHashTable* hash)
- : Section(elf::SHT_DYNAMIC,
+ DynamicTable(Zone* zone,
+ StringTable* strtab,
+ SymbolTable* symtab,
+ SymbolHashTable* hash)
+ : Section(elf::SectionHeaderType::SHT_DYNAMIC,
/*allocate=*/true,
/*executable=*/false,
- /*writable=*/true,
- compiler::target::kWordSize) {
- section_link = strtab->section_index();
- section_entry_size = kElfDynamicTableEntrySize;
+ /*writable=*/true) {
+ link = strtab->index();
+ entry_size = sizeof(elf::DynamicEntry);
- AddEntry(elf::DT_HASH, hash->memory_offset());
- AddEntry(elf::DT_STRTAB, strtab->memory_offset());
- AddEntry(elf::DT_STRSZ, strtab->MemorySize());
- AddEntry(elf::DT_SYMTAB, symtab->memory_offset());
- AddEntry(elf::DT_SYMENT, kElfSymbolTableEntrySize);
- AddEntry(elf::DT_NULL, 0);
+ AddEntry(zone, elf::DynamicEntryType::DT_HASH, hash->memory_offset());
+ AddEntry(zone, elf::DynamicEntryType::DT_STRTAB, strtab->memory_offset());
+ AddEntry(zone, elf::DynamicEntryType::DT_STRSZ, strtab->MemorySize());
+ AddEntry(zone, elf::DynamicEntryType::DT_SYMTAB, symtab->memory_offset());
+ AddEntry(zone, elf::DynamicEntryType::DT_SYMENT, sizeof(elf::Symbol));
+ AddEntry(zone, elf::DynamicEntryType::DT_NULL, 0);
}
- intptr_t FileSize() const {
- return entries_.length() * kElfDynamicTableEntrySize;
- }
+ intptr_t FileSize() const { return entries_.length() * entry_size; }
intptr_t MemorySize() const { return FileSize(); }
void Write(ElfWriteStream* stream) {
for (intptr_t i = 0; i < entries_.length(); i++) {
- const intptr_t start = stream->position();
-#if defined(TARGET_ARCH_IS_32_BIT)
- stream->WriteWord(entries_[i]->tag);
- stream->WriteAddr(entries_[i]->value);
-#else
- stream->WriteXWord(entries_[i]->tag);
- stream->WriteAddr(entries_[i]->value);
-#endif
- const intptr_t end = stream->position();
- ASSERT_EQUAL(end - start, kElfDynamicTableEntrySize);
+ entries_[i]->Write(stream);
}
}
- class Entry : public ZoneAllocated {
- public:
- intptr_t tag;
+ struct Entry : public ZoneAllocated {
+ Entry(elf::DynamicEntryType tag, intptr_t value) : tag(tag), value(value) {}
+
+ void Write(ElfWriteStream* stream) {
+ const intptr_t start = stream->position();
+#if defined(TARGET_ARCH_IS_32_BIT)
+ stream->WriteWord(static_cast<uint32_t>(tag));
+ stream->WriteAddr(value);
+#else
+ stream->WriteXWord(static_cast<uint64_t>(tag));
+ stream->WriteAddr(value);
+#endif
+ ASSERT_EQUAL(stream->position() - start, sizeof(elf::DynamicEntry));
+ }
+
+ elf::DynamicEntryType tag;
intptr_t value;
};
- void AddEntry(intptr_t tag, intptr_t value) {
- Entry* entry = new Entry();
- entry->tag = tag;
- entry->value = value;
+ void AddEntry(Zone* zone, elf::DynamicEntryType tag, intptr_t value) {
+ auto const entry = new (zone) Entry(tag, value);
entries_.Add(entry);
}
@@ -660,20 +775,21 @@
// A segment for representing the dynamic table segment in the program header
// table. There is no corresponding section for this segment.
-class DynamicSegment : public BlobSection {
+class DynamicSegment : public Segment {
public:
- explicit DynamicSegment(DynamicTable* dynamic)
- : BlobSection(/*type=*/kInvalidSection,
- /*segment_type=*/elf::PT_DYNAMIC,
- /*allocate=*/true,
- /*executable=*/false,
- /*writable=*/true,
- /*filesz=*/dynamic->FileSize(),
- /*memsz=*/dynamic->MemorySize()) {
- set_memory_offset(dynamic->memory_offset());
- }
+ explicit DynamicSegment(Zone* zone, DynamicTable* dynamic)
+ : Segment(zone, dynamic, elf::ProgramHeaderType::PT_DYNAMIC) {}
+};
- void Write(ElfWriteStream* stream) { UNREACHABLE(); }
+// A segment for representing the dynamic table segment in the program header
+// table. There is no corresponding section for this segment.
+class NoteSegment : public Segment {
+ public:
+ NoteSegment(Zone* zone, Section* note)
+ : Segment(zone, note, elf::ProgramHeaderType::PT_NOTE) {
+ ASSERT_EQUAL(static_cast<uint32_t>(note->type),
+ static_cast<uint32_t>(elf::SectionHeaderType::SHT_NOTE));
+ }
};
static const intptr_t kProgramTableSegmentSize = Elf::kPageSize;
@@ -684,6 +800,7 @@
// we may not serialize both VM and isolate. Here, we always serialize both,
// so make a BSS segment large enough for both, with the VM entries coming
// first.
+static constexpr const char* kSnapshotBssAsmSymbol = "_kDartBSSData";
static const intptr_t kBssIsolateOffset =
BSS::kVmEntryCount * compiler::target::kWordSize;
static const intptr_t kBssSize =
@@ -695,10 +812,9 @@
type_(type),
dwarf_(dwarf),
bss_(CreateBSS(zone, type, kBssSize)),
- shstrtab_(new (zone) StringTable(/*allocate=*/false)),
- dynstrtab_(new (zone) StringTable(/*allocate=*/true)),
- dynsym_(new (zone) SymbolTable(/*dynamic=*/true)),
- memory_offset_(kProgramTableSegmentSize) {
+ shstrtab_(new (zone) StringTable(zone, /*allocate=*/false)),
+ dynstrtab_(new (zone) StringTable(zone, /*allocate=*/true)),
+ dynsym_(new (zone) SymbolTable(zone, /*dynamic=*/true)) {
// Separate debugging information should always have a Dwarf object.
ASSERT(type_ == Type::Snapshot || dwarf_ != nullptr);
// Assumed by various offset logic in this file.
@@ -706,17 +822,18 @@
// The first section in the section header table is always a reserved
// entry containing only 0 values.
sections_.Add(new (zone_) ReservedSection());
- // Go ahead and add the section header string table, since it doesn't have
- // an in-memory segment and so can be added to until we compute file offsets.
- AddSection(shstrtab_, ".shstrtab");
- if (dwarf_ != nullptr) {
+ if (!IsStripped()) {
// Not a stripped ELF file, so allocate static string and symbol tables.
- strtab_ = new (zone_) StringTable(/* allocate= */ false);
- AddSection(strtab_, ".strtab");
- symtab_ = new (zone_) SymbolTable(/*dynamic=*/false);
- AddSection(symtab_, ".symtab");
- symtab_->section_link = strtab_->section_index();
+ strtab_ = new (zone_) StringTable(zone_, /* allocate= */ false);
+ symtab_ = new (zone_) SymbolTable(zone, /*dynamic=*/false);
}
+ // We add an initial segment to represent reserved space for the program
+ // header, and so we can always assume there's at least one segment in the
+ // segments_ array. We later remove this and replace it with appropriately
+ // calculated segments in Elf::FinalizeProgramTable().
+ auto const start_segment =
+ new (zone_) ProgramTableLoadSegment(zone_, kProgramTableSegmentSize);
+ segments_.Add(start_segment);
// Note that the BSS segment must be the first user-defined segment because
// it cannot be placed in between any two non-writable segments, due to a bug
// in Jelly Bean's ELF loader. See also Elf::WriteProgramTable().
@@ -724,127 +841,85 @@
// We add it in all cases, even to the separate debugging information ELF,
// to ensure that relocated addresses are consistent between ELF snapshots
// and ELF separate debugging information.
- AddSection(bss_, ".bss");
- AddSegmentSymbol(bss_, "_kDartBSSData");
+ AddSection(bss_, ".bss", kSnapshotBssAsmSymbol);
+}
+
+intptr_t Elf::NextMemoryOffset() const {
+ return Utils::RoundUp(LastLoadSegment()->MemoryEnd(), Elf::kPageSize);
}
uword Elf::BssStart(bool vm) const {
return bss_->memory_offset() + (vm ? 0 : kBssIsolateOffset);
}
-void Elf::AddSection(Section* section, const char* name) {
+intptr_t Elf::AddSection(Section* section,
+ const char* name,
+ const char* symbol_name) {
ASSERT(section_table_file_size_ < 0);
ASSERT(!shstrtab_->HasBeenFinalized());
- section->set_section_name(shstrtab_->AddString(name));
- section->set_section_index(sections_.length());
+ section->set_name(shstrtab_->AddString(name));
+ section->set_index(sections_.length());
sections_.Add(section);
- if (section->MemorySize() > 0) {
- ASSERT(program_table_file_size_ < 0);
- memory_offset_ = Utils::RoundUp(memory_offset_, section->alignment);
- section->set_memory_offset(memory_offset_);
- segments_.Add(section);
- memory_offset_ += section->MemorySize();
- memory_offset_ = Utils::RoundUp(memory_offset_, kPageSize);
- }
-}
-intptr_t Elf::AddSegmentSymbol(const Section* section, const char* name) {
- // While elf::STT_SECTION might seem more appropriate, those symbols are
- // usually local and dlsym won't return them.
- auto const info = (elf::STB_GLOBAL << 4) | elf::STT_FUNC;
- auto const section_index = section->section_index();
- // For shared libraries, this is the offset from the DSO base. For static
- // libraries, this is section relative.
- auto const address = section->memory_offset();
- auto const size = section->MemorySize();
- AddDynamicSymbol(name, info, section_index, address, size);
- return address;
+ // No memory offset, so just return -1.
+ if (!section->IsAllocated()) return -1;
+
+ ASSERT(program_table_file_size_ < 0);
+ auto const last_load = LastLoadSegment();
+ if (!last_load->Add(section)) {
+ // We can't add this section to the last load segment, so create a new one.
+ // The new segment starts at the next aligned address.
+ auto const type = elf::ProgramHeaderType::PT_LOAD;
+ auto const start_address =
+ Utils::RoundUp(last_load->MemoryEnd(), Segment::Alignment(type));
+ section->set_memory_offset(start_address);
+ auto const segment = new (zone_) Segment(zone_, section, type);
+ segments_.Add(segment);
+ }
+ if (symbol_name != nullptr) {
+ section->symbol_name = symbol_name;
+ }
+ return section->memory_offset();
}
intptr_t Elf::AddText(const char* name, const uint8_t* bytes, intptr_t size) {
// When making a separate debugging info file for assembly, we don't have
// the binary text segment contents.
ASSERT(type_ == Type::DebugInfo || bytes != nullptr);
- Section* image;
- if (type_ == Type::DebugInfo) {
- image = new (zone_) NoBits(true, true, false, size);
- } else {
- image = new (zone_) ProgramBits(true, true, false, bytes, size);
- }
- AddSection(image, ".text");
-
- return AddSegmentSymbol(image, name);
-}
-
-static bool FindSymbol(StringTable* strings,
- SymbolTable* symbols,
- const char* name,
- intptr_t* offset,
- intptr_t* size) {
- auto const name_index = strings->Lookup(name);
- if (name_index < 0) return false;
- auto const symbol = symbols->FindSymbolWithNameIndex(name_index);
- if (symbol == nullptr) return false;
- if (offset != nullptr) {
- *offset = symbol->offset;
- }
- if (size != nullptr) {
- *size = symbol->size;
- }
- return true;
-}
-
-bool Elf::FindDynamicSymbol(const char* name,
- intptr_t* offset,
- intptr_t* size) const {
- return FindSymbol(dynstrtab_, dynsym_, name, offset, size);
-}
-
-bool Elf::FindStaticSymbol(const char* name,
- intptr_t* offset,
- intptr_t* size) const {
- if (strtab_ == nullptr || symtab_ == nullptr) return false;
- return FindSymbol(strtab_, symtab_, name, offset, size);
+ auto const image = new (zone_)
+ BitsContainer(type_, /*executable=*/true,
+ /*writable=*/false, size, bytes, Elf::kPageSize);
+ return AddSection(image, ".text", name);
}
Section* Elf::CreateBSS(Zone* zone, Type type, intptr_t size) {
- Section* image;
- if (type == Type::DebugInfo) {
- image = new (zone) NoBits(true, false, true, size);
- } else {
+ uint8_t* bytes = nullptr;
+ if (type != Type::DebugInfo) {
// Ideally the BSS segment would take no space in the object, but Android's
// "strip" utility truncates the memory-size of our segments to their
// file-size.
//
// Therefore we must insert zero-filled pages for the BSS.
- uint8_t* const bytes = zone->Alloc<uint8_t>(size);
+ bytes = zone->Alloc<uint8_t>(size);
memset(bytes, 0, size);
- image = new (zone) ProgramBits(true, false, true, bytes, size);
}
- static_assert(Image::kBssAlignment <= kPageSize,
- "ELF .bss section is not aligned as expected by Image class");
- ASSERT_EQUAL(image->alignment, kPageSize);
- return image;
+ return new (zone) BitsContainer(type, /*executable=*/false, /*writable=*/true,
+ kBssSize, bytes, Image::kBssAlignment);
}
intptr_t Elf::AddROData(const char* name, const uint8_t* bytes, intptr_t size) {
ASSERT(bytes != nullptr);
- Section* image;
- if (type_ == Type::DebugInfo) {
- image = new (zone_) NoBits(true, false, false, size);
- } else {
- image = new (zone_) ProgramBits(true, false, false, bytes, size);
- }
- AddSection(image, ".rodata");
-
- return AddSegmentSymbol(image, name);
+ auto const image = new (zone_)
+ BitsContainer(type_, /*executable=*/false,
+ /*writable=*/false, size, bytes, kMaxObjectAlignment);
+ return AddSection(image, ".rodata", name);
}
void Elf::AddDebug(const char* name, const uint8_t* bytes, intptr_t size) {
- ASSERT(dwarf_ != nullptr);
+ ASSERT(!IsStripped());
ASSERT(bytes != nullptr);
- ProgramBits* image =
- new (zone_) ProgramBits(false, false, false, bytes, size);
+ auto const image = new (zone_)
+ BitsContainer(elf::SectionHeaderType::SHT_PROGBITS, size, bytes);
AddSection(image, name);
}
@@ -869,8 +944,7 @@
intptr_t section_index,
intptr_t address,
intptr_t size) {
- if (dwarf_ == nullptr) return; // No static info kept in stripped ELF files.
-
+ if (IsStripped()) return; // No static info kept in stripped ELF files.
ASSERT(!symtab_->HasBeenFinalized() && !strtab_->HasBeenFinalized());
auto const name_index = strtab_->AddString(name);
auto const symbol =
@@ -988,24 +1062,47 @@
};
static constexpr intptr_t kInitialDwarfBufferSize = 64 * KB;
+#endif
static uint8_t* ZoneReallocate(uint8_t* ptr, intptr_t len, intptr_t new_len) {
return Thread::Current()->zone()->Realloc<uint8_t>(ptr, len, new_len);
}
-#endif
-const Section* Elf::FindSegmentForAddress(intptr_t address) const {
- for (intptr_t i = 0; i < segments_.length(); i++) {
- auto const segment = segments_[i];
- auto const start = segment->memory_offset();
- auto const end = start + segment->MemorySize();
- if (address >= start && address < end) {
+Segment* Elf::LastLoadSegment() const {
+ for (intptr_t i = segments_.length() - 1; i >= 0; i--) {
+ auto const segment = segments_.At(i);
+ if (segment->type == elf::ProgramHeaderType::PT_LOAD) {
return segment;
}
}
+ // There should always be a load segment, since one is added in construction.
+ UNREACHABLE();
+}
+
+const Section* Elf::FindSectionForAddress(intptr_t address) const {
+ for (auto const section : sections_) {
+ if (!section->IsAllocated()) continue;
+ auto const start = section->memory_offset();
+ auto const end = start + section->MemorySize();
+ if (address >= start && address < end) {
+ return section;
+ }
+ }
return nullptr;
}
+void Elf::AddSectionSymbols() {
+ for (auto const section : sections_) {
+ if (section->symbol_name == nullptr) continue;
+ ASSERT(section->memory_offset_is_set());
+ // While elf::STT_SECTION might seem more appropriate, those symbols are
+ // usually local and dlsym won't return them.
+ auto const info = (elf::STB_GLOBAL << 4) | elf::STT_FUNC;
+ AddDynamicSymbol(section->symbol_name, info, section->index(),
+ section->memory_offset(), section->MemorySize());
+ }
+}
+
void Elf::FinalizeDwarfSections() {
if (dwarf_ == nullptr) return;
#if defined(DART_PRECOMPILER)
@@ -1032,9 +1129,9 @@
auto const isolate_start =
symbol_to_address_map.LookupValue(kIsolateSnapshotInstructionsAsmSymbol);
ASSERT(isolate_start > 0);
- auto const vm_text = FindSegmentForAddress(vm_start);
+ auto const vm_text = FindSectionForAddress(vm_start);
ASSERT(vm_text != nullptr);
- auto const isolate_text = FindSegmentForAddress(isolate_start);
+ auto const isolate_text = FindSectionForAddress(isolate_start);
ASSERT(isolate_text != nullptr);
SnapshotTextObjectNamer namer(zone_);
@@ -1044,10 +1141,10 @@
auto const name = namer.SnapshotNameFor(i, code);
const auto& pair = dwarf_->CodeAddress(code);
ASSERT(pair.offset > 0);
- auto const segment = pair.vm ? vm_text : isolate_text;
- const intptr_t address = segment->memory_offset() + pair.offset;
+ auto const section = pair.vm ? vm_text : isolate_text;
+ const intptr_t address = section->memory_offset() + pair.offset;
auto const info = (elf::STB_GLOBAL << 4) | elf::STT_FUNC;
- AddStaticSymbol(name, info, segment->section_index(), address, code.Size());
+ AddStaticSymbol(name, info, section->index(), address, code.Size());
symbol_to_address_map.Insert({name, address});
}
@@ -1081,27 +1178,57 @@
}
void Elf::Finalize() {
- FinalizeDwarfSections();
+ AddSectionSymbols();
- // Unlike the static tables, we must wait until finalization to add the
- // dynamic tables, as adding them marks them as finalized.
+ // The Build ID depends on the symbols being in place, so must be run after
+ // AddSectionSymbols(). Unfortunately, it currently depends on the contents
+ // of the .text and .rodata sections, so it can't come earlier in the file
+ // without changing how we add the .text and .rodata sections (since we
+ // determine memory offsets for those sections when we add them, and the
+ // text sections must have the memory offsets to do BSS relocations).
+ if (auto const build_id = GenerateBuildId()) {
+ AddSection(build_id, ".note.gnu.build-id", kSnapshotBuildIdAsmSymbol);
+
+ // Add a PT_NOTE segment for the build ID.
+ segments_.Add(new (zone_) NoteSegment(zone_, build_id));
+ }
+
+ // Adding the dynamic symbol table and associated sections.
AddSection(dynstrtab_, ".dynstr");
AddSection(dynsym_, ".dynsym");
- dynsym_->section_link = dynstrtab_->section_index();
+ dynsym_->link = dynstrtab_->index();
- auto const hash = new (zone_) SymbolHashTable(dynstrtab_, dynsym_);
+ auto const hash = new (zone_) SymbolHashTable(zone_, dynstrtab_, dynsym_);
AddSection(hash, ".hash");
- dynamic_ = new (zone_) DynamicTable(dynstrtab_, dynsym_, hash);
- AddSection(dynamic_, ".dynamic");
+ auto const dynamic =
+ new (zone_) DynamicTable(zone_, dynstrtab_, dynsym_, hash);
+ AddSection(dynamic, ".dynamic");
- // Also add a PT_DYNAMIC segment for the dynamic symbol table.
- dynamic_segment_ = new (zone_) DynamicSegment(dynamic_);
- segments_.Add(dynamic_segment_);
+ // Add a PT_DYNAMIC segment for the dynamic symbol table.
+ segments_.Add(new (zone_) DynamicSegment(zone_, dynamic));
- // At this point, all sections and user-defined segments have been added. Add
- // any program table-specific segments and then calculate file offsets for all
- // sections and segments.
+ // Currently, we add all (non-reserved) unallocated sections after all
+ // allocated sections. If we put unallocated sections between allocated
+ // sections, they would affect the file offset but not the memory offset
+ // of the later allocated sections.
+ //
+ // However, memory offsets must be page-aligned to the file offset for the
+ // ELF file to be successfully loaded. This means we'd either have to add
+ // extra padding _or_ determine file offsets before memory offsets. The
+ // latter would require us to handle BSS relocations during ELF finalization,
+ // instead of while writing the .text section content.
+ FinalizeDwarfSections();
+ if (!IsStripped()) {
+ AddSection(strtab_, ".strtab");
+ AddSection(symtab_, ".symtab");
+ symtab_->link = strtab_->index();
+ }
+ AddSection(shstrtab_, ".shstrtab");
+
+ // At this point, all non-programmatically calculated sections and segments
+ // have been added. Add any programatically calculated sections and segments
+ // and then calculate file offsets.
FinalizeProgramTable();
ComputeFileOffsets();
@@ -1113,15 +1240,95 @@
WriteSectionTable(&wrapped);
}
+// Need to include the final \0 terminator in both byte count and byte output.
+static const uint32_t kBuildIdNameLength = strlen(elf::ELF_NOTE_GNU) + 1;
+// We generate a 128-bit hash, where each 32 bits is a hash of the contents of
+// the following segments in order:
+//
+// .text(VM) | .text(Isolate) | .rodata(VM) | .rodata(Isolate)
+static constexpr intptr_t kBuildIdSegmentNamesLength = 4;
+static constexpr const char* kBuildIdSegmentNames[kBuildIdSegmentNamesLength]{
+ kVmSnapshotInstructionsAsmSymbol,
+ kIsolateSnapshotInstructionsAsmSymbol,
+ kVmSnapshotDataAsmSymbol,
+ kIsolateSnapshotDataAsmSymbol,
+};
+static constexpr uint32_t kBuildIdDescriptionLength =
+ kBuildIdSegmentNamesLength * sizeof(uint32_t);
+static const intptr_t kBuildIdDescriptionOffset =
+ sizeof(elf::Note) + kBuildIdNameLength;
+static const intptr_t kBuildIdSize =
+ kBuildIdDescriptionOffset + kBuildIdDescriptionLength;
+
+static const Symbol* LookupSymbol(StringTable* strings,
+ SymbolTable* symbols,
+ const char* name) {
+ ASSERT(strings != nullptr);
+ ASSERT(symbols != nullptr);
+ auto const name_index = strings->Lookup(name);
+ if (name_index < 0) return nullptr;
+ return symbols->FindSymbolWithNameIndex(name_index);
+}
+
+static uint32_t HashBitsContainer(const BitsContainer* bits) {
+ uint32_t hash = 0;
+ auto const size = bits->MemorySize();
+ auto const end = bits->bytes() + size;
+ auto const non_word_size = size % kWordSize;
+ auto const end_of_words =
+ reinterpret_cast<const uword*>(bits->bytes() + (size - non_word_size));
+ for (auto cursor = reinterpret_cast<const uword*>(bits->bytes());
+ cursor < end_of_words; cursor++) {
+ hash = CombineHashes(hash, *cursor);
+ }
+ for (auto cursor = reinterpret_cast<const uint8_t*>(end_of_words);
+ cursor < end; cursor++) {
+ hash = CombineHashes(hash, *cursor);
+ }
+ return FinalizeHash(hash, 32);
+}
+
+Section* Elf::GenerateBuildId() {
+ uint8_t* notes_buffer = nullptr;
+ WriteStream stream(¬es_buffer, ZoneReallocate, kBuildIdSize);
+ stream.WriteFixed(kBuildIdNameLength);
+ stream.WriteFixed(kBuildIdDescriptionLength);
+ stream.WriteFixed(static_cast<uint32_t>(elf::NoteType::NT_GNU_BUILD_ID));
+ stream.WriteBytes(elf::ELF_NOTE_GNU, kBuildIdNameLength);
+ const intptr_t description_start = stream.bytes_written();
+ for (intptr_t i = 0; i < kBuildIdSegmentNamesLength; i++) {
+ auto const name = kBuildIdSegmentNames[i];
+ auto const symbol = LookupSymbol(dynstrtab_, dynsym_, name);
+ if (symbol == nullptr) {
+ FATAL1("No symbol %s found for expected segment\n", name);
+ }
+ auto const bits = sections_[symbol->section_index]->AsBitsContainer();
+ if (bits == nullptr) {
+ FATAL1("Section for symbol %s is not a BitsContainer", name);
+ }
+ ASSERT_EQUAL(bits->MemorySize(), symbol->size);
+ // We don't actually have the bytes (i.e., this is a separate debugging
+ // info file for an assembly snapshot), so we can't calculate the build ID.
+ if (bits->bytes() == nullptr) return nullptr;
+
+ stream.WriteFixed(HashBitsContainer(bits));
+ }
+ ASSERT_EQUAL(stream.bytes_written() - description_start,
+ kBuildIdDescriptionLength);
+ return new (zone_) BitsContainer(
+ elf::SectionHeaderType::SHT_NOTE, /*allocate=*/true, /*executable=*/false,
+ /*writable=*/false, stream.bytes_written(), notes_buffer, kNoteAlignment);
+}
+
void Elf::FinalizeProgramTable() {
ASSERT(program_table_file_size_ < 0);
- program_table_file_offset_ = kElfHeaderSize;
+ program_table_file_offset_ = sizeof(elf::ElfHeader);
// There are two segments we need the size of the program table to create, so
// calculate it as if those two segments were already in place.
program_table_file_size_ =
- (2 + segments_.length()) * kElfProgramTableEntrySize;
+ (2 + segments_.length()) * sizeof(elf::ProgramHeader);
// We pre-allocated the virtual memory space for the program table itself.
// Check that we didn't generate too many segments. Currently we generate a
@@ -1132,42 +1339,59 @@
program_table_file_offset_ + program_table_file_size_;
RELEASE_ASSERT(program_table_segment_size < kProgramTableSegmentSize);
+ // Remove the original stand-in segment we added in the constructor.
+ segments_.EraseAt(0);
+
// Self-reference to program header table. Required by Android but not by
// Linux. Must appear before any PT_LOAD entries.
segments_.InsertAt(
- 0, new (zone_) ProgramTableSelfSegment(program_table_file_offset_,
+ 0, new (zone_) ProgramTableSelfSegment(zone_, program_table_file_offset_,
program_table_file_size_));
// Segment for loading the initial part of the ELF file, including the
// program header table. Required by Android but not by Linux.
- segments_.InsertAt(
- 1, new (zone_) ProgramTableLoadSegment(program_table_segment_size));
+ segments_.InsertAt(1, new (zone_) ProgramTableLoadSegment(
+ zone_, program_table_segment_size));
}
+static const intptr_t kElfSectionTableAlignment = compiler::target::kWordSize;
+
void Elf::ComputeFileOffsets() {
// We calculate the size and offset of the program header table during
// finalization.
ASSERT(program_table_file_offset_ > 0 && program_table_file_size_ > 0);
intptr_t file_offset = program_table_file_offset_ + program_table_file_size_;
+ // When calculating file offsets for sections, we'll need to know if we've
+ // changed segments. Start with the one for the program table.
+ const auto* current_segment = segments_[1];
- // The first (reserved) section's file offset is set to 0 during construction,
- // so skip it.
- ASSERT(sections_.length() >= 1 && sections_[0]->section_type == 0);
- // The others are output to the file in order after the program header table.
+ // The non-reserved sections are output to the file in order after the program
+ // header table. If we're entering a new segment, then we need to align
+ // according to the PT_LOAD segment alignment as well to keep the file offsets
+ // aligned with the memory addresses.
+ auto const load_align = Segment::Alignment(elf::ProgramHeaderType::PT_LOAD);
for (intptr_t i = 1; i < sections_.length(); i++) {
- Section* section = sections_[i];
+ auto const section = sections_[i];
file_offset = Utils::RoundUp(file_offset, section->alignment);
+ if (section->IsAllocated() && section->load_segment != current_segment) {
+ file_offset = Utils::RoundUp(file_offset, load_align);
+ current_segment = section->load_segment;
+ }
section->set_file_offset(file_offset);
+#if defined(DEBUG)
+ if (section->IsAllocated()) {
+ // For files that will be dynamically loaded, make sure the file offsets
+ // of allocated sections are page aligned to the memory offsets.
+ ASSERT_EQUAL(section->file_offset() % load_align,
+ section->memory_offset() % load_align);
+ }
+#endif
file_offset += section->FileSize();
}
- // Make the dynamic segment's file offset the same as the dynamic table now
- // that it's been calculated for the latter.
- dynamic_segment_->set_file_offset(dynamic_->file_offset());
-
file_offset = Utils::RoundUp(file_offset, kElfSectionTableAlignment);
section_table_file_offset_ = file_offset;
- section_table_file_size_ = sections_.length() * kElfSectionTableEntrySize;
+ section_table_file_size_ = sections_.length() * sizeof(elf::SectionHeader);
file_offset += section_table_file_size_;
}
@@ -1223,27 +1447,46 @@
#endif
stream->WriteWord(flags);
- stream->WriteHalf(kElfHeaderSize);
- stream->WriteHalf(kElfProgramTableEntrySize);
+ stream->WriteHalf(sizeof(elf::ElfHeader));
+ stream->WriteHalf(sizeof(elf::ProgramHeader));
stream->WriteHalf(segments_.length());
- stream->WriteHalf(kElfSectionTableEntrySize);
+ stream->WriteHalf(sizeof(elf::SectionHeader));
stream->WriteHalf(sections_.length());
- stream->WriteHalf(shstrtab_->section_index());
+ stream->WriteHalf(shstrtab_->index());
- ASSERT_EQUAL(stream->position(), kElfHeaderSize);
+ ASSERT_EQUAL(stream->position(), sizeof(elf::ElfHeader));
}
void Elf::WriteProgramTable(ElfWriteStream* stream) {
ASSERT(program_table_file_size_ >= 0); // Check for finalization.
ASSERT(stream->position() == program_table_file_offset_);
-
- for (intptr_t i = 0; i < segments_.length(); i++) {
- Section* section = segments_[i];
+#if defined(DEBUG)
+ // Here, we count the number of times that a PT_LOAD writable segment is
+ // followed by a non-writable segment. We initialize last_writable to true so
+ // that we catch the case where the first segment is non-writable.
+ bool last_writable = true;
+ int non_writable_groups = 0;
+#endif
+ for (auto const segment : segments_) {
+#if defined(DEBUG)
+ if (segment->type == elf::ProgramHeaderType::PT_LOAD) {
+ if (last_writable && !segment->IsWritable()) {
+ non_writable_groups++;
+ }
+ last_writable = segment->IsWritable();
+ }
+#endif
const intptr_t start = stream->position();
- section->WriteSegmentEntry(stream);
+ segment->WriteProgramHeader(stream);
const intptr_t end = stream->position();
- ASSERT_EQUAL(end - start, kElfProgramTableEntrySize);
+ ASSERT_EQUAL(end - start, sizeof(elf::ProgramHeader));
}
+#if defined(DEBUG)
+ // All PT_LOAD non-writable segments must be contiguous. If not, some older
+ // Android dynamic linkers fail to handle writable segments between
+ // non-writable ones. See https://github.com/flutter/flutter/issues/43259.
+ ASSERT(non_writable_groups <= 1);
+#endif
}
void Elf::WriteSectionTable(ElfWriteStream* stream) {
@@ -1251,26 +1494,36 @@
stream->Align(kElfSectionTableAlignment);
ASSERT_EQUAL(stream->position(), section_table_file_offset_);
- for (intptr_t i = 0; i < sections_.length(); i++) {
- Section* section = sections_[i];
+ for (auto const section : sections_) {
const intptr_t start = stream->position();
- section->WriteSectionEntry(stream);
+ section->WriteSectionHeader(stream);
const intptr_t end = stream->position();
- ASSERT_EQUAL(end - start, kElfSectionTableEntrySize);
+ ASSERT_EQUAL(end - start, sizeof(elf::SectionHeader));
}
}
void Elf::WriteSections(ElfWriteStream* stream) {
+ ASSERT(section_table_file_size_ >= 0); // Check for finalization.
+
// Skip the reserved first section, as its alignment is 0 (which will cause
// stream->Align() to fail) and it never contains file contents anyway.
- ASSERT_EQUAL(sections_[0]->section_type, elf::SHT_NULL);
+ ASSERT_EQUAL(static_cast<uint32_t>(sections_[0]->type),
+ static_cast<uint32_t>(elf::SectionHeaderType::SHT_NULL));
ASSERT_EQUAL(sections_[0]->alignment, 0);
+ auto const load_align = Segment::Alignment(elf::ProgramHeaderType::PT_LOAD);
+ const Segment* current_segment = segments_[1];
for (intptr_t i = 1; i < sections_.length(); i++) {
Section* section = sections_[i];
stream->Align(section->alignment);
- ASSERT(stream->position() == section->file_offset());
+ if (section->IsAllocated() && section->load_segment != current_segment) {
+ // Changing segments, so align accordingly.
+ stream->Align(load_align);
+ current_segment = section->load_segment;
+ }
+ ASSERT_EQUAL(stream->position(), section->file_offset());
section->Write(stream);
- ASSERT(stream->position() == section->file_offset() + section->FileSize());
+ ASSERT_EQUAL(stream->position(),
+ section->file_offset() + section->FileSize());
}
}
diff --git a/runtime/vm/elf.h b/runtime/vm/elf.h
index b410bff4..da0af3c 100644
--- a/runtime/vm/elf.h
+++ b/runtime/vm/elf.h
@@ -14,12 +14,10 @@
namespace dart {
class Dwarf;
-class DynamicSegment;
-class DynamicTable;
class ElfWriteStream;
class Section;
+class Segment;
class StringTable;
-class Symbol;
class SymbolTable;
class Elf : public ZoneAllocated {
@@ -39,34 +37,40 @@
static const intptr_t kPageSize = 4096;
+ bool IsStripped() const { return dwarf_ == nullptr; }
+
Zone* zone() { return zone_; }
const Dwarf* dwarf() const { return dwarf_; }
Dwarf* dwarf() { return dwarf_; }
uword BssStart(bool vm) const;
- intptr_t NextMemoryOffset() const { return memory_offset_; }
- intptr_t NextSectionIndex() const { return sections_.length(); }
+ // What the next memory offset for a kPageSize-aligned section would be.
+ //
+ // Only used by BlobImageWriter::WriteText() to determine the memory offset
+ // for the text section before it is added.
+ intptr_t NextMemoryOffset() const;
+ intptr_t AddNoBits(const char* name, const uint8_t* bytes, intptr_t size);
intptr_t AddText(const char* name, const uint8_t* bytes, intptr_t size);
intptr_t AddROData(const char* name, const uint8_t* bytes, intptr_t size);
void AddDebug(const char* name, const uint8_t* bytes, intptr_t size);
- // Returns whether the symbol was found. If found, sets the contents of
- // offset and size appropriately if either or both are not nullptr.
- bool FindDynamicSymbol(const char* name,
- intptr_t* offset,
- intptr_t* size) const;
- // Returns whether the symbol was found. If found, sets the contents of
- // offset and size appropriately if either or both are not nullptr.
- bool FindStaticSymbol(const char* name,
- intptr_t* offset,
- intptr_t* size) const;
-
void Finalize();
private:
- void AddSection(Section* section, const char* name);
- intptr_t AddSegmentSymbol(const Section* section, const char* name);
+ static Section* CreateBSS(Zone* zone, Type type, intptr_t size);
+
+ // Adds the section and also creates a PT_LOAD segment for the section if it
+ // is an allocated section.
+ //
+ // For allocated sections, if symbol_name is provided, a symbol for the
+ // section will be added to the dynamic table (if allocated) and static
+ // table (if not stripped) during finalization.
+ //
+ // Returns the memory offset if the section is allocated.
+ intptr_t AddSection(Section* section,
+ const char* name,
+ const char* symbol_name = nullptr);
void AddStaticSymbol(const char* name,
intptr_t info,
intptr_t section_index,
@@ -78,10 +82,11 @@
intptr_t address,
intptr_t size);
- static Section* CreateBSS(Zone* zone, Type type, intptr_t size);
+ Segment* LastLoadSegment() const;
+ const Section* FindSectionForAddress(intptr_t address) const;
+ Section* GenerateBuildId();
- const Section* FindSegmentForAddress(intptr_t address) const;
-
+ void AddSectionSymbols();
void FinalizeDwarfSections();
void FinalizeProgramTable();
void ComputeFileOffsets();
@@ -94,6 +99,7 @@
Zone* const zone_;
StreamingWriteStream* const unwrapped_stream_;
const Type type_;
+
// If nullptr, then the ELF file should be stripped of static information like
// the static symbol table (and its corresponding string table).
Dwarf* const dwarf_;
@@ -108,16 +114,12 @@
StringTable* const dynstrtab_;
SymbolTable* const dynsym_;
- // Can only be created once the dynamic symbol table is complete.
- DynamicTable* dynamic_ = nullptr;
- DynamicSegment* dynamic_segment_ = nullptr;
-
// The static tables are lazily created when static symbols are added.
StringTable* strtab_ = nullptr;
SymbolTable* symtab_ = nullptr;
GrowableArray<Section*> sections_;
- GrowableArray<Section*> segments_;
+ GrowableArray<Segment*> segments_;
intptr_t memory_offset_;
intptr_t section_table_file_offset_ = -1;
intptr_t section_table_file_size_ = -1;
diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc
index ccb436a..cb2ef41 100644
--- a/runtime/vm/image_snapshot.cc
+++ b/runtime/vm/image_snapshot.cc
@@ -412,6 +412,8 @@
}
// Append the direct-mapped RO data objects after the clustered snapshot.
+ // We need to do this before WriteText because WriteText currently adds the
+ // finalized contents of the clustered_stream as data sections.
offset_space_ = vm ? V8SnapshotProfileWriter::kVmData
: V8SnapshotProfileWriter::kIsolateData;
WriteROData(clustered_stream);
@@ -1076,9 +1078,15 @@
assembly_stream_.Print(".globl %s\n", data_symbol);
Align(kMaxObjectAlignment);
assembly_stream_.Print("%s:\n", data_symbol);
- uword buffer = reinterpret_cast<uword>(clustered_stream->buffer());
- intptr_t length = clustered_stream->bytes_written();
+ const uword buffer = reinterpret_cast<uword>(clustered_stream->buffer());
+ const intptr_t length = clustered_stream->bytes_written();
WriteByteSequence(buffer, buffer + length);
+#if defined(DART_PRECOMPILER)
+ if (debug_elf_ != nullptr) {
+ // Add a NoBits section for the ROData as well.
+ debug_elf_->AddROData(data_symbol, clustered_stream->buffer(), length);
+ }
+#endif // defined(DART_PRECOMPILER)
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
@@ -1486,18 +1494,20 @@
auto const segment_base2 =
elf_->AddText(instructions_symbol, instructions_blob_stream_.buffer(),
instructions_blob_stream_.bytes_written());
- ASSERT(segment_base == segment_base2);
+ ASSERT_EQUAL(segment_base2, segment_base);
// Write the .rodata section here like the AssemblyImageWriter.
elf_->AddROData(data_symbol, clustered_stream->buffer(),
clustered_stream->bytes_written());
}
if (debug_elf_ != nullptr) {
- // To keep memory addresses consistent, we need to add corresponding
- // sections (though these will be NOBITS sections).
+ // To keep memory addresses consistent, we create elf::SHT_NOBITS sections
+ // in the debugging information. We still pass along the buffers because
+ // we'll need the buffer bytes at generation time to calculate the build ID
+ // so it'll match the one in the snapshot.
auto const debug_segment_base2 = debug_elf_->AddText(
instructions_symbol, instructions_blob_stream_.buffer(),
instructions_blob_stream_.bytes_written());
- ASSERT(debug_segment_base == debug_segment_base2);
+ ASSERT_EQUAL(debug_segment_base2, debug_segment_base);
debug_elf_->AddROData(data_symbol, clustered_stream->buffer(),
clustered_stream->bytes_written());
}
diff --git a/tests/standalone_2/dwarf_stack_trace_test.dart b/tests/standalone_2/dwarf_stack_trace_test.dart
index 0ecac2e..dac3e58 100644
--- a/tests/standalone_2/dwarf_stack_trace_test.dart
+++ b/tests/standalone_2/dwarf_stack_trace_test.dart
@@ -76,6 +76,8 @@
// means we can't depend on virtual addresses in the snapshot lining up with
// those in the separate debugging information.
if (explicits.isNotEmpty) {
+ // Direct-to-ELF snapshots should have a build ID.
+ Expect.isNotNull(dwarf.buildId);
Expect.deepEquals(relocatedAddresses, virtualAddresses);
Expect.deepEquals(explicits, virtualAddresses);
}