blob: e975ecb93ffa350f044f717033d24980f9b7b71d [file] [log] [blame]
// 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);
}