Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Bundler version 2 #33413

Merged
merged 6 commits into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/installer/corehost/cli/apphost/bundle/file_entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ namespace bundle
file_entry_t()
: m_offset(0)
, m_size(0)
, m_type(bundle::file_type_t::__last)
, m_type(file_type_t::__last)
, m_relative_path()
{
}
Expand Down
5 changes: 3 additions & 2 deletions src/installer/corehost/cli/apphost/bundle/file_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ namespace bundle

enum file_type_t : uint8_t
{
unknown,
assembly,
ready2run,
native_binary,
deps_json,
runtime_config_json,
extract,
symbols,
__last
};
}
Expand Down
21 changes: 16 additions & 5 deletions src/installer/corehost/cli/apphost/bundle/header.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,24 @@

using namespace bundle;

bool header_fixed_t::is_valid() const
bool header_fixed_t::is_valid(bool exact_match) const
{
// The AppHost expects the bundle_header to be an exact_match for which it was built.
// The framework accepts backwards compatible header versions.

return num_embedded_files > 0 &&
((major_version < header_t::major_version) ||
(major_version == header_t::major_version && minor_version <= header_t::minor_version));
(exact_match) ?
(major_version == header_t::major_version) && (minor_version == header_t::minor_version) :
((major_version < header_t::major_version) ||
(major_version == header_t::major_version && minor_version <= header_t::minor_version));

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write this in multiple if statements with an early return style? This is starting to get difficult to read.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, please check if the version in the header is either 1 or 2 before any other test, and return false otherwise. Just a sanity check before making any assumptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've rewritten the check using if() {} else {} blocks.

Also, please check if the version in the header is either 1 or 2 before any other test

I think this check is implicit, because major_version is a uint, that's bounded by header.major_version -- the constant 2.


header_t header_t::read(reader_t& reader)
header_t header_t::read(reader_t& reader, bool need_exact_version)
{
const header_fixed_t* fixed_header = reinterpret_cast<const header_fixed_t*>(reader.read_direct(sizeof(header_fixed_t)));

if (!fixed_header->is_valid())
if (!fixed_header->is_valid(need_exact_version))
{
trace::error(_X("Failure processing application bundle."));
trace::error(_X("Bundle header version compatibility check failed."));
Expand All @@ -33,5 +39,10 @@ header_t header_t::read(reader_t& reader)
// bundle_id is a component of the extraction path
reader.read_path_string(header.m_bundle_id);

if (fixed_header->major_version > 1)
{
header.m_v2_header = reinterpret_cast<const header_fixed_v2_t*>(reader.read_direct(sizeof(header_fixed_v2_t)));
}

return header;
}
50 changes: 45 additions & 5 deletions src/installer/corehost/cli/apphost/bundle/header.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@

namespace bundle
{
// The Bundle Header contains:
// The Bundle Header (v1)
// Fixed size thunk (header_fixed_t)
// - Major Version
// - Minor Version
// - Number of embedded files
// Variable size portion:
// - Bundle ID (7-bit extension encoded length prefixed string)
// The Bundle Header (v2) [additional content]
// Fixed size thunk (header_fixed_v2_t)
// - DepsJson Location (Offset, Size)
// - RuntimeConfig Location (Offset, Size)
// - Flags

#pragma pack(push, 1)
struct header_fixed_t
Expand All @@ -27,7 +32,39 @@ namespace bundle
uint32_t minor_version;
int32_t num_embedded_files;

bool is_valid() const;
bool is_valid(bool exact_match = false) const;
};
#pragma pack(pop)

// netcoreapp3_compat_mode flag is set on a .net5 app, which chooses to build single-file apps in .netcore3.x compat mode,
// This indicates that:
// All published files are bundled into the app; some of them will be extracted to disk.
// AppContext.BaseDirectory is set to the extraction directory (and not the AppHost directory).
enum header_flags_t : uint64_t
{
none = 0,
netcoreapp3_compat_mode = 1
};

#pragma pack(push, 1)
struct location_t
{
public:
int64_t offset;
int64_t size;
};

// header_fixed_v2_t is available in single-file apps targetting .net5+ frameworks.
// It stores information that facilitates the host to process contents of the bundle without extraction.
//
// The location of deps.json and runtimeconfig.json is already available in the Bundle manifest.
// However, the data is cached here in order to simplify the bundle-processing performed by hostfxr.
struct header_fixed_v2_t
{
public:
location_t deps_json_location;
location_t runtimeconfig_json_location;
header_flags_t flags;
};
#pragma pack(pop)

Expand All @@ -37,19 +74,22 @@ namespace bundle
header_t(int32_t num_embedded_files = 0)
: m_num_embedded_files(num_embedded_files)
, m_bundle_id()
, m_v2_header(NULL)

{
}

static header_t read(reader_t& reader);
static header_t read(reader_t& reader, bool need_exact_version);
const pal::string_t& bundle_id() { return m_bundle_id; }
int32_t num_embedded_files() { return m_num_embedded_files; }
int32_t num_embedded_files() { return m_num_embedded_files; }

static const uint32_t major_version = 1;
static const uint32_t major_version = 2;
static const uint32_t minor_version = 0;

private:
int32_t m_num_embedded_files;
pal::string_t m_bundle_id;
const header_fixed_v2_t* m_v2_header;

};
}
Expand Down
2 changes: 1 addition & 1 deletion src/installer/corehost/cli/apphost/bundle/runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ StatusCode runner_t::extract()

