mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:09:48 +00:00
da691ea9c1
Bug: https://github.com/dart-lang/sdk/issues/39106 Change-Id: If1c88b4969fa44ffc6d764d3d1e34732acdf4d64 Cq-Include-Trybots: luci.dart.try:pkg-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/238541 Reviewed-by: Aske Simon Christensen <askesc@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
335 lines
12 KiB
Dart
335 lines
12 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:math';
|
|
import 'dart:typed_data';
|
|
|
|
int align(int size, int base) {
|
|
final int over = size % base;
|
|
if (over != 0) {
|
|
return size + (base - over);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
class BytesBacked {
|
|
ByteData data;
|
|
|
|
BytesBacked(this.data);
|
|
|
|
int get size => data.lengthInBytes;
|
|
|
|
Future<void> write(RandomAccessFile output) async {
|
|
await output.writeFrom(Uint8List.sublistView(data));
|
|
}
|
|
}
|
|
|
|
class CoffFileHeader extends BytesBacked {
|
|
CoffFileHeader._(ByteData data) : super(data);
|
|
|
|
static const _fileHeaderSize = 20;
|
|
static const _sectionCountOffset = 2;
|
|
static const _optionalHeaderSizeOffset = 16;
|
|
|
|
static CoffFileHeader fromTypedData(TypedData source, int offset) {
|
|
if (source.lengthInBytes < offset + _fileHeaderSize) {
|
|
throw 'File is truncated within the COFF file header';
|
|
}
|
|
final buffer = Uint8List(_fileHeaderSize);
|
|
buffer.setAll(
|
|
0, Uint8List.sublistView(source, offset, offset + _fileHeaderSize));
|
|
return CoffFileHeader._(ByteData.sublistView(buffer));
|
|
}
|
|
|
|
int get sectionCount => data.getUint16(_sectionCountOffset, Endian.little);
|
|
set sectionCount(int value) =>
|
|
data.setUint16(_sectionCountOffset, value, Endian.little);
|
|
|
|
int get optionalHeaderSize =>
|
|
data.getUint16(_optionalHeaderSizeOffset, Endian.little);
|
|
}
|
|
|
|
class CoffOptionalHeader extends BytesBacked {
|
|
CoffOptionalHeader._(ByteData data) : super(data);
|
|
|
|
static const _pe32Magic = 0x10b;
|
|
static const _pe32PlusMagic = 0x20b;
|
|
|
|
static const _magicOffset = 0;
|
|
static const _sectionAlignmentOffset = 32;
|
|
static const _fileAlignmentOffset = 36;
|
|
static const _imageSizeOffset = 56;
|
|
static const _headersSizeOffset = 60;
|
|
|
|
static CoffOptionalHeader fromTypedData(
|
|
TypedData source, int offset, int size) {
|
|
if (source.lengthInBytes < offset + size) {
|
|
throw 'File is truncated within the COFF optional header';
|
|
}
|
|
final buffer = Uint8List(size);
|
|
buffer.setAll(0, Uint8List.sublistView(source, offset, offset + size));
|
|
final data = ByteData.sublistView(buffer);
|
|
final magic = data.getUint16(_magicOffset, Endian.little);
|
|
if (magic != _pe32Magic && magic != _pe32PlusMagic) {
|
|
throw 'Not a PE32 or PE32+ image file';
|
|
}
|
|
return CoffOptionalHeader._(data);
|
|
}
|
|
|
|
// The alignment used for virtual addresses of sections, _not_ file offsets.
|
|
int get sectionAlignment =>
|
|
data.getUint32(_sectionAlignmentOffset, Endian.little);
|
|
|
|
// The alignment used for file offsets of section data and other contents.
|
|
int get fileAlignment => data.getUint32(_fileAlignmentOffset, Endian.little);
|
|
|
|
int get headersSize => data.getUint32(_headersSizeOffset, Endian.little);
|
|
set headersSize(int value) =>
|
|
data.setUint32(_headersSizeOffset, value, Endian.little);
|
|
|
|
int get imageSize => data.getUint32(_imageSizeOffset, Endian.little);
|
|
set imageSize(int value) =>
|
|
data.setUint32(_imageSizeOffset, value, Endian.little);
|
|
}
|
|
|
|
class CoffSectionHeader extends BytesBacked {
|
|
CoffSectionHeader._(ByteData data) : super(data);
|
|
|
|
static const _virtualSizeOffset = 8;
|
|
static const _virtualAddressOffset = 12;
|
|
static const _fileSizeOffset = 16;
|
|
static const _fileOffsetOffset = 20;
|
|
static const _characteristicsOffset = 36;
|
|
|
|
static const _discardableFlag = 0x02000000;
|
|
|
|
String get name => String.fromCharCodes(Uint8List.sublistView(data, 0, 8));
|
|
set name(String name) {
|
|
// Each section header has only eight bytes for the section name.
|
|
// First reset it to zeroes, then copy over the UTF-8 encoded version.
|
|
final buffer = Uint8List.sublistView(data, 0, 8);
|
|
buffer.fillRange(0, 8, 0);
|
|
buffer.setAll(0, utf8.encode(name));
|
|
}
|
|
|
|
int get virtualAddress =>
|
|
data.getUint32(_virtualAddressOffset, Endian.little);
|
|
set virtualAddress(int offset) =>
|
|
data.setUint32(_virtualAddressOffset, offset, Endian.little);
|
|
|
|
int get virtualSize => data.getUint32(_virtualSizeOffset, Endian.little);
|
|
set virtualSize(int offset) =>
|
|
data.setUint32(_virtualSizeOffset, offset, Endian.little);
|
|
|
|
int get fileOffset => data.getUint32(_fileOffsetOffset, Endian.little);
|
|
set fileOffset(int offset) =>
|
|
data.setUint32(_fileOffsetOffset, offset, Endian.little);
|
|
|
|
int get fileSize => data.getUint32(_fileSizeOffset, Endian.little);
|
|
set fileSize(int offset) =>
|
|
data.setUint32(_fileSizeOffset, offset, Endian.little);
|
|
|
|
int get characteristics =>
|
|
data.getUint32(_characteristicsOffset, Endian.little);
|
|
set characteristics(int value) =>
|
|
data.setUint32(_characteristicsOffset, value, Endian.little);
|
|
|
|
bool get isDiscardable => characteristics & _discardableFlag != 0;
|
|
set isDiscardable(bool value) {
|
|
if (value) {
|
|
characteristics |= _discardableFlag;
|
|
} else {
|
|
characteristics &= ~_discardableFlag;
|
|
}
|
|
}
|
|
}
|
|
|
|
class CoffSectionTable extends BytesBacked {
|
|
CoffSectionTable._(ByteData data) : super(data);
|
|
|
|
static const _entrySize = 40;
|
|
|
|
static CoffSectionTable fromTypedData(
|
|
TypedData source, int offset, int sections) {
|
|
final size = sections * _entrySize;
|
|
if (source.lengthInBytes < offset + size) {
|
|
throw 'File is truncated within the COFF section table';
|
|
}
|
|
final buffer = Uint8List(size);
|
|
buffer.setAll(0, Uint8List.sublistView(source, offset, offset + size));
|
|
return CoffSectionTable._(ByteData.sublistView(buffer));
|
|
}
|
|
|
|
Iterable<CoffSectionHeader> get entries sync* {
|
|
for (int i = 0; i < size; i += _entrySize) {
|
|
yield CoffSectionHeader._(ByteData.sublistView(data, i, i + _entrySize));
|
|
}
|
|
}
|
|
|
|
int get addressEnd => entries.fold(
|
|
0, (i, entry) => max(i, entry.virtualAddress + entry.virtualSize));
|
|
int get offsetEnd =>
|
|
entries.fold(0, (i, entry) => max(i, entry.fileOffset + entry.fileSize));
|
|
|
|
CoffSectionHeader allocateNewSectionHeader() {
|
|
final newBuffer = Uint8List(size + _entrySize);
|
|
newBuffer.setAll(0, Uint8List.sublistView(data));
|
|
data = ByteData.sublistView(newBuffer);
|
|
return CoffSectionHeader._(
|
|
ByteData.sublistView(data, size - _entrySize, size));
|
|
}
|
|
}
|
|
|
|
class CoffHeaders {
|
|
final int _coffOffset;
|
|
final CoffFileHeader fileHeader;
|
|
final CoffOptionalHeader optionalHeader;
|
|
final CoffSectionTable sectionTable;
|
|
|
|
CoffHeaders._(this._coffOffset, this.fileHeader, this.optionalHeader,
|
|
this.sectionTable);
|
|
|
|
static CoffHeaders fromTypedData(TypedData source, int offset) {
|
|
final fileHeader = CoffFileHeader.fromTypedData(source, offset);
|
|
final optionalHeader = CoffOptionalHeader.fromTypedData(
|
|
source, offset + fileHeader.size, fileHeader.optionalHeaderSize);
|
|
final sectionTable = CoffSectionTable.fromTypedData(
|
|
source,
|
|
offset + fileHeader.size + optionalHeader.size,
|
|
fileHeader.sectionCount);
|
|
return CoffHeaders._(offset, fileHeader, optionalHeader, sectionTable);
|
|
}
|
|
|
|
// Keep in sync with kSnapshotSectionName in snapshot_utils.cc.
|
|
static const _snapshotSectionName = "snapshot";
|
|
|
|
int get size => optionalHeader.headersSize;
|
|
|
|
void addSnapshotSectionHeader(int length) {
|
|
final oldHeadersSize = optionalHeader.headersSize;
|
|
final address =
|
|
align(sectionTable.addressEnd, optionalHeader.sectionAlignment);
|
|
final offset = align(sectionTable.offsetEnd, optionalHeader.fileAlignment);
|
|
|
|
// Create and fill the new section header entry.
|
|
final newHeader = sectionTable.allocateNewSectionHeader();
|
|
newHeader.name = _snapshotSectionName;
|
|
newHeader.virtualAddress = address;
|
|
newHeader.virtualSize = length;
|
|
newHeader.fileOffset = offset;
|
|
newHeader.fileSize = align(length, optionalHeader.fileAlignment);
|
|
newHeader.isDiscardable = true;
|
|
// Leave the rest of the header fields with zero values.
|
|
|
|
// Increment the number of sections in the file header.
|
|
fileHeader.sectionCount += 1;
|
|
|
|
// Adjust the header size stored in the optional header, which must be
|
|
// a multiple of fileAlignment.
|
|
optionalHeader.headersSize = align(
|
|
_coffOffset + fileHeader.size + optionalHeader.size + sectionTable.size,
|
|
optionalHeader.fileAlignment);
|
|
|
|
// If the size of the headers changed, we'll need to adjust the section
|
|
// offsets.
|
|
final headersSizeDiff = optionalHeader.headersSize - oldHeadersSize;
|
|
if (headersSizeDiff > 0) {
|
|
// Safety check that section virtual addresses need not be adjusted, as
|
|
// that requires rewriting much more of the fields and section contents.
|
|
// (Generally, the size of the headers is much smaller than the section
|
|
// alignment and so this is not expected to happen.)
|
|
if (size ~/ optionalHeader.sectionAlignment !=
|
|
oldHeadersSize ~/ optionalHeader.sectionAlignment) {
|
|
throw 'Adding the snapshot would require adjusting virtual addresses';
|
|
}
|
|
assert(headersSizeDiff % optionalHeader.fileAlignment == 0);
|
|
for (final entry in sectionTable.entries) {
|
|
entry.fileOffset += headersSizeDiff;
|
|
}
|
|
}
|
|
|
|
// Adjust the image size stored in the optional header, which must be a
|
|
// multiple of section alignment (as it is the size in memory, not on disk).
|
|
optionalHeader.imageSize = align(
|
|
newHeader.virtualAddress + newHeader.virtualSize,
|
|
optionalHeader.sectionAlignment);
|
|
}
|
|
|
|
Future<void> write(RandomAccessFile output) async {
|
|
await fileHeader.write(output);
|
|
await optionalHeader.write(output);
|
|
await sectionTable.write(output);
|
|
// Pad to the recorded headers size, which includes the MS-DOS stub.
|
|
final written = await output.position();
|
|
await output.writeFrom(Uint8List(size - written));
|
|
}
|
|
}
|
|
|
|
class PortableExecutable {
|
|
final Uint8List source;
|
|
final CoffHeaders headers;
|
|
final int sourceFileHeaderOffset;
|
|
final int sourceSectionContentsOffset;
|
|
|
|
PortableExecutable._(this.source, this.headers, this.sourceFileHeaderOffset,
|
|
this.sourceSectionContentsOffset);
|
|
|
|
static const _expectedPESignature = <int>[80, 69, 0, 0];
|
|
static const _offsetForPEOffset = 0x3c;
|
|
|
|
static Future<PortableExecutable> fromFile(File file) async {
|
|
final source = await file.readAsBytes();
|
|
final byteData = ByteData.sublistView(source);
|
|
final peOffset = byteData.getUint32(_offsetForPEOffset, Endian.little);
|
|
for (int i = 0; i < _expectedPESignature.length; i++) {
|
|
if (byteData.getUint8(peOffset + i) != _expectedPESignature[i]) {
|
|
throw 'Not a Portable Executable file';
|
|
}
|
|
}
|
|
final fileHeaderOffset = peOffset + _expectedPESignature.length;
|
|
final headers = CoffHeaders.fromTypedData(source, fileHeaderOffset);
|
|
final sectionContentsOffset = headers.size;
|
|
return PortableExecutable._(
|
|
source, headers, fileHeaderOffset, sectionContentsOffset);
|
|
}
|
|
|
|
Future<void> _fileAlignSectionEnd(RandomAccessFile output) async {
|
|
final current = await output.position();
|
|
final padding =
|
|
align(current, headers.optionalHeader.fileAlignment) - current;
|
|
await output.writeFrom(Uint8List(padding));
|
|
}
|
|
|
|
Future<void> appendSnapshotAndWrite(File output, File snapshot) async {
|
|
final stream = await output.open(mode: FileMode.write);
|
|
// Write MS-DOS stub.
|
|
await stream.writeFrom(source, 0, sourceFileHeaderOffset);
|
|
// Write headers with additional snapshot section.
|
|
final snapshotBytes = await snapshot.readAsBytes();
|
|
headers.addSnapshotSectionHeader(snapshotBytes.length);
|
|
await headers.write(stream);
|
|
// Write original section contents with alignment padding.
|
|
await stream.writeFrom(source, sourceSectionContentsOffset);
|
|
await _fileAlignSectionEnd(stream);
|
|
// Write snapshot with alignment padding.
|
|
await stream.writeFrom(snapshotBytes);
|
|
await _fileAlignSectionEnd(stream);
|
|
await stream.close();
|
|
}
|
|
}
|
|
|
|
// Writes an "appended" dart runtime + script snapshot file in a format
|
|
// compatible with Portable Executable files.
|
|
Future writeAppendedPortableExecutable(
|
|
String dartaotruntimePath, String payloadPath, String outputPath) async {
|
|
File originalExecutableFile = File(dartaotruntimePath);
|
|
File newSegmentFile = File(payloadPath);
|
|
File outputFile = File(outputPath);
|
|
|
|
final pe = await PortableExecutable.fromFile(originalExecutableFile);
|
|
await pe.appendSnapshotAndWrite(outputFile, newSegmentFile);
|
|
}
|