ac212a05b6
Also uses some newer language features to cleanup nearby code. Change-Id: I542911cd21ac0d77692edd7734b694c92d341ded Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/346202 Reviewed-by: Tess Strickland <sstrickl@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
176 lines
7.3 KiB
Dart
176 lines
7.3 KiB
Dart
// 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 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) {
|
|
4 => readUint32(),
|
|
8 => readUint64(),
|
|
_ => 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) {
|
|
4 => writeUint32(value),
|
|
8 => writeUint64(value),
|
|
_ => 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 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);
|
|
}
|