| // Copyright (c) 2022, 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. |
| |
| // This file is a reimplementation of the header file mach-o/loader.h, which is |
| // part of the Apple system headers. All comments, which detail the format of |
| // Mach-O files, have been reproduced from the original header. |
| |
| import 'dart:io'; |
| import 'dart:typed_data'; |
| |
| import 'src/macho_utils.dart'; |
| |
| class OffsetsAdjuster { |
| /// A sorted list of non-negative offsets signifying the start of |
| /// non-overlapping adjustment ranges, so either up to (but not including) the |
| /// next listed offset or the end of the file. |
| final offsets = <int>[0]; |
| |
| /// The map of offsets to the adjustment needed for their adjustment range. |
| final adjustments = <int, int>{0: 0}; |
| |
| /// Adds a new adjustment at the given offset. If there is not an existing |
| /// adjustment range starting at that offset, the adjustment range containing |
| /// that offset is split, and all adjustment ranges that start at that offset |
| /// or at later offsets have their adjustment increased accordingly. |
| void add(int offset, int adjustment) { |
| assert(offset >= 0); |
| assert(adjustment >= 0); |
| var startingIndex = offsets.lastIndexWhere((o) => o <= offset); |
| // If the offset didn't already have an adjustment range, split the previous |
| // range |
| if (offsets[startingIndex] != offset) { |
| final newIndex = startingIndex + 1; |
| // Add a new adjustment range that starts at the offset and has an |
| // initial adjustment value that's the same as the previous range the |
| // offset was in. |
| offsets.insert(newIndex, offset); |
| adjustments[offset] = adjustments[offsets[startingIndex]]!; |
| // Start making adjustments at the new range. |
| startingIndex = newIndex; |
| } |
| // Adjust the adjustments for all ranges that start at the given offset |
| // or later. |
| for (var i = startingIndex; i < offsets.length; i++) { |
| final off = offsets[i]; |
| adjustments[off] = adjustments[offset]! + adjustment; |
| } |
| } |
| |
| /// Adjusts the given offset according to the adjustment ranges collected. |
| int adjust(int offset) { |
| assert(offset >= 0); |
| // We should always get a valid index since we add an entry for the start |
| // of the file. |
| final rangeStart = offsets.lastWhere((o) => o <= offset); |
| return offset + adjustments[rangeStart]!; |
| } |
| } |
| |
| enum CpuType { |
| i386(_typeX86, 'I386'), |
| x64(_typeX86 | _architectureABI64, 'X64'), |
| arm(_typeArm, 'ARM'), |
| arm64(_typeArm | _architectureABI64, 'ARM64'); |
| |
| final int _code; |
| final String _headerName; |
| |
| const CpuType(this._code, this._headerName); |
| |
| /// Marks an architecture as using a 64-bit ABI. |
| static const _architectureABI64 = 0x0100000; |
| |
| /// The base type of all X86 architecture variants. |
| static const _typeX86 = 0x7; |
| |
| /// The base type of all ARM architecture variants. |
| static const _typeArm = 0x12; |
| |
| static const _prefix = 'CPU_TYPE'; |
| |
| static CpuType? fromCode(int code) { |
| for (final value in values) { |
| if (value._code == code) { |
| return value; |
| } |
| } |
| return null; |
| } |
| |
| /// Returns whether the architecture represented by a given `cputype` value |
| /// from the MachO header uses 64-bit pointers. |
| static bool cpuTypeUses64BitPointers(int code) => |
| code & _architectureABI64 == _architectureABI64; |
| |
| /// Returns whether the architecture represented by this uses 64-bit pointers. |
| bool get uses64BitPointers => cpuTypeUses64BitPointers(_code); |
| |
| @override |
| String toString() => '${_prefix}_$_headerName'; |
| } |
| |
| enum VirtualMemoryProtection { |
| none(0x00, 'NONE'), |
| read(0x01, 'READ'), |
| write(0x02, 'WRITE'), |
| execute(0x04, 'EXECUTE'); |
| |
| final int code; |
| final String _headerName; |
| |
| const VirtualMemoryProtection(this.code, this._headerName); |
| |
| static const _prefix = 'VM_PROT'; |
| |
| @override |
| String toString() => '${_prefix}_$_headerName'; |
| } |
| |
| enum LoadCommandType { |
| // Note that entries marked 'obsolete' in the C headers have not been |
| // translated into enum values below. |
| // |
| // We also do not translate entries that we need not understand (even |
| // if LC_REQ_DYLD is set). |
| |
| /// A 32-bit segment of the file that is mapped into memory at runtime. |
| segment(0x1, 'SEGMENT'), |
| |
| /// The static symbol table. |
| symbolTable(0x2, 'SYMTAB'), |
| |
| /// The dynamic symbol table. |
| dynamicSymbolTable(0xb, 'DYSYMTAB'), |
| |
| /// A 64-bit segment of the file that is mapped into memory at runtime. |
| segment64(0x19, 'SEGMENT_64'), |
| |
| /// A code signature. |
| codeSignature(0x1d, 'CODE_SIGNATURE'), |
| |
| /// Information to split segments. |
| segmentSplitInfo(0x1e, 'SEGMENT_SPLIT_INFO'), |
| |
| /// 32-bit encrypted segment information. |
| encryptionInfo(0x21, 'ENCRYPTION_INFO'), |
| |
| /// Compressed dynamically linked shared library information. |
| /// |
| /// Note that the C headers include a separate name for the version that has |
| /// LD_REQ_DYLD set: LC_DYLD_INFO_ONLY. We do not include this name. |
| dynamicLibraryInfo(0x22, 'DYLD_INFO'), |
| |
| /// Compressed table of function start addresses. |
| functionStarts(0x26, 'FUNCTION_STARTS'), |
| |
| /// Main entry point (replacement for LC_UNIXTHREAD). |
| main(0x28 | _reqDyld, 'MAIN'), |
| |
| /// Table of non-instructions in the text segment. |
| dataInCode(0x29, 'DATA_IN_CODE'), |
| |
| /// Code signing DRs copied from dynamically linked shared libraries. |
| dynamicLibraryCodeSigningDRs(0x2b, 'DYLIB_CODE_SIGN_DRS'), |
| |
| /// 64-bit encrypted segment information. |
| encryptionInfo64(0x2c, 'ENCRYPTION_INFO_64'), |
| |
| /// Optimization hints in object files. |
| linkerOptimizationHint(0x2e, 'LINKER_OPTIMIZATION_HINT'), |
| |
| /// Arbitrary data included within a MachO file. |
| note(0x31, 'NOTE'), |
| |
| /// A trie of dynamically linked shared library exports. |
| dynamicLibraryExportsTrie(0x33 | _reqDyld, 'DYLD_EXPORTS_TRIE'), |
| |
| /// Chained fixups for dynamically linked shared libraries. |
| dynamicLibraryChainedFixups(0x34 | _reqDyld, 'DYLD_CHAINED_FIXUPS'), |
| |
| /// A fileset entry. |
| fileSetEntry(0x35 | _reqDyld, 'FILESET_ENTRY'); |
| |
| /// After MacOS X 10.1 when a new load command is added that is required to be |
| /// understood by the dynamic linker for the image to execute properly, this |
| /// bit will be or'ed into the load command constant. If the dynamic |
| /// linker sees such a load command it does not understand will issue a |
| /// "unknown load command required for execution" error and refuse to use the |
| /// image. Other load commands without this bit that are not understood will |
| /// simply be ignored. |
| /// |
| /// `LC_REQ_DYLD` in the C headers. |
| static const _reqDyld = 0x80000000; |
| |
| final int code; |
| final String _headerName; |
| |
| const LoadCommandType(this.code, this._headerName); |
| |
| static const _prefix = 'LC'; |
| |
| static LoadCommandType? fromCode(int code) { |
| for (final value in values) { |
| if ((value.code & _reqDyld) != 0) { |
| /// LC_REQ_DYLD is set on the enum value, so it can only match exactly. |
| if (value.code == code) { |
| return value; |
| } |
| } else { |
| /// The LC_REQ_DYLD bit should be ignored for matching purposes. |
| final stripped = code & ~_reqDyld; |
| if (value.code == stripped) { |
| return value; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @override |
| String toString() => '${_prefix}_$_headerName'; |
| } |
| |
| /// A load command describes how to load and use various parts of the file |
| /// contents (e.g., where to find the TEXT and DATA sections). |
| abstract class MachOLoadCommand { |
| /// The encoding of this load command's type as a 32-bit value. |
| /// |
| /// `uint32_t cmd` in the C headers. |
| final int code; |
| |
| /// The total size of the load command in bytes, including the type and size |
| /// fields. |
| /// |
| /// `uint32_t cmdsize` in the C headers. |
| final int size; |
| |
| MachOLoadCommand(this.code, this.size); |
| |
| static MachOLoadCommand fromStream(MachOReader stream) { |
| final code = stream.readUint32(); |
| final size = stream.readUint32(); |
| |
| final type = LoadCommandType.fromCode(code); |
| if (type == null) { |
| // Not a MachO section that needs to be adjusted for offset changes, so |
| // just read the bytes without interpretation. |
| final contents = stream.readBytes(size - 2 * 4); |
| return MachOGenericLoadCommand(code, size, contents); |
| } |
| |
| return switch (type) { |
| LoadCommandType.segment || |
| LoadCommandType.segment64 => |
| MachOSegmentCommand.fromStream(code, size, stream), |
| LoadCommandType.dynamicLibraryInfo => |
| MachODyldInfoCommand.fromStream(code, size, stream), |
| LoadCommandType.symbolTable => |
| MachOSymtabCommand.fromStream(code, size, stream), |
| LoadCommandType.dynamicSymbolTable => |
| MachODysymtabCommand.fromStream(code, size, stream), |
| LoadCommandType.codeSignature || |
| LoadCommandType.segmentSplitInfo || |
| LoadCommandType.functionStarts || |
| LoadCommandType.dataInCode || |
| LoadCommandType.dynamicLibraryCodeSigningDRs || |
| LoadCommandType.linkerOptimizationHint || |
| LoadCommandType.dynamicLibraryExportsTrie || |
| LoadCommandType.dynamicLibraryChainedFixups => |
| MachOLinkeditDataCommand.fromStream(code, size, stream), |
| LoadCommandType.encryptionInfo || |
| LoadCommandType.encryptionInfo64 => |
| MachOEncryptionInfoCommand.fromStream(code, size, stream), |
| LoadCommandType.main => |
| MachOEntryPointCommand.fromStream(code, size, stream), |
| LoadCommandType.note => MachONoteCommand.fromStream(code, size, stream), |
| LoadCommandType.fileSetEntry => |
| MachOFileSetEntryCommand.fromStream(code, size, stream), |
| }; |
| } |
| |
| /// The type for this load command. Returns null for MachOGenericLoadCommand. |
| LoadCommandType? get type => LoadCommandType.fromCode(code); |
| |
| /// Whether or not the dynamic linker is required to understand this load |
| /// command. |
| bool get mustBeUnderstood => (code & LoadCommandType._reqDyld) != 0; |
| |
| /// Returns a version of the load command with any file offsets appropriately |
| /// adjusted as needed. |
| MachOLoadCommand adjust(OffsetsAdjuster adjuster); |
| |
| void writeSync(MachOWriter stream) { |
| stream |
| ..writeUint32(code) |
| ..writeUint32(size); |
| writeContentsSync(stream); |
| } |
| |
| /// Subclasses need to implement this serializer, which should NOT |
| /// attempt to serialize the cmd and the cmdsize to the stream. That |
| /// logic is handled by the parent class automatically. |
| void writeContentsSync(MachOWriter stream); |
| } |
| |
| /// A MachO load command that can be copied wholesale without any adjustments. |
| class MachOGenericLoadCommand extends MachOLoadCommand { |
| final Uint8List contents; |
| |
| MachOGenericLoadCommand(super.cmd, super.cmdsize, this.contents); |
| |
| @override |
| MachOLoadCommand adjust(OffsetsAdjuster adjuster) => this; |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeBytes(contents); |
| } |
| } |
| |
| /// The header of a MachO file. There are 32-bit and 64-bit variations, but |
| /// the only difference is that 64-bit headers have a reserved field for |
| /// padding. Thus, we merge the two into a single class. |
| class MachOHeader { |
| /// The magic number identifier for the MachO file. Denotes whether the file |
| /// is 32-bit or 64-bit, and whether its endianness matches the host. |
| /// |
| /// `uint32_t magic` in the C headers. |
| final int magic; |
| |
| /// The CPU type assumed by this MachO file. |
| /// |
| /// `cpu_type_t cputype` in the C headers, where `cpu_type_t` is |
| /// `integer_t` which is `int`. |
| final int cpu; |
| |
| /// The machine type assumed by this MachO file. |
| /// |
| /// `cpu_subtype_t cpusubtype` in the C headers, where `cpu_subtype_t` is |
| /// `integer_t` which is `int`. |
| final int machine; |
| |
| /// The type of the MachO file. |
| /// |
| /// `uint32_t filetype` in the C headers. |
| final int type; |
| |
| /// The number of load commands in this MachO file. |
| /// |
| /// `uint32_t ncmds` in the C headers. |
| final int loadCommandsCount; |
| |
| /// The total size of the load commands. |
| /// |
| /// `uint32_t sizeofcmds` in the C headers. |
| final int loadCommandsSize; |
| |
| /// A bit array of flags in the MachO file. |
| /// |
| /// `uint32_t flags` in the C headers. |
| final int flags; |
| |
| /// Reserved space in 64-bit headers, null in 32-bit headers. |
| /// |
| /// `uint32_t reserved` in the C headers. |
| final int? reserved; |
| |
| MachOHeader(this.magic, this.cpu, this.machine, this.type, |
| this.loadCommandsCount, this.loadCommandsSize, this.flags, this.reserved); |
| |
| /// Constant for the magic field of a 32-bit mach_header with host endianness. |
| static const int _magic32 = 0xfeedface; |
| |
| /// Constant for the magic field of a 32-bit mach_header with swapped |
| /// endianness. |
| static const int _cigam32 = 0xcefaedfe; |
| |
| /// Constant for the magic field of a 64-bit mach_header with host endianness. |
| static const int _magic64 = 0xfeedfacf; |
| |
| /// Constant for the magic field of a 64-bit mach_header with swapped |
| /// endianness. |
| static const int _cigam64 = 0xcffaedfe; |
| |
| static MachOHeader? fromStream(RandomAccessFile original) { |
| // First, read the magic value using host endianness, so we can determine |
| // whether the file uses host endianness or swapped endianness. |
| final magic = MachOReader(original, Endian.host).readUint32(); |
| |
| if (!validMagic(magic)) return null; |
| |
| final is64Bit = magic == _magic64 || magic == _cigam64; |
| final endian = magicEndian(magic); |
| |
| // Now recreate the MachOReader with the right endianness. |
| final stream = MachOReader(original, endian); |
| final cpu = stream.readInt32(); |
| final machine = stream.readInt32(); |
| final type = stream.readUint32(); |
| final loadCommandsCount = stream.readUint32(); |
| final loadCommandsSize = stream.readUint32(); |
| final flags = stream.readUint32(); |
| final reserved = is64Bit ? stream.readUint32() : null; |
| |
| return MachOHeader(magic, cpu, machine, type, loadCommandsCount, |
| loadCommandsSize, flags, reserved); |
| } |
| |
| static bool validMagic(int magic) => |
| magic == _magic32 || |
| magic == _magic64 || |
| magic == _cigam32 || |
| magic == _cigam64; |
| |
| static Endian magicEndian(int magic) => |
| (magic == _magic64 || magic == _magic32) |
| ? Endian.host |
| : Endian.host == Endian.big |
| ? Endian.little |
| : Endian.big; |
| |
| // A faster check than rechecking the magic number. |
| bool get is64Bit => reserved != null; |
| Endian get endian => magicEndian(magic); |
| |
| // The size of the header when written to disk. Seven 32-bit fields, plus |
| // an extra 32-bit field for 64-bit headers to align it to word size. |
| int get size => 7 * 4 + (is64Bit ? 4 : 0); |
| |
| void writeSync(RandomAccessFile original) { |
| // Like reading, first we write the magic value using host endianness, |
| // and then write the rest of the value using the detected endianness. |
| MachOWriter(original, Endian.host).writeUint32(magic); |
| final stream = MachOWriter(original, endian); |
| stream |
| ..writeInt32(cpu) |
| ..writeInt32(machine) |
| ..writeUint32(type) |
| ..writeUint32(loadCommandsCount) |
| ..writeUint32(loadCommandsSize) |
| ..writeUint32(flags); |
| if (is64Bit) { |
| stream.writeUint32(reserved!); |
| } |
| } |
| } |
| |
| /// A segment load command indicates that a part of this file is to be mapped |
| /// into a task's address space. If the segment has sections, the section |
| /// structures follow directly after the segment load command and their size |
| /// is reflected in the [size] of the load command. |
| /// |
| /// In the C headers, there are two different structs for 32-bit and 64-bit |
| /// segments. Here, we combine them into one, with the [type] of the load |
| /// command determining whether we read and write 32-bit or 64-bit values for |
| /// particular fields. |
| class MachOSegmentCommand extends MachOLoadCommand { |
| /// The name of the segment. |
| /// |
| /// `char segname[16]` in the C headers. |
| final String name; |
| |
| /// The memory address at which this segment should be placed. |
| /// |
| /// `uint64_t vmaddr` for 64-bit segment commands and `uint32_t vmaddr` |
| /// for 32-bit segment commands in the C headers. |
| final int memoryAddress; |
| |
| /// The memory size for this segment. |
| /// |
| /// `uint64_t vmsize` for 64-bit segment commands and `uint32_t vmsize` |
| /// for 32-bit segment commands in the C headers. |
| final int memorySize; |
| |
| /// The file offset for this segment. |
| /// |
| /// `uint64_t fileoff` for 64-bit segment commands and `uint32_t fileoff` |
| /// for 32-bit segment commands in the C headers. |
| final int fileOffset; |
| |
| /// The file size of this segment. |
| /// |
| /// `uint64_t filesize` for 64-bit segment commands and `uint32_t filesize` |
| /// for 32-bit segment commands in the C headers. |
| final int fileSize; |
| |
| /// The maximum VM protection allowed for this segment. |
| /// |
| /// `vm_prot_t maxprot` in the C headers, where `vm_prot_t` is `int`. |
| final int maxProtection; |
| |
| /// The initial VM protection used for this segment. |
| /// |
| /// `vm_prot_t initprot` in the C headers, where `vm_prot_t` is `int`. |
| final int initialProtection; |
| |
| /// The flags set for this segment. |
| /// |
| /// `uint32_t flags` in the C headers. |
| final int flags; |
| |
| /// The list of sections structures in the segment. |
| /// |
| /// Note that we do not keep a separate field for the number of sections |
| /// (`uint32_t nsects` in the C headers), but instead just use the length |
| /// of this list. |
| final List<MachOSection> sections; |
| |
| MachOSegmentCommand( |
| super.code, |
| super.size, |
| this.name, |
| this.memoryAddress, |
| this.memorySize, |
| this.fileOffset, |
| this.fileSize, |
| this.maxProtection, |
| this.initialProtection, |
| this.flags, |
| this.sections, |
| ); |
| |
| static const _nameLength = 16; |
| |
| static MachOSegmentCommand fromStream( |
| int code, int size, MachOReader stream) { |
| final is64Bit = LoadCommandType.fromCode(code) == LoadCommandType.segment64; |
| final wordSize = is64Bit ? 8 : 4; |
| |
| final name = stream.readFixedLengthNullTerminatedString(_nameLength); |
| final memoryAddress = stream.readUword(wordSize); |
| final memorySize = stream.readUword(wordSize); |
| final fileOffset = stream.readUword(wordSize); |
| final fileSize = stream.readUword(wordSize); |
| final maxProtection = stream.readInt32(); |
| final initialProtection = stream.readInt32(); |
| final sectionCount = stream.readUint32(); |
| final flags = stream.readUint32(); |
| |
| final sections = List<MachOSection>.generate( |
| sectionCount, (_) => MachOSection.fromStream(stream, is64Bit), |
| growable: false); |
| |
| return MachOSegmentCommand( |
| code, |
| size, |
| name, |
| memoryAddress, |
| memorySize, |
| fileOffset, |
| fileSize, |
| maxProtection, |
| initialProtection, |
| flags, |
| sections); |
| } |
| |
| int get _wordSize => |
| LoadCommandType.fromCode(code) == LoadCommandType.segment64 ? 8 : 4; |
| |
| @override |
| MachOSegmentCommand adjust(OffsetsAdjuster adjuster) => MachOSegmentCommand( |
| code, |
| size, |
| name, |
| memoryAddress, |
| memorySize, |
| adjuster.adjust(fileOffset), |
| fileSize, |
| maxProtection, |
| initialProtection, |
| flags, |
| sections.map((s) => s.adjust(adjuster)).toList()); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeFixedLengthNullTerminatedString(name, _nameLength); |
| stream.writeUword(memoryAddress, _wordSize); |
| stream.writeUword(memorySize, _wordSize); |
| stream.writeUword(fileOffset, _wordSize); |
| stream.writeUword(fileSize, _wordSize); |
| stream.writeInt32(maxProtection); |
| stream.writeInt32(initialProtection); |
| stream.writeUint32(sections.length); |
| stream.writeUint32(flags); |
| |
| for (final section in sections) { |
| section.writeContentsSync(stream); |
| } |
| } |
| } |
| |
| enum SectionType { |
| /// A standard section that has no special meaning. |
| regular(0x0, 'REGULAR'), |
| |
| /// This section has no file contents, but instead any virtual memory for |
| /// this section is zero-filled at startup. Must be smaller than 4 gigabytes, |
| /// but can be mixed with other types of sections in a given segment. |
| zeroFill(0x1, 'ZEROFILL'), |
| |
| /// This section has no file contents, but instead any virtual memory for |
| /// this section is zero-filled at startup. Can be larger than 4 gigabytes, |
| /// but can only be in a segment with the same kind of section. |
| gigabyteZeroFill(0xc, 'GB_ZEROFILL'), |
| |
| /// This section has no file contents, but instead any virtual memory for |
| /// this section is zero-filled at startup. Used for BSS sections. |
| threadLocalZeroFill(0x12, 'THREAD_LOCAL_ZEROFILL'); |
| |
| final int _code; |
| final String _headerName; |
| |
| const SectionType(this._code, this._headerName); |
| |
| static SectionType? fromFlags(int flags) { |
| final code = flags & _typeMask; |
| for (final value in values) { |
| if (value._code == code) { |
| return value; |
| } |
| } |
| return null; |
| } |
| |
| static const _prefix = 'S'; |
| static const _typeSize = 8; |
| static const _typeMask = (1 << _typeSize) - 1; |
| |
| @override |
| String toString() => '${_prefix}_$_headerName'; |
| } |
| |
| /// A section describes a specific portion of a segment. The specifics of |
| /// sections are mostly unimportant for our work here, except that we need |
| /// to recognize S_ZEROFILL/S_GB_ZEROFILL sections so they can be ignored for |
| /// file offset purposes. |
| /// |
| /// In the C headers, sections are represented by two structs, one for 32-bit |
| /// headers and one for 64-bit headers. Other than whether specific fields |
| /// are 32-bit or 64-bit, the only other difference is that the 64-bit header |
| /// contains one more 32-bit reserved field. |
| class MachOSection { |
| /// Name of this section. |
| final String name; |
| |
| /// Name of the containing segment. |
| final String segmentName; |
| |
| /// Memory address of this section. |
| final int memoryAddress; |
| |
| /// The size of this section in memory (and in the file contents if not |
| /// a zero fill section). |
| final int size; |
| |
| /// File offset of the contents of this section. |
| final int fileOffset; |
| final int alignment; |
| |
| /// File offset of the relocation entries. |
| final int relocationsFileOffset; |
| |
| /// Nuber of relocation entries. |
| final int relocationsCount; |
| |
| /// Flags, which encode both the section type and any section attributes. |
| final int flags; |
| |
| /// Reserved (for offset or index). |
| /// |
| /// `uint32_t reserved1` in the C headers. |
| final int reserved1; |
| // Reserved (for count or sizeof). |
| /// |
| /// `uint32_t reserved2` in the C headers. |
| final int reserved2; |
| |
| /// Reserved in 64-bit sections for padding purposes. Null in 32-bit sections. |
| /// |
| /// `uint32_t reserved3` in the C headers. |
| final int? reserved3; |
| |
| MachOSection( |
| this.name, |
| this.segmentName, |
| this.memoryAddress, |
| this.size, |
| this.fileOffset, |
| this.alignment, |
| this.relocationsFileOffset, |
| this.relocationsCount, |
| this.flags, |
| this.reserved1, |
| this.reserved2, |
| this.reserved3, |
| ); |
| |
| static MachOSection fromStream(MachOReader stream, bool is64Bit) { |
| final wordSize = is64Bit ? 8 : 4; |
| |
| final name = stream.readFixedLengthNullTerminatedString(_nameLength); |
| final segmentName = stream |
| .readFixedLengthNullTerminatedString(MachOSegmentCommand._nameLength); |
| final memoryAddress = stream.readUword(wordSize); |
| final size = stream.readUword(wordSize); |
| final fileOffset = stream.readUint32(); |
| final alignment = stream.readUint32(); |
| final relocationsFileOffset = stream.readUint32(); |
| final relocationsCount = stream.readUint32(); |
| final flags = stream.readUint32(); |
| final reserved1 = stream.readUint32(); |
| final reserved2 = stream.readUint32(); |
| int? reserved3; |
| if (is64Bit) { |
| reserved3 = stream.readUint32(); |
| } |
| |
| return MachOSection( |
| name, |
| segmentName, |
| memoryAddress, |
| size, |
| fileOffset, |
| alignment, |
| relocationsFileOffset, |
| relocationsCount, |
| flags, |
| reserved1, |
| reserved2, |
| reserved3); |
| } |
| |
| static const _nameLength = 16; |
| static const _attributesMask = 0xffffffff & ~SectionType._typeMask; |
| |
| bool get is64Bit => reserved3 != null; |
| |
| SectionType? get type => SectionType.fromFlags(flags); |
| int get attributes => flags & _attributesMask; |
| |
| bool get isZeroFill { |
| final cachedType = type; // Don't recalculate for each comparison. |
| return cachedType == SectionType.zeroFill || |
| cachedType == SectionType.gigabyteZeroFill || |
| cachedType == SectionType.threadLocalZeroFill; |
| } |
| |
| MachOSection adjust(OffsetsAdjuster adjuster) => MachOSection( |
| name, |
| segmentName, |
| memoryAddress, |
| size, |
| adjuster.adjust(fileOffset), |
| alignment, |
| adjuster.adjust(relocationsFileOffset), |
| relocationsCount, |
| flags, |
| reserved1, |
| reserved2, |
| reserved3); |
| |
| void writeContentsSync(MachOWriter stream) { |
| final wordSize = is64Bit ? 8 : 4; |
| stream.writeFixedLengthNullTerminatedString(name, _nameLength); |
| stream.writeFixedLengthNullTerminatedString( |
| segmentName, MachOSegmentCommand._nameLength); |
| stream.writeUword(memoryAddress, wordSize); |
| stream.writeUword(size, wordSize); |
| stream.writeUint32(fileOffset); |
| stream.writeUint32(alignment); |
| stream.writeUint32(relocationsFileOffset); |
| stream.writeUint32(relocationsCount); |
| stream.writeUint32(flags); |
| stream.writeUint32(reserved1); |
| stream.writeUint32(reserved2); |
| if (is64Bit) { |
| stream.writeUint32(reserved3!); |
| } |
| } |
| } |
| |
| /// A load command that describes a static symbol table. |
| class MachOSymtabCommand extends MachOLoadCommand { |
| /// The file offset of the symbol table. |
| /// |
| /// `uint32_t symoff` in the C headers. |
| final int fileOffset; |
| |
| /// The number of symbol table entries. |
| /// |
| /// `uint32_t symoff` in the C headers. |
| final int symbolsCount; |
| |
| /// The file offset of the string table. |
| /// |
| /// `uint32_t stroff` in the C headers. |
| final int stringTableFileOffset; |
| |
| /// The file size of the string table. |
| /// |
| /// `uint32_t strsize` in the C headers. |
| final int stringTableSize; |
| |
| MachOSymtabCommand( |
| super.code, |
| super.size, |
| this.fileOffset, |
| this.symbolsCount, |
| this.stringTableFileOffset, |
| this.stringTableSize, |
| ); |
| |
| static MachOSymtabCommand fromStream(int code, int size, MachOReader stream) { |
| final fileOffset = stream.readUint32(); |
| final symbolsCount = stream.readUint32(); |
| final stringTableFileOffset = stream.readUint32(); |
| final stringTableSize = stream.readUint32(); |
| |
| return MachOSymtabCommand(code, size, fileOffset, symbolsCount, |
| stringTableFileOffset, stringTableSize); |
| } |
| |
| @override |
| MachOSymtabCommand adjust(OffsetsAdjuster adjuster) => MachOSymtabCommand( |
| code, |
| size, |
| adjuster.adjust(fileOffset), |
| symbolsCount, |
| adjuster.adjust(stringTableFileOffset), |
| stringTableSize); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint32(fileOffset); |
| stream.writeUint32(symbolsCount); |
| stream.writeUint32(stringTableFileOffset); |
| stream.writeUint32(stringTableSize); |
| } |
| } |
| |
| /// A load command representing the dynamic symbol table. |
| class MachODysymtabCommand extends MachOLoadCommand { |
| /// The index of the local symbols. |
| /// |
| /// `uint32_t ilocalsym` in the C headers. |
| final int localSymbolsIndex; |
| |
| /// The number of local symbols. |
| /// |
| /// `uint32_t nlocalsym` in the C headers. |
| final int localSymbolsCount; |
| |
| /// The index of the externally defined symbols. |
| /// |
| /// `uint32_t iextdefsym` in the C headers. |
| final int externalSymbolsIndex; |
| |
| /// The number of externally defined symbols. |
| /// |
| /// `uint32_t nextdefsym` in the C headers. |
| final int externalSymbolsCount; |
| |
| /// The index of the undefined symbols. |
| /// |
| /// `uint32_t iundefsym` in the C headers. |
| final int undefinedSymbolsIndex; |
| |
| /// The number of undefined symbols. |
| /// |
| /// `uint32_t nundefsym` in the C headers. |
| final int undefinedSymbolsCount; |
| |
| /// The file offset of the table of contents. |
| /// |
| /// `uint32_t tocoff` in the C headers. |
| final int tableOfContentsFileOffset; |
| |
| /// The number of entries in the table of contents. |
| /// |
| /// `uint32_t ntoc` in the C headers. |
| final int tableOfContentsEntryCount; |
| |
| /// The file offset of the module table. |
| /// |
| /// `uint32_t modtaboff` in the C headers. |
| final int moduleTableFileOffset; |
| |
| /// The number of entries in the module table. |
| /// |
| /// `uint32_t nmodtab` in the C headers. |
| final int moduleTableEntryCount; |
| |
| /// The file offset of the referenced symbol table. |
| /// |
| /// `uint32_t extrefsymoff` in the C headers. |
| final int referencedSymbolTableFileOffset; |
| |
| /// The number of entries in the referenced symbol table. |
| /// |
| /// `uint32_t nextrefsyms` in the C headers. |
| final int referencedSymbolTableEntryCount; |
| |
| /// The file offset of the indirect symbol table. |
| /// |
| /// `uint32_t indirectsymoff` in the C headers. |
| final int indirectSymbolTableFileOffset; |
| |
| /// The number of entries in the indirect symbol table. |
| /// |
| /// `uint32_t nindirectsyms` in the C headers. |
| final int indirectSymbolTableEntryCount; |
| |
| /// The file offset of external relocation entries. |
| /// |
| /// `uint32_t extreloff` in the C headers. |
| final int externalRelocationsFileOffset; |
| |
| /// The number of external relocation entries. |
| /// |
| /// `uint32_t nextrel` in the C headers. |
| final int externalRelocationsCount; |
| |
| /// The file offset of local relocation entries. |
| /// |
| /// `uint32_t locreloff` in the C headers. |
| final int localRelocationsFileOffset; |
| |
| /// The number of local relocation entries. |
| /// |
| /// `uint32_t nlocrel` in the C headers. |
| final int localRelocationsCount; |
| |
| MachODysymtabCommand( |
| super.code, |
| super.size, |
| this.localSymbolsIndex, |
| this.localSymbolsCount, |
| this.externalSymbolsIndex, |
| this.externalSymbolsCount, |
| this.undefinedSymbolsIndex, |
| this.undefinedSymbolsCount, |
| this.tableOfContentsFileOffset, |
| this.tableOfContentsEntryCount, |
| this.moduleTableFileOffset, |
| this.moduleTableEntryCount, |
| this.referencedSymbolTableFileOffset, |
| this.referencedSymbolTableEntryCount, |
| this.indirectSymbolTableFileOffset, |
| this.indirectSymbolTableEntryCount, |
| this.externalRelocationsFileOffset, |
| this.externalRelocationsCount, |
| this.localRelocationsFileOffset, |
| this.localRelocationsCount); |
| |
| static MachODysymtabCommand fromStream( |
| int code, int size, MachOReader stream) { |
| final localSymbolsIndex = stream.readUint32(); |
| final localSymbolsCount = stream.readUint32(); |
| final externalSymbolsIndex = stream.readUint32(); |
| final externalSymbolsCount = stream.readUint32(); |
| final undefinedSymbolsIndex = stream.readUint32(); |
| final undefinedSymbolsCount = stream.readUint32(); |
| final tableOfContentsFileOffset = stream.readUint32(); |
| final tableOfContentsEntryCount = stream.readUint32(); |
| final moduleTableFileOffset = stream.readUint32(); |
| final moduleTableEntryCount = stream.readUint32(); |
| final referencedSymbolTableFileOffset = stream.readUint32(); |
| final referencedSymbolTableEntryCount = stream.readUint32(); |
| final indirectSymbolTableFileOffset = stream.readUint32(); |
| final indirectSymbolTableEntryCount = stream.readUint32(); |
| final externalRelocationsFileOffset = stream.readUint32(); |
| final externalRelocationsCount = stream.readUint32(); |
| final localRelocationsFileOffset = stream.readUint32(); |
| final localRelocationsCount = stream.readUint32(); |
| |
| return MachODysymtabCommand( |
| code, |
| size, |
| localSymbolsIndex, |
| localSymbolsCount, |
| externalSymbolsIndex, |
| externalSymbolsCount, |
| undefinedSymbolsIndex, |
| undefinedSymbolsCount, |
| tableOfContentsFileOffset, |
| tableOfContentsEntryCount, |
| moduleTableFileOffset, |
| moduleTableEntryCount, |
| referencedSymbolTableFileOffset, |
| referencedSymbolTableEntryCount, |
| indirectSymbolTableFileOffset, |
| indirectSymbolTableEntryCount, |
| externalRelocationsFileOffset, |
| externalRelocationsCount, |
| localRelocationsFileOffset, |
| localRelocationsCount); |
| } |
| |
| @override |
| MachODysymtabCommand adjust(OffsetsAdjuster adjuster) => MachODysymtabCommand( |
| code, |
| size, |
| localSymbolsIndex, |
| localSymbolsCount, |
| externalSymbolsIndex, |
| externalSymbolsCount, |
| undefinedSymbolsIndex, |
| undefinedSymbolsCount, |
| adjuster.adjust(tableOfContentsFileOffset), |
| tableOfContentsEntryCount, |
| adjuster.adjust(moduleTableFileOffset), |
| moduleTableEntryCount, |
| adjuster.adjust(referencedSymbolTableFileOffset), |
| referencedSymbolTableEntryCount, |
| adjuster.adjust(indirectSymbolTableFileOffset), |
| indirectSymbolTableEntryCount, |
| adjuster.adjust(externalRelocationsFileOffset), |
| externalRelocationsCount, |
| adjuster.adjust(localRelocationsFileOffset), |
| localRelocationsCount); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint32(localSymbolsIndex); |
| stream.writeUint32(localSymbolsCount); |
| stream.writeUint32(externalSymbolsIndex); |
| stream.writeUint32(externalSymbolsCount); |
| stream.writeUint32(undefinedSymbolsIndex); |
| stream.writeUint32(undefinedSymbolsCount); |
| stream.writeUint32(tableOfContentsFileOffset); |
| stream.writeUint32(tableOfContentsEntryCount); |
| stream.writeUint32(moduleTableFileOffset); |
| stream.writeUint32(moduleTableEntryCount); |
| stream.writeUint32(referencedSymbolTableFileOffset); |
| stream.writeUint32(referencedSymbolTableEntryCount); |
| stream.writeUint32(indirectSymbolTableFileOffset); |
| stream.writeUint32(indirectSymbolTableEntryCount); |
| stream.writeUint32(externalRelocationsFileOffset); |
| stream.writeUint32(externalRelocationsCount); |
| stream.writeUint32(localRelocationsFileOffset); |
| stream.writeUint32(localRelocationsCount); |
| } |
| } |
| |
| /// A load command that contains the offsets and sizes of a blob of data in |
| /// the __LINKEDIT segment. The contents of the blob is determined by the |
| /// specific `code` value used for the load command. |
| class MachOLinkeditDataCommand extends MachOLoadCommand { |
| /// The file offset for the segment data. |
| /// |
| /// `uint32_t dataoff` in the C headers. |
| final int dataFileOffset; |
| |
| /// The file size of the segment data. |
| /// |
| /// `uint32_t datasize` in the C headers. |
| final int dataSize; |
| |
| MachOLinkeditDataCommand( |
| super.code, |
| super.size, |
| this.dataFileOffset, |
| this.dataSize, |
| ); |
| |
| static MachOLinkeditDataCommand fromStream( |
| int code, final int size, MachOReader stream) { |
| final dataFileOffset = stream.readUint32(); |
| final dataSize = stream.readUint32(); |
| |
| return MachOLinkeditDataCommand(code, size, dataFileOffset, dataSize); |
| } |
| |
| @override |
| MachOLinkeditDataCommand adjust(OffsetsAdjuster adjuster) => |
| MachOLinkeditDataCommand( |
| code, size, adjuster.adjust(dataFileOffset), dataSize); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint32(dataFileOffset); |
| stream.writeUint32(dataSize); |
| } |
| } |
| |
| /// A load command that contains the offset and size of an encrypted segment. |
| class MachOEncryptionInfoCommand extends MachOLoadCommand { |
| /// The file offset for the encrypted segment. |
| /// |
| /// `uint32_t cryptoff` in the C headers. |
| final int fileOffset; |
| |
| /// The file size of the encrypted segment. |
| /// |
| /// `uint32_t cryptsize` in the C headers. |
| final int fileSize; |
| |
| /// The id of the encryption system used for the encrypted segment. |
| /// A value of 0 means that the segment is not yet encrypted. |
| /// |
| /// `uint32_t cryptid` in the C headers. |
| final int encryptionSystem; |
| |
| /// Padding for 64-bit encryption info load commands. Null for 32-bit |
| /// encryption info load commands. |
| /// |
| /// `uint32_t pad` for `encryption_info_command_64` in the C headers. |
| final int? padding; |
| |
| MachOEncryptionInfoCommand(super.code, super.size, this.fileOffset, |
| this.fileSize, this.encryptionSystem, this.padding); |
| |
| static MachOEncryptionInfoCommand fromStream( |
| int code, final int size, MachOReader stream) { |
| final is64Bit = |
| LoadCommandType.fromCode(code) == LoadCommandType.encryptionInfo64; |
| |
| final fileOffset = stream.readUint32(); |
| final fileSize = stream.readUint32(); |
| final encryptionSystem = stream.readUint32(); |
| int? padding; |
| if (is64Bit) { |
| padding = stream.readUint32(); |
| } |
| |
| return MachOEncryptionInfoCommand( |
| code, size, fileOffset, fileSize, encryptionSystem, padding); |
| } |
| |
| @override |
| MachOEncryptionInfoCommand adjust(OffsetsAdjuster adjuster) => |
| MachOEncryptionInfoCommand(code, size, adjuster.adjust(fileOffset), |
| fileSize, encryptionSystem, padding); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint32(fileOffset); |
| stream.writeUint32(fileSize); |
| stream.writeUint32(encryptionSystem); |
| if (padding != null) { |
| stream.writeUint32(padding!); |
| } |
| } |
| } |
| |
| /// A load command that contains the file offsets and sizes of the new |
| /// compressed form of the information dyld needs to load the image on |
| /// MacOS 10.6 and later. |
| class MachODyldInfoCommand extends MachOLoadCommand { |
| /// File offset for rebase information. |
| /// |
| /// `uint32_t rebase_off` in the C headers. |
| final int rebaseOffset; |
| |
| /// File size of rebase information. |
| /// |
| /// `uint32_t rebase_size` in the C headers. |
| final int rebaseSize; |
| |
| /// File offset for binding information. |
| /// |
| /// `uint32_t bind_off` in the C headers. |
| final int bindingOffset; |
| |
| /// File size of binding information. |
| /// |
| /// `uint32_t bind_size` in the C headers. |
| final int bindingSize; |
| |
| /// File offset for weak binding information. |
| /// |
| /// `uint32_t weak_bind_off` in the C headers. |
| final int weakBindingOffset; |
| |
| /// File size of weak binding information. |
| /// |
| /// `uint32_t weak_bind_size` in the C headers. |
| final int weakBindingSize; |
| |
| /// File offset for lazy binding information. |
| /// |
| /// `uint32_t lazy_bind_off` in the C headers. |
| final int lazyBindingOffset; |
| |
| /// File size of lazy binding information. |
| /// |
| /// `uint32_t lazy_bind_size` in the C headers. |
| final int lazyBindingSize; |
| |
| /// File offset for export information. |
| /// |
| /// `uint32_t export_off` in the C headers. |
| final int exportOffset; |
| |
| /// File size of export information. |
| /// |
| /// `uint32_t export_size` in the C headers. |
| final int exportSize; |
| |
| MachODyldInfoCommand( |
| super.code, |
| super.size, |
| this.rebaseOffset, |
| this.rebaseSize, |
| this.bindingOffset, |
| this.bindingSize, |
| this.weakBindingOffset, |
| this.weakBindingSize, |
| this.lazyBindingOffset, |
| this.lazyBindingSize, |
| this.exportOffset, |
| this.exportSize); |
| |
| static MachODyldInfoCommand fromStream( |
| int code, int size, MachOReader stream) { |
| final rebaseOffset = stream.readUint32(); |
| final rebaseSize = stream.readUint32(); |
| final bindingOffset = stream.readUint32(); |
| final bindingSize = stream.readUint32(); |
| final weakBindingOffset = stream.readUint32(); |
| final weakBindingSize = stream.readUint32(); |
| final lazyBindingOffset = stream.readUint32(); |
| final lazyBindingSize = stream.readUint32(); |
| final exportOffset = stream.readUint32(); |
| final exportSize = stream.readUint32(); |
| |
| return MachODyldInfoCommand( |
| code, |
| size, |
| rebaseOffset, |
| rebaseSize, |
| bindingOffset, |
| bindingSize, |
| weakBindingOffset, |
| weakBindingSize, |
| lazyBindingOffset, |
| lazyBindingSize, |
| exportOffset, |
| exportSize); |
| } |
| |
| @override |
| MachODyldInfoCommand adjust(OffsetsAdjuster adjuster) => MachODyldInfoCommand( |
| code, |
| size, |
| adjuster.adjust(rebaseOffset), |
| rebaseSize, |
| adjuster.adjust(bindingOffset), |
| bindingSize, |
| adjuster.adjust(weakBindingOffset), |
| weakBindingSize, |
| adjuster.adjust(lazyBindingOffset), |
| lazyBindingSize, |
| adjuster.adjust(exportOffset), |
| exportSize); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint32(rebaseOffset); |
| stream.writeUint32(rebaseSize); |
| stream.writeUint32(bindingOffset); |
| stream.writeUint32(bindingSize); |
| stream.writeUint32(weakBindingOffset); |
| stream.writeUint32(weakBindingSize); |
| stream.writeUint32(lazyBindingOffset); |
| stream.writeUint32(lazyBindingSize); |
| stream.writeUint32(exportOffset); |
| stream.writeUint32(exportSize); |
| } |
| } |
| |
| /// A load command used for executables to specify the file offset of the entry |
| /// point of the program. |
| class MachOEntryPointCommand extends MachOLoadCommand { |
| /// The file offset of the entry point to the program (e.g., main()). |
| /// |
| /// `uint64_t entryoff` in the C headers. |
| final int entryOffset; |
| |
| /// When non-zero, specifies the initial stack size. |
| /// |
| /// `uint64_t stacksize` in the C headers. |
| final int stackSize; |
| |
| MachOEntryPointCommand( |
| super.code, super.size, this.entryOffset, this.stackSize); |
| |
| static MachOEntryPointCommand fromStream( |
| int code, int size, MachOReader stream) { |
| final entryOffset = stream.readUint64(); |
| final stackSize = stream.readUint64(); |
| return MachOEntryPointCommand(code, size, entryOffset, stackSize); |
| } |
| |
| @override |
| MachOEntryPointCommand adjust(OffsetsAdjuster adjuster) => |
| MachOEntryPointCommand( |
| code, size, adjuster.adjust(entryOffset), stackSize); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint64(entryOffset); |
| stream.writeUint64(stackSize); |
| } |
| } |
| |
| /// A load command that references an array of entries in the __LINKEDIT |
| /// segment, each of which describes a range of data located in the __TEXT |
| /// segment. |
| class MachODataInCodeEntry extends MachOLoadCommand { |
| /// The file offset of the array of data in code entries. |
| /// |
| /// `uint32_t offset` in the C headers. |
| final int offset; |
| |
| /// The length of the array of data in bytes. |
| /// |
| /// `uint16_t length` in the C headers. |
| final int length; |
| |
| /// The kind of entries contained in the array as a `DICE_KIND_` value. |
| /// |
| /// `uint16_t kind` in the C headers. |
| final int kind; |
| |
| MachODataInCodeEntry( |
| super.code, |
| super.size, |
| this.offset, |
| this.length, |
| this.kind, |
| ); |
| |
| static MachODataInCodeEntry fromStream( |
| int code, int size, MachOReader stream) { |
| final offset = stream.readUint32(); |
| final length = stream.readUint16(); |
| final kind = stream.readUint16(); |
| return MachODataInCodeEntry(code, size, offset, length, kind); |
| } |
| |
| @override |
| MachODataInCodeEntry adjust(OffsetsAdjuster adjuster) => |
| MachODataInCodeEntry(code, size, adjuster.adjust(offset), length, kind); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint32(offset); |
| stream.writeUint16(length); |
| stream.writeUint16(kind); |
| } |
| } |
| |
| /// A load command that points to an arbitrary block of data in the file. |
| class MachONoteCommand extends MachOLoadCommand { |
| /// The owner name for this note. |
| /// |
| /// `char data_owner[16]` in the C headers. |
| final String dataOwner; |
| |
| /// The file offset for the data. |
| /// |
| /// `uint64_t offset` in the C headers. |
| final int fileOffset; |
| |
| /// The file size of the data. |
| /// |
| /// `uint64_t size` in the C headers. |
| final int fileSize; |
| |
| MachONoteCommand( |
| super.code, super.size, this.dataOwner, this.fileOffset, this.fileSize); |
| |
| static MachONoteCommand fromStream(int code, int size, MachOReader stream) { |
| final dataOwner = |
| stream.readFixedLengthNullTerminatedString(_dataOwnerLength); |
| final fileOffset = stream.readUint64(); |
| final fileSize = stream.readUint64(); |
| return MachONoteCommand(code, size, dataOwner, fileOffset, fileSize); |
| } |
| |
| /// Constructs a note load command given the content of the note-specific |
| /// fields, using the default values for the code and size fields. |
| static MachONoteCommand fromFields( |
| String dataOwner, int fileOffset, int fileSize) => |
| MachONoteCommand(LoadCommandType.note.code, _defaultSize, dataOwner, |
| fileOffset, fileSize); |
| |
| /// The maximum size of the dataOwner field contents. |
| static const _dataOwnerLength = 16; |
| |
| // The total size of any given note load command. A note load command contains |
| // the 4-byte cmd and cmdsize fields that start every load command, a 16 byte |
| // name field, and then two additional 8-byte fields. |
| static const _defaultSize = 4 + 4 + 16 + 8 + 8; |
| |
| @override |
| MachONoteCommand adjust(OffsetsAdjuster adjuster) => MachONoteCommand( |
| code, size, dataOwner, adjuster.adjust(fileOffset), fileSize); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeFixedLengthNullTerminatedString(dataOwner, _dataOwnerLength); |
| stream.writeUint64(fileOffset); |
| stream.writeUint64(fileSize); |
| } |
| } |
| |
| /// A load command that specifies a fileset entry. |
| class MachOFileSetEntryCommand extends MachOLoadCommand { |
| /// The memory address of the dynamically linked shared library. |
| /// |
| /// `uint64_t vmaddr` in the C headers. |
| final int memoryAddress; |
| |
| /// The file offset of the dynamically linked shared library. |
| /// |
| /// `uint64_t fileoff` in the C headers. |
| final int fileOffset; |
| |
| /// The contained entry id. |
| /// |
| /// `lc_str entry_id` in the C headers. |
| final int entryId; |
| |
| /// Reserved padding. |
| /// |
| /// `uint32_t reserved` in the C headers. |
| final int reserved; |
| |
| MachOFileSetEntryCommand(super.code, super.size, this.memoryAddress, |
| this.fileOffset, this.entryId, this.reserved); |
| |
| static MachOFileSetEntryCommand fromStream( |
| int code, int size, MachOReader stream) { |
| final memoryAddress = stream.readUint64(); |
| final fileOffset = stream.readUint64(); |
| final entryId = stream.readLCString(); |
| final reserved = stream.readUint32(); |
| return MachOFileSetEntryCommand( |
| code, size, memoryAddress, fileOffset, entryId, reserved); |
| } |
| |
| @override |
| MachOFileSetEntryCommand adjust(OffsetsAdjuster adjuster) => |
| MachOFileSetEntryCommand(code, size, memoryAddress, |
| adjuster.adjust(fileOffset), entryId, reserved); |
| |
| @override |
| void writeContentsSync(MachOWriter stream) { |
| stream.writeUint64(memoryAddress); |
| stream.writeUint64(fileOffset); |
| stream.writeLCString(entryId); |
| stream.writeUint32(reserved); |
| } |
| } |
| |
| /// The headers and load commands of a MachO file. Note that this class does |
| /// not contain the contents of the load commands. Instead, those are expected |
| /// to be copied over from the original file appropriately. |
| class MachOFile { |
| /// The header of the MachO file. |
| final MachOHeader header; |
| |
| /// The load commands of the MachO file, which represent how to read the |
| /// non-header contents of the MachO file. |
| final List<MachOLoadCommand> commands; |
| |
| /// Whether or not the parsed MachO file has a code signature. |
| final bool hasCodeSignature; |
| |
| MachOFile._(this.header, this.commands, this.hasCodeSignature); |
| |
| static MachOFile fromFile(File file) { |
| // Ensure the file is long enough to contain the magic bytes. |
| final fileLength = file.lengthSync(); |
| if (fileLength < 4) { |
| throw FormatException( |
| 'File was not formatted properly. Length was too short: $fileLength'); |
| } |
| |
| final stream = file.openSync(); |
| final header = MachOHeader.fromStream(stream); |
| if (header == null) { |
| throw FormatException( |
| 'Could not parse a MachO header from the file: ${file.path}'); |
| } |
| |
| final uses64BitPointers = CpuType.cpuTypeUses64BitPointers(header.cpu); |
| final reader = MachOReader(stream, header.endian, uses64BitPointers); |
| |
| final commands = List<MachOLoadCommand>.generate( |
| header.loadCommandsCount, (_) => MachOLoadCommand.fromStream(reader)); |
| |
| final size = _totalSize(header, commands); |
| assert(size == stream.positionSync()); |
| |
| final hasCodeSignature = |
| commands.any((c) => c.type == LoadCommandType.codeSignature); |
| |
| return MachOFile._(header, commands, hasCodeSignature); |
| } |
| |
| /// Returns a new MachOFile that is like the input, but with the empty segment |
| /// used to reserve header space dropped and with a new note load command |
| /// inserted prior to the __LINKEDIT segment load command. Any file offsets |
| /// in other load commands that reference the __LINKEDIT segment are adjusted |
| /// appropriately. |
| MachOFile adjustHeaderForSnapshot(int snapshotSize) { |
| // This is not an idempotent operation. |
| if (snapshotNote != null) { |
| throw const FormatException( |
| 'The executable already has a Dart snapshot inserted'); |
| } |
| |
| final reserved = reservedSegment; |
| if (reserved == null) { |
| throw const FormatException('$reservedSegmentName segment not found'); |
| } |
| |
| final linkedit = linkEditSegment; |
| if (linkedit == null) { |
| throw const FormatException('__LINKEDIT segment not found'); |
| } |
| |
| // We insert the contents of the snapshot where the old linkedit segment |
| // started in the original executable, aligned appropriately. |
| final fileOffset = align(linkedit.fileOffset, segmentAlignment); |
| final fileSize = snapshotSize; |
| |
| final note = |
| MachONoteCommand.fromFields(snapshotNoteName, fileOffset, fileSize); |
| |
| // Now we need to build the new header from these modified pieces. |
| final newHeader = MachOHeader( |
| header.magic, |
| header.cpu, |
| header.machine, |
| header.type, |
| // We remove the reserved section and replace it with the note. |
| header.loadCommandsCount, |
| header.loadCommandsSize - reserved.size + note.size, |
| header.flags, |
| header.reserved); |
| |
| // We'll want the __LINKEDIT segment to start at the next aligned file |
| // offset after the end of the snapshot, so we'll need to adjust all |
| // file offsets pointing into it (including its own segment) accordingly. |
| final snapshotEnd = |
| align(note.fileOffset + note.fileSize, segmentAlignment); |
| final adjuster = OffsetsAdjuster() |
| ..add(linkedit.fileOffset, snapshotEnd - linkedit.fileOffset); |
| final newCommands = <MachOLoadCommand>[]; |
| for (final command in commands) { |
| if (command == reserved) { |
| // Drop the reserved segment on the floor, as we only add it so there's |
| // enough header space to add the note. |
| continue; |
| } |
| if (command == linkedit) { |
| // Insert the new note prior to the __LINKEDIT segment. |
| newCommands.add(note); |
| } |
| newCommands.add(command.adjust(adjuster)); |
| } |
| |
| final newFile = MachOFile._(newHeader, newCommands, hasCodeSignature); |
| |
| if (newFile.size > size) { |
| throw FormatException('Cannot add new note load command to header: ' |
| 'new size ${newFile.size} > the old size $size)'); |
| } |
| |
| return newFile; |
| } |
| |
| /// The name of the segment containing all the structs created and maintained |
| /// by the link editor. |
| static const _linkEditSegmentName = '__LINKEDIT'; |
| |
| /// Retrieves the segment load command used to reserve header space for the |
| /// snapshot information. Returns null if not found or if it is of an |
| /// unexpected form. |
| MachOSegmentCommand? get reservedSegment { |
| final reservedIndex = commands.indexWhere( |
| (c) => c is MachOSegmentCommand && c.name == reservedSegmentName); |
| if (reservedIndex < 0) { |
| return null; |
| } |
| final reserved = commands[reservedIndex] as MachOSegmentCommand; |
| assert(reserved.fileSize == 0); |
| assert(reserved.sections.single.name == reservedSectionName); |
| return reserved; |
| } |
| |
| /// Retrieves the __LINKEDIT segment load command. Returns null if not found. |
| MachOSegmentCommand? get linkEditSegment { |
| final linkEditIndex = commands.indexWhere( |
| (c) => c is MachOSegmentCommand && c.name == _linkEditSegmentName); |
| if (linkEditIndex < 0) { |
| return null; |
| } |
| |
| // __LINKEDIT should be the last segment load command. |
| assert( |
| !commands.skip(linkEditIndex + 1).any((c) => c is MachOSegmentCommand)); |
| |
| return commands[linkEditIndex] as MachOSegmentCommand; |
| } |
| |
| /// Retrieves the note load command that points to the snapshot contents in |
| /// the executable. Returns null if not found. |
| MachONoteCommand? get snapshotNote { |
| final snapshotIndex = commands.indexWhere( |
| (c) => c is MachONoteCommand && c.dataOwner == snapshotNoteName); |
| if (snapshotIndex < 0) { |
| return null; |
| } |
| return commands[snapshotIndex] as MachONoteCommand; |
| } |
| |
| static bool containsSnapshot(File file) => |
| MachOFile.fromFile(file).snapshotNote != null; |
| |
| static int _totalSize(MachOHeader header, List<MachOLoadCommand> commands) => |
| commands.fold(header.size, (i, c) => i + c.size); |
| |
| int get size => _totalSize(header, commands); |
| |
| /// Writes the MachO file to the given [RandomAccessFile] stream. |
| void writeSync(RandomAccessFile stream) { |
| header.writeSync(stream); |
| |
| final uses64BitPointers = CpuType.cpuTypeUses64BitPointers(header.cpu); |
| final writer = MachOWriter(stream, header.endian, uses64BitPointers); |
| // Write all of the commands. |
| for (final command in commands) { |
| command.writeSync(writer); |
| } |
| } |
| } |