Version 2.17.0-235.0.dev
Merge commit 'da691ea9c1383dcbccfa75440112f3ae9e875ed0' into 'dev'
diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart
index 8b6b46e..d89c33d 100644
--- a/pkg/dart2native/lib/dart2native.dart
+++ b/pkg/dart2native/lib/dart2native.dart
@@ -5,8 +5,8 @@
import 'dart:io';
import 'dart:typed_data';
-import 'package:dart2native/dart2native_macho.dart'
- show writeAppendedMachOExecutable;
+import 'dart2native_macho.dart' show writeAppendedMachOExecutable;
+import 'dart2native_pe.dart' show writeAppendedPortableExecutable;
// Maximum page size across all supported architectures (arm64 macOS has 16K
// pages, some arm64 Linux distributions have 64K pages).
@@ -20,6 +20,9 @@
if (Platform.isMacOS) {
return await writeAppendedMachOExecutable(
dartaotruntimePath, payloadPath, outputPath);
+ } else if (Platform.isWindows) {
+ return await writeAppendedPortableExecutable(
+ dartaotruntimePath, payloadPath, outputPath);
}
final dartaotruntime = File(dartaotruntimePath);
diff --git a/pkg/dart2native/lib/dart2native_pe.dart b/pkg/dart2native/lib/dart2native_pe.dart
new file mode 100644
index 0000000..144112f
--- /dev/null
+++ b/pkg/dart2native/lib/dart2native_pe.dart
@@ -0,0 +1,334 @@
+// 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);
+}
diff --git a/pkg/dart2wasm/dart2wasm.md b/pkg/dart2wasm/dart2wasm.md
index 8dc3203..bfc2d47 100644
--- a/pkg/dart2wasm/dart2wasm.md
+++ b/pkg/dart2wasm/dart2wasm.md
@@ -16,12 +16,12 @@
| `--`[`no-`]`lazy-constants` | no | Instantiate constants lazily.
| `--`[`no-`]`local-nullability` | no | Use non-nullable types for non-nullable locals and temporaries.
| `--`[`no-`]`name-section` | yes | Emit Name Section with function names.
-| `--`[`no-`]`nominal-types` | no | Emit experimental nominal types.
+| `--`[`no-`]`nominal-types` | yes | Emit nominal types.
| `--`[`no-`]`parameter-nullability` | yes | Use non-nullable types for non-nullable parameters and return values.
| `--`[`no-`]`polymorphic-specialization` | no | Do virtual calls by switching on the class ID instead of using `call_indirect`.
| `--`[`no-`]`print-kernel` | no | Print IR for each function before compiling it.
| `--`[`no-`]`print-wasm` | no | Print Wasm instructions of each compiled function.
-| `--`[`no-`]`runtime-types` | yes | Use RTTs for allocations and casts.
+| `--`[`no-`]`runtime-types` | no | Use RTTs for allocations and casts.
| `--`[`no-`]`string-data-segments` | no | Use experimental array init from data segment for string constants.
| `--watch` *offset* | | Print stack trace leading to the byte at offset *offset* in the `.wasm` output file. Can be specified multiple times.
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 8fdc1b8..3bf91b4 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -32,12 +32,12 @@
bool lazyConstants = false;
bool localNullability = false;
bool nameSection = true;
- bool nominalTypes = false;
+ bool nominalTypes = true;
bool parameterNullability = true;
bool polymorphicSpecialization = false;
bool printKernel = false;
bool printWasm = false;
- bool runtimeTypes = true;
+ bool runtimeTypes = false;
bool stringDataSegments = false;
List<int>? watchPoints = null;
diff --git a/runtime/bin/snapshot_utils.cc b/runtime/bin/snapshot_utils.cc
index 243fac3..fd0d4dc 100644
--- a/runtime/bin/snapshot_utils.cc
+++ b/runtime/bin/snapshot_utils.cc
@@ -16,6 +16,9 @@
#if defined(DART_TARGET_OS_MACOS)
#include <platform/mach_o.h>
#endif
+#if defined(DART_TARGET_OS_WINDOWS)
+#include <platform/pe.h>
+#endif
#include "platform/utils.h"
#define LOG_SECTION_BOUNDARIES false
@@ -260,7 +263,7 @@
// Parse the first 4bytes and extract the magic number.
uint32_t magic;
file->SetPosition(0);
- file->Read(&magic, sizeof(uint32_t));
+ file->ReadFully(&magic, sizeof(uint32_t));
const bool is64Bit =
magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64;
@@ -289,11 +292,11 @@
return nullptr;
} else {
mach_o::mach_header_64 header;
- file->Read(&header, sizeof(header));
+ file->ReadFully(&header, sizeof(header));
for (uint32_t i = 0; i < header.ncmds; ++i) {
mach_o::load_command command;
- file->Read(&command, sizeof(mach_o::load_command));
+ file->ReadFully(&command, sizeof(mach_o::load_command));
file->SetPosition(file->Position() - sizeof(command));
if (command.cmd != mach_o::LC_SEGMENT &&
@@ -303,11 +306,11 @@
}
mach_o::segment_command_64 segment;
- file->Read(&segment, sizeof(segment));
+ file->ReadFully(&segment, sizeof(segment));
for (uint32_t j = 0; j < segment.nsects; ++j) {
mach_o::section_64 section;
- file->Read(§ion, sizeof(section));
+ file->ReadFully(§ion, sizeof(section));
if (segment.cmd == mach_o::LC_SEGMENT_64 &&
strcmp(section.segname, kMachOAppSnapshotSegmentName) == 0 &&
@@ -323,7 +326,7 @@
std::unique_ptr<uint8_t[]> snapshot(new uint8_t[section.size]);
file->SetPosition(section.offset);
- file->Read(snapshot.get(), sizeof(uint8_t) * section.size);
+ file->ReadFully(snapshot.get(), sizeof(uint8_t) * section.size);
Dart_LoadedElf* handle = Dart_LoadELF_Memory(
snapshot.get(), section.size, &error, &vm_data_buffer,
@@ -347,12 +350,101 @@
}
#endif // defined(DART_TARGET_OS_MACOS)
+#if defined(DART_TARGET_OS_WINDOWS)
+// Keep in sync with CoffSectionTable._snapshotSectionName from
+// pkg/dart2native/lib/dart2native_pe.dart.
+static const char kSnapshotSectionName[] = "snapshot";
+// Ignore the null terminator, as it won't be present if the string length is
+// exactly pe::kCoffSectionNameSize.
+static_assert(sizeof(kSnapshotSectionName) - 1 <= pe::kCoffSectionNameSize,
+ "Section name of snapshot too large");
+
+AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromPE(
+ const char* container_path) {
+ File* const file = File::Open(NULL, container_path, File::kRead);
+ if (file == nullptr) {
+ return nullptr;
+ }
+ RefCntReleaseScope<File> rs(file);
+
+ // Ensure file is actually PE-formatted.
+ if (!IsPEFormattedBinary(container_path)) {
+ Syslog::PrintErr(
+ "Attempted load target was not formatted as expected: "
+ "expected PE32 or PE32+ image file.\n");
+ return nullptr;
+ }
+
+ // Parse the offset into the PE contents (i.e., skipping the MS-DOS stub).
+ uint32_t pe_offset;
+ file->SetPosition(pe::kPEOffsetOffset);
+ file->ReadFully(&pe_offset, sizeof(pe_offset));
+
+ // Skip past the magic bytes to the COFF file header and COFF optional header.
+ const intptr_t coff_offset = pe_offset + sizeof(pe::kPEMagic);
+ file->SetPosition(coff_offset);
+ pe::coff_file_header file_header;
+ file->ReadFully(&file_header, sizeof(file_header));
+ // The optional header follows directly after the file header.
+ pe::coff_optional_header opt_header;
+ file->ReadFully(&opt_header, sizeof(opt_header));
+
+ // Skip to the section table.
+ const intptr_t coff_symbol_table_offset =
+ coff_offset + sizeof(file_header) + file_header.optional_header_size;
+ file->SetPosition(coff_symbol_table_offset);
+ for (intptr_t i = 0; i < file_header.num_sections; i++) {
+ pe::coff_section_header section_header;
+ file->ReadFully(§ion_header, sizeof(section_header));
+ if (strncmp(section_header.name, kSnapshotSectionName,
+ pe::kCoffSectionNameSize) == 0) {
+ // We have to do the loading manually even though currently the snapshot
+ // data is at the end of the file because the file alignment for
+ // PE sections can be less than the page size, and TryReadAppSnapshotElf
+ // won't work if the file offset isn't page-aligned.
+ const char* error = nullptr;
+ const uint8_t* vm_data_buffer = nullptr;
+ const uint8_t* vm_instructions_buffer = nullptr;
+ const uint8_t* isolate_data_buffer = nullptr;
+ const uint8_t* isolate_instructions_buffer = nullptr;
+
+ const intptr_t offset = section_header.file_offset;
+ const intptr_t size = section_header.file_size;
+
+ std::unique_ptr<uint8_t[]> snapshot(new uint8_t[size]);
+ file->SetPosition(offset);
+ file->ReadFully(snapshot.get(), sizeof(uint8_t) * size);
+
+ Dart_LoadedElf* const handle =
+ Dart_LoadELF_Memory(snapshot.get(), size, &error, &vm_data_buffer,
+ &vm_instructions_buffer, &isolate_data_buffer,
+ &isolate_instructions_buffer);
+
+ if (handle == nullptr) {
+ Syslog::PrintErr("Loading failed: %s\n", error);
+ return nullptr;
+ }
+
+ return new ElfAppSnapshot(handle, vm_data_buffer, vm_instructions_buffer,
+ isolate_data_buffer,
+ isolate_instructions_buffer);
+ }
+ }
+
+ return nullptr;
+}
+#endif // defined(DART_TARGET_OS_WINDOWS)
+
AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElf(
const char* container_path) {
#if defined(DART_TARGET_OS_MACOS)
if (IsMachOFormattedBinary(container_path)) {
return TryReadAppendedAppSnapshotElfFromMachO(container_path);
}
+#elif defined(DART_TARGET_OS_WINDOWS)
+ if (IsPEFormattedBinary(container_path)) {
+ return TryReadAppendedAppSnapshotElfFromPE(container_path);
+ }
#endif
File* file = File::Open(NULL, container_path, File::kRead);
@@ -472,6 +564,57 @@
}
#endif // defined(DART_TARGET_OS_MACOS)
+#if defined(DART_TARGET_OS_WINDOWS)
+bool Snapshot::IsPEFormattedBinary(const char* filename) {
+ File* file = File::Open(NULL, filename, File::kRead);
+ if (file == nullptr) {
+ return false;
+ }
+ RefCntReleaseScope<File> rs(file);
+
+ // Parse the PE offset.
+ uint32_t pe_offset;
+ // Ensure the file is long enough to contain the PE offset.
+ if (file->Length() <
+ static_cast<intptr_t>(pe::kPEOffsetOffset + sizeof(pe_offset))) {
+ return false;
+ }
+ file->SetPosition(pe::kPEOffsetOffset);
+ file->Read(&pe_offset, sizeof(pe_offset));
+
+ // Ensure the file is long enough to contain the PE magic bytes.
+ if (file->Length() <
+ static_cast<intptr_t>(pe_offset + sizeof(pe::kPEMagic))) {
+ return false;
+ }
+ // Check the magic bytes.
+ file->SetPosition(pe_offset);
+ for (size_t i = 0; i < sizeof(pe::kPEMagic); i++) {
+ char c;
+ file->Read(&c, sizeof(c));
+ if (c != pe::kPEMagic[i]) {
+ return false;
+ }
+ }
+
+ // Check that there is a coff optional header.
+ pe::coff_file_header file_header;
+ pe::coff_optional_header opt_header;
+ file->Read(&file_header, sizeof(file_header));
+ if (file_header.optional_header_size < sizeof(opt_header)) {
+ return false;
+ }
+ file->Read(&opt_header, sizeof(opt_header));
+ // Check the magic bytes in the coff optional header.
+ if (opt_header.magic != pe::kPE32Magic &&
+ opt_header.magic != pe::kPE32PlusMagic) {
+ return false;
+ }
+
+ return true;
+}
+#endif // defined(DART_TARGET_OS_WINDOWS)
+
AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri,
bool force_load_elf_from_memory,
bool decode_uri) {
diff --git a/runtime/bin/snapshot_utils.h b/runtime/bin/snapshot_utils.h
index f26f7b2..4359d66 100644
--- a/runtime/bin/snapshot_utils.h
+++ b/runtime/bin/snapshot_utils.h
@@ -41,6 +41,9 @@
#if defined(DART_TARGET_OS_MACOS)
static bool IsMachOFormattedBinary(const char* container_path);
#endif
+#if defined(DART_TARGET_OS_WINDOWS)
+ static bool IsPEFormattedBinary(const char* container_path);
+#endif
static AppSnapshot* TryReadAppendedAppSnapshotElf(const char* container_path);
static AppSnapshot* TryReadAppSnapshot(
@@ -62,6 +65,10 @@
static AppSnapshot* TryReadAppendedAppSnapshotElfFromMachO(
const char* container_path);
#endif
+#if defined(DART_TARGET_OS_WINDOWS)
+ static AppSnapshot* TryReadAppendedAppSnapshotElfFromPE(
+ const char* container_path);
+#endif
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(Snapshot);
diff --git a/runtime/platform/pe.h b/runtime/platform/pe.h
new file mode 100644
index 0000000..952e856
--- /dev/null
+++ b/runtime/platform/pe.h
@@ -0,0 +1,66 @@
+// 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.
+
+#ifndef RUNTIME_PLATFORM_PE_H_
+#define RUNTIME_PLATFORM_PE_H_
+
+#include <platform/globals.h>
+
+namespace dart {
+
+namespace pe {
+
+#pragma pack(push, 1)
+
+static const intptr_t kPEOffsetOffset = 0x3c;
+static const char kPEMagic[] = {'P', 'E', '\0', '\0'};
+
+struct coff_file_header {
+ uint16_t machine;
+ uint16_t num_sections;
+ uint32_t timestamp;
+ uint32_t symbol_table_offset;
+ uint32_t num_symbols;
+ uint16_t optional_header_size;
+ uint16_t characteristics;
+};
+
+// Does not include the BaseOfData field for PE32 (not PE32+) files, but we
+// don't need that to load a snapshot out of a PE file.
+struct coff_optional_header {
+ uint16_t magic;
+ uint8_t linker_major_version;
+ uint8_t linker_minor_version;
+ uint32_t code_size;
+ uint32_t initialized_data_size;
+ uint32_t uninitialized_data_size;
+ uint32_t entry_point_address;
+ uint32_t code_base_address;
+};
+
+static const uint16_t kPE32Magic = 0x10b;
+static const uint16_t kPE32PlusMagic = 0x20b;
+
+static const intptr_t kCoffSectionNameSize = 8;
+
+struct coff_section_header {
+ char name[kCoffSectionNameSize];
+ uint32_t virtual_size;
+ uint32_t virtual_address;
+ uint32_t file_size;
+ uint32_t file_offset;
+ uint32_t relocations_offset;
+ uint32_t line_numbers_offset;
+ uint16_t num_relocations;
+ uint16_t num_line_numbers;
+ uint32_t characteristics;
+};
+
+#pragma pack(pop)
+
+} // namespace pe
+
+} // namespace dart
+
+#endif // RUNTIME_PLATFORM_PE_H_
diff --git a/tools/VERSION b/tools/VERSION
index 5843869..006ee29 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 234
+PRERELEASE 235
PRERELEASE_PATCH 0
\ No newline at end of file