diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn index e9becae65ae..18c08b4e78e 100644 --- a/runtime/bin/BUILD.gn +++ b/runtime/bin/BUILD.gn @@ -162,7 +162,7 @@ static_library("crashpad") { } } -template("build_elf_loader") { +template("build_shared_object_loaders") { source_set(target_name) { configs += [ "..:dart_arch_config", @@ -175,6 +175,10 @@ template("build_elf_loader") { sources = [ "elf_loader.cc", "elf_loader.h", + "macho_loader.cc", + "macho_loader.h", + "mappable.cc", + "mappable.h", "virtual_memory.h", "virtual_memory_fuchsia.cc", "virtual_memory_posix.cc", @@ -184,11 +188,11 @@ template("build_elf_loader") { } } -build_elf_loader("elf_loader") { +build_shared_object_loaders("shared_object_loaders") { deps = [ ":libdart_builtin" ] } -build_elf_loader("elf_loader_product") { +build_shared_object_loaders("shared_object_loaders_product") { deps = [ ":libdart_builtin_product" ] } @@ -928,13 +932,13 @@ dart_executable("dartaotruntime") { if (dart_runtime_mode == "release") { extra_deps += [ - ":elf_loader_product", ":native_assets_api_product", + ":shared_object_loaders_product", ] } else { extra_deps += [ - ":elf_loader", ":native_assets_api", + ":shared_object_loaders", ] } @@ -966,8 +970,8 @@ dart_executable("dartaotruntime_product") { ] extra_deps += [ - ":elf_loader_product", ":native_assets_api_product", + ":shared_object_loaders_product", ] } @@ -999,9 +1003,9 @@ if (build_analyze_snapshot) { ] if (use_product_mode) { - extra_deps += [ ":elf_loader_product" ] + extra_deps += [ ":shared_object_loaders_product" ] } else { - extra_deps += [ ":elf_loader" ] + extra_deps += [ ":shared_object_loaders" ] } } } diff --git a/runtime/bin/elf_loader.cc b/runtime/bin/elf_loader.cc index 9028becc47e..1ba6f00696b 100644 --- a/runtime/bin/elf_loader.cc +++ b/runtime/bin/elf_loader.cc @@ -4,165 +4,27 @@ #include "bin/elf_loader.h" +#include +#include + #include "platform/globals.h" + #if defined(DART_HOST_OS_FUCHSIA) #include #endif -#include -#include +#include "platform/elf.h" +#include "platform/unwinding_records.h" #include "bin/file.h" +#include "bin/mappable.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, - /*executable=*/true)); -} - -#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 { @@ -560,6 +422,7 @@ MappedMemory* LoadedElf::MapFilePiece(uword file_start, } // namespace dart using namespace dart::bin::elf; // NOLINT +using Mappable = dart::bin::Mappable; #if defined(DART_HOST_OS_FUCHSIA) || defined(DART_HOST_OS_LINUX) DART_EXPORT Dart_LoadedElf* Dart_LoadELF_Fd(int fd, diff --git a/runtime/bin/macho_loader.cc b/runtime/bin/macho_loader.cc new file mode 100644 index 00000000000..637319fbede --- /dev/null +++ b/runtime/bin/macho_loader.cc @@ -0,0 +1,556 @@ +// Copyright (c) 2025, 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/macho_loader.h" + +#include +#include + +#include "platform/globals.h" + +#if defined(DART_HOST_OS_FUCHSIA) +#include +#endif + +#include "platform/mach_o.h" +#include "platform/unwinding_records.h" + +#include "bin/file.h" +#include "bin/mappable.h" +#include "bin/virtual_memory.h" + +namespace dart { +namespace bin { + +namespace mach_o { + +class LoadCommandIterator { + public: + LoadCommandIterator(const void* start, size_t size) + : end_(reinterpret_cast(reinterpret_cast(start) + + size)), + current_(start) {} + + void Advance() { + ASSERT(!Done()); + const uint32_t size = current()->cmdsize; + current_ = + reinterpret_cast(reinterpret_cast(current_) + size); + } + + bool Done() { return current_ >= end_; } + + const dart::mach_o::load_command* current() const { + return reinterpret_cast(current_); + } + + private: + const void* end_; + const void* current_; +}; + +/// A loader for a subset of Mach-O which may be used to load objects produced +/// by Dart_CreateAppAOTSnapshotAsMachO. +class LoadedMachODylib { + public: + LoadedMachODylib(std::unique_ptr mappable, + uint64_t macho_data_offset) + : mappable_(std::move(mappable)), macho_data_offset_(macho_data_offset) {} + + ~LoadedMachODylib(); + + /// Loads the Mach-O dynamic library 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 Mach-O dynamic library. + /// + /// 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 LoadSegments(); + bool ReadDynamicSymbolTable(); + + 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 macho_data_offset_; + + // Initialized on error. + const char* error_ = nullptr; + + // Initialized by ReadHeader(). + dart::mach_o::mach_header header_; + std::unique_ptr load_commands_mapping_; + const void* load_commands_ = nullptr; + + // Initialized by LoadSegments(). + std::unique_ptr base_; + + // Initialized by ReadDynamicSymbolTable(). + const char* string_table_ = nullptr; + std::unique_ptr string_table_mapping_; + const dart::mach_o::nlist* external_symbols_ = nullptr; + uword external_symbol_count_ = 0; + std::unique_ptr external_symbols_mapping_; + +#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT) + // 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(LoadedMachODylib); +}; + +#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 LoadedMachODylib::Load() { + VirtualMemory::Init(); + + if (error_ != nullptr) { + return false; + } + + CHECK_ERROR(Utils::IsAligned(macho_data_offset_, PageSize()), + "File offset must be page-aligned."); + + ASSERT(mappable_ != nullptr); + CHECK_ERROR(mappable_->SetPosition(macho_data_offset_), + "Invalid file offset."); + + CHECK(ReadHeader()); + CHECK(LoadSegments()); + CHECK(ReadDynamicSymbolTable()); + + mappable_.reset(); + + return true; +} + +LoadedMachODylib::~LoadedMachODylib() { +#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT) + for (intptr_t i = 0; i < dynamic_runtime_function_tables_.length(); i++) { + UnwindingRecordsPlatform::UnregisterDynamicTable( + dynamic_runtime_function_tables_[i]); + } +#endif + + // Unmap the image. + base_.reset(); + + // Explicitly destroy all the mappings before closing the file. + load_commands_mapping_.reset(); + string_table_mapping_.reset(); + external_symbols_mapping_.reset(); +} + +bool LoadedMachODylib::ReadHeader() { + CHECK_ERROR(mappable_->ReadFully(&header_, sizeof(dart::mach_o::mach_header)), + "Could not read Mach-O file."); + + CHECK_ERROR(header_.magic == dart::mach_o::MH_MAGIC || + header_.magic == dart::mach_o::MH_MAGIC_64, + "Expected a host-endian Mach-O object."); + + CHECK_ERROR(header_.filetype == dart::mach_o::MH_DYLIB, + "Can only load Mach-O dynamic libraries."); + +#if defined(TARGET_ARCH_IA32) + CHECK_ERROR(header_.cputype == dart::mach_o::CPU_TYPE_I386, + "Architecture mismatch."); + CHECK_ERROR(header_.cpusubtype == dart::mach_o::CPU_SUBTYPE_I386_ALL, + "Unexpected subtype of X86 specified"); +#elif defined(TARGET_ARCH_X64) + CHECK_ERROR(header_.cputype == dart::mach_o::CPU_TYPE_X86_64, + "Architecture mismatch."); + CHECK_ERROR(header_.cpusubtype == dart::mach_o::CPU_SUBTYPE_X86_64_ALL, + "Unexpected subtype of X86_64 specified"); +#elif defined(TARGET_ARCH_ARM) + CHECK_ERROR(header_.cputype == dart::mach_o::CPU_TYPE_ARM, + "Architecture mismatch."); + CHECK_ERROR(header_.cpusubtype == dart::mach_o::CPU_SUBTYPE_ARM_ALL, + "Unexpected subtype of ARM specified"); +#elif defined(TARGET_ARCH_ARM64) + CHECK_ERROR(header_.cputype == dart::mach_o::CPU_TYPE_ARM64, + "Architecture mismatch."); + CHECK_ERROR(header_.cpusubtype == dart::mach_o::CPU_SUBTYPE_ARM64_ALL, + "Unexpected subtype of ARM64 specified"); +#else + // Not an architecture with appropriate constants defined in , + // which means we set the cpu type and subtype to ANY as the snapshot header + // check after loading also catches any architecture mismatches. + CHECK_ERROR(header_.cputype == dart::mach_o::CPU_TYPE_ANY, + "Architecture mismatch."); + CHECK_ERROR(header_.cpusubtype == dart::mach_o::CPU_SUBTYPE_ANY, + "Unexpected subtype specified"); +#endif + + const uword file_start = header_.magic == dart::mach_o::MH_MAGIC_64 + ? sizeof(dart::mach_o::mach_header_64) + : sizeof(dart::mach_o::mach_header); + const uword file_length = header_.sizeofcmds; + load_commands_mapping_.reset( + MapFilePiece(file_start, file_length, + reinterpret_cast(&load_commands_))); + CHECK_ERROR(load_commands_mapping_ != nullptr, + "Could not mmap the load commands."); + return true; +} + +bool LoadedMachODylib::LoadSegments() { + // Calculate the total amount of virtual memory needed. + uint64_t total_memory = 0; + { + LoadCommandIterator it(load_commands_, header_.sizeofcmds); + while (!it.Done()) { + auto* const current = it.current(); + if (current->cmd == dart::mach_o::LC_SEGMENT) { + auto* const segment = + reinterpret_cast(current); + total_memory = Utils::Maximum( + segment->vmaddr + segment->vmsize, total_memory); + } else if (current->cmd == dart::mach_o::LC_SEGMENT_64) { + auto* const segment_64 = + reinterpret_cast(current); + total_memory = Utils::Maximum( + segment_64->vmaddr + segment_64->vmsize, total_memory); + } + it.Advance(); + } + } + 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."); + + { + LoadCommandIterator it(load_commands_, header_.sizeofcmds); + while (!it.Done()) { + auto* const current = it.current(); + uint64_t memory_offset, memory_size, file_offset, file_size; + dart::mach_o::vm_prot_t initprot; + if (current->cmd == dart::mach_o::LC_SEGMENT) { + auto* const segment = + reinterpret_cast(current); + memory_offset = segment->vmaddr; + memory_size = segment->vmsize; + file_offset = segment->fileoff; + file_size = segment->filesize; + initprot = segment->initprot; + } else if (current->cmd == dart::mach_o::LC_SEGMENT_64) { + auto* const segment_64 = + reinterpret_cast(current); + memory_offset = segment_64->vmaddr; + memory_size = segment_64->vmsize; + file_offset = segment_64->fileoff; + file_size = segment_64->filesize; + initprot = segment_64->initprot; + } else { + it.Advance(); + continue; + } + + const uint64_t adjustment = memory_offset % PageSize(); + CHECK_ERROR( + adjustment == (file_offset % PageSize()), + "Difference between file and memory offset must be page-aligned."); + + void* const memory_start = + static_cast(base_->address()) + memory_offset - adjustment; + const uword file_start = macho_data_offset_ + file_offset - adjustment; + const uword length = memory_size + adjustment; + + File::MapType map_type = File::kReadOnly; + if (initprot == + (dart::mach_o::VM_PROT_READ | dart::mach_o::VM_PROT_WRITE)) { + map_type = File::kReadWrite; + } else if (initprot == + (dart::mach_o::VM_PROT_READ | dart::mach_o::VM_PROT_EXECUTE)) { + map_type = File::kReadExecute; + } else if (initprot == dart::mach_o::VM_PROT_READ) { + map_type = File::kReadOnly; + } else { + Syslog::PrintErr("VM protection flags were: 0x%x\n", initprot); + ERROR("Unsupported VM protection flags 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."); + it.Advance(); + 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(ARCH_IS_64_BIT) + // For executable pages register unwinding information that should be + // present on the page. + if (map_type == File::kReadExecute) { + // RegisterExecutableMemory checks the end of the memory space, so + // if there are zerofill sections or the like in this segment, then + // the offset of the unwinding records is incorrectly calculated. + CHECK_ERROR(memory_size == file_size, + "Executable segment contains zerofill sections."); + void* ptable = nullptr; + UnwindingRecordsPlatform::RegisterExecutableMemory(memory->address(), + length, &ptable); + dynamic_runtime_function_tables_.Add(ptable); + } +#else + USE(file_size); +#endif + it.Advance(); + } + } + + return true; +} + +bool LoadedMachODylib::ReadDynamicSymbolTable() { + const dart::mach_o::symtab_command* symtab = nullptr; + const dart::mach_o::dysymtab_command* dysymtab = nullptr; + LoadCommandIterator it(load_commands_, header_.sizeofcmds); + while (!it.Done()) { + auto* const c = it.current(); + if (c->cmd == dart::mach_o::LC_SYMTAB) { + symtab = reinterpret_cast(c); + } else if (c->cmd == dart::mach_o::LC_DYSYMTAB) { + dysymtab = reinterpret_cast(c); + } + it.Advance(); + } + CHECK_ERROR(symtab != nullptr, "Could not locate symbol table."); + CHECK_ERROR(dysymtab != nullptr, "Could not locate dynamic symbol table."); + CHECK_ERROR(dysymtab->iextdefsym + dysymtab->nextdefsym <= symtab->nsyms, + "Dynamic symbol table offsets are out of range."); + + { + const uword file_start = symtab->stroff; + const uword file_length = symtab->strsize; + string_table_mapping_.reset( + MapFilePiece(file_start, file_length, + reinterpret_cast(&string_table_))); + CHECK_ERROR(string_table_mapping_ != nullptr, + "Could not mmap the string table."); + } + external_symbol_count_ = dysymtab->nextdefsym; + { + // Note that the offset iextdefsym is the offset into the symbol table in + // terms of symbols, not in terms of raw bytes. + const intptr_t symbol_size = sizeof(dart::mach_o::nlist); + const uword file_start = + symtab->symoff + dysymtab->iextdefsym * symbol_size; + const uword file_length = dysymtab->nextdefsym * symbol_size; + external_symbols_mapping_.reset( + MapFilePiece(file_start, file_length, + reinterpret_cast(&external_symbols_))); + CHECK_ERROR(external_symbols_mapping_ != nullptr, + "Could not mmap the external symbols."); + } + return true; +} + +bool LoadedMachODylib::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; + } + + for (uword i = 0; i < external_symbol_count_; ++i) { + const auto& sym = external_symbols_[i]; + const char* name = string_table_ + sym.n_idx; + 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.n_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* LoadedMachODylib::MapFilePiece(uword file_start, + uword file_length, + const void** mem_start) { + const uword adjustment = (macho_data_offset_ + file_start) % PageSize(); + const uword mapping_offset = macho_data_offset_ + file_start - adjustment; + const uword mapping_length = + Utils::RoundUp(macho_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 mach_o +} // namespace bin +} // namespace dart + +using namespace dart::bin::mach_o; // NOLINT +using Mappable = dart::bin::Mappable; + +#if defined(DART_HOST_OS_FUCHSIA) || defined(DART_HOST_OS_LINUX) +DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib_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 macho( + new LoadedMachODylib(std::move(mappable), file_offset)); + + if (!macho->Load() || + !macho->ResolveSymbols(vm_snapshot_data, vm_snapshot_instrs, + vm_isolate_data, vm_isolate_instrs)) { + *error = macho->error(); + return nullptr; + } + + return reinterpret_cast(macho.release()); +} +#endif + +DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib( + 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 macho( + new LoadedMachODylib(std::move(mappable), file_offset)); + + if (!macho->Load() || + !macho->ResolveSymbols(vm_snapshot_data, vm_snapshot_instrs, + vm_isolate_data, vm_isolate_instrs)) { + *error = macho->error(); + return nullptr; + } + + return reinterpret_cast(macho.release()); +} + +DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib_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 macho( + new LoadedMachODylib(std::move(mappable), /*file_offset=*/0)); + + if (!macho->Load() || + !macho->ResolveSymbols(vm_snapshot_data, vm_snapshot_instrs, + vm_isolate_data, vm_isolate_instrs)) { + *error = macho->error(); + return nullptr; + } + + return reinterpret_cast(macho.release()); +} + +DART_EXPORT void Dart_UnloadMachODylib(Dart_LoadedMachODylib* loaded) { + delete reinterpret_cast(loaded); +} diff --git a/runtime/bin/macho_loader.h b/runtime/bin/macho_loader.h new file mode 100644 index 00000000000..54e50c4bb27 --- /dev/null +++ b/runtime/bin/macho_loader.h @@ -0,0 +1,70 @@ +// Copyright (c) 2025, 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. + +#ifndef RUNTIME_BIN_MACHO_LOADER_H_ +#define RUNTIME_BIN_MACHO_LOADER_H_ + +#include "../include/dart_api.h" + +typedef struct { +} Dart_LoadedMachODylib; + +/// Load an Mach-O dynamic library object from a file. +/// +/// On success, return a handle to the library which may be used to close it +/// in Dart_UnloadMachODylib. On error, returns 'nullptr' and sets 'error'. The +/// error string should not be 'free'-d. +/// +/// `file_offset` may be non-zero to read an snapshot embedded inside another +/// type of file. +/// +/// Look up the Dart snapshot symbols "_kVmSnapshotData", +/// "_kVmSnapshotInstructions", "_kVmIsolateData" and "_kVmIsolateInstructions" +/// into the respectively named out-parameters. +/// +/// Dart_LoadMachODylib_Fd takes ownership of the file descriptor. +/// Dart_LoadMachODylib_Memory does not take ownership of the memory, but +/// borrows it for the duration of the call. The memory can be release as soon +// as Dart_LoadAOTSnapshot_Memory returns. +#if defined(__Fuchsia__) || defined(__linux__) || defined(__FreeBSD__) +DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib_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); +#endif + +/// Please see documentation for Dart_LoadMachODylib_Fd. +DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib( + 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); + +/// Please see documentation for Dart_LoadMachODylib_Fd. +DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib_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); + +/// Unloads an MachO dynamic library object loaded through +/// Dart_LoadMachODylib{_Fd, _Memory}. +/// +/// Unlike dlclose(), this does not use reference counting. +/// Dart_LoadMachODylib{_Fd, _Memory} will return load the target library +/// separately each time it is called, and the results must be unloaded +/// separately. +DART_EXPORT void Dart_UnloadMachODylib(Dart_LoadedMachODylib* loaded); + +#endif // RUNTIME_BIN_MACHO_LOADER_H_ diff --git a/runtime/bin/main_impl.cc b/runtime/bin/main_impl.cc index 3f48e0ab984..0096d05bf10 100644 --- a/runtime/bin/main_impl.cc +++ b/runtime/bin/main_impl.cc @@ -457,7 +457,7 @@ static Dart_Isolate CreateAndSetupKernelIsolate(const char* script_uri, // Kernel isolate uses an app JIT snapshot or uses the dill file. if ((kernel_snapshot_uri != nullptr) && ((app_snapshot = Snapshot::TryReadAppSnapshot( - kernel_snapshot_uri, /*force_load_elf_from_memory=*/false, + kernel_snapshot_uri, /*force_load_from_memory=*/false, /*decode_uri=*/false)) != nullptr) && app_snapshot->IsJIT()) { const uint8_t* isolate_snapshot_data = nullptr; @@ -627,7 +627,7 @@ static Dart_Isolate CreateAndSetupDartDevIsolate(const char* script_uri, bool isolate_run_app_snapshot = true; // dartdev isolate uses an app JIT snapshot or uses the dill file. if (((app_snapshot = Snapshot::TryReadAppSnapshot( - dartdev_path.get(), /*force_load_elf_from_memory=*/false, + dartdev_path.get(), /*force_load_from_memory=*/false, /*decode_uri=*/false)) != nullptr) && app_snapshot->IsJIT()) { const uint8_t* isolate_snapshot_data = nullptr; @@ -713,9 +713,9 @@ static Dart_Isolate CreateIsolateGroupAndSetupHelper( isolate_snapshot_instructions = app_isolate_snapshot_instructions; } else { // AOT: All isolates need to be run from AOT compiled snapshots. - const bool kForceLoadElfFromMemory = false; + const bool kForceLoadFromMemory = false; app_snapshot = - Snapshot::TryReadAppSnapshot(script_uri, kForceLoadElfFromMemory); + Snapshot::TryReadAppSnapshot(script_uri, kForceLoadFromMemory); if (app_snapshot == nullptr || !app_snapshot->IsAOT()) { *error = Utils::SCreate( "The uri(%s) provided to `Isolate.spawnUri()` does not " @@ -863,7 +863,7 @@ static Dart_Isolate CreateIsolateGroupAndSetup(const char* script_uri, dontneed_safe = false; #elif defined(DEBUG) // If the snapshot isn't file-backed, madvise(DONT_NEED) is destructive. - if (Options::force_load_elf_from_memory()) { + if (Options::force_load_from_memory()) { dontneed_safe = false; } #endif @@ -1009,7 +1009,7 @@ void RunMainIsolate(const char* script_name, dontneed_safe = false; #elif defined(DEBUG) // If the snapshot isn't file-backed, madvise(DONT_NEED) is destructive. - if (Options::force_load_elf_from_memory()) { + if (Options::force_load_from_memory()) { dontneed_safe = false; } #endif @@ -1227,7 +1227,7 @@ void main(int argc, char** argv) { const size_t kPathBufSize = PATH_MAX + 1; char executable_path[kPathBufSize]; if (Platform::ResolveExecutablePathInto(executable_path, kPathBufSize) > 0) { - app_snapshot = Snapshot::TryReadAppendedAppSnapshotElf(executable_path); + app_snapshot = Snapshot::TryReadAppendedAppSnapshot(executable_path); if (app_snapshot != nullptr) { script_name = argv[0]; @@ -1273,10 +1273,10 @@ void main(int argc, char** argv) { if (app_snapshot == nullptr) { // For testing purposes we add a flag to debug-mode to use the // in-memory ELF loader. - const bool force_load_elf_from_memory = - false DEBUG_ONLY(|| Options::force_load_elf_from_memory()); + const bool force_load_from_memory = + false DEBUG_ONLY(|| Options::force_load_from_memory()); app_snapshot = - Snapshot::TryReadAppSnapshot(script_name, force_load_elf_from_memory); + Snapshot::TryReadAppSnapshot(script_name, force_load_from_memory); } if (app_snapshot != nullptr && app_snapshot->IsJITorAOT()) { if (app_snapshot->IsAOT() && !Dart_IsPrecompiledRuntime()) { diff --git a/runtime/bin/main_options.h b/runtime/bin/main_options.h index 9c6c6b7578c..5b696f74ade 100644 --- a/runtime/bin/main_options.h +++ b/runtime/bin/main_options.h @@ -69,7 +69,7 @@ namespace bin { V(v, verbose, verbose_option) #define DEBUG_BOOL_OPTIONS_LIST(V) \ - V(force_load_elf_from_memory, force_load_elf_from_memory) + V(force_load_from_memory, force_load_from_memory) // A list of flags taking arguments from an enum. Organized as: // V(flag_name, enum_type, field_name) diff --git a/runtime/bin/mappable.cc b/runtime/bin/mappable.cc new file mode 100644 index 00000000000..cb2ce48c2d8 --- /dev/null +++ b/runtime/bin/mappable.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2025, 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/mappable.h" + +#include "platform/globals.h" + +#include "bin/file.h" + +namespace dart { +namespace bin { + +Mappable* Mappable::FromPath(const char* path) { + return new FileMappable(File::Open(/*namespc=*/nullptr, path, File::kRead, + /*executable=*/true)); +} + +#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); +} + +} // namespace bin +} // namespace dart diff --git a/runtime/bin/mappable.h b/runtime/bin/mappable.h new file mode 100644 index 00000000000..607348392af --- /dev/null +++ b/runtime/bin/mappable.h @@ -0,0 +1,144 @@ +// Copyright (c) 2025, 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. + +#ifndef RUNTIME_BIN_MAPPABLE_H_ +#define RUNTIME_BIN_MAPPABLE_H_ + +#include "platform/globals.h" + +#include "bin/file.h" +#include "bin/virtual_memory.h" + +namespace dart { +namespace bin { + +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); +}; + +} // namespace bin +} // namespace dart + +#endif // RUNTIME_BIN_MAPPABLE_H_ diff --git a/runtime/bin/snapshot_utils.cc b/runtime/bin/snapshot_utils.cc index c56a385c3b3..906056456a6 100644 --- a/runtime/bin/snapshot_utils.cc +++ b/runtime/bin/snapshot_utils.cc @@ -12,6 +12,7 @@ #include "bin/elf_loader.h" #include "bin/error_exit.h" #include "bin/file.h" +#include "bin/macho_loader.h" #include "bin/platform.h" #include "include/dart_api.h" #if defined(DART_TARGET_OS_MACOS) @@ -154,6 +155,26 @@ static AppSnapshot* TryReadAppSnapshotBlobs(const char* script_name, } #endif // !defined(DART_PRECOMPILED_RUNTIME) +static DartUtils::MagicNumber ReadMagicNumberAt(File& file, int64_t offset) { + // Attempt to read a magic number from the specified offset, even if there + // are less than kMaxMagicNumberSize bytes available. + const int64_t remaining = file.Length() - offset; + if (remaining <= 0) { + Syslog::PrintErr("File truncated before or at offset 0x%" Px64 ".\n", + offset); + return DartUtils::kUnknownMagicNumber; + } + if (!file.SetPosition(offset)) { + return DartUtils::kUnknownMagicNumber; + } + uint8_t header[DartUtils::kMaxMagicNumberSize]; + auto const read_size = Utils::Minimum(remaining, sizeof(header)); + if (!file.ReadFully(&header, read_size)) { + return DartUtils::kUnknownMagicNumber; + } + return DartUtils::SniffForMagicNumber(header, read_size); +} + #if defined(DART_PRECOMPILED_RUNTIME) class DylibAppSnapshot : public AppSnapshot { public: @@ -293,13 +314,12 @@ class ElfAppSnapshot : public AppSnapshot { const uint8_t* isolate_snapshot_instructions_; }; -static AppSnapshot* TryReadAppSnapshotElf( - const char* script_name, - uint64_t file_offset, - bool force_load_elf_from_memory = false) { +static AppSnapshot* TryReadAppSnapshotElf(const char* script_name, + uint64_t file_offset, + bool force_load_from_memory) { const char* error = nullptr; #if defined(NATIVE_SHARED_OBJECT_FORMAT_ELF) - if (file_offset == 0 && !force_load_elf_from_memory) { + if (file_offset == 0 && !force_load_from_memory) { // The load as a dynamic library should succeed, since this is a platform // that natively understands ELF. if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary( @@ -314,7 +334,7 @@ static AppSnapshot* TryReadAppSnapshotElf( *isolate_data_buffer = nullptr, *isolate_instructions_buffer = nullptr; Dart_LoadedElf* handle = nullptr; - if (force_load_elf_from_memory) { + if (force_load_from_memory) { File* const file = File::Open(/*namespc=*/nullptr, script_name, File::kRead); if (file == nullptr) return nullptr; @@ -342,8 +362,128 @@ static AppSnapshot* TryReadAppSnapshotElf( isolate_data_buffer, isolate_instructions_buffer); } +class MachODylibAppSnapshot : public AppSnapshot { + public: + MachODylibAppSnapshot(DartUtils::MagicNumber magic_number, + Dart_LoadedMachODylib* macho, + const uint8_t* vm_snapshot_data, + const uint8_t* vm_snapshot_instructions, + const uint8_t* isolate_snapshot_data, + const uint8_t* isolate_snapshot_instructions) + : AppSnapshot{magic_number}, + macho_(macho), + vm_snapshot_data_(vm_snapshot_data), + vm_snapshot_instructions_(vm_snapshot_instructions), + isolate_snapshot_data_(isolate_snapshot_data), + isolate_snapshot_instructions_(isolate_snapshot_instructions) {} + + virtual ~MachODylibAppSnapshot() { Dart_UnloadMachODylib(macho_); } + + void SetBuffers(const uint8_t** vm_data_buffer, + const uint8_t** vm_instructions_buffer, + const uint8_t** isolate_data_buffer, + const uint8_t** isolate_instructions_buffer) { + *vm_data_buffer = vm_snapshot_data_; + *vm_instructions_buffer = vm_snapshot_instructions_; + *isolate_data_buffer = isolate_snapshot_data_; + *isolate_instructions_buffer = isolate_snapshot_instructions_; + } + + private: + Dart_LoadedMachODylib* macho_; + const uint8_t* vm_snapshot_data_; + const uint8_t* vm_snapshot_instructions_; + const uint8_t* isolate_snapshot_data_; + const uint8_t* isolate_snapshot_instructions_; +}; + +static AppSnapshot* TryReadAppSnapshotMachODylib( + DartUtils::MagicNumber magic_number, + const char* script_name, + uint64_t file_offset, + bool force_load_from_memory) { + const char* error = nullptr; +#if defined(NATIVE_SHARED_OBJECT_FORMAT_MACHO) + if (file_offset == 0 && !force_load_from_memory) { + // The load as a dynamic library should succeed, since this is a platform + // that natively understands Mach-O. + if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary( + magic_number, script_name, &error)) { + return snapshot; + } + Syslog::PrintErr("Loading dynamic library failed: %s\n", error); + return nullptr; + } +#endif + const uint8_t *vm_data_buffer = nullptr, *vm_instructions_buffer = nullptr, + *isolate_data_buffer = nullptr, + *isolate_instructions_buffer = nullptr; + Dart_LoadedMachODylib* handle = nullptr; + if (force_load_from_memory) { + File* const file = + File::Open(/*namespc=*/nullptr, script_name, File::kRead); + if (file == nullptr) return nullptr; + MappedMemory* memory = file->Map(File::kReadOnly, /*position=*/0, + /*length=*/file->Length()); + if (memory == nullptr) { + Syslog::PrintErr("File mapping failed\n"); + return nullptr; + } + const uint8_t* address = + reinterpret_cast(memory->address()); + handle = Dart_LoadMachODylib_Memory( + address + file_offset, file->Length(), &error, &vm_data_buffer, + &vm_instructions_buffer, &isolate_data_buffer, + &isolate_instructions_buffer); + delete memory; + file->Release(); + } else { + handle = + Dart_LoadMachODylib(script_name, file_offset, &error, &vm_data_buffer, + &vm_instructions_buffer, &isolate_data_buffer, + &isolate_instructions_buffer); + } + if (handle == nullptr) { + Syslog::PrintErr("Loading failed: %s\n", error); + return nullptr; + } + return new MachODylibAppSnapshot(magic_number, handle, vm_data_buffer, + vm_instructions_buffer, isolate_data_buffer, + isolate_instructions_buffer); +} + +static AppSnapshot* TryReadAppSnapshotAt(const char* script_name, + File& file, + int64_t file_offset, + bool force_load_from_memory = false) { + auto const magic_number = ReadMagicNumberAt(file, file_offset); + if (magic_number == DartUtils::kAotELFMagicNumber) { + return TryReadAppSnapshotElf(script_name, file_offset, + force_load_from_memory); + } + + if (magic_number == DartUtils::kAotMachO32MagicNumber || + magic_number == DartUtils::kAotMachO64MagicNumber) { + return TryReadAppSnapshotMachODylib(magic_number, script_name, file_offset, + force_load_from_memory); + } + + if (file_offset == 0) { + // This is a non-appended snapshot which is not handled by any of the + // non-native loaders, so attempt to load it as a native dynamic library. + const char* error = nullptr; + if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary( + magic_number, script_name, &error)) { + return snapshot; + } + Syslog::PrintErr("Loading dynamic library failed: %s\n", error); + } + + return nullptr; +} + #if defined(DART_TARGET_OS_MACOS) -AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO( +AppSnapshot* Snapshot::TryReadAppendedAppSnapshotFromMachO( const char* container_path) { // Ensure file is actually MachO-formatted. DartUtils::MagicNumber magic_number; @@ -397,9 +537,7 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO( continue; } - // A note with the correct name was found, so we assume that the - // file contents for that note contains an ELF snapshot. - return TryReadAppSnapshotElf(container_path, note.offset); + return TryReadAppSnapshotAt(container_path, *file, note.offset); } return nullptr; @@ -415,7 +553,7 @@ static const char kSnapshotSectionName[] = "snapshot"; static_assert(sizeof(kSnapshotSectionName) - 1 <= pe::kCoffSectionNameSize, "Section name of snapshot too large"); -AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromPE( +AppSnapshot* Snapshot::TryReadAppendedAppSnapshotFromPE( const char* container_path) { File* const file = File::Open(nullptr, container_path, File::kRead); if (file == nullptr) { @@ -467,23 +605,46 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromPE( const intptr_t offset = section_header.file_offset; const intptr_t size = section_header.file_size; + auto const magic_number = ReadMagicNumberAt(*file, offset); + std::unique_ptr snapshot(new uint8_t[size]); file->SetPosition(offset); file->ReadFully(snapshot.get(), sizeof(uint8_t) * size); - Dart_LoadedElf* const handle = - Dart_LoadELF_Memory(snapshot.get(), size, &error, &vm_data_buffer, - &vm_instructions_buffer, &isolate_data_buffer, - &isolate_instructions_buffer); + if (magic_number == DartUtils::kAotELFMagicNumber) { + Dart_LoadedElf* const handle = + Dart_LoadELF_Memory(snapshot.get(), size, &error, &vm_data_buffer, + &vm_instructions_buffer, &isolate_data_buffer, + &isolate_instructions_buffer); - if (handle == nullptr) { - Syslog::PrintErr("Loading failed: %s\n", error); - return nullptr; + if (handle == nullptr) { + Syslog::PrintErr("Loading failed: %s\n", error); + return nullptr; + } + + return new ElfAppSnapshot(handle, vm_data_buffer, + vm_instructions_buffer, isolate_data_buffer, + isolate_instructions_buffer); } - return new ElfAppSnapshot(handle, vm_data_buffer, vm_instructions_buffer, - isolate_data_buffer, - isolate_instructions_buffer); + if (magic_number == DartUtils::kAotMachO32MagicNumber || + magic_number == DartUtils::kAotMachO64MagicNumber) { + Dart_LoadedMachODylib* const handle = Dart_LoadMachODylib_Memory( + snapshot.get(), size, &error, &vm_data_buffer, + &vm_instructions_buffer, &isolate_data_buffer, + &isolate_instructions_buffer); + + if (handle == nullptr) { + Syslog::PrintErr("Loading failed: %s\n", error); + return nullptr; + } + + return new MachODylibAppSnapshot( + magic_number, handle, vm_data_buffer, vm_instructions_buffer, + isolate_data_buffer, isolate_instructions_buffer); + } + + return nullptr; } } @@ -491,15 +652,14 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromPE( } #endif // defined(DART_TARGET_OS_WINDOWS) -AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElf( - const char* container_path) { +AppSnapshot* Snapshot::TryReadAppendedAppSnapshot(const char* container_path) { #if defined(DART_TARGET_OS_MACOS) if (IsMachOFormattedBinary(container_path)) { - return TryReadAppendedAppSnapshotElfFromMachO(container_path); + return TryReadAppendedAppSnapshotFromMachO(container_path); } #elif defined(DART_TARGET_OS_WINDOWS) if (IsPEFormattedBinary(container_path)) { - return TryReadAppendedAppSnapshotElfFromPE(container_path); + return TryReadAppendedAppSnapshotFromPE(container_path); } #endif @@ -509,25 +669,32 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElf( } RefCntReleaseScope rs(file); - // Check for payload appended at the end of the container file. - // If header is found, jump to payload offset. - int64_t appended_header[2]; - if (!file->SetPosition(file->Length() - sizeof(appended_header))) { - return nullptr; - } - if (!file->ReadFully(&appended_header, sizeof(appended_header))) { - return nullptr; - } - // Length is always encoded as Little Endian. - const uint64_t appended_offset = - Utils::LittleEndianToHost64(appended_header[0]); - if (memcmp(&appended_header[1], appjit_magic_number.bytes, - appjit_magic_number.length) != 0 || - appended_offset <= 0) { + // For other appended snapshots, the header for the appended snapshot + // information are two 64-bit integers at the end of the file: + // ... + // snapshot offset (length of snapshot is to appended header) + // DartUtils::kAppJITMagicNumber + const int64_t magic_number_offset = file->Length() - kInt64Size; + auto const magic_number = ReadMagicNumberAt(*file, magic_number_offset); + if (magic_number != DartUtils::kAppJITMagicNumber) { return nullptr; } - return TryReadAppSnapshotElf(container_path, appended_offset); + const int64_t snapshot_offset_offset = magic_number_offset - kInt64Size; + int64_t snapshot_offset; + if (!file->SetPosition(snapshot_offset_offset)) { + return nullptr; + } + if (!file->ReadFully(&snapshot_offset, sizeof(snapshot_offset))) { + return nullptr; + } + // The offset is always encoded as Little Endian. + snapshot_offset = Utils::LittleEndianToHost64(snapshot_offset); + if (snapshot_offset <= 0) { + return nullptr; + } + + return TryReadAppSnapshotAt(container_path, *file, snapshot_offset); } #endif // defined(DART_PRECOMPILED_RUNTIME) @@ -539,13 +706,7 @@ bool Snapshot::IsMachOFormattedBinary(const char* filename, } RefCntReleaseScope rs(file); - uint8_t header[DartUtils::kMaxMagicNumberSize]; - if (!file->ReadFully(&header, DartUtils::kMaxMagicNumberSize)) { - // The file isn't long enough to contain the magic bytes. - return false; - } - DartUtils::MagicNumber magic_number = - DartUtils::SniffForMagicNumber(header, sizeof(header)); + auto const magic_number = ReadMagicNumberAt(*file, /*offset=*/0); if (out != nullptr) { *out = magic_number; } @@ -605,7 +766,7 @@ bool Snapshot::IsPEFormattedBinary(const char* filename) { #endif // defined(DART_TARGET_OS_WINDOWS) AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri, - bool force_load_elf_from_memory, + bool force_load_from_memory, bool decode_uri) { CStringUniquePtr decoded_path(nullptr); const char* script_name = nullptr; @@ -629,39 +790,13 @@ AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri, return nullptr; } RefCntReleaseScope rs(file); - if ((file->Length() - file->Position()) < DartUtils::kMaxMagicNumberSize) { - return nullptr; - } - uint8_t header[DartUtils::kMaxMagicNumberSize]; - ASSERT(sizeof(header) == DartUtils::kMaxMagicNumberSize); - if (!file->ReadFully(&header, DartUtils::kMaxMagicNumberSize)) { - return nullptr; - } - DartUtils::MagicNumber magic_number = - DartUtils::SniffForMagicNumber(header, sizeof(header)); + const intptr_t offset = 0; #if defined(DART_PRECOMPILED_RUNTIME) - if (!DartUtils::IsAotMagicNumber(magic_number)) { - return nullptr; - } - - // For testing AOT with the standalone embedder, we also support loading - // from a dynamic library to simulate what happens on iOS. - const intptr_t file_offset = 0; - if (magic_number == DartUtils::kAotELFMagicNumber) { - return TryReadAppSnapshotElf(script_name, file_offset, - force_load_elf_from_memory); - } else { - // This is not a format for which we have a non-native loader, so - // attempt to load it as a native dynamic library. - const char* error = nullptr; - if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary( - magic_number, script_name, &error)) { - return snapshot; - } - Syslog::PrintErr("Loading dynamic library failed: %s\n", error); - } + return TryReadAppSnapshotAt(script_name, *file, offset, + force_load_from_memory); #else + auto const magic_number = ReadMagicNumberAt(*file, offset); if (magic_number == DartUtils::kAppJITMagicNumber) { // Return the JIT snapshot. return TryReadAppSnapshotBlobs(script_name, file); diff --git a/runtime/bin/snapshot_utils.h b/runtime/bin/snapshot_utils.h index 2b25de94242..8ede183411e 100644 --- a/runtime/bin/snapshot_utils.h +++ b/runtime/bin/snapshot_utils.h @@ -52,11 +52,10 @@ class Snapshot { static bool IsPEFormattedBinary(const char* container_path); #endif - static AppSnapshot* TryReadAppendedAppSnapshotElf(const char* container_path); - static AppSnapshot* TryReadAppSnapshot( - const char* script_uri, - bool force_load_elf_from_memory = false, - bool decode_uri = true); + static AppSnapshot* TryReadAppendedAppSnapshot(const char* container_path); + static AppSnapshot* TryReadAppSnapshot(const char* script_uri, + bool force_load_from_memory = false, + bool decode_uri = true); static void WriteAppSnapshot(const char* filename, uint8_t* isolate_data_buffer, intptr_t isolate_data_size, @@ -65,11 +64,11 @@ class Snapshot { private: #if defined(DART_TARGET_OS_MACOS) - static AppSnapshot* TryReadAppendedAppSnapshotElfFromMachO( + static AppSnapshot* TryReadAppendedAppSnapshotFromMachO( const char* container_path); #endif #if defined(DART_TARGET_OS_WINDOWS) - static AppSnapshot* TryReadAppendedAppSnapshotElfFromPE( + static AppSnapshot* TryReadAppendedAppSnapshotFromPE( const char* container_path); #endif diff --git a/runtime/platform/mach_o.h b/runtime/platform/mach_o.h index 789c8704497..f9543ba0e2b 100644 --- a/runtime/platform/mach_o.h +++ b/runtime/platform/mach_o.h @@ -243,10 +243,11 @@ static constexpr uint32_t S_ATTR_SOME_INSTRUCTIONS = 0x00000400; // ones used in our Mach-O writer are listed. // Segment and section names for the text segment, which also contains -// constant data. +// constant data and unwinding information. static constexpr char SEG_TEXT[] = "__TEXT"; static constexpr char SECT_TEXT[] = "__text"; static constexpr char SECT_CONST[] = "__const"; +static constexpr char SECT_UNWIND_INFO[] = "__unwind_info"; // Segment and section names for the data segment, which contains // non-constant data (like the BSS section). diff --git a/runtime/tests/vm/dart/exported_symbols_test.dart b/runtime/tests/vm/dart/exported_symbols_test.dart index eed39790490..508901dd758 100644 --- a/runtime/tests/vm/dart/exported_symbols_test.dart +++ b/runtime/tests/vm/dart/exported_symbols_test.dart @@ -353,18 +353,16 @@ main() { "Dart_VersionString", "Dart_WriteHeapSnapshot", "Dart_WriteProfileToTimeline", - ]; - - if (isAOTRuntime) { - expectedSymbols.addAll([ + if (isAOTRuntime) ...[ "Dart_LoadELF", "Dart_LoadELF_Memory", + "Dart_LoadMachODylib", + "Dart_LoadMachODylib_Memory", "Dart_UnloadELF", - ]); - if (!Platform.isMacOS) { - expectedSymbols.addAll(["Dart_LoadELF_Fd"]); - } - } + "Dart_UnloadMachODylib", + if (!Platform.isMacOS) ...["Dart_LoadELF_Fd", "Dart_LoadMachODylib_Fd"], + ], + ]; Expect.setEquals(expectedSymbols, symbols); } diff --git a/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart b/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart index 4ba1a547d9f..679c58a517f 100644 --- a/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart +++ b/runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart @@ -242,12 +242,6 @@ Future checkCases( List obfuscateds, ) async { checkStaticSymbolTables(unobfuscated, obfuscateds); - if (!Platform.isMacOS && unobfuscated.container is! Elf) { - assert(unobfuscated.container is MachO); - // Don't try and run Mach-O snapshots on systems where it is not the native - // format because there is no MachOLoader in the runtime. - return; - } await checkTraces(unobfuscated, obfuscateds); } diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart index 325aa3c0380..f696cf4608c 100644 --- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart +++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart @@ -31,9 +31,7 @@ Future main() async { runNonDwarf, [ runElf, - // Only generate Mach-O on MacOS, since there is no MachOLoader - // to run the binary on platforms where that isn't the native format. - if (Platform.isMacOS) runMachODylib, + runMachODylib, // Don't run assembly on Windows since DLLs don't contain DWARF. if (!Platform.isWindows) runAssembly, ], diff --git a/runtime/vm/mach_o.cc b/runtime/vm/mach_o.cc index 003236f0ca8..59fe296ec4a 100644 --- a/runtime/vm/mach_o.cc +++ b/runtime/vm/mach_o.cc @@ -10,10 +10,12 @@ #include "openssl/sha.h" #include "platform/mach_o.h" +#include "platform/unwinding_records.h" #include "vm/dwarf.h" #include "vm/dwarf_so_writer.h" #include "vm/hash_map.h" #include "vm/os.h" +#include "vm/unwinding_records.h" #include "vm/zone_text_buffer.h" namespace dart { @@ -596,12 +598,13 @@ class MachOSection : public MachOContents { const GrowableArray& portions() const { return portions_; } - void AddPortion(const uint8_t* bytes, - intptr_t size, - const SharedObjectWriter::RelocationArray* relocations, - const SharedObjectWriter::SymbolDataArray* symbols = nullptr, - const char* symbol_name = nullptr, - intptr_t label = 0) { + void 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(!HasContents() || bytes != nullptr); @@ -777,14 +780,18 @@ class MachOSegment : public MachOCommand { return file_size; } - intptr_t MemorySize() const override { + intptr_t UnpaddedMemorySize() const { intptr_t memory_size = SelfMemorySize(); for (auto* const c : contents_) { ASSERT(c->IsAllocated()); // Segments never contain unallocated contents. memory_size = Utils::RoundUp(memory_size, c->Alignment()); memory_size += c->MemorySize(); } - return Utils::RoundUp(memory_size, Alignment()); + return memory_size; + } + + intptr_t MemorySize() const override { + return Utils::RoundUp(UnpaddedMemorySize(), Alignment()); } // The initial segment of the Mach-O file always includes the header @@ -1737,6 +1744,7 @@ class MachOHeader : public MachOContents { private: void GenerateUuid(); void CreateBSS(); + void GenerateUnwindingInformation(); void GenerateMiscellaneousCommands(); void InitializeSymbolTables(); void FinalizeDwarfSections(); @@ -1911,6 +1919,10 @@ void MachOHeader::Finalize() { // debugging information. CreateBSS(); + // Generate appropriate unwinding information for the target platform, + // for example, unwinding records on Windows. + GenerateUnwindingInformation(); + FinalizeDwarfSections(); // Create and initialize the dynamic and static symbol tables. @@ -2062,6 +2074,71 @@ void MachOHeader::CreateBSS() { } } +void MachOHeader::GenerateUnwindingInformation() { + ASSERT(text_segment_ != nullptr); +#if !defined(TARGET_ARCH_IA32) + // Unwinding information is added to the text segment in Mach-O files. + // Thus, we need the size of the unwinding information even for debugging + // information, since adding the unwinding information changes the memory size + // of the initial text segment and thus changes the values for symbols + // of sections in later segments. + // + // However, since the debugging information should never be loaded by + // the Mach-O loader, we don't actually need to generate the instructions, + // just use an appropriate zerofill section for it. + const bool use_zerofill = type_ == SnapshotType::DebugInfo; + auto const section_type = + use_zerofill ? mach_o::S_ZEROFILL : mach_o::S_REGULAR; +#if defined(DEBUG) + for (auto* const c : text_segment_->contents()) { + // The header always has contents, but the restriction on zerofill + // sections coming after content-containing sections only matters with + // respect to sections, not other contents. + if (c->IsMachOHeader()) continue; + // RegisterExecutablePages looks at the end of the loaded segment for + // the unwinding information, which means the unwinding info needs to be + // the last section, but any sections without file contents are ordered + // after any sections with file contents in a segment. + ASSERT_EQUAL(use_zerofill, !c->HasContents()); + } +#endif + +#if defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_IS_64_BIT) + // Append Windows unwinding instructions as another section at the end of + // the text segment. + auto* const section = new (zone()) MachOSection( + zone(), mach_o::SECT_UNWIND_INFO, section_type, mach_o::S_NO_ATTRIBUTES, + /*has_contents=*/!use_zerofill, compiler::target::kWordSize); + const intptr_t records_size = UnwindingRecordsPlatform::SizeInBytes(); + // The memory space of the text segment is padded to the alignment size, so + // we need to make sure the resulting data has initial padding so that the + // records are at the end of the segment without extra padding. + const intptr_t section_start = + Utils::RoundUp(text_segment_->UnpaddedMemorySize(), section->Alignment()); + const intptr_t section_size = + Utils::RoundUp(section_start + records_size, text_segment_->Alignment()) - + section_start; + const uint8_t* bytes = nullptr; + if (!use_zerofill) { + ZoneWriteStream stream(zone(), /*initial_size=*/section_size); + uint8_t* unwinding_instructions = zone()->Alloc(records_size); + intptr_t records_start = section_size - records_size; + stream.SetPosition(records_start); + stream.WriteBytes(UnwindingRecords::GenerateRecordsInto( + records_start, unwinding_instructions), + records_size); + ASSERT_EQUAL(section_size, stream.Position()); + bytes = stream.buffer(); + } + section->AddPortion(bytes, section_size); + text_segment_->AddContents(section); + ASSERT_EQUAL(section_start + section_size, text_segment_->MemorySize()); +#else + USE(section_type); +#endif // defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_IS_64_BIT) +#endif // !defined(TARGET_ARCH_IA32) +} + void MachOHeader::GenerateMiscellaneousCommands() { // Not idempotent; ASSERT(!HasCommand(MachOBuildVersion::kCommandCode)); diff --git a/tests/ffi/ffi_induce_a_crash_test.dart b/tests/ffi/ffi_induce_a_crash_test.dart index da377730a3b..9b11e33463d 100644 --- a/tests/ffi/ffi_induce_a_crash_test.dart +++ b/tests/ffi/ffi_induce_a_crash_test.dart @@ -6,7 +6,7 @@ // // SharedObjects=ffi_test_functions // VMOptions= -// VMOptions=--force_load_elf_from_memory +// VMOptions=--force_load_from_memory import 'dart:ffi'; import 'dart:io';