// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // 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. #include "vm/elf.h" #include "platform/elf.h" #include "platform/unwinding_records.h" #include "vm/cpu.h" #include "vm/dwarf.h" #include "vm/dwarf_so_writer.h" #include "vm/hash_map.h" #include "vm/image_snapshot.h" #include "vm/stack_frame.h" #include "vm/thread.h" #include "vm/unwinding_records.h" #include "vm/zone_text_buffer.h" namespace dart { #if defined(DART_PRECOMPILER) class ElfWriteStream : public SharedObjectWriter::DelegatingWriteStream { public: explicit ElfWriteStream(BaseWriteStream* stream, const ElfWriter& elf) : SharedObjectWriter::DelegatingWriteStream(stream, elf), elf_(elf) {} void WriteStringTableIndex(); void WriteHalf(uint16_t value) { stream_->WriteFixed(value); } void WriteWord(uint32_t value) { stream_->WriteFixed(value); } void WriteAddr(compiler::target::uword value) { stream_->WriteFixed(value); } void WriteOff(compiler::target::uword value) { stream_->WriteFixed(value); } #if defined(TARGET_ARCH_IS_64_BIT) void WriteXWord(uint64_t value) { stream_->WriteFixed(value); } #endif bool HasValueForLabel(intptr_t label, intptr_t* value) const override; private: const ElfWriter& elf_; DISALLOW_COPY_AND_ASSIGN(ElfWriteStream); }; static constexpr intptr_t kLinearInitValue = -1; #define DEFINE_LINEAR_FIELD_METHODS(name) \ intptr_t name() const { \ ASSERT(name##_ != kLinearInitValue); \ return name##_; \ } \ 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) intptr_t name##_ = kLinearInitValue; // We only allow for dynamic casting to a subset of section types, since // these are the only ones we need to distinguish at runtime. #define FOR_EACH_SECTION_TYPE(V) \ V(ReservedSection) \ V(ElfSymbolTable) \ V(DynamicTable) \ V(BitsContainer) \ V(TextSection) V(DataSection) V(BssSection) V(PseudoSection) V(SectionTable) #define DEFINE_TYPE_CHECK_FOR(Type) \ bool Is##Type() const { \ return true; \ } #define DECLARE_SECTION_TYPE_CLASS(Type) class Type; FOR_EACH_SECTION_TYPE(DECLARE_SECTION_TYPE_CLASS) #undef DECLARE_SECTION_TYPE_CLASS class BitsContainer; class Segment; class ElfStringTable; // Align note sections and segments to 4 byte boundaries. static constexpr intptr_t kNoteAlignment = 4; class ElfSection : public ZoneObject { public: ElfSection(elf::SectionHeaderType t, bool allocate, bool executable, bool writable, intptr_t alignment = compiler::target::kWordSize) : type(t), flags(EncodeFlags(allocate, executable, writable)), alignment(alignment), // Non-segments will never have a memory offset, here represented by 0. memory_offset_(allocate ? kLinearInitValue : 0) { // Only SHT_NULL sections (namely, the reserved section) are allowed to have // an alignment of 0 (as the written section header entry for the reserved // section must be all 0s). ASSERT(alignment > 0 || type == elf::SectionHeaderType::SHT_NULL); // Non-zero alignments must be a power of 2. ASSERT(alignment == 0 || Utils::IsPowerOfTwo(alignment)); } virtual ~ElfSection() {} const char* ToCString(const ElfStringTable& string_table); // Linker view. 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 link = elf::SHN_UNDEF; intptr_t info = 0; intptr_t entry_size = 0; // This field is set for all sections, but due to reordering, we may set it // more than once. intptr_t index = elf::SHN_UNDEF; #define FOR_EACH_SECTION_LINEAR_FIELD(M) \ M(name) \ M(file_offset) FOR_EACH_SECTION_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS); // Only needs to be overridden for sections that may not be allocated or // for allocated sections where MemorySize() and FileSize() may differ. virtual intptr_t FileSize() const { if (!IsAllocated()) { UNREACHABLE(); } return MemorySize(); } // Loader view. #define FOR_EACH_SEGMENT_LINEAR_FIELD(M) M(memory_offset) FOR_EACH_SEGMENT_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS); // Only needs to be overridden for sections that may be allocated. virtual intptr_t MemorySize() const { if (IsAllocated()) { UNREACHABLE(); } return 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 the size of a section can change. bool HasBeenFinalized() const { // Sections can grow or shrink up until ElfWriter::ComputeOffsets has been // run, which sets the file (and memory, if applicable) offsets. return file_offset_is_set(); } #define DEFINE_BASE_TYPE_CHECKS(Type) \ Type* As##Type() { \ return Is##Type() ? reinterpret_cast(this) : nullptr; \ } \ const Type* As##Type() const { \ return const_cast(const_cast(this)->As##Type()); \ } \ virtual bool Is##Type() const { return false; } FOR_EACH_SECTION_TYPE(DEFINE_BASE_TYPE_CHECKS) #undef DEFINE_BASE_TYPE_CHECKS // Only some sections support merging. virtual bool CanMergeWith(const ElfSection& other) const { return false; } virtual void Merge(const ElfSection& other) { UNREACHABLE(); } // Writes the file contents of the section. virtual void Write(ElfWriteStream* stream) const { UNREACHABLE(); } virtual void WriteSectionHeader(ElfWriteStream* stream) const { #if defined(TARGET_ARCH_IS_32_BIT) stream->WriteWord(name()); stream->WriteWord(static_cast(type)); stream->WriteWord(flags); stream->WriteAddr(memory_offset()); stream->WriteOff(file_offset()); stream->WriteWord(FileSize()); stream->WriteWord(link); stream->WriteWord(info); stream->WriteWord(alignment); stream->WriteWord(entry_size); #else stream->WriteWord(name()); stream->WriteWord(static_cast(type)); stream->WriteXWord(flags); stream->WriteAddr(memory_offset()); stream->WriteOff(file_offset()); stream->WriteXWord(FileSize()); stream->WriteWord(link); stream->WriteWord(info); stream->WriteXWord(alignment); stream->WriteXWord(entry_size); #endif } virtual bool IsExcludedFromDebugInfo() const { return false; } private: static intptr_t EncodeFlags(bool allocate, bool executable, bool writable) { // We currently don't allow sections that are both executable and writable. ASSERT(!executable || !writable); intptr_t flags = 0; if (allocate) flags |= elf::SHF_ALLOC; if (executable) flags |= elf::SHF_EXECINSTR; if (writable) flags |= elf::SHF_WRITE; return flags; } FOR_EACH_SECTION_LINEAR_FIELD(DEFINE_LINEAR_FIELD); FOR_EACH_SEGMENT_LINEAR_FIELD(DEFINE_LINEAR_FIELD); #undef FOR_EACH_SECTION_LINEAR_FIELD #undef FOR_EACH_SEGMENT_LINEAR_FIELD }; #undef DEFINE_LINEAR_FIELD #undef DEFINE_LINEAR_FIELD_METHODS class Segment : public ZoneObject { public: Segment(Zone* zone, ElfSection* 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. ASSERT(initial_section != nullptr); sections_.Add(initial_section); } virtual ~Segment() {} const GrowableArray& sections() const { return sections_; } intptr_t Alignment() const { switch (type) { case elf::ProgramHeaderType::PT_LOAD: return ElfWriter::kPageSize; case elf::ProgramHeaderType::PT_PHDR: case elf::ProgramHeaderType::PT_DYNAMIC: return compiler::target::kWordSize; case elf::ProgramHeaderType::PT_NOTE: return kNoteAlignment; case elf::ProgramHeaderType::PT_GNU_STACK: return 1; default: UNREACHABLE(); return 0; } } 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) const { #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. stream->WriteWord(FileSize()); stream->WriteWord(MemorySize()); stream->WriteWord(flags); stream->WriteWord(Alignment()); #else stream->WriteWord(static_cast(type)); stream->WriteWord(flags); stream->WriteOff(FileOffset()); stream->WriteAddr(MemoryOffset()); // Virtual address. stream->WriteAddr(MemoryOffset()); // Physical address. stream->WriteXWord(FileSize()); stream->WriteXWord(MemorySize()); stream->WriteXWord(Alignment()); #endif } // Adds a given section to the end of this segment. Returns whether the // section was successfully added. bool Add(ElfSection* section) { ASSERT(section != nullptr); // We can't add if memory offsets have already been calculated. ASSERT(!section->memory_offset_is_set()); // We only add additional sections to load segments. ASSERT(type == elf::ProgramHeaderType::PT_LOAD); // We only add sections with the same executable and writable bits. if (IsExecutable() != section->IsExecutable() || IsWritable() != section->IsWritable()) { return false; } sections_.Add(section); 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(); } const elf::ProgramHeaderType type; const intptr_t flags; private: 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; } 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 ElfSection { public: ReservedSection() : ElfSection(elf::SectionHeaderType::SHT_NULL, /*allocate=*/false, /*executable=*/false, /*writable=*/false, /*alignment=*/0) { set_file_offset(0); } DEFINE_TYPE_CHECK_FOR(ReservedSection); intptr_t FileSize() const { return 0; } }; // Specifies the permissions used for the stack, notably whether the stack // should be executable. If absent, the stack will be executable. class GnuStackSection : public ElfSection { public: GnuStackSection() : ElfSection(elf::SectionHeaderType::SHT_NULL, /*allocate=*/false, /*executable=*/false, /*writable=*/true) { set_file_offset(0); } intptr_t FileSize() const { return 0; } }; class ElfStringTable : public ElfSection { public: explicit ElfStringTable(Zone* zone, bool allocate) : ElfSection(elf::SectionHeaderType::SHT_STRTAB, allocate, /*executable=*/false, /*writable=*/false), dynamic_(allocate), text_(zone, 128), text_indices_(zone) { Add(""); } intptr_t FileSize() const { return text_.length(); } intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; } void Write(ElfWriteStream* stream) const { stream->WriteBytes(text_.buffer(), text_.length()); } intptr_t Add(const char* str) { ASSERT(str != nullptr); if (auto const kv = text_indices_.Lookup(str)) { return kv->value; } intptr_t offset = text_.length(); text_.AddString(str); text_.AddChar('\0'); text_indices_.Insert({str, offset}); return offset; } const char* At(intptr_t index) const { if (index >= text_.length()) return nullptr; return text_.buffer() + index; } static constexpr intptr_t kNotIndexed = CStringIntMapKeyValueTrait::kNoValue; // Returns the index of |str| if it is present in the string table // and |kNotIndexed| otherwise. intptr_t Lookup(const char* str) const { return text_indices_.LookupValue(str); } const bool dynamic_; ZoneTextBuffer text_; CStringIntMap text_indices_; }; class ElfSymbolTable : public ElfSection { public: ElfSymbolTable(Zone* zone, ElfStringTable* table, bool dynamic) : ElfSection(dynamic ? elf::SectionHeaderType::SHT_DYNSYM : elf::SectionHeaderType::SHT_SYMTAB, dynamic, /*executable=*/false, /*writable=*/false), zone_(zone), table_(table), dynamic_(dynamic), symbols_(zone, 1), by_label_index_(zone) { link = table_->index; entry_size = sizeof(elf::Symbol); // The first symbol table entry is reserved and must be all zeros. // (String tables always have the empty string at the 0th index.) ASSERT_EQUAL(table_->Lookup(""), 0); symbols_.Add({/*name_index=*/0, elf::STB_LOCAL, elf::STT_NOTYPE, /*size=*/0, elf::SHN_UNDEF, /*offset=*/0, /*label =*/0}); // The info field on a symbol table section holds the index of the first // non-local symbol, so since there are none yet, it points past the single // symbol we do have. info = 1; } DEFINE_TYPE_CHECK_FOR(ElfSymbolTable) const ElfStringTable& strtab() const { return *table_; } intptr_t FileSize() const { return symbols_.length() * entry_size; } intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; } struct Symbol { void Write(ElfWriteStream* stream) const { const intptr_t start = stream->Position(); ASSERT(section_index == elf::SHN_UNDEF || offset > 0); stream->WriteWord(name_index); #if defined(TARGET_ARCH_IS_32_BIT) stream->WriteAddr(offset); stream->WriteWord(size); stream->WriteByte(elf::SymbolInfo(binding, type)); stream->WriteByte(0); stream->WriteHalf(section_index); #else stream->WriteByte(elf::SymbolInfo(binding, type)); stream->WriteByte(0); stream->WriteHalf(section_index); stream->WriteAddr(offset); stream->WriteXWord(size); #endif ASSERT_EQUAL(stream->Position() - start, sizeof(elf::Symbol)); } intptr_t name_index; intptr_t binding; intptr_t type; intptr_t size; // Must be updated whenever sections are reordered. intptr_t section_index; // Initialized to the section-relative offset, must be updated to the // snapshot-relative offset before writing. intptr_t offset; // Only used within the VM and not written as part of the ELF file. If 0, // this symbol cannot be looked up via label. intptr_t label; private: DISALLOW_ALLOCATION(); }; const GrowableArray& symbols() const { return symbols_; } void Initialize(const GrowableArray& sections); void Write(ElfWriteStream* stream) const { for (const auto& symbol : symbols_) { const intptr_t start = stream->Position(); symbol.Write(stream); ASSERT_EQUAL(stream->Position() - start, entry_size); } } void AddSymbol(const char* name, intptr_t binding, intptr_t type, intptr_t size, intptr_t index, intptr_t offset, intptr_t label) { ASSERT(label > 0); ASSERT(!table_->HasBeenFinalized()); auto const name_index = table_->Add(name); ASSERT(name_index != 0); const intptr_t new_index = symbols_.length(); symbols_.Add({name_index, binding, type, size, index, offset, label}); by_label_index_.Insert(label, new_index); // The info field on a symbol table section holds the index of the first // non-local symbol, so that local symbols can be skipped if desired. Thus, // we need to make sure local symbols are before any non-local ones. if (binding == elf::STB_LOCAL) { if (info != new_index) { // There are non-local symbols, as otherwise [info] would be the // index of the new symbol. Since the order doesn't otherwise matter, // swap the new local symbol with the value at index [info], so when // [info] is incremented it will point just past the new local symbol. ASSERT(symbols_[info].binding != elf::STB_LOCAL); symbols_.Swap(info, new_index); // Since by_label_index has indices into symbols_, we need to update it. by_label_index_.Update({symbols_[info].label, info}); by_label_index_.Update({symbols_[new_index].label, new_index}); } info += 1; } } void UpdateSectionIndices(const GrowableArray& index_map) { #if defined(DEBUG) const intptr_t map_size = index_map.length(); // The first entry must be 0 so that symbols with index SHN_UNDEF, like // the initial reserved symbol, are unchanged. ASSERT_EQUAL(index_map[0], 0); for (intptr_t i = 1; i < map_size; i++) { ASSERT(index_map[i] != 0); ASSERT(index_map[i] < map_size); } #endif for (auto& symbol : symbols_) { DEBUG_ASSERT(symbol.section_index < map_size); symbol.section_index = index_map[symbol.section_index]; } } void Finalize(const GrowableArray& address_map) { #if defined(DEBUG) const intptr_t map_size = address_map.length(); // The first entry must be 0 so that symbols with index SHN_UNDEF, like // the initial reserved symbol, are unchanged. ASSERT_EQUAL(address_map[0], 0); for (intptr_t i = 1; i < map_size; i++) { // No section begins at the start of the snapshot. ASSERT(address_map[i] != 0); } #endif for (auto& symbol : symbols_) { DEBUG_ASSERT(symbol.section_index < map_size); symbol.offset += address_map[symbol.section_index]; } } const Symbol* FindLabel(intptr_t label) const { ASSERT(label > 0); const intptr_t symbols_index = by_label_index_.Lookup(label); if (symbols_index == 0) return nullptr; // Not found. return &symbols_[symbols_index]; } private: static intptr_t ElfSymbolType(SharedObjectWriter::SymbolData::Type type) { switch (type) { case SharedObjectWriter::SymbolData::Type::Section: return elf::STT_SECTION; case SharedObjectWriter::SymbolData::Type::Function: return elf::STT_FUNC; case SharedObjectWriter::SymbolData::Type::Object: return elf::STT_OBJECT; } UNREACHABLE(); } Zone* const zone_; ElfStringTable* const table_; const bool dynamic_; GrowableArray symbols_; // Maps positive symbol labels to indexes in symbols_. No entry for the // reserved symbol, which has index 0, the same as the IntMap's kNoValue. IntMap by_label_index_; }; bool ElfWriteStream::HasValueForLabel(intptr_t label, intptr_t* value) const { auto* const symbol = elf_.symtab().FindLabel(label); if (symbol != nullptr) { *value = symbol->offset; } return symbol != nullptr; } class SymbolHashTable : public ElfSection { public: SymbolHashTable(Zone* zone, ElfSymbolTable* symtab) : ElfSection(elf::SectionHeaderType::SHT_HASH, /*allocate=*/true, /*executable=*/false, /*writable=*/false), buckets_(zone, 0), chains_(zone, 0) { link = symtab->index; entry_size = sizeof(int32_t); const auto& symbols = symtab->symbols(); const intptr_t num_symbols = symbols.length(); buckets_.FillWith(elf::STN_UNDEF, 0, num_symbols); chains_.FillWith(elf::STN_UNDEF, 0, num_symbols); for (intptr_t i = 1; i < num_symbols; i++) { const auto& symbol = symbols[i]; uint32_t hash = HashSymbolName(symtab->strtab().At(symbol.name_index)); uint32_t probe = hash % num_symbols; chains_[i] = buckets_[probe]; // next = head buckets_[probe] = i; // head = symbol } } intptr_t MemorySize() const { return entry_size * (buckets_.length() + chains_.length() + 2); } void Write(ElfWriteStream* stream) const { stream->WriteWord(buckets_.length()); stream->WriteWord(chains_.length()); for (const int32_t bucket : buckets_) { stream->WriteWord(bucket); } for (const int32_t chain : chains_) { stream->WriteWord(chain); } } static uint32_t HashSymbolName(const void* p) { auto* name = reinterpret_cast(p); uint32_t h = 0; while (*name != '\0') { h = (h << 4) + *name++; uint32_t g = h & 0xf0000000; h ^= g; h ^= g >> 24; } return h; } private: GrowableArray buckets_; // "Head" GrowableArray chains_; // "Next" }; class DynamicTable : public ElfSection { public: // .dynamic section is expected to be writable on most Linux systems // unless dynamic linker is explicitly built with support for an read-only // .dynamic section (DL_RO_DYN_SECTION). DynamicTable(Zone* zone, ElfSymbolTable* symtab, SymbolHashTable* hash) : ElfSection(elf::SectionHeaderType::SHT_DYNAMIC, /*allocate=*/true, /*executable=*/false, /*writable=*/true), symtab_(symtab), hash_(hash) { link = strtab().index; entry_size = sizeof(elf::DynamicEntry); AddEntry(zone, elf::DynamicEntryType::DT_HASH, kInvalidEntry); AddEntry(zone, elf::DynamicEntryType::DT_STRTAB, kInvalidEntry); AddEntry(zone, elf::DynamicEntryType::DT_STRSZ, kInvalidEntry); AddEntry(zone, elf::DynamicEntryType::DT_SYMTAB, kInvalidEntry); AddEntry(zone, elf::DynamicEntryType::DT_SYMENT, sizeof(elf::Symbol)); AddEntry(zone, elf::DynamicEntryType::DT_NULL, 0); } static constexpr intptr_t kInvalidEntry = -1; DEFINE_TYPE_CHECK_FOR(DynamicTable) const SymbolHashTable& hash() const { return *hash_; } const ElfSymbolTable& symtab() const { return *symtab_; } const ElfStringTable& strtab() const { return symtab().strtab(); } intptr_t MemorySize() const { return entries_.length() * entry_size; } void Write(ElfWriteStream* stream) const { for (intptr_t i = 0; i < entries_.length(); i++) { entries_[i]->Write(stream); } } void Finalize() { FinalizeEntry(elf::DynamicEntryType::DT_HASH, hash().memory_offset()); FinalizeEntry(elf::DynamicEntryType::DT_STRTAB, strtab().memory_offset()); FinalizeEntry(elf::DynamicEntryType::DT_STRSZ, strtab().MemorySize()); FinalizeEntry(elf::DynamicEntryType::DT_SYMTAB, symtab().memory_offset()); } private: struct Entry : public ZoneObject { Entry(elf::DynamicEntryType tag, intptr_t value) : tag(tag), value(value) {} void Write(ElfWriteStream* stream) const { ASSERT(value != kInvalidEntry); 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(Zone* zone, elf::DynamicEntryType tag, intptr_t value) { auto const entry = new (zone) Entry(tag, value); entries_.Add(entry); } void FinalizeEntry(elf::DynamicEntryType tag, intptr_t value) { for (auto* entry : entries_) { if (entry->tag == tag) { entry->value = value; break; } } } ElfSymbolTable* const symtab_; SymbolHashTable* const hash_; GrowableArray entries_; }; class BitsContainer : public ElfSection { public: // Fully specified BitsContainer information. Unless otherwise specified, // BitContainers are aligned on byte boundaries (i.e., no padding is used). BitsContainer(elf::SectionHeaderType type, bool allocate, bool executable, bool writable, int alignment = 1, bool is_excluded_from_debug_info = false) : ElfSection(type, allocate, executable, writable, alignment), is_excluded_from_debug_info_(is_excluded_from_debug_info) {} // For BitsContainers used only as unallocated sections. explicit BitsContainer(elf::SectionHeaderType type, intptr_t alignment = 1) : BitsContainer(type, /*allocate=*/false, /*executable=*/false, /*writable=*/false, alignment) {} // For BitsContainers used as segments whose type differ on the type of the // ELF file. Creates an elf::SHT_PROGBITS section if type is Snapshot, // otherwise creates an elf::SHT_NOBITS section. BitsContainer(ElfWriter::Type t, bool executable, bool writable, intptr_t alignment = 1) : BitsContainer(t == ElfWriter::Type::Snapshot ? elf::SectionHeaderType::SHT_PROGBITS : elf::SectionHeaderType::SHT_NOBITS, /*allocate=*/true, executable, writable, alignment, /*is_excluded_from_debug_info=*/true) {} DEFINE_TYPE_CHECK_FOR(BitsContainer) bool IsNoBits() const { return type == elf::SectionHeaderType::SHT_NOBITS; } bool HasBytes() const { return portions_.length() != 0 && portions_[0].bytes != nullptr; } struct Portion { void Write(ElfWriteStream* stream, intptr_t section_start) const { ASSERT(bytes != nullptr); if (relocations != nullptr) { const intptr_t address = section_start + offset; stream->WriteBytesWithRelocations(bytes, size, address, *relocations); } else { stream->WriteBytes(bytes, size); } } intptr_t offset; const char* symbol_name; intptr_t label; const uint8_t* bytes; intptr_t size; const SharedObjectWriter::RelocationArray* relocations; const SharedObjectWriter::SymbolDataArray* symbols; private: DISALLOW_ALLOCATION(); }; const GrowableArray& portions() const { return portions_; } const Portion& AddPortion( const uint8_t* bytes, intptr_t size, const SharedObjectWriter::RelocationArray* relocations = nullptr, const SharedObjectWriter::SymbolDataArray* symbols = nullptr, const char* symbol_name = nullptr, intptr_t label = 0) { // Any named portion should also have a valid symbol label. ASSERT(symbol_name == nullptr || label > 0); ASSERT(IsNoBits() || bytes != nullptr); ASSERT(bytes != nullptr || relocations == nullptr); // Make sure all portions are consistent in containing bytes. ASSERT(portions_.is_empty() || HasBytes() == (bytes != nullptr)); const intptr_t offset = Utils::RoundUp(total_size_, alignment); portions_.Add( {offset, symbol_name, label, bytes, size, relocations, symbols}); const Portion& portion = portions_.Last(); total_size_ = offset + size; return portion; } void Write(ElfWriteStream* stream) const { if (type == elf::SectionHeaderType::SHT_NOBITS) return; intptr_t start_position = stream->Position(); // Used for checks. for (const auto& portion : portions_) { stream->Align(alignment); ASSERT_EQUAL(stream->Position(), start_position + portion.offset); portion.Write(stream, memory_offset()); } ASSERT_EQUAL(stream->Position(), start_position + total_size_); } // Returns the hash for the portion corresponding to symbol_name. // Returns 0 if the portion has no bytes or no portions have that name. uint64_t Hash(const char* symbol_name) const { for (const auto& portion : portions_) { if (strcmp(symbol_name, portion.symbol_name) == 0) { if (portion.bytes == nullptr) return 0; const uint64_t hash = Utils::StringHash64(portion.bytes, portion.size); // Ensure a non-zero return. return hash == 0 ? 1 : hash; } } return 0; } intptr_t FileSize() const { return IsNoBits() ? 0 : total_size_; } intptr_t MemorySize() const { return IsAllocated() ? total_size_ : 0; } virtual bool IsExcludedFromDebugInfo() const { return is_excluded_from_debug_info_; } private: const bool is_excluded_from_debug_info_; GrowableArray portions_; intptr_t total_size_ = 0; }; class NoteSection : public BitsContainer { public: NoteSection() : BitsContainer(elf::SectionHeaderType::SHT_NOTE, /*allocate=*/true, /*executable=*/false, /*writable=*/false, kNoteAlignment) {} }; // Abstract bits container that allows merging by just appending the portion // information (with properly adjusted offsets) of the other to this one. class ConcatenableBitsContainer : public BitsContainer { public: ConcatenableBitsContainer(ElfWriter::Type type, bool executable, bool writable, intptr_t alignment) : BitsContainer(type, executable, writable, alignment) {} virtual bool CanMergeWith(const ElfSection& other) const = 0; virtual void Merge(const ElfSection& other) { ASSERT(other.IsBitsContainer()); ASSERT(CanMergeWith(other)); for (const auto& portion : other.AsBitsContainer()->portions()) { AddPortion(portion.bytes, portion.size, portion.relocations, portion.symbols, portion.symbol_name, portion.label); } } }; class TextSection : public ConcatenableBitsContainer { public: explicit TextSection(ElfWriter::Type t) : ConcatenableBitsContainer(t, /*executable=*/true, /*writable=*/false, ImageWriter::kTextAlignment) {} DEFINE_TYPE_CHECK_FOR(TextSection); virtual bool CanMergeWith(const ElfSection& other) const { return other.IsTextSection(); } }; class DataSection : public ConcatenableBitsContainer { public: explicit DataSection(ElfWriter::Type t) : ConcatenableBitsContainer(t, /*executable=*/false, /*writable=*/false, ImageWriter::kRODataAlignment) {} DEFINE_TYPE_CHECK_FOR(DataSection); virtual bool CanMergeWith(const ElfSection& other) const { return other.IsDataSection(); } }; class BssSection : public ConcatenableBitsContainer { public: explicit BssSection(ElfWriter::Type t) : ConcatenableBitsContainer(t, /*executable=*/false, /*writable=*/true, ImageWriter::kBssAlignment) {} DEFINE_TYPE_CHECK_FOR(BssSection); virtual bool CanMergeWith(const ElfSection& other) const { return other.IsBssSection(); } }; // Represents portions of the file/memory space which do not correspond to // sections from the section header. Should never be added to the section table, // but may be added to segments. class PseudoSection : public ElfSection { public: // All PseudoSections are aligned to target word size. static constexpr intptr_t kAlignment = compiler::target::kWordSize; PseudoSection(bool allocate, bool executable, bool writable) : ElfSection(elf::SectionHeaderType::SHT_NULL, allocate, executable, writable, kAlignment) {} DEFINE_TYPE_CHECK_FOR(PseudoSection) void Write(ElfWriteStream* stream) const = 0; }; class ProgramTable : public PseudoSection { public: explicit ProgramTable(Zone* zone) : PseudoSection(/*allocate=*/true, /*executable=*/false, /*writable=*/false), segments_(zone, 0) { entry_size = sizeof(elf::ProgramHeader); } const GrowableArray& segments() const { return segments_; } intptr_t SegmentCount() const { return segments_.length(); } intptr_t MemorySize() const { return segments_.length() * sizeof(elf::ProgramHeader); } void Add(Segment* segment) { ASSERT(segment != nullptr); segments_.Add(segment); } void Write(ElfWriteStream* stream) const; private: GrowableArray segments_; }; // This particular PseudoSection should not appear in segments either (hence // being marked non-allocated), but is directly held by the Elf object. class SectionTable : public PseudoSection { public: explicit SectionTable(Zone* zone) : PseudoSection(/*allocate=*/false, /*executable=*/false, /*writable=*/false), zone_(zone), sections_(zone_, 2), shstrtab_(zone_, /*allocate=*/false) { entry_size = sizeof(elf::SectionHeader); // The section at index 0 (elf::SHN_UNDEF) must be all 0s. ASSERT_EQUAL(shstrtab_.Lookup(""), 0); Add(new (zone_) ReservedSection(), ""); Add(&shstrtab_, ".shstrtab"); } const GrowableArray& sections() const { return sections_; } intptr_t SectionCount() const { return sections_.length(); } intptr_t StringTableIndex() const { return shstrtab_.index; } const ElfStringTable& shstrtab() const { return shstrtab_; } bool HasSectionNamed(const char* name) { return shstrtab_.Lookup(name) != ElfStringTable::kNotIndexed; } void Add(ElfSection* section, const char* name = nullptr) { ASSERT(!section->IsPseudoSection()); ASSERT(name != nullptr || section->name_is_set()); if (name != nullptr) { // First, check for an existing section with the same table name. if (auto* const old_section = Find(name)) { ASSERT(old_section->CanMergeWith(*section)); old_section->Merge(*section); return; } // No existing section with this name. const intptr_t name_index = shstrtab_.Add(name); section->set_name(name_index); } section->index = sections_.length(); sections_.Add(section); } ElfSection* Find(const char* name) const { const intptr_t name_index = shstrtab_.Lookup(name); if (name_index == ElfStringTable::kNotIndexed) { // We're guaranteed that no section with this name has been added yet. return nullptr; } // We check walk all sections to check for uniqueness in DEBUG mode. ElfSection* result = nullptr; for (ElfSection* const section : sections_) { if (section->name() == name_index) { #if defined(DEBUG) ASSERT(result == nullptr); result = section; #else return section; #endif } } return result; } TextSection* FindTextSection() const { if (auto section = Find(ElfWriter::kTextName)) { return section->AsTextSection(); } return nullptr; } intptr_t FileSize() const { return sections_.length() * sizeof(elf::SectionHeader); } void Write(ElfWriteStream* stream) const; // Reorders the sections for creating a minimal amount of segments and // creates and returns an appropriate program table. // // Also takes and adjusts section indices in the static symbol table, since it // is not recorded in sections_ for stripped outputs. ProgramTable* CreateProgramTable(ElfSymbolTable* symtab); private: Zone* const zone_; GrowableArray sections_; ElfStringTable shstrtab_; }; void ElfWriteStream::WriteStringTableIndex() { return WriteHalf(elf_.section_table().StringTableIndex()); } class ElfHeader : public PseudoSection { public: ElfHeader(const ProgramTable& program_table, const SectionTable& section_table) : PseudoSection(/*allocate=*/true, /*executable=*/false, /*writable=*/false), program_table_(program_table), section_table_(section_table) {} intptr_t MemorySize() const { return sizeof(elf::ElfHeader); } void Write(ElfWriteStream* stream) const; private: const ProgramTable& program_table_; const SectionTable& section_table_; }; #undef DEFINE_TYPE_CHECK_FOR #undef FOR_EACH_SECTION_TYPE ElfWriter::ElfWriter(Zone* zone, BaseWriteStream* stream, Type type, Dwarf* dwarf) : SharedObjectWriter(zone, stream, type, dwarf), section_table_(new (zone) SectionTable(zone)) {} void ElfWriter::AddText(const char* name, intptr_t label, const uint8_t* bytes, intptr_t size, const SharedObjectWriter::RelocationArray* relocations, const SharedObjectWriter::SymbolDataArray* symbols) { auto* const container = new (zone_) TextSection(type_); container->AddPortion(bytes, size, relocations, symbols, name, label); section_table_->Add(container, kTextName); } void ElfWriter::CreateBSS() { // Not idempotent. ASSERT(section_table_->Find(kBssName) == nullptr); // No text section means no BSS section. auto* const text_section = section_table_->Find(kTextName); if (text_section == nullptr) return; ASSERT(text_section->IsTextSection()); auto* const bss_container = new (zone_) BssSection(type_); for (const auto& portion : text_section->AsBitsContainer()->portions()) { size_t size; const char* symbol_name; intptr_t label; // First determine whether this is the VM's text portion or the isolate's. if (strcmp(portion.symbol_name, kSnapshotTextAsmSymbol) == 0) { size = BSS::kIsolateGroupEntryCount * compiler::target::kWordSize; symbol_name = kSnapshotBssAsmSymbol; label = kIsolateBssLabel; } else { // Not VM or isolate text. UNREACHABLE(); } uint8_t* bytes = nullptr; if (type_ == Type::Snapshot) { // 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 data for the BSS. bytes = zone_->Alloc(size); memset(bytes, 0, size); } // For the BSS section, we add the section symbols as local symbols in the // static symbol table, as these addresses are only used for relocation. // (This matches the behavior in the assembly output.) auto* symbols = new (zone_) SharedObjectWriter::SymbolDataArray(); symbols->Add({symbol_name, SymbolData::Type::Section, 0, size, label}); bss_container->AddPortion(bytes, size, /*relocations=*/nullptr, symbols); } section_table_->Add(bss_container, kBssName); } void ElfWriter::AddROData( const char* name, intptr_t label, const uint8_t* bytes, intptr_t size, const SharedObjectWriter::RelocationArray* relocations, const SharedObjectWriter::SymbolDataArray* symbols) { auto* const container = new (zone_) DataSection(type_); container->AddPortion(bytes, size, relocations, symbols, name, label); section_table_->Add(container, kDataName); } void ElfSymbolTable::Initialize(const GrowableArray& sections) { for (auto* const section : sections) { // The values of all added symbols are memory addresses. if (!section->IsAllocated()) continue; if (auto* const bits = section->AsBitsContainer()) { for (const auto& portion : section->AsBitsContainer()->portions()) { if (portion.symbol_name != nullptr) { // Global dynamic symbols for the content of a given section, which is // always a single structured element (and thus we use STT_OBJECT). const intptr_t binding = elf::STB_GLOBAL; const intptr_t type = elf::STT_OBJECT; // Some tools assume the static symbol table is a superset of the // dynamic symbol table when it exists and only use it, so put all // dynamic symbols there also. (see dartbug.com/41783). AddSymbol(portion.symbol_name, binding, type, portion.size, section->index, portion.offset, portion.label); } if (!dynamic_ && portion.symbols != nullptr) { for (const auto& symbol_data : *portion.symbols) { const intptr_t type = ElfSymbolType(symbol_data.type); // Local static-only symbols, e.g., code payloads or RO objects. AddSymbol(symbol_data.name, elf::STB_LOCAL, type, symbol_data.size, section->index, portion.offset + symbol_data.offset, symbol_data.label); } } } } } } void ElfWriter::InitializeSymbolTables() { // Not idempotent. ASSERT(symtab_ == nullptr); // Create static and dynamic symbol tables. auto* const dynstrtab = new (zone_) ElfStringTable(zone_, /*allocate=*/true); section_table_->Add(dynstrtab, ".dynstr"); auto* const dynsym = new (zone_) ElfSymbolTable(zone_, dynstrtab, /*dynamic=*/true); section_table_->Add(dynsym, ".dynsym"); dynsym->Initialize(section_table_->sections()); // Now the dynamic symbol table is populated, set up the hash table and // dynamic table. auto* const hash = new (zone_) SymbolHashTable(zone_, dynsym); section_table_->Add(hash, ".hash"); auto* const dynamic = new (zone_) DynamicTable(zone_, dynsym, hash); section_table_->Add(dynamic, kDynamicTableName); // We only add the static string and symbol tables to the section table if // this is an unstripped output, but we always create them as they are used // to resolve relocations. auto* const strtab = new (zone_) ElfStringTable(zone_, /*allocate=*/false); if (!IsStripped()) { section_table_->Add(strtab, ".strtab"); } symtab_ = new (zone_) ElfSymbolTable(zone_, strtab, /*dynamic=*/false); if (!IsStripped()) { section_table_->Add(symtab_, ".symtab"); } symtab_->Initialize(section_table_->sections()); } void ElfWriter::FinalizeEhFrame() { #if !defined(TARGET_ARCH_IA32) auto text_section = section_table_->FindTextSection(); // No text section added means no .eh_frame. if (text_section == nullptr) return; GrowableArray fdes(zone_, 0); for (const auto& portion : text_section->portions()) { ASSERT(portion.label != 0); fdes.Add({portion.label, portion.size}); } ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize); DwarfSharedObjectStream dwarf_stream(zone_, &stream); Dwarf::WriteCallFrameInformationRecords(&dwarf_stream, fdes); auto* const eh_frame = new (zone_) BitsContainer(type_, /*executable=*/false, /*writable=*/false); eh_frame->AddPortion(dwarf_stream.buffer(), dwarf_stream.bytes_written(), dwarf_stream.relocations()); section_table_->Add(eh_frame, ".eh_frame"); #if defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_IS_64_BIT) // Append Windows unwinding instructions to the end of .text section. { // NOLINT auto* const unwinding_instructions_frame = new (zone_) TextSection(type_); ZoneWriteStream stream( zone(), /*initial_size=*/UnwindingRecordsPlatform::SizeInBytes()); uint8_t* unwinding_instructions = zone()->Alloc(UnwindingRecordsPlatform::SizeInBytes()); intptr_t start_offset = Utils::RoundUp(text_section->FileSize(), text_section->alignment); stream.WriteBytes(UnwindingRecords::GenerateRecordsInto( start_offset, unwinding_instructions), UnwindingRecordsPlatform::SizeInBytes()); unwinding_instructions_frame->AddPortion(stream.buffer(), stream.bytes_written()); section_table_->Add(unwinding_instructions_frame, kTextName); } #endif #endif // !defined(TARGET_ARCH_IA32) } void ElfWriter::FinalizeDwarfSections() { if (dwarf_ == nullptr) return; // Currently we only output DWARF information involving code. ASSERT(section_table_->HasSectionNamed(kTextName)); auto add_debug = [&](const char* name, const DwarfSharedObjectStream& stream) { auto const container = new (zone_) BitsContainer(elf::SectionHeaderType::SHT_PROGBITS); container->AddPortion(stream.buffer(), stream.bytes_written(), stream.relocations()); section_table_->Add(container, name); }; { ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize); DwarfSharedObjectStream dwarf_stream(zone_, &stream); dwarf_->WriteAbbreviations(&dwarf_stream); add_debug(".debug_abbrev", dwarf_stream); } { ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize); DwarfSharedObjectStream dwarf_stream(zone_, &stream); dwarf_->WriteDebugInfo(&dwarf_stream); add_debug(".debug_info", dwarf_stream); } { ZoneWriteStream stream(zone(), DwarfSharedObjectStream::kInitialBufferSize); DwarfSharedObjectStream dwarf_stream(zone_, &stream); dwarf_->WriteLineNumberProgram(&dwarf_stream); add_debug(".debug_line", dwarf_stream); } } ProgramTable* SectionTable::CreateProgramTable(ElfSymbolTable* symtab) { const intptr_t num_sections = sections_.length(); // Should have at least the reserved entry in sections_. ASSERT(!sections_.is_empty()); ASSERT_EQUAL(sections_[0]->alignment, 0); // The new program table that collects the segments for allocated sections // and a few special segments. auto* const program_table = new (zone_) ProgramTable(zone_); GrowableArray reordered_sections(zone_, num_sections); // Maps the old indices of sections to the new ones. GrowableArray index_map(zone_, num_sections); index_map.FillWith(0, 0, num_sections); Segment* current_segment = nullptr; // Only called for sections in the section table (i.e., not special sections // appearing in segments only or the section table itself). auto add_to_reordered_sections = [&](ElfSection* section) { intptr_t new_index = reordered_sections.length(); index_map[section->index] = new_index; section->index = new_index; reordered_sections.Add(section); if (section->IsAllocated()) { ASSERT(current_segment != nullptr); if (!current_segment->Add(section)) { // The current segment is incompatible for the current section, so // create a new one. current_segment = new (zone_) Segment(zone_, section, elf::ProgramHeaderType::PT_LOAD); program_table->Add(current_segment); } } }; // The first section in the section header table is always a reserved // entry containing only 0 values, so copy it over from sections_. add_to_reordered_sections(sections_[0]); // There are few important invariants originating from Android idiosyncrasies // we are trying to maintain when ordering sections: // // - Android requires the program header table be in the first load segment, // so create PseudoSections representing the ELF header and program header // table to initialize that segment. // // - The Android dynamic linker in Jelly Bean incorrectly assumes that all // non-writable segments are contiguous. Thus we write them all together. // The bug is here: https://github.com/aosp-mirror/platform_bionic/blob/94963af28e445384e19775a838a29e6a71708179/linker/linker.c#L1991-L2001 // // - On Android native libraries can be mapped directly from an APK // they are stored uncompressed in it. In such situations the name // of the mapping no longer provides enough information for libunwindstack // to find the original ELF file and instead it has to rely on heuristics // to locate program header table. These heuristics currently assume that // program header table will be located in the RO mapping which precedes // RX mapping. // // These invariants imply the following order of segments: RO (program // header, .note.gnu.build-id, .dynstr, .dynsym, .hash, .rodata // and .eh_frame), RX (.text), RW (.dynamic and .bss). // auto* const elf_header = new (zone_) ElfHeader(*program_table, *this); // Self-reference to program header table. Required by Android but not by // Linux. Must appear before any PT_LOAD entries. program_table->Add(new (zone_) Segment(zone_, program_table, elf::ProgramHeaderType::PT_PHDR)); // Create the initial load segment which contains the ELF header and program // table. current_segment = new (zone_) Segment(zone_, elf_header, elf::ProgramHeaderType::PT_LOAD); program_table->Add(current_segment); current_segment->Add(program_table); // We now do several passes over the collected sections to reorder them in // a way that minimizes segments (and thus padding) in the resulting snapshot. auto add_sections_matching = [&](const std::function& should_add) { // We emit section in the following order: // // * all non-NOBITS and non-PROGBITS sections; // * all PROGBITS sections which don't get excluded from debug info; // * all sections which get excluded from debug info (these are all // either PROGBITS or NOBITS depending on the snapshot type); // * all NOBITS sections which don't get excluded from debug info; // // This order guarantees that NOBITS sections are all grouped together // at the end of the segment and the order of sections is the same // for both snapshot ELF and debug info ELF (in other words it does not // change when a section switches its type from PROGBITS to NOBITS). // // Consistent order is important because different sections have // different alignment requirements, which means order might // affect memory offset of section. This might cascade down to the // text section and create inconsistency between snapshot and debug // info. // // See also ElfWriter::AssertConsistency. for (auto* const section : sections_) { if (section->type != elf::SectionHeaderType::SHT_NOBITS && section->type != elf::SectionHeaderType::SHT_PROGBITS) { if (should_add(section)) { add_to_reordered_sections(section); } } } for (auto* const section : sections_) { if (section->type == elf::SectionHeaderType::SHT_PROGBITS && !section->IsExcludedFromDebugInfo() && should_add(section)) { add_to_reordered_sections(section); } } for (auto* const section : sections_) { if (section->IsExcludedFromDebugInfo() && should_add(section)) { add_to_reordered_sections(section); } } for (auto* const section : sections_) { if (section->type == elf::SectionHeaderType::SHT_NOBITS && !section->IsExcludedFromDebugInfo() && should_add(section)) { add_to_reordered_sections(section); } } }; // If a build ID was created, we put it right after the program table so it // can be read with a minimum number of bytes from the ELF file. auto* const build_id = Find(ElfWriter::kBuildIdNoteName); if (build_id != nullptr) { ASSERT(build_id->type == elf::SectionHeaderType::SHT_NOTE); add_to_reordered_sections(build_id); } // Now add the other non-writable, non-executable allocated sections. add_sections_matching([&](ElfSection* section) -> bool { if (section == build_id) return false; // Already added. return section->IsAllocated() && !section->IsWritable() && !section->IsExecutable(); }); // Now add the executable sections in a new segment. add_sections_matching([](ElfSection* section) -> bool { return section->IsExecutable(); // Implies IsAllocated() && !IsWritable() }); // Now add all the writable sections. add_sections_matching([](ElfSection* section) -> bool { return section->IsWritable(); // Implies IsAllocated() && !IsExecutable() }); // We put all non-reserved unallocated sections last. Otherwise, they would // affect the file offset but not the memory offset of any following allocated // sections. Doing it in this order makes it easier to keep file and memory // offsets page-aligned with respect to each other, which is required for // some loaders. add_sections_matching([](ElfSection* section) -> bool { // Don't re-add the initial reserved section. return !section->IsReservedSection() && !section->IsAllocated(); }); // All sections should have been accounted for in the loops above. ASSERT_EQUAL(sections_.length(), reordered_sections.length()); // Replace the content of sections_ with the reordered sections. sections_.Clear(); sections_.AddArray(reordered_sections); // This must be true for uses of the map to be correct. ASSERT_EQUAL(index_map[elf::SHN_UNDEF], elf::SHN_UNDEF); // Since the section indices have been updated, change links to match // and update the indexes of symbols in any symbol tables. for (auto* const section : sections_) { // SHN_UNDEF maps to SHN_UNDEF, so no need to check for it. section->link = index_map[section->link]; if (auto* const table = section->AsElfSymbolTable()) { table->UpdateSectionIndices(index_map); } } if (symtab->index == elf::SHN_UNDEF) { // The output is stripped, so this wasn't finalized during the loop above. symtab->UpdateSectionIndices(index_map); } // Add any special non-load segments. if (build_id != nullptr) { // Add a PT_NOTE segment for the build ID. program_table->Add( new (zone_) Segment(zone_, build_id, elf::ProgramHeaderType::PT_NOTE)); } // Add a PT_DYNAMIC segment for the dynamic symbol table. ASSERT(HasSectionNamed(ElfWriter::kDynamicTableName)); auto* const dynamic = Find(ElfWriter::kDynamicTableName)->AsDynamicTable(); program_table->Add( new (zone_) Segment(zone_, dynamic, elf::ProgramHeaderType::PT_DYNAMIC)); // Add a PT_GNU_STACK segment to prevent the loading of our snapshot from // switch the stack to be executable. auto* const gnu_stack = new (zone_) GnuStackSection(); program_table->Add(new (zone_) Segment(zone_, gnu_stack, elf::ProgramHeaderType::PT_GNU_STACK)); return program_table; } void ElfWriter::Finalize() { // Generate the build ID now that we have all user-provided sections. GenerateBuildId(); // We add a BSS section in all cases, even to the separate debugging // information, to ensure that relocated addresses are consistent between ELF // snapshots and the corresponding separate debugging information. CreateBSS(); FinalizeEhFrame(); FinalizeDwarfSections(); // Create and initialize the dynamic and static symbol tables and any // other associated sections now that all other sections have been added. InitializeSymbolTables(); // Creates an appropriate program table containing load segments for allocated // sections and any other segments needed. May reorder sections to minimize // the number of load segments, so also takes the static symbol table so // symbol section indices can be adjusted if needed. program_table_ = section_table_->CreateProgramTable(symtab_); // Calculate file and memory offsets, and finalizes symbol values in any // symbol tables. ComputeOffsets(); #if defined(DEBUG) if (type_ == Type::Snapshot) { // For files that will be dynamically loaded, ensure the file offsets // of allocated sections are page aligned to the memory offsets. for (auto* const segment : program_table_->segments()) { for (auto* const section : segment->sections()) { ASSERT_EQUAL(section->file_offset() % ElfWriter::kPageSize, section->memory_offset() % ElfWriter::kPageSize); } } } #endif // Finally, write the ELF file contents. ElfWriteStream wrapped(unwrapped_stream_, *this); auto write_section = [&](const ElfSection* section) { wrapped.Align(section->alignment); ASSERT_EQUAL(wrapped.Position(), section->file_offset()); section->Write(&wrapped); ASSERT_EQUAL(wrapped.Position(), section->file_offset() + section->FileSize()); }; // To match ComputeOffsets, first we write allocated sections and then // unallocated sections. We access the allocated sections via the load // segments so we can properly align the stream for each entered segment. intptr_t section_index = 1; // We don't visit the reserved section. for (auto* const segment : program_table_->segments()) { if (segment->type != elf::ProgramHeaderType::PT_LOAD) continue; wrapped.Align(segment->Alignment()); for (auto* const section : segment->sections()) { ASSERT(section->IsAllocated()); write_section(section); if (!section->IsPseudoSection()) { ASSERT_EQUAL(section->index, section_index); section_index++; } } } const auto& sections = section_table_->sections(); for (; section_index < sections.length(); section_index++) { auto* const section = sections[section_index]; ASSERT(!section->IsAllocated()); write_section(section); } // Finally, write the section table. write_section(section_table_); } void ElfWriter::AssertConsistency(const ElfWriter* snapshot, const ElfWriter* debug_info) { // We do not care about consistency if there is no .text section. if (snapshot->section_table_->FindTextSection() == nullptr && debug_info->section_table_->FindTextSection() == nullptr) { return; } // Sections emitted before .text must go in the same order and end up // at the same memory offsets. The reason for this rigid check is to // guarantee that text section ends up at the same memory offset in // both snapshot and debug info ELF files. If we allow sections to be // reordered that might influence memory offset because sections have // different alignment requirements and thus they might pack more // tight in some orderings. // // See reordering algorithm in SectionTable::CreateProgramTable (specifically // add_sections_matching helper). const auto& snapshot_sections = snapshot->section_table_->sections(); const auto& debug_info_sections = debug_info->section_table_->sections(); const auto& snapshot_shstrtab = snapshot->section_table_->shstrtab(); const auto& debug_info_shstrtab = debug_info->section_table_->shstrtab(); bool failed = false; for (intptr_t i = 0; i < snapshot_sections.length(); i++) { if (i >= debug_info_sections.length()) { OS::PrintErr( "mismatch snapshot and debug-info ELF section tables: " "not enough sections in debug info\n"); failed = true; break; } auto snapshot_section = snapshot_sections.At(i); auto debug_info_section = debug_info_sections.At(i); auto snapshot_section_name = snapshot_shstrtab.At(snapshot_section->name()); if (!(snapshot_section->type == debug_info_section->type || (snapshot_section->type == elf::SectionHeaderType::SHT_PROGBITS && debug_info_section->type == elf::SectionHeaderType::SHT_NOBITS)) || snapshot_section->memory_offset() != debug_info_section->memory_offset() || strcmp(snapshot_section_name, debug_info_shstrtab.At(debug_info_section->name())) != 0) { OS::PrintErr("mismatch in section table at index %" Pd ": snapshot has %s, debug info has %s\n", i, snapshot_section->ToCString(snapshot_shstrtab), debug_info_section->ToCString(debug_info_shstrtab)); failed = true; break; } if (strcmp(snapshot_section_name, ElfWriter::kTextName) == 0) { break; } } if (!failed) { return; } const auto dump_sections = [](const GrowableArray& sections, const ElfStringTable& shstrtab) { for (intptr_t i = 0; i < sections.length(); i++) { OS::PrintErr("[% 2" Pd "] %s\n", i, sections[i]->ToCString(shstrtab)); } }; OS::PrintErr("Snapshot ELF sections:\n"); dump_sections(snapshot_sections, snapshot_shstrtab); OS::PrintErr("Debug info ELF sections:\n"); dump_sections(debug_info_sections, debug_info_shstrtab); FATAL("Mismatch between snapshot and debug info ELF was detected"); } // For the build ID, we generate a 128-bit hash, where each 64 bits is a hash of // the contents of the following segments in order: // // .text(Isolate) | .rodata(Isolate) static constexpr const char* kBuildIdSegmentNames[]{ kSnapshotTextAsmSymbol, kSnapshotDataAsmSymbol, }; static constexpr intptr_t kBuildIdSegmentNamesLength = ARRAY_SIZE(kBuildIdSegmentNames); // Includes the note name, but not the description. static constexpr intptr_t kBuildIdHeaderSize = sizeof(elf::Note) + sizeof(elf::ELF_NOTE_GNU); void ElfWriter::GenerateBuildId() { // Not idempotent. ASSERT(section_table_->Find(kBuildIdNoteName) == nullptr); uint64_t hashes[kBuildIdSegmentNamesLength]; // Currently, we construct the build ID out of data from two different // sections: the .text section and the .rodata section. // // TODO(dartbug.com/43274): Generate build IDs for separate debugging // information for assembly snapshots. auto* const text_section = section_table_->Find(kTextName); if (text_section == nullptr) return; ASSERT(text_section->IsTextSection()); auto* const text_bits = text_section->AsBitsContainer(); auto* const data_section = section_table_->Find(kDataName); ASSERT(data_section == nullptr || data_section->IsDataSection()); // Hash each component by first hashing the associated text section and, if // there's not one, hashing the associated data section (if any). // // Any component of the build ID which does not have an associated section // in the result is kept as 0. bool has_any_text = false; for (intptr_t i = 0; i < kBuildIdSegmentNamesLength; i++) { auto* const name = kBuildIdSegmentNames[i]; hashes[i] = text_bits->Hash(name); if (hashes[i] != 0) { has_any_text = true; } else if (data_section != nullptr) { hashes[i] = data_section->AsBitsContainer()->Hash(name); } } // If none of the sections in the hash were text sections, then we don't need // a build ID, as it is only used to symbolicize non-symbolic stack traces. if (!has_any_text) return; auto const description_bytes = reinterpret_cast(hashes); const size_t description_length = sizeof(hashes); // Now that we have the description field contents, create the section. ZoneWriteStream stream(zone(), kBuildIdHeaderSize + description_length); stream.WriteFixed(sizeof(elf::ELF_NOTE_GNU)); stream.WriteFixed(description_length); stream.WriteFixed(elf::NoteType::NT_GNU_BUILD_ID); ASSERT_EQUAL(stream.Position(), sizeof(elf::Note)); stream.WriteBytes(elf::ELF_NOTE_GNU, sizeof(elf::ELF_NOTE_GNU)); ASSERT_EQUAL(stream.bytes_written(), kBuildIdHeaderSize); stream.WriteBytes(description_bytes, description_length); auto* const container = new (zone_) NoteSection(); container->AddPortion(stream.buffer(), stream.bytes_written(), /*relocations=*/nullptr, /*symbols=*/nullptr, kSnapshotBuildIdAsmSymbol, kBuildIdLabel); section_table_->Add(container, kBuildIdNoteName); } void ElfWriter::ComputeOffsets() { intptr_t file_offset = 0; intptr_t memory_offset = 0; // Maps indices of allocated sections in the section table to memory offsets. const intptr_t num_sections = section_table_->SectionCount(); GrowableArray address_map(zone_, num_sections); address_map.Add(0); // Don't adjust offsets for symbols with index SHN_UNDEF. auto calculate_section_offsets = [&](ElfSection* section) { file_offset = Utils::RoundUp(file_offset, section->alignment); section->set_file_offset(file_offset); file_offset += section->FileSize(); if (section->IsAllocated()) { memory_offset = Utils::RoundUp(memory_offset, section->alignment); section->set_memory_offset(memory_offset); memory_offset += section->MemorySize(); } }; intptr_t section_index = 1; // We don't visit the reserved section. for (auto* const segment : program_table_->segments()) { if (segment->type != elf::ProgramHeaderType::PT_LOAD) continue; // Adjust file and memory offsets for segment alignment on entry. file_offset = Utils::RoundUp(file_offset, segment->Alignment()); memory_offset = Utils::RoundUp(memory_offset, segment->Alignment()); for (auto* const section : segment->sections()) { ASSERT(section->IsAllocated()); calculate_section_offsets(section); if (!section->IsPseudoSection()) { // Note: this assumes that the sections in the section header has all // allocated sections before all (non-reserved) unallocated sections and // in the same order as the load segments in in the program table. address_map.Add(section->memory_offset()); ASSERT_EQUAL(section->index, section_index); section_index++; } } } const auto& sections = section_table_->sections(); for (; section_index < sections.length(); section_index++) { auto* const section = sections[section_index]; ASSERT(!section->IsAllocated()); calculate_section_offsets(section); } ASSERT_EQUAL(section_index, sections.length()); // Now that all sections have been handled, set the file offset for the // section table, as it will be written after the last section. calculate_section_offsets(section_table_); #if defined(DEBUG) // Double check that segment starts are aligned as expected. for (auto* const segment : program_table_->segments()) { ASSERT(Utils::IsAligned(segment->MemoryOffset(), segment->Alignment())); } #endif // This must be true for uses of the map to be correct. ASSERT_EQUAL(address_map[elf::SHN_UNDEF], 0); // Adjust addresses in symbol tables as we now have section memory offsets. // Also finalize the entries of the dynamic table, as some are memory offsets. for (auto* const section : sections) { if (auto* const table = section->AsElfSymbolTable()) { table->Finalize(address_map); } else if (auto* const dynamic = section->AsDynamicTable()) { dynamic->Finalize(); } } // Also adjust addresses in symtab for stripped snapshots. if (IsStripped()) { ASSERT_EQUAL(symtab_->index, elf::SHN_UNDEF); symtab_->Finalize(address_map); } } void ElfHeader::Write(ElfWriteStream* stream) const { ASSERT_EQUAL(file_offset(), 0); ASSERT_EQUAL(memory_offset(), 0); #if defined(TARGET_ARCH_IS_32_BIT) uint8_t size = elf::ELFCLASS32; #else uint8_t size = elf::ELFCLASS64; #endif uint8_t e_ident[16] = {0x7f, 'E', 'L', 'F', size, elf::ELFDATA2LSB, elf::EV_CURRENT, elf::ELFOSABI_SYSV, 0, 0, 0, 0, 0, 0, 0, 0}; stream->WriteBytes(e_ident, 16); stream->WriteHalf(elf::ET_DYN); // Shared library. #if defined(TARGET_ARCH_IA32) stream->WriteHalf(elf::EM_386); #elif defined(TARGET_ARCH_X64) stream->WriteHalf(elf::EM_X86_64); #elif defined(TARGET_ARCH_ARM) stream->WriteHalf(elf::EM_ARM); #elif defined(TARGET_ARCH_ARM64) stream->WriteHalf(elf::EM_AARCH64); #elif defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) stream->WriteHalf(elf::EM_RISCV); #else FATAL("Unknown ELF architecture"); #endif stream->WriteWord(elf::EV_CURRENT); // Version stream->WriteAddr(0); // "Entry point" stream->WriteOff(program_table_.file_offset()); stream->WriteOff(section_table_.file_offset()); #if defined(TARGET_ARCH_ARM) uword flags = elf::EF_ARM_ABI | (TargetCPUFeatures::hardfp_supported() ? elf::EF_ARM_ABI_FLOAT_HARD : elf::EF_ARM_ABI_FLOAT_SOFT); #elif defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) uword flags = elf::EF_RISCV_RVC | elf::EF_RISCV_FLOAT_ABI_DOUBLE; #else uword flags = 0; #endif stream->WriteWord(flags); stream->WriteHalf(sizeof(elf::ElfHeader)); stream->WriteHalf(program_table_.entry_size); stream->WriteHalf(program_table_.SegmentCount()); stream->WriteHalf(section_table_.entry_size); stream->WriteHalf(section_table_.SectionCount()); stream->WriteStringTableIndex(); } void ProgramTable::Write(ElfWriteStream* stream) const { ASSERT(segments_.length() > 0); // Make sure all relevant segments were created by checking the type of the // first. ASSERT(segments_[0]->type == elf::ProgramHeaderType::PT_PHDR); const intptr_t start = stream->Position(); // Should be immediately following the ELF header. ASSERT_EQUAL(start, sizeof(elf::ElfHeader)); #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 (intptr_t i = 0; i < segments_.length(); i++) { const Segment* const segment = segments_[i]; ASSERT(segment->type != elf::ProgramHeaderType::PT_NULL); ASSERT_EQUAL(i == 0, segment->type == elf::ProgramHeaderType::PT_PHDR); #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(); segment->WriteProgramHeader(stream); const intptr_t end = stream->Position(); ASSERT_EQUAL(end - start, entry_size); } #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 SectionTable::Write(ElfWriteStream* stream) const { for (intptr_t i = 0; i < sections_.length(); i++) { const ElfSection* const section = sections_[i]; ASSERT_EQUAL(i == 0, section->IsReservedSection()); ASSERT_EQUAL(section->index, i); ASSERT(section->link < sections_.length()); const intptr_t start = stream->Position(); section->WriteSectionHeader(stream); const intptr_t end = stream->Position(); ASSERT_EQUAL(end - start, entry_size); } } static const char* SectionHeaderTypeToString(elf::SectionHeaderType type) { switch (type) { case elf::SectionHeaderType::SHT_NULL: return "SHT_NULL"; case elf::SectionHeaderType::SHT_PROGBITS: return "SHT_PROGBITS"; case elf::SectionHeaderType::SHT_SYMTAB: return "SHT_SYMTAB"; case elf::SectionHeaderType::SHT_STRTAB: return "SHT_STRTAB"; case elf::SectionHeaderType::SHT_HASH: return "SHT_HASH"; case elf::SectionHeaderType::SHT_NOTE: return "SHT_NOTE"; case elf::SectionHeaderType::SHT_NOBITS: return "SHT_NOBITS"; case elf::SectionHeaderType::SHT_DYNAMIC: return "SHT_DYNAMIC"; case elf::SectionHeaderType::SHT_DYNSYM: return "SHT_DYNSYM"; default: return "Unknown"; } } const char* ElfSection::ToCString(const ElfStringTable& string_table) { return Thread::Current()->zone()->PrintToString( "ElfSection [%s]{type=%s, align=%" Pd ", flags=%s%s%s, fsz=%" Px ", msz=%" Px ", foffs=%" Px ", moffs=%" Px "}", string_table.At(name()), SectionHeaderTypeToString(type), alignment, IsAllocated() ? "A" : "", IsWritable() ? "W" : "", IsExecutable() ? "X" : "", IsAllocated() ? FileSize() : -1, IsAllocated() ? MemorySize() : -1, file_offset_, memory_offset_); } #endif // DART_PRECOMPILER } // namespace dart