| // 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. |
| |
| import 'dart:convert'; |
| import 'dart:io'; |
| import 'dart:typed_data'; |
| |
| // Note that these values MUST match the arguments to -add_empty_section in |
| // runtime/BUILD.gn. |
| const String reservedSegmentName = "__CUSTOM"; |
| const String reservedSectionName = "__space_for_note"; |
| |
| // Note that this value MUST match runtime/bin/snapshot_utils.cc, which looks |
| // specifically for the snapshot in this note. |
| const String snapshotNoteName = "__dart_app_snap"; |
| |
| /// The page size for aligning segments in MachO files. X64 MacOS uses 4k pages, |
| /// and ARM64 MacOS uses 16k pages, so we use 16k here. |
| const int segmentAlignmentLog2 = 14; |
| const int segmentAlignment = 1 << segmentAlignmentLog2; |
| |
| /// Pads the given size so that it is a multiple of the given alignment. |
| int align(int size, int alignment) { |
| final int unpadded = size % alignment; |
| return size + (unpadded != 0 ? alignment - unpadded : 0); |
| } |
| |
| /// A reader utility class that wraps a stream to include endian information |
| /// and whether or not the architecture for the MachO file uses 64-bit pointers. |
| class MachOReader { |
| final RandomAccessFile _stream; |
| final Endian _endian; |
| |
| /// Used to determine whether to read 4 or 8 bytes for the `lc_str` union |
| /// type, which depends on the `__LP64__` define (e.g., is the target |
| /// architecture using 64-bit pointers). May be null if no `lc_str` values |
| /// will be read by this reader. |
| final bool? _arch64BitPointers; |
| |
| MachOReader(this._stream, this._endian, [this._arch64BitPointers]); |
| |
| /// Reads a 16-bit unsigned integer from the contained stream. |
| int readUint16() => |
| ByteData.sublistView(_stream.readSync(2)).getUint16(0, _endian); |
| |
| /// Reads a 32-bit unsigned integer from the contained stream. |
| int readUint32() => |
| ByteData.sublistView(_stream.readSync(4)).getUint32(0, _endian); |
| |
| /// Reads a 64-bit unsigned integer from the contained stream. |
| int readUint64() => |
| ByteData.sublistView(_stream.readSync(8)).getUint64(0, _endian); |
| |
| /// Reads a 32-bit signed integer from the contained stream. |
| int readInt32() => |
| ByteData.sublistView(_stream.readSync(4)).getInt32(0, _endian); |
| |
| /// Reads an unsigned integer of the given word size from the contained |
| /// stream. Throws an [ArgumentError] for if [wordSize] is not 4 or 8. |
| int readUword(int wordSize) { |
| switch (wordSize) { |
| case 4: |
| return readUint32(); |
| case 8: |
| return readUint64(); |
| default: |
| throw ArgumentError('Unexpected word size: $wordSize', 'wordSize'); |
| } |
| } |
| |
| /// Reads a fixed length string from the contained stream. The string may be |
| /// null terminated, in which case the returned string may contain less |
| /// code points than the provided size. |
| String readFixedLengthNullTerminatedString(int size) { |
| final buffer = _stream.readSync(size); |
| return String.fromCharCodes(buffer.takeWhile((value) => value != 0)); |
| } |
| |
| /// Reads an `lc_str` value from the contained stream. For 32-bit |
| /// architectures, or 64-bit architectures using 32-bit pointers, this is |
| /// 32 bits. On 64-bit architectures using 64-bit pointers, this is 64 bits. |
| int readLCString() => _arch64BitPointers! ? readUint64() : readUint32(); |
| |
| /// Reads [size] bytes from the contained stream. |
| Uint8List readBytes(int size) => _stream.readSync(size); |
| } |
| |
| /// A writer utility class that wraps a stream to include endian information |
| /// and whether or not the architecture for the MachO file uses 64-bit pointers. |
| class MachOWriter { |
| final RandomAccessFile _stream; |
| final Endian _endian; |
| |
| /// Used to determine whether to write 4 or 8 bytes for the `lc_str` union |
| /// type, which depends on the `__LP64__` define (e.g., is the target |
| /// architecture using 64-bit pointers). May be null if no `lc_str` values |
| /// will be written by this reader. |
| final bool? _arch64BitPointers; |
| |
| MachOWriter(this._stream, this._endian, [this._arch64BitPointers]); |
| |
| /// Writes a 16-bit unsigned integer to the contained stream. Throws |
| /// a [FormatException] if the value is negative or does not fit in 16 bits. |
| void writeUint16(int value) { |
| if (value < 0) { |
| throw FormatException("Attempted to write a negative value $value"); |
| } |
| if ((value >> 16) != 0) { |
| throw FormatException("Attempted to write an unsigned value that doesn't " |
| "fit in 16 bits: $value"); |
| } |
| final buffer = ByteData(2)..setUint16(0, value, _endian); |
| _stream.writeFromSync(Uint8List.sublistView(buffer)); |
| } |
| |
| /// Writes a 32-bit unsigned integer to the contained stream. Throws |
| /// a [FormatException] if the value is negative or does not fit in 32 bits. |
| void writeUint32(int value) { |
| if (value < 0) { |
| throw FormatException("Attempted to write a negative value $value"); |
| } |
| if ((value >> 32) != 0) { |
| throw FormatException("Attempted to write an unsigned value that doesn't " |
| "fit in 32 bits: $value"); |
| } |
| final buffer = ByteData(4)..setUint32(0, value, _endian); |
| _stream.writeFromSync(Uint8List.sublistView(buffer)); |
| } |
| |
| /// Writes a 64-bit unsigned integer to the contained stream. |
| void writeUint64(int value) { |
| final buffer = ByteData(8)..setUint64(0, value, _endian); |
| _stream.writeFromSync(Uint8List.sublistView(buffer)); |
| } |
| |
| /// Writes a 32-bit unsigned integer to the contained stream. Throws |
| /// a [FormatException] if the signed value does not fit in 32 bits. |
| void writeInt32(int value) { |
| if (((value < 0 ? ~value : value) >> 31) != 0) { |
| throw FormatException("Attempted to write a signed value that doesn't " |
| "fit in 32 bits: $value"); |
| } |
| final buffer = ByteData(4)..setInt32(0, value, _endian); |
| _stream.writeFromSync(Uint8List.sublistView(buffer)); |
| } |
| |
| /// Writes an unsigned integer with a given word size to the contained |
| /// stream. Throws an [ArgumentError] for if [wordSize] is not 4 or 8. |
| void writeUword(int value, int wordSize) { |
| switch (wordSize) { |
| case 4: |
| return writeUint32(value); |
| case 8: |
| return writeUint64(value); |
| default: |
| throw ArgumentError('Unexpected word size: $wordSize', 'wordSize'); |
| } |
| } |
| |
| /// Writes the given string as an ASCII-encoded null terminated string |
| /// with a given fixed length. Throws a format exception if the ASCII |
| /// character length of the string is longer than the given length. |
| /// If the ASCII character length of the string is the same as the given |
| /// length, there will be no null terminator in the written data. |
| void writeFixedLengthNullTerminatedString(String s, int length) { |
| final Uint8List buffer = Uint8List(length); |
| final encoded = ascii.encode(s); |
| if (encoded.length > length) { |
| throw FormatException('Attempted to write a string longer than $length ' |
| 'characters: "$s"'); |
| } |
| buffer.setRange(0, encoded.length, encoded); |
| _stream.writeFromSync(buffer); |
| } |
| |
| /// Writes an `lc_str` value to the contained stream. For 32-bit |
| /// architectures, or 64-bit architectures using 32-bit pointers, this is |
| /// 32 bits. On 64-bit architectures using 64-bit pointers, this is 64 bits. |
| void writeLCString(int value) => |
| _arch64BitPointers! ? writeUint64(value) : writeUint32(value); |
| |
| /// Writes the given bytes to the contained stream. |
| void writeBytes(Uint8List bytes) => _stream.writeFromSync(bytes); |
| } |