[dart2native] Allow for signing of Windows standalone executables.

Instead of simply concatenating the dartaotruntime executable and the
ELF snapshot, we instead insert the ELF snapshot as a COFF section,
so the resulting PE file can be signed.

TEST=pkg/dartdev/test/commands/compile_test

Bug: https://github.com/dart-lang/sdk/issues/39106
Change-Id: Iaafb5833fcb9c69c0dd374b64769f22b8b9f53a2
Cq-Include-Trybots: luci.dart.try:pkg-win-release-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/236100
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Tess Strickland
2022-03-24 10:08:01 +00:00
committed by Commit Bot
parent e773e6e713
commit cdb703859a
5 changed files with 556 additions and 8 deletions
+5 -2
View File
@@ -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 @@ Future writeAppendedExecutable(
if (Platform.isMacOS) {
return await writeAppendedMachOExecutable(
dartaotruntimePath, payloadPath, outputPath);
} else if (Platform.isWindows) {
return await writeAppendedPortableExecutable(
dartaotruntimePath, payloadPath, outputPath);
}
final dartaotruntime = File(dartaotruntimePath);
+329
View File
@@ -0,0 +1,329 @@
// 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 oldSize = size;
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 sizes stored in the optional header.
optionalHeader.headersSize = align(
_coffOffset + fileHeader.size + optionalHeader.size + sectionTable.size,
optionalHeader.fileAlignment);
optionalHeader.imageSize += align(length, optionalHeader.fileAlignment);
// If the size of the headers changed, we'll need to adjust the section
// offsets and image size accordingly.
final headersSizeDiff = size - oldSize;
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 !=
oldSize ~/ 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;
}
optionalHeader.imageSize += headersSizeDiff;
}
}
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);
}
+149 -6
View File
@@ -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 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
// 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 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
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 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
}
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(&section, sizeof(section));
file->ReadFully(&section, sizeof(section));
if (segment.cmd == mach_o::LC_SEGMENT_64 &&
strcmp(section.segname, kMachOAppSnapshotSegmentName) == 0 &&
@@ -323,7 +326,7 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
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 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
}
#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(&section_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 @@ bool Snapshot::IsMachOFormattedBinary(const char* filename) {
}
#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) {
+7
View File
@@ -41,6 +41,9 @@ class Snapshot {
#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 @@ class Snapshot {
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);
+66
View File
@@ -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_