b0c4ddf919
To create a Dart standalone executable on MacOS, we modify the dartaotruntime executable to add the snapshot contents, and the VM looks into the executable on disk to find the snapshot to load. Previously, we did this by adding a new 64-bit segment load command with a single section, where the section's file offset and size describes the inserted snapshot. This meant the Mach-O header size increased by 152 bytes. Originally, this wasn't an issue as there was plenty of padding, but later clang updates removed most of this padding, and so writing the new header actually overwrote the initial contents of the first section in the file, which happens to be the __text section. In addition, since the first section's offset was now declared to be within the header, utilities that strictly validated the Mach-O format, like codesign, would report errors. This CL changes it so that we actually reserve space in the dartaotruntime header using the -add_empty_section flag to the linker. In addition, we change from using a segment load command to using a (40 byte) note load command. This is because a segment load command specifies that the contents should be loaded in memory, but we don't use that loaded version. Instead, the VM reloads it from the executable on disk so it can appropriately mmap the different parts of the snapshot. A note section instead just declares a section of the executable as arbitrary data that the owner can read from the file and use as desired, which is semantically closer to our current usage. This CL also adds a test to pkg/dartdev/test/commands/compile_test to ensure that corrupting a random part of the snapshot in the executable causes signature verification to fail. This CL also reverts CL 256208, thus relanding the clang changes starting from June that originally raised awareness of the issue by greatly reduced the amount of padding after the load commands. TEST=pkg/dartdev/test/commands/compile_test Bug: https://github.com/dart-lang/sdk/issues/49783 Change-Id: Iee554d87b0eabaecd7a534ca4e4facfefbce6385 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/+/260108 Reviewed-by: Ryan Macnak <rmacnak@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
186 lines
7.4 KiB
Dart
186 lines
7.4 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 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);
|
|
}
|