// Read the bundle header
reader.set_offset(marker_t::header_offset());
header_t header = header_t::read(reader);
header_t header = header_t::read(reader, /* need_exact_version: */ true);

// Read the bundle manifest
// Reader is at the correct offset
Expand Down
169 changes: 11 additions & 158 deletions src/installer/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Text;
using System.Runtime.InteropServices;

namespace Microsoft.NET.HostModel.AppHost
{
Expand Down Expand Up @@ -164,160 +163,6 @@ private static unsafe int KMPSearch(byte[] pattern, byte* bytes, long bytesLengt
return -1;
}

/// <summary>
/// The first two bytes of a PE file are a constant signature.
/// </summary>
private const UInt16 PEFileSignature = 0x5A4D;

/// <summary>
/// The offset of the PE header pointer in the DOS header.
/// </summary>
private const int PEHeaderPointerOffset = 0x3C;

/// <summary>
/// The offset of the Subsystem field in the PE header.
/// </summary>
private const int SubsystemOffset = 0x5C;

/// <summary>
/// The value of the sybsystem field which indicates Windows GUI (Graphical UI)
/// </summary>
private const UInt16 WindowsGUISubsystem = 0x2;

/// <summary>
/// The value of the subsystem field which indicates Windows CUI (Console)
/// </summary>
private const UInt16 WindowsCUISubsystem = 0x3;

/// <summary>
/// Check whether the apphost file is a windows PE image by looking at the first few bytes.
/// </summary>
/// <param name="accessor">The memory accessor which has the apphost file opened.</param>
/// <returns>true if the accessor represents a PE image, false otherwise.</returns>
internal static unsafe bool IsPEImage(MemoryMappedViewAccessor accessor)
{
byte* pointer = null;

try
{
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
byte* bytes = pointer + accessor.PointerOffset;

// https://en.wikipedia.org/wiki/Portable_Executable
// Validate that we're looking at Windows PE file
if (((UInt16*)bytes)[0] != PEFileSignature || accessor.Capacity < PEHeaderPointerOffset + sizeof(UInt32))
{
return false;
}
return true;
}
finally
{
if (pointer != null)
{
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}

/// <summary>
/// This method will attempt to set the subsystem to GUI. The apphost file should be a windows PE file.
/// </summary>
/// <param name="accessor">The memory accessor which has the apphost file opened.</param>
internal static unsafe void SetWindowsGraphicalUserInterfaceBit(MemoryMappedViewAccessor accessor)
{
byte* pointer = null;

try
{
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
byte* bytes = pointer + accessor.PointerOffset;

// https://en.wikipedia.org/wiki/Portable_Executable
UInt32 peHeaderOffset = ((UInt32*)(bytes + PEHeaderPointerOffset))[0];

if (accessor.Capacity < peHeaderOffset + SubsystemOffset + sizeof(UInt16))
{
throw new AppHostNotPEFileException();
}

UInt16* subsystem = ((UInt16*)(bytes + peHeaderOffset + SubsystemOffset));

// https://docs.microsoft.com/en-us/windows/desktop/Debug/pe-format#windows-subsystem
// The subsystem of the prebuilt apphost should be set to CUI
if (subsystem[0] != WindowsCUISubsystem)
{
throw new AppHostNotCUIException();
}

// Set the subsystem to GUI
subsystem[0] = WindowsGUISubsystem;
}
finally
{
if (pointer != null)
{
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}

public static unsafe void SetWindowsGraphicalUserInterfaceBit(string filePath)
{
using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath))
{
using (var accessor = mappedFile.CreateViewAccessor())
{
SetWindowsGraphicalUserInterfaceBit(accessor);
}
}
}

/// <summary>
/// This method will return the subsystem CUI/GUI value. The apphost file should be a windows PE file.
/// </summary>
/// <param name="accessor">The memory accessor which has the apphost file opened.</param>
internal static unsafe UInt16 GetWindowsGraphicalUserInterfaceBit(MemoryMappedViewAccessor accessor)
{
byte* pointer = null;

try
{
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
byte* bytes = pointer + accessor.PointerOffset;

// https://en.wikipedia.org/wiki/Portable_Executable
UInt32 peHeaderOffset = ((UInt32*)(bytes + PEHeaderPointerOffset))[0];

if (accessor.Capacity < peHeaderOffset + SubsystemOffset + sizeof(UInt16))
{
throw new AppHostNotPEFileException();
}

UInt16* subsystem = ((UInt16*)(bytes + peHeaderOffset + SubsystemOffset));

return subsystem[0];
}
finally
{
if (pointer != null)
{
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}

public static unsafe UInt16 GetWindowsGraphicalUserInterfaceBit(string filePath)
{
using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath))
{
using (var accessor = mappedFile.CreateViewAccessor())
{
return GetWindowsGraphicalUserInterfaceBit(accessor);
}
}
}

public static void CopyFile(string sourcePath, string destinationPath)
{
var destinationDirectory = new FileInfo(destinationPath).Directory.FullName;
Expand All @@ -330,5 +175,13 @@ public static void CopyFile(string sourcePath, string destinationPath)
File.Copy(sourcePath, destinationPath, overwrite: true);
}

// The SDK calls into BinaryUtils.GetWindowsGraphicalUserInterfaceBit()
// Keep this method until the HostModel changes reach SDK repo, and the code there
// is updated to use PEUtils.GetWindowsGraphicalUserInterfaceBit()
public static unsafe UInt16 GetWindowsGraphicalUserInterfaceBit(string filePath)
{
return PEUtils.GetWindowsGraphicalUserInterfaceBit(filePath);
}

}
}
Loading