// 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 "bin/elf_loader.h" #include "platform/globals.h" #if defined(DART_HOST_OS_FUCHSIA) #include #endif #include #include #include "bin/file.h" #include "bin/virtual_memory.h" #include "platform/elf.h" #include "platform/unwinding_records.h" namespace dart { namespace bin { namespace elf { class Mappable { public: static Mappable* FromPath(const char* path); #if defined(DART_HOST_OS_FUCHSIA) || defined(DART_HOST_OS_LINUX) static Mappable* FromFD(int fd); #endif static Mappable* FromMemory(const uint8_t* memory, size_t size); virtual MappedMemory* Map(File::MapType type, uint64_t position, uint64_t length, void* start = nullptr) = 0; virtual bool SetPosition(uint64_t position) = 0; virtual bool ReadFully(void* dest, int64_t length) = 0; virtual ~Mappable() {} protected: Mappable() {} private: DISALLOW_COPY_AND_ASSIGN(Mappable); }; class FileMappable : public Mappable { public: explicit FileMappable(File* file) : Mappable(), file_(file) {} ~FileMappable() override { file_->Release(); } MappedMemory* Map(File::MapType type, uint64_t position, uint64_t length, void* start = nullptr) override { return file_->Map(type, position, length, start); } bool SetPosition(uint64_t position) override { return file_->SetPosition(position); } bool ReadFully(void* dest, int64_t length) override { return file_->ReadFully(dest, length); } private: File* const file_; DISALLOW_COPY_AND_ASSIGN(FileMappable); }; class MemoryMappable : public Mappable { public: MemoryMappable(const uint8_t* memory, size_t size) : Mappable(), memory_(memory), size_(size), position_(memory) {} ~MemoryMappable() override {} MappedMemory* Map(File::MapType type, uint64_t position, uint64_t length, void* start = nullptr) override { if (position > size_) return nullptr; MappedMemory* result = nullptr; const uword map_size = Utils::RoundUp(length, VirtualMemory::PageSize()); if (start == nullptr) { auto* memory = VirtualMemory::Allocate( map_size, type == File::kReadExecute, "dart-compiled-image"); if (memory == nullptr) return nullptr; result = new MappedMemory(memory->address(), memory->size()); memory->release(); delete memory; } else { result = new MappedMemory(start, map_size, /*should_unmap=*/false); } size_t remainder = 0; if ((position + length) > size_) { remainder = position + length - size_; length = size_ - position; } memcpy(result->address(), memory_ + position, length); // NOLINT memset(reinterpret_cast(result->address()) + length, 0, remainder); auto mode = VirtualMemory::kReadOnly; switch (type) { case File::kReadExecute: mode = VirtualMemory::kReadExecute; break; case File::kReadWrite: mode = VirtualMemory::kReadWrite; break; case File::kReadOnly: mode = VirtualMemory::kReadOnly; break; default: UNREACHABLE(); } VirtualMemory::Protect(result->address(), result->size(), mode); return result; } bool SetPosition(uint64_t position) override { if (position > size_) return false; position_ = memory_ + position; return true; } bool ReadFully(void* dest, int64_t length) override { if ((position_ + length) > (memory_ + size_)) return false; memcpy(dest, position_, length); return true; } private: const uint8_t* const memory_; const size_t size_; const uint8_t* position_; DISALLOW_COPY_AND_ASSIGN(MemoryMappable); }; Mappable* Mappable::FromPath(const char* path) { return new FileMappable(File::Open(/*namespc=*/nullptr, path, File::kRead)); } #if defined(DART_HOST_OS_FUCHSIA) || defined(DART_HOST_OS_LINUX) Mappable* Mappable::FromFD(int fd) { return new FileMappable(File::OpenFD(fd)); } #endif Mappable* Mappable::FromMemory(const uint8_t* memory, size_t size) { return new MemoryMappable(memory, size); } /// A loader for a subset of ELF which may be used to load objects produced by /// Dart_CreateAppAOTSnapshotAsElf. class LoadedElf { public: explicit LoadedElf(std::unique_ptr mappable, uint64_t elf_data_offset) : mappable_(std::move(mappable)), elf_data_offset_(elf_data_offset) {} ~LoadedElf(); /// Loads the ELF object into memory. Returns whether the load was successful. /// On failure, the error may be retrieved by 'error()'. bool Load(); /// Reads Dart-specific symbols from the loaded ELF. /// /// Stores the address of the corresponding symbol in each non-null output /// parameter. /// /// Fails if any output parameter is non-null but points to null and the /// corresponding symbol was not found, or if the dynamic symbol table could /// not be decoded. /// /// Has the side effect of initializing the relocated addresses for the text /// sections corresponding to non-null output parameters in the BSS segment. /// /// On failure, the error may be retrieved by 'error()'. bool ResolveSymbols(const uint8_t** vm_data, const uint8_t** vm_instrs, const uint8_t** isolate_data, const uint8_t** isolate_instrs); const char* error() { return error_; } private: bool ReadHeader(); bool ReadProgramTable(); bool LoadSegments(); bool ReadSectionTable(); bool ReadSectionStringTable(); bool ReadSections(); static uword PageSize() { return VirtualMemory::PageSize(); } // Unlike File::Map, allows non-aligned 'start' and 'length'. MappedMemory* MapFilePiece(uword start, uword length, const void** mapping_start); // Initialized on a successful Load(). std::unique_ptr mappable_; const uint64_t elf_data_offset_; // Initialized on error. const char* error_ = nullptr; // Initialized by ReadHeader(). dart::elf::ElfHeader header_; // Initialized by ReadProgramTable(). std::unique_ptr program_table_mapping_; const dart::elf::ProgramHeader* program_table_ = nullptr; // Initialized by LoadSegments(). std::unique_ptr base_; // Initialized by ReadSectionTable(). std::unique_ptr section_table_mapping_; const dart::elf::SectionHeader* section_table_ = nullptr; // Initialized by ReadSectionStringTable(). std::unique_ptr section_string_table_mapping_; const char* section_string_table_ = nullptr; // Initialized by ReadSections(). const char* dynamic_string_table_ = nullptr; const dart::elf::Symbol* dynamic_symbol_table_ = nullptr; uword dynamic_symbol_count_ = 0; #if defined(DART_HOST_OS_WINDOWS) && \ (defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64)) // Dynamic table for looking up unwinding exceptions info. // Initialized by LoadSegments as we load executable segment. MallocGrowableArray dynamic_runtime_function_tables_; #endif DISALLOW_COPY_AND_ASSIGN(LoadedElf); }; #define CHECK(value) \ if (!(value)) { \ ASSERT(error_ != nullptr); \ return false; \ } #define ERROR(message) \ { \ error_ = (message); \ return false; \ } #define CHECK_ERROR(value, message) \ if (!(value)) { \ error_ = (message); \ return false; \ } bool LoadedElf::Load() { UnwindingRecordsPlatform::Init(); VirtualMemory::Init(); if (error_ != nullptr) { return false; } CHECK_ERROR(Utils::IsAligned(elf_data_offset_, PageSize()), "File offset must be page-aligned."); ASSERT(mappable_ != nullptr); CHECK_ERROR(mappable_->SetPosition(elf_data_offset_), "Invalid file offset."); CHECK(ReadHeader()); CHECK(ReadProgramTable()); CHECK(LoadSegments()); CHECK(ReadSectionTable()); CHECK(ReadSectionStringTable()); CHECK(ReadSections()); return true; } LoadedElf::~LoadedElf() { #if defined(DART_HOST_OS_WINDOWS) && \ (defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64)) for (intptr_t i = 0; i < dynamic_runtime_function_tables_.length(); i++) { UnwindingRecordsPlatform::UnregisterDynamicTable( dynamic_runtime_function_tables_[i]); } UnwindingRecordsPlatform::Cleanup(); #endif // Unmap the image. base_.reset(); // Explicitly destroy all the mappings before closing the file. program_table_mapping_.reset(); section_table_mapping_.reset(); section_string_table_mapping_.reset(); } bool LoadedElf::ReadHeader() { CHECK_ERROR(mappable_->ReadFully(&header_, sizeof(dart::elf::ElfHeader)), "Could not read ELF file."); CHECK_ERROR(header_.ident[dart::elf::EI_DATA] == dart::elf::ELFDATA2LSB, "Expected little-endian ELF object."); CHECK_ERROR(header_.type == dart::elf::ET_DYN, "Can only load dynamic libraries."); #if defined(TARGET_ARCH_IA32) CHECK_ERROR(header_.machine == dart::elf::EM_386, "Architecture mismatch."); #elif defined(TARGET_ARCH_X64) CHECK_ERROR(header_.machine == dart::elf::EM_X86_64, "Architecture mismatch."); #elif defined(TARGET_ARCH_ARM) CHECK_ERROR(header_.machine == dart::elf::EM_ARM, "Architecture mismatch."); #elif defined(TARGET_ARCH_ARM64) CHECK_ERROR(header_.machine == dart::elf::EM_AARCH64, "Architecture mismatch."); #elif defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) CHECK_ERROR(header_.machine == dart::elf::EM_RISCV, "Architecture mismatch."); #else #error Unsupported architecture architecture. #endif CHECK_ERROR(header_.version == dart::elf::EV_CURRENT, "Unexpected ELF version."); CHECK_ERROR(header_.header_size == sizeof(dart::elf::ElfHeader), "Unexpected header size."); CHECK_ERROR( header_.program_table_entry_size == sizeof(dart::elf::ProgramHeader), "Unexpected program header size."); CHECK_ERROR( header_.section_table_entry_size == sizeof(dart::elf::SectionHeader), "Unexpected section header size."); return true; } bool LoadedElf::ReadProgramTable() { const uword file_start = header_.program_table_offset; const uword file_length = header_.num_program_headers * sizeof(dart::elf::ProgramHeader); program_table_mapping_.reset( MapFilePiece(file_start, file_length, reinterpret_cast(&program_table_))); CHECK_ERROR(program_table_mapping_ != nullptr, "Could not mmap the program table."); return true; } bool LoadedElf::ReadSectionTable() { const uword file_start = header_.section_table_offset; const uword file_length = header_.num_section_headers * sizeof(dart::elf::SectionHeader); section_table_mapping_.reset( MapFilePiece(file_start, file_length, reinterpret_cast(§ion_table_))); CHECK_ERROR(section_table_mapping_ != nullptr, "Could not mmap the section table."); return true; } bool LoadedElf::ReadSectionStringTable() { const dart::elf::SectionHeader header = section_table_[header_.shstrtab_section_index]; section_string_table_mapping_.reset( MapFilePiece(header.file_offset, header.file_size, reinterpret_cast(§ion_string_table_))); CHECK_ERROR(section_string_table_mapping_ != nullptr, "Could not mmap the section string table."); return true; } bool LoadedElf::LoadSegments() { // Calculate the total amount of virtual memory needed. uword total_memory = 0; for (uword i = 0; i < header_.num_program_headers; ++i) { const dart::elf::ProgramHeader header = program_table_[i]; // Only PT_LOAD segments need to be loaded. if (header.type != dart::elf::ProgramHeaderType::PT_LOAD) continue; total_memory = Utils::Maximum( static_cast(header.memory_offset + header.memory_size), total_memory); CHECK_ERROR(Utils::IsPowerOfTwo(header.alignment), "Alignment must be a power of two."); } total_memory = Utils::RoundUp(total_memory, PageSize()); base_.reset(VirtualMemory::Allocate(total_memory, /*is_executable=*/false, "dart-compiled-image")); CHECK_ERROR(base_ != nullptr, "Could not reserve virtual memory."); for (uword i = 0; i < header_.num_program_headers; ++i) { const dart::elf::ProgramHeader header = program_table_[i]; // Only PT_LOAD segments need to be loaded. if (header.type != dart::elf::ProgramHeaderType::PT_LOAD) continue; const uword memory_offset = header.memory_offset, file_offset = header.file_offset; CHECK_ERROR( (memory_offset % PageSize()) == (file_offset % PageSize()), "Difference between file and memory offset must be page-aligned."); const intptr_t adjustment = header.memory_offset % PageSize(); void* const memory_start = static_cast(base_->address()) + memory_offset - adjustment; const uword file_start = elf_data_offset_ + file_offset - adjustment; const uword length = header.memory_size + adjustment; File::MapType map_type = File::kReadOnly; if (header.flags == (dart::elf::PF_R | dart::elf::PF_W)) { map_type = File::kReadWrite; } else if (header.flags == (dart::elf::PF_R | dart::elf::PF_X)) { map_type = File::kReadExecute; } else if (header.flags == dart::elf::PF_R) { map_type = File::kReadOnly; } else { ERROR("Unsupported segment flag set."); } #if defined(DART_HOST_OS_FUCHSIA) // mmap is less flexible on Fuchsia than on Linux and Darwin, in (at least) // two important ways: // // 1. We cannot map a file opened as RX into an RW mapping, even if the // mode is MAP_PRIVATE (which implies copy-on-write). // 2. We cannot atomically replace an existing anonymous mapping with a // file mapping: we must first unmap the existing mapping. if (map_type == File::kReadWrite) { CHECK_ERROR(mappable_->SetPosition(file_start), "Could not advance file position."); CHECK_ERROR(mappable_->ReadFully(memory_start, length), "Could not read file."); continue; } CHECK_ERROR(munmap(memory_start, length) == 0, "Could not unmap reservation."); #endif std::unique_ptr memory( mappable_->Map(map_type, file_start, length, memory_start)); CHECK_ERROR(memory != nullptr, "Could not map segment."); CHECK_ERROR(memory->address() == memory_start, "Mapping not at requested address."); #if defined(DART_HOST_OS_WINDOWS) && \ (defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64)) // For executable pages register unwinding information that should be // present on the page. if (map_type == File::kReadExecute) { void* ptable = nullptr; UnwindingRecordsPlatform::RegisterExecutableMemory(memory->address(), length, &ptable); dynamic_runtime_function_tables_.Add(ptable); } #endif } return true; } bool LoadedElf::ReadSections() { for (uword i = 0; i < header_.num_section_headers; ++i) { const dart::elf::SectionHeader header = section_table_[i]; const char* const name = section_string_table_ + header.name; if (strcmp(name, ".dynstr") == 0) { CHECK_ERROR(header.memory_offset != 0, ".dynstr must be loaded."); dynamic_string_table_ = static_cast(base_->address()) + header.memory_offset; } else if (strcmp(name, ".dynsym") == 0) { CHECK_ERROR(header.memory_offset != 0, ".dynsym must be loaded."); dynamic_symbol_table_ = reinterpret_cast( base_->start() + header.memory_offset); dynamic_symbol_count_ = header.file_size / sizeof(dart::elf::Symbol); } } CHECK_ERROR(dynamic_string_table_ != nullptr, "Couldn't find .dynstr."); CHECK_ERROR(dynamic_symbol_table_ != nullptr, "Couldn't find .dynsym."); return true; } bool LoadedElf::ResolveSymbols(const uint8_t** vm_data, const uint8_t** vm_instrs, const uint8_t** isolate_data, const uint8_t** isolate_instrs) { if (error_ != nullptr) { return false; } // The first entry of the symbol table is reserved. for (uword i = 1; i < dynamic_symbol_count_; ++i) { const dart::elf::Symbol sym = dynamic_symbol_table_[i]; const char* name = dynamic_string_table_ + sym.name; const uint8_t** output = nullptr; if (strcmp(name, kVmSnapshotDataAsmSymbol) == 0) { output = vm_data; } else if (strcmp(name, kVmSnapshotInstructionsAsmSymbol) == 0) { output = vm_instrs; } else if (strcmp(name, kIsolateSnapshotDataAsmSymbol) == 0) { output = isolate_data; } else if (strcmp(name, kIsolateSnapshotInstructionsAsmSymbol) == 0) { output = isolate_instrs; } if (output != nullptr) { *output = reinterpret_cast(base_->start() + sym.value); } } CHECK_ERROR(isolate_data == nullptr || *isolate_data != nullptr, "Could not find isolate snapshot data."); CHECK_ERROR(isolate_instrs == nullptr || *isolate_instrs != nullptr, "Could not find isolate instructions."); return true; } MappedMemory* LoadedElf::MapFilePiece(uword file_start, uword file_length, const void** mem_start) { const uword adjustment = (elf_data_offset_ + file_start) % PageSize(); const uword mapping_offset = elf_data_offset_ + file_start - adjustment; const uword mapping_length = Utils::RoundUp(elf_data_offset_ + file_start + file_length, PageSize()) - mapping_offset; MappedMemory* const mapping = mappable_->Map(bin::File::kReadOnly, mapping_offset, mapping_length); if (mapping != nullptr) { *mem_start = reinterpret_cast(mapping->start() + (file_start % PageSize())); } return mapping; } } // namespace elf } // namespace bin } // namespace dart using namespace dart::bin::elf; // NOLINT #if defined(DART_HOST_OS_FUCHSIA) || defined(DART_HOST_OS_LINUX) DART_EXPORT Dart_LoadedElf* Dart_LoadELF_Fd(int fd, uint64_t file_offset, const char** error, const uint8_t** vm_snapshot_data, const uint8_t** vm_snapshot_instrs, const uint8_t** vm_isolate_data, const uint8_t** vm_isolate_instrs) { std::unique_ptr mappable(Mappable::FromFD(fd)); std::unique_ptr elf( new LoadedElf(std::move(mappable), file_offset)); if (!elf->Load() || !elf->ResolveSymbols(vm_snapshot_data, vm_snapshot_instrs, vm_isolate_data, vm_isolate_instrs)) { *error = elf->error(); return nullptr; } return reinterpret_cast(elf.release()); } #endif #if !defined(DART_HOST_OS_FUCHSIA) DART_EXPORT Dart_LoadedElf* Dart_LoadELF(const char* filename, uint64_t file_offset, const char** error, const uint8_t** vm_snapshot_data, const uint8_t** vm_snapshot_instrs, const uint8_t** vm_isolate_data, const uint8_t** vm_isolate_instrs) { std::unique_ptr mappable(Mappable::FromPath(filename)); if (mappable == nullptr) { *error = "Couldn't open file."; return nullptr; } std::unique_ptr elf( new LoadedElf(std::move(mappable), file_offset)); if (!elf->Load() || !elf->ResolveSymbols(vm_snapshot_data, vm_snapshot_instrs, vm_isolate_data, vm_isolate_instrs)) { *error = elf->error(); return nullptr; } return reinterpret_cast(elf.release()); } #endif DART_EXPORT Dart_LoadedElf* Dart_LoadELF_Memory( const uint8_t* snapshot, uint64_t snapshot_size, const char** error, const uint8_t** vm_snapshot_data, const uint8_t** vm_snapshot_instrs, const uint8_t** vm_isolate_data, const uint8_t** vm_isolate_instrs) { std::unique_ptr mappable( Mappable::FromMemory(snapshot, snapshot_size)); if (mappable == nullptr) { *error = "Couldn't open file."; return nullptr; } std::unique_ptr elf( new LoadedElf(std::move(mappable), /*file_offset=*/0)); if (!elf->Load() || !elf->ResolveSymbols(vm_snapshot_data, vm_snapshot_instrs, vm_isolate_data, vm_isolate_instrs)) { *error = elf->error(); return nullptr; } return reinterpret_cast(elf.release()); } DART_EXPORT void Dart_UnloadELF(Dart_LoadedElf* loaded) { delete reinterpret_cast(loaded); }