| // 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 <memory> | |
| #include <utility> | |
| #include "platform/globals.h" | |
| #if defined(DART_HOST_OS_FUCHSIA) | |
| #include <sys/mman.h> | |
| #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 ValueObject { | |
| public: | |
| LoadCommandIterator(const void* start, size_t size) | |
| : end_(reinterpret_cast<const void*>(reinterpret_cast<uword>(start) + | |
| size)), | |
| current_(start) {} | |
| void Advance() { | |
| ASSERT(!Done()); | |
| const uint32_t size = current()->cmdsize; | |
| current_ = | |
| reinterpret_cast<const void*>(reinterpret_cast<uword>(current_) + size); | |
| } | |
| bool Done() { return current_ >= end_; } | |
| const dart::mach_o::load_command* current() const { | |
| return reinterpret_cast<const dart::mach_o::load_command*>(current_); | |
| } | |
| private: | |
| const void* end_; | |
| const void* current_; | |
| DISALLOW_COPY_AND_ASSIGN(LoadCommandIterator); | |
| }; | |
| // For MachO structs of constant size that are contiguous in memory. | |
| template <typename T> | |
| class MachOStructIterator : public ValueObject { | |
| public: | |
| MachOStructIterator(const T* start, size_t size_in_bytes) | |
| : start_(start), | |
| end_(reinterpret_cast<const T*>(reinterpret_cast<uword>(start) + | |
| size_in_bytes)) { | |
| ASSERT_EQUAL(0, size_in_bytes % sizeof(T)); | |
| } | |
| const T* begin() const { return start_; } | |
| const T* end() const { return end_; } | |
| private: | |
| const T* start_; | |
| const T* end_; | |
| DISALLOW_COPY_AND_ASSIGN(MachOStructIterator); | |
| }; | |
| /// 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> 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** snapshot_data, | |
| const uint8_t** snapshot_text); | |
| 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> 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<MappedMemory> load_commands_mapping_; | |
| const void* load_commands_ = nullptr; | |
| // Initialized by LoadSegments(). | |
| std::unique_ptr<VirtualMemory> base_; | |
| // Initialized by ReadDynamicSymbolTable(). | |
| const char* string_table_ = nullptr; | |
| std::unique_ptr<MappedMemory> string_table_mapping_; | |
| const dart::mach_o::nlist* external_symbols_ = nullptr; | |
| uword external_symbol_count_ = 0; | |
| std::unique_ptr<MappedMemory> external_symbols_mapping_; | |
| #if defined(UNWINDING_RECORDS_WINDOWS_HOST) | |
| // Dynamic table for looking up unwinding exceptions info. | |
| // Initialized by LoadSegments as we load executable segment. | |
| MallocGrowableArray<void*> 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(UNWINDING_RECORDS_WINDOWS_HOST) | |
| 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_ARM64E) | |
| CHECK_ERROR(header_.cputype == dart::mach_o::CPU_TYPE_ARM64, | |
| "Architecture mismatch."); | |
| CHECK_ERROR(header_.cpusubtype == dart::mach_o::CPU_SUBTYPE_ARM64E_V0, | |
| "Unexpected subtype of ARM64 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 <mach/machine.h>, | |
| // 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<const void**>(&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<const dart::mach_o::segment_command*>(current); | |
| total_memory = Utils::Maximum<uint64_t>( | |
| segment->vmaddr + segment->vmsize, total_memory); | |
| } else if (current->cmd == dart::mach_o::LC_SEGMENT_64) { | |
| auto* const segment_64 = | |
| reinterpret_cast<const dart::mach_o::segment_command_64*>(current); | |
| total_memory = Utils::Maximum<uint64_t>( | |
| 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 defined(UNWINDING_RECORDS_WINDOWS_HOST) | |
| uint64_t records_address = 0, records_size = 0; | |
| #endif | |
| if (current->cmd == dart::mach_o::LC_SEGMENT) { | |
| auto* const segment = | |
| reinterpret_cast<const dart::mach_o::segment_command*>(current); | |
| memory_offset = segment->vmaddr; | |
| memory_size = segment->vmsize; | |
| file_offset = segment->fileoff; | |
| file_size = segment->filesize; | |
| initprot = segment->initprot; | |
| #if defined(UNWINDING_RECORDS_WINDOWS_HOST) | |
| if ((initprot & dart::mach_o::VM_PROT_EXECUTE) != 0) { | |
| auto* const start = | |
| reinterpret_cast<const dart::mach_o::section*>(segment + 1); | |
| const size_t size_in_bytes = segment->cmdsize - sizeof(*segment); | |
| MachOStructIterator sections(start, size_in_bytes); | |
| for (const auto& section : sections) { | |
| if (strcmp(section.sectname, dart::mach_o::SECT_UNWIND_INFO) == 0) { | |
| records_address = section.addr; | |
| records_size = section.size; | |
| break; | |
| } | |
| } | |
| } | |
| #endif | |
| } else if (current->cmd == dart::mach_o::LC_SEGMENT_64) { | |
| auto* const segment_64 = | |
| reinterpret_cast<const dart::mach_o::segment_command_64*>(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; | |
| #if defined(UNWINDING_RECORDS_WINDOWS_HOST) | |
| if ((initprot & dart::mach_o::VM_PROT_EXECUTE) != 0) { | |
| auto* const start = | |
| reinterpret_cast<const dart::mach_o::section_64*>(segment_64 + 1); | |
| const size_t size_in_bytes = | |
| segment_64->cmdsize - sizeof(*segment_64); | |
| MachOStructIterator sections(start, size_in_bytes); | |
| for (const auto& section : sections) { | |
| if (strcmp(section.sectname, dart::mach_o::SECT_UNWIND_INFO) == 0) { | |
| records_address = section.addr; | |
| records_size = section.size; | |
| break; | |
| } | |
| } | |
| } | |
| #endif | |
| } 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<char*>(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<MappedMemory> 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(UNWINDING_RECORDS_WINDOWS_HOST) | |
| // For executable pages register unwinding information that should be | |
| // present on the page. | |
| if (map_type == File::kReadExecute) { | |
| CHECK_ERROR(records_address != 0, | |
| "No __unwind_info section found in segment"); | |
| CHECK_ERROR(records_size == UnwindingRecordsPlatform::SizeInBytes(), | |
| "__unwind_info section does not contain expected " | |
| "unwinding records"); | |
| void* ptable = nullptr; | |
| void* start = memory->address(); | |
| void* records_start = reinterpret_cast<void*>( | |
| reinterpret_cast<uword>(memory->address()) + adjustment + | |
| (records_address - memory_offset)); | |
| UnwindingRecordsPlatform::RegisterExecutableMemory( | |
| start, length, records_start, &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<const dart::mach_o::symtab_command*>(c); | |
| } else if (c->cmd == dart::mach_o::LC_DYSYMTAB) { | |
| dysymtab = reinterpret_cast<const dart::mach_o::dysymtab_command*>(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<const void**>(&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<const void**>(&external_symbols_))); | |
| CHECK_ERROR(external_symbols_mapping_ != nullptr, | |
| "Could not mmap the external symbols."); | |
| } | |
| return true; | |
| } | |
| bool LoadedMachODylib::ResolveSymbols(const uint8_t** data, | |
| const uint8_t** text) { | |
| 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; | |
| bool is_text = false; | |
| if (strcmp(name, kSnapshotDataAsmSymbol) == 0) { | |
| output = data; | |
| } else if (strcmp(name, kSnapshotTextAsmSymbol) == 0) { | |
| output = text; | |
| // dyld decides based on the section the symbol comes from, but our loader | |
| // doesn't keep track of the sections. | |
| is_text = true; | |
| } | |
| if (output != nullptr) { | |
| auto* addr = | |
| reinterpret_cast<const uint8_t*>(base_->start() + sym.n_value); | |
| #if defined(HOST_ARCH_ARM64E) | |
| if (is_text) { | |
| addr = | |
| ptrauth_sign_unauthenticated(addr, ptrauth_key_function_pointer, 0); | |
| } | |
| #else | |
| USE(is_text); | |
| #endif | |
| *output = addr; | |
| } | |
| } | |
| CHECK_ERROR(data == nullptr || *data != nullptr, | |
| "Could not find snapshot data."); | |
| CHECK_ERROR(text == nullptr || *text != nullptr, | |
| "Could not find snapshot text."); | |
| 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<uint8_t*>(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** snapshot_data, | |
| const uint8_t** snapshot_text) { | |
| std::unique_ptr<Mappable> mappable(Mappable::FromFD(fd)); | |
| std::unique_ptr<LoadedMachODylib> macho( | |
| new LoadedMachODylib(std::move(mappable), file_offset)); | |
| if (!macho->Load() || !macho->ResolveSymbols(snapshot_data, snapshot_text)) { | |
| *error = macho->error(); | |
| return nullptr; | |
| } | |
| return reinterpret_cast<Dart_LoadedMachODylib*>(macho.release()); | |
| } | |
| #endif | |
| DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib( | |
| const char* filename, | |
| uint64_t file_offset, | |
| const char** error, | |
| const uint8_t** snapshot_data, | |
| const uint8_t** snapshot_text) { | |
| std::unique_ptr<Mappable> mappable(Mappable::FromPath(filename)); | |
| if (mappable == nullptr) { | |
| *error = "Couldn't open file."; | |
| return nullptr; | |
| } | |
| std::unique_ptr<LoadedMachODylib> macho( | |
| new LoadedMachODylib(std::move(mappable), file_offset)); | |
| if (!macho->Load() || !macho->ResolveSymbols(snapshot_data, snapshot_text)) { | |
| *error = macho->error(); | |
| return nullptr; | |
| } | |
| return reinterpret_cast<Dart_LoadedMachODylib*>(macho.release()); | |
| } | |
| DART_EXPORT Dart_LoadedMachODylib* Dart_LoadMachODylib_Memory( | |
| const uint8_t* snapshot, | |
| uint64_t snapshot_size, | |
| const char** error, | |
| const uint8_t** snapshot_data, | |
| const uint8_t** snapshot_text) { | |
| std::unique_ptr<Mappable> mappable( | |
| Mappable::FromMemory(snapshot, snapshot_size)); | |
| if (mappable == nullptr) { | |
| *error = "Couldn't open file."; | |
| return nullptr; | |
| } | |
| std::unique_ptr<LoadedMachODylib> macho( | |
| new LoadedMachODylib(std::move(mappable), /*macho_data_offset=*/0)); | |
| if (!macho->Load() || !macho->ResolveSymbols(snapshot_data, snapshot_text)) { | |
| *error = macho->error(); | |
| return nullptr; | |
| } | |
| return reinterpret_cast<Dart_LoadedMachODylib*>(macho.release()); | |
| } | |
| DART_EXPORT void Dart_UnloadMachODylib(Dart_LoadedMachODylib* loaded) { | |
| delete reinterpret_cast<LoadedMachODylib*>(loaded); | |
| } |