Files
sdk/pkg/dart2native/lib/macho_utils.dart
T
Tess Strickland be4d469e38 [pkg/dart2native] Refactor MachO parsing.
* Move parsing functions to static methods of the classes that
  represent the data being parsed.

* Move calculations for adjusting offsets in load commands into the
  corresponding classes.

* Make function to insert a new payload segment an instance method
  of MachOFile.

* Remove unnecessary abstractions and definitions for MachO load
  commands that we don't need to parse.

These refactorings are being done to make later changes easier to
review, by first lifting the initial refactorings out into a
separate CL.

Issue: https://github.com/dart-lang/sdk/issues/49783
Change-Id: I133eb368cbb9ee0d8e4f3998ba1a0bbe8555b8aa
Cq-Include-Trybots: luci.dart.try:analyzer-mac-release-try,dart-sdk-mac-arm64-try,dart-sdk-mac-try,pkg-mac-release-arm64-try,pkg-mac-release-try,vm-kernel-precomp-mac-product-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256821
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
2022-09-07 11:32:37 +00:00

183 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 two values MUST match the ones in
// runtime/bin/snapshot_utils.cc, which looks specifically for the snapshot in
// this segment and section.
const String snapshotSegmentName = "__CUSTOM";
const String snapshotSectionName = "__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);
}