diff --git a/pkg/native_stack_traces/CHANGELOG.md b/pkg/native_stack_traces/CHANGELOG.md index 9e00323da2e..10e8638a56b 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 980e8e0a5cd..c44d615d845 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 96b93c9e704..72f762394d5 100644 --- a/pkg/native_stack_traces/lib/src/dwarf.dart +++ b/pkg/native_stack_traces/lib/src/dwarf.dart @@ -1214,6 +1214,18 @@ class Dwarf { 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 cee46f0416f..b0c7c543962 100644 --- a/pkg/native_stack_traces/lib/src/elf.dart +++ b/pkg/native_stack_traces/lib/src/elf.dart @@ -322,7 +322,7 @@ class ProgramHeaderEntry { ..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 @@ class SectionHeaderEntry { 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 @@ class SectionHeaderEntry { _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 @@ class Section { 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 @@ class Section { } } +/// 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 3b0ad2d2e9c..10a0ef87586 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 0e43ad99eb6..56120e78fa4 100644 --- a/runtime/include/dart_api.h +++ b/runtime/include/dart_api.h @@ -3524,17 +3524,20 @@ typedef void (*Dart_StreamingWriteCallback)(void* callback_data, // 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 39b8e81cb39..42c2f8780df 100644 --- a/runtime/platform/elf.h +++ b/runtime/platform/elf.h @@ -36,8 +36,10 @@ struct ElfHeader { }; enum class ProgramHeaderType : uint32_t { + PT_NULL = 0, PT_LOAD = 1, PT_DYNAMIC = 2, + PT_NOTE = 4, PT_PHDR = 6, }; @@ -63,10 +65,22 @@ struct ProgramHeader { #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 @@ struct SectionHeader { 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 @@ struct Symbol { #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_X = 1; 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 SHN_UNDEF = 0; 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_OBJECT = 1; // I.e., data. 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 0fd40845299..2ce45e3070f 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 @@ class ElfWriteStream : public ValueObject { 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(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(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 @@ class Section : public ZoneAllocated { 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 @@ class Section : public ZoneAllocated { #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(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(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 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 @@ class ReservedSection : public Section { 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 @@ class BlobSection : public Section { }; // 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 @@ class ProgramTableLoadSegment : public BlobSection { // // 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 @@ class Symbol : public ZoneAllocated { 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 @@ class Symbol : public ZoneAllocated { 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 Symbol : public ZoneAllocated { 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 @@ class SymbolTable : public Section { 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 @@ static uint32_t ElfHash(const unsigned char* name) { 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(nbucket_); for (intptr_t i = 0; i < nbucket_; i++) { bucket_[i] = elf::STN_UNDEF; @@ -581,7 +694,7 @@ class SymbolHashTable : public Section { } } - 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 SymbolHashTable : public Section { 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(tag)); + stream->WriteAddr(value); +#else + stream->WriteXWord(static_cast(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 @@ class DynamicTable : public Section { // 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(note->type), + static_cast(elf::SectionHeaderType::SHT_NOTE)); + } }; static const intptr_t kProgramTableSegmentSize = Elf::kPageSize; @@ -684,6 +800,7 @@ static const intptr_t kProgramTableSegmentSize = Elf::kPageSize; // 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 @@ Elf::Elf(Zone* zone, StreamingWriteStream* stream, Type type, Dwarf* dwarf) 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 @@ Elf::Elf(Zone* zone, StreamingWriteStream* stream, Type type, Dwarf* dwarf) // 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 @@ Elf::Elf(Zone* zone, StreamingWriteStream* stream, Type type, Dwarf* dwarf) // 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(size); + bytes = zone->Alloc(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 @@ void Elf::AddStaticSymbol(const char* name, 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 @@ class DwarfElfStream : public DwarfWriteStream { }; 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(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 @@ void Elf::FinalizeDwarfSections() { 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 @@ void Elf::FinalizeDwarfSections() { 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::FinalizeDwarfSections() { } 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 @@ void Elf::Finalize() { 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(bits->bytes() + (size - non_word_size)); + for (auto cursor = reinterpret_cast(bits->bytes()); + cursor < end_of_words; cursor++) { + hash = CombineHashes(hash, *cursor); + } + for (auto cursor = reinterpret_cast(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(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 @@ void Elf::FinalizeProgramTable() { 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 @@ void Elf::WriteHeader(ElfWriteStream* stream) { #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 @@ void Elf::WriteSectionTable(ElfWriteStream* stream) { 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(sections_[0]->type), + static_cast(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 b410bff462c..da0af3c61e6 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 @@ class Elf : public ZoneAllocated { 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 @@ class Elf : public ZoneAllocated { intptr_t address, intptr_t size); - static Section* CreateBSS(Zone* zone, Type type, intptr_t size); - - const Section* FindSegmentForAddress(intptr_t address) const; + Segment* LastLoadSegment() const; + const Section* FindSectionForAddress(intptr_t address) const; + Section* GenerateBuildId(); + void AddSectionSymbols(); void FinalizeDwarfSections(); void FinalizeProgramTable(); void ComputeFileOffsets(); @@ -94,6 +99,7 @@ class Elf : public ZoneAllocated { 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 @@ class Elf : public ZoneAllocated { 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 sections_; - GrowableArray segments_; + GrowableArray 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 ccb436ab9b5..cb2ef41b3e9 100644 --- a/runtime/vm/image_snapshot.cc +++ b/runtime/vm/image_snapshot.cc @@ -412,6 +412,8 @@ void ImageWriter::Write(WriteStream* clustered_stream, bool vm) { } // 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 @@ void AssemblyImageWriter::WriteText(WriteStream* clustered_stream, bool vm) { assembly_stream_.Print(".globl %s\n", data_symbol); Align(kMaxObjectAlignment); assembly_stream_.Print("%s:\n", data_symbol); - uword buffer = reinterpret_cast(clustered_stream->buffer()); - intptr_t length = clustered_stream->bytes_written(); + const uword buffer = reinterpret_cast(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 @@ void BlobImageWriter::WriteText(WriteStream* clustered_stream, bool vm) { 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 0ecac2e30d2..dac3e580c36 100644 --- a/tests/standalone_2/dwarf_stack_trace_test.dart +++ b/tests/standalone_2/dwarf_stack_trace_test.dart @@ -76,6 +76,8 @@ Future checkStackTrace(String rawStack, Dwarf dwarf, // 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); }