Skip to content

Commit

Permalink
Single-File: Run from Bundle
Browse files Browse the repository at this point in the history
This change implements:

* Runtime changes necessary to load assemblies directly from the bundle:
    * Design notes about [Load from Bundle](https://github.com/dotnet/designs/blob/master/accepted/2020/single-file/design.md#peimage-loader)
    * Most of these changes are directly from dotnet/coreclr#26504 and dotnet/coreclr#26904

* Hostpolicy change to not add bundled assemblies to TPA list:
    * Design notes about [Dependency Resolution](https://github.com/dotnet/designs/blob/master/accepted/2020/single-file/design.md#dependency-resolution)
    * TBD (separately) items: Fix for hammer servicing dotnet#36031

Fixes dotnet#32822
  • Loading branch information
swaroop-sridhar committed May 18, 2020
1 parent bacef40 commit 0541e4b
Show file tree
Hide file tree
Showing 37 changed files with 558 additions and 193 deletions.
114 changes: 89 additions & 25 deletions src/coreclr/src/binder/assemblybinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

#include "assemblybinder.hpp"
#include "assemblyname.hpp"

#include "assembly.hpp"
#include "applicationcontext.hpp"
#include "bindertracing.h"
Expand Down Expand Up @@ -380,23 +379,29 @@ namespace BINDER_SPACE

_ASSERTE(ppSystemAssembly != NULL);

StackSString sCoreLibDir(systemDirectory);
ReleaseHolder<Assembly> pSystemAssembly;

StackSString sCoreLibDir(systemDirectory);
if (!sCoreLibDir.EndsWith(DIRECTORY_SEPARATOR_CHAR_W))
{
sCoreLibDir.Append(DIRECTORY_SEPARATOR_CHAR_W);
}

StackSString sCoreLib;
StackSString sCoreLib(sCoreLibDir);
StackSString coreLibName(CoreLibName_IL_W);
sCoreLib.Append(coreLibName);

// At run-time, System.Private.CoreLib.dll is expected to be the NI image.
sCoreLib = sCoreLibDir;
sCoreLib.Append(CoreLibName_IL_W);
// System.Private.CoreLib.dll is expected to be found at one of the following locations:
// * Non-single-file app: In systemDirectory, beside coreclr.dll
// * Framework-dependent single-file app: In system directory, beside coreclr.dll
// * Self-contained single-file app: Within the single-file bundle.
IF_FAIL_GO(AssemblyBinder::GetAssembly(sCoreLib,
TRUE /* fIsInGAC */,
fBindToNativeImage,
&pSystemAssembly));
TRUE /* fIsInGAC */,
fBindToNativeImage,
&pSystemAssembly,
NULL /* szMDAssemblyPath */,
Bundle::ProbeAppBundle(coreLibName, /*pathIsBundleRelative */ true)));

*ppSystemAssembly = pSystemAssembly.Extract();

Expand All @@ -418,25 +423,32 @@ namespace BINDER_SPACE

_ASSERTE(ppSystemAssembly != NULL);

StackSString sMscorlibSatellite(systemDirectory);
ReleaseHolder<Assembly> pSystemAssembly;
// Satellite assembly's relative path
StackSString relativePath;

// append culture name
if (!cultureName.IsEmpty())
{
CombinePath(sMscorlibSatellite, cultureName, sMscorlibSatellite);
CombinePath(relativePath, cultureName, relativePath);
}

// append satellite assembly's simple name
CombinePath(sMscorlibSatellite, simpleName, sMscorlibSatellite);
CombinePath(relativePath, simpleName, relativePath);

// append extension
sMscorlibSatellite.Append(W(".dll"));
relativePath.Append(W(".dll"));

// Satellite assembly's absolute path
StackSString sMscorlibSatellite(systemDirectory);
CombinePath(sMscorlibSatellite, relativePath, sMscorlibSatellite);

ReleaseHolder<Assembly> pSystemAssembly;
IF_FAIL_GO(AssemblyBinder::GetAssembly(sMscorlibSatellite,
TRUE /* fIsInGAC */,
FALSE /* fExplicitBindToNativeImage */,
&pSystemAssembly));
&pSystemAssembly,
NULL /* szMDAssemblyPath */,
Bundle::ProbeAppBundle(relativePath, /*pathIsBundleRelative */ true)));

*ppSystemAssembly = pSystemAssembly.Extract();

Expand Down Expand Up @@ -553,7 +565,9 @@ namespace BINDER_SPACE
// specified. Generally only NGEN PDB generation has
// this TRUE.
fExplicitBindToNativeImage,
&pAssembly));
&pAssembly,
NULL /* szMDAssemblyPath */,
Bundle::ProbeAppBundle(assemblyPath)));

AssemblyName *pAssemblyName;
pAssemblyName = pAssembly->GetAssemblyName();
Expand Down Expand Up @@ -850,6 +864,13 @@ namespace BINDER_SPACE

/*
* BindByTpaList is the entry-point for the custom binding algorithm in CoreCLR.
*
* The search for assemblies will proceed in the following order:
*
* If this application is a single-file bundle, the meta-data contained in the bundle
* will be probed to find the requested assembly. If the assembly is not found,
* The list of platform assemblies (TPAs) are considered next.
*
* Platform assemblies are specified as a list of files. This list is the only set of
* assemblies that we will load as platform. They can be specified as IL or NIs.
*
Expand Down Expand Up @@ -903,11 +924,53 @@ namespace BINDER_SPACE
}
else
{
ReleaseHolder<Assembly> pTPAAssembly;
SString& simpleName = pRequestedAssemblyName->GetSimpleName();

// Is assembly in the bundle?
// Single-file bundle contents take precedence over TPA.
// The list of bundled assemblies is contained in the bundle manifest, and NOT in the TPA.
// Therefore the bundle is first probed using the assembly's simple name.
// If found, the assembly is loaded from the bundle.
if (Bundle::AppIsBundle())
{
SString candidates[] = { W(".dll"), W(".ni.dll") };

// Loop through the binding paths looking for a matching assembly
for (int i = 0; i < 2; i++)
{
SString assemblyFileName(simpleName);
assemblyFileName.Append(candidates[i]);

SString assemblyFilePath(Bundle::AppBundle->BasePath());
assemblyFilePath.Append(assemblyFileName);

hr = GetAssembly(assemblyFilePath,
TRUE, // fIsInGAC
FALSE, // fExplicitBindToNativeImage
&pTPAAssembly,
NULL, // szMDAssemblyPath
Bundle::ProbeAppBundle(assemblyFileName, /* pathIsBundleRelative */ true));

if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
// Any other error is fatal
IF_FAIL_GO(hr);

if (TestCandidateRefMatchesDef(pRequestedAssemblyName, pTPAAssembly->GetAssemblyName(), true /*tpaListAssembly*/))
{
// We have found the requested assembly match in the bundle with validation of the full-qualified name.
// Bind to it.
pBindResult->SetResult(pTPAAssembly);
GO_WITH_HRESULT(S_OK);
}
}
}
}

// Is assembly on TPA list?
SString &simpleName = pRequestedAssemblyName->GetSimpleName();
SimpleNameToFileNameMap * tpaMap = pApplicationContext->GetTpaList();
const SimpleNameToFileNameMapEntry *pTpaEntry = tpaMap->LookupPtr(simpleName.GetUnicode());
ReleaseHolder<Assembly> pTPAAssembly;
if (pTpaEntry != nullptr)
{
if (pTpaEntry->m_wszNIFileName != nullptr)
Expand Down Expand Up @@ -1020,20 +1083,21 @@ namespace BINDER_SPACE
}

/* static */
HRESULT AssemblyBinder::GetAssembly(SString &assemblyPath,
BOOL fIsInGAC,
HRESULT AssemblyBinder::GetAssembly(SString &assemblyPath,
BOOL fIsInGAC,

// When binding to the native image, should we
// assume assemblyPath explicitly specifies that
// NI? (If not, infer the path to the NI
// implicitly.)
BOOL fExplicitBindToNativeImage,
BOOL fExplicitBindToNativeImage,

Assembly **ppAssembly,
Assembly **ppAssembly,

// If assemblyPath refers to a native image without metadata,
// szMDAssemblyPath gives the alternative file to get metadata.
LPCTSTR szMDAssemblyPath)
LPCTSTR szMDAssemblyPath,
BundleFileLocation bundleFileLocation)
{
HRESULT hr = S_OK;

Expand All @@ -1053,7 +1117,7 @@ namespace BINDER_SPACE
{
LPCTSTR szAssemblyPath = const_cast<LPCTSTR>(assemblyPath.GetUnicode());

hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, &pNativePEImage, fExplicitBindToNativeImage);
hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, &pNativePEImage, fExplicitBindToNativeImage, bundleFileLocation);
IF_FAIL_GO(hr);

// If we found a native image, it might be an MSIL assembly masquerading as an native image
Expand All @@ -1068,7 +1132,7 @@ namespace BINDER_SPACE
BinderReleasePEImage(pPEImage);
BinderReleasePEImage(pNativePEImage);

hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, &pNativePEImage, false);
hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, &pNativePEImage, false, bundleFileLocation);
IF_FAIL_GO(hr);
}
}
Expand All @@ -1094,7 +1158,7 @@ namespace BINDER_SPACE
}
else
{
hr = BinderAcquirePEImage(szMDAssemblyPath, &pPEImage, NULL, FALSE);
hr = BinderAcquirePEImage(szMDAssemblyPath, &pPEImage, NULL, FALSE, bundleFileLocation);
IF_FAIL_GO(hr);

hr = BinderAcquireImport(pPEImage, &pIMetaDataAssemblyImport, dwPAFlags, FALSE);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/binder/coreclrbindercommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "assemblybinder.hpp"
#include "coreclrbindercommon.h"
#include "clrprivbindercoreclr.h"
#include "bundle.h"

using namespace BINDER_SPACE;

Expand Down
21 changes: 12 additions & 9 deletions src/coreclr/src/binder/inc/assembly.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@
#include "clrprivbinderassemblyloadcontext.h"
#endif // !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)

STDAPI BinderAcquirePEImage(LPCTSTR szAssemblyPath,
PEImage **ppPEImage,
PEImage **ppNativeImage,
BOOL fExplicitBindToNativeImage);

STDAPI BinderAcquireImport(PEImage *pPEImage,
IMDInternalImport **pIMetaDataAssemblyImport,
DWORD *pdwPAFlags,
BOOL bNativeImage);
#include "bundle.h"

STDAPI BinderAcquirePEImage(LPCTSTR szAssemblyPath,
PEImage **ppPEImage,
PEImage **ppNativeImage,
BOOL fExplicitBindToNativeImage,
BundleFileLocation bundleFileLocation);

STDAPI BinderAcquireImport(PEImage *pPEImage,
IMDInternalImport **pIMetaDataAssemblyImport,
DWORD *pdwPAFlags,
BOOL bNativeImage);

STDAPI BinderHasNativeHeader(PEImage *pPEImage,
BOOL *result);
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/src/binder/inc/assemblybinder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "bindertypes.hpp"
#include "bindresult.hpp"
#include "coreclrbindercommon.h"
#include "bundle.h"

class CLRPrivBinderAssemblyLoadContext;
class CLRPrivBinderCoreCLR;
Expand Down Expand Up @@ -54,7 +55,8 @@ namespace BINDER_SPACE
/* in */ BOOL fIsInGAC,
/* in */ BOOL fExplicitBindToNativeImage,
/* out */ Assembly **ppAssembly,
/* in */ LPCTSTR szMDAssemblyPath = NULL);
/* in */ LPCTSTR szMDAssemblyPath = NULL,
/* in */ BundleFileLocation bundleFileLocation = BundleFileLocation::Invalid());

#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
static HRESULT BindUsingHostAssemblyResolver (/* in */ INT_PTR pManagedAssemblyLoadContextToBindWithin,
Expand Down
22 changes: 20 additions & 2 deletions src/coreclr/src/dlls/mscoree/unixinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#ifdef FEATURE_GDBJIT
#include "../../vm/gdbjithelpers.h"
#endif // FEATURE_GDBJIT
#include "bundle.h"

#define ASSERTE_ALL_BUILDS(expr) _ASSERTE_ALL_BUILDS(__FILE__, (expr))

Expand Down Expand Up @@ -112,7 +113,8 @@ static void ConvertConfigPropertiesToUnicode(
const char** propertyValues,
int propertyCount,
LPCWSTR** propertyKeysWRef,
LPCWSTR** propertyValuesWRef)
LPCWSTR** propertyValuesWRef,
BundleProbe** bundleProbe)
{
LPCWSTR* propertyKeysW = new (nothrow) LPCWSTR[propertyCount];
ASSERTE_ALL_BUILDS(propertyKeysW != nullptr);
Expand All @@ -124,6 +126,13 @@ static void ConvertConfigPropertiesToUnicode(
{
propertyKeysW[propertyIndex] = StringToUnicode(propertyKeys[propertyIndex]);
propertyValuesW[propertyIndex] = StringToUnicode(propertyValues[propertyIndex]);

if (strcmp(propertyKeys[propertyIndex], "BUNDLE_PROBE") == 0)
{
// If this application is a single-file bundle, the bundle-probe callback
// is passed in as the value of "BUNDLE_PROBE" property (encoded as a string).
*bundleProbe = (BundleProbe*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0);
}
}

*propertyKeysWRef = propertyKeysW;
Expand Down Expand Up @@ -183,12 +192,21 @@ int coreclr_initialize(

LPCWSTR* propertyKeysW;
LPCWSTR* propertyValuesW;
BundleProbe* bundleProbe = nullptr;

ConvertConfigPropertiesToUnicode(
propertyKeys,
propertyValues,
propertyCount,
&propertyKeysW,
&propertyValuesW);
&propertyValuesW,
&bundleProbe);

if (bundleProbe != nullptr)
{
static Bundle bundle(StringToUnicode(exePath), bundleProbe);
Bundle::AppBundle = &bundle;
}

// This will take ownership of propertyKeysWTemp and propertyValuesWTemp
Configuration::InitializeConfigurationKnobs(propertyCount, propertyKeysW, propertyValuesW);
Expand Down
62 changes: 62 additions & 0 deletions src/coreclr/src/inc/bundle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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.

/*****************************************************************************
** **
** bundle.h - Information about applications bundled as a single-file **
** **
*****************************************************************************/

#ifndef _BUNDLE_H_
#define _BUNDLE_H_

#include <sstring.h>

class Bundle;

struct BundleFileLocation
{
INT64 Size;
INT64 Offset;

BundleFileLocation()
{
LIMITED_METHOD_CONTRACT;

Size = 0;
Offset = 0;
}

static BundleFileLocation Invalid() { LIMITED_METHOD_CONTRACT; return BundleFileLocation(); }

const SString &Path() const;

bool IsValid() const { LIMITED_METHOD_CONTRACT; return Offset != 0; }
};

typedef bool(__stdcall BundleProbe)(LPCWSTR, INT64*, INT64*);

class Bundle
{
public:
Bundle(LPCWSTR bundlePath, BundleProbe *probe);
BundleFileLocation Probe(LPCWSTR path, bool pathIsBundleRelative = false) const;

const SString &Path() const { LIMITED_METHOD_CONTRACT; return m_path; }
const SString &BasePath() const { LIMITED_METHOD_CONTRACT; return m_basePath; }

static Bundle* AppBundle; // The BundleInfo for the current app, initialized by coreclr_initialize.
static bool AppIsBundle() { LIMITED_METHOD_CONTRACT; return AppBundle != nullptr; }
static BundleFileLocation ProbeAppBundle(LPCWSTR path, bool pathIsBundleRelative = false);

private:

SString m_path; // The path to single-file executable
BundleProbe *m_probe;

SString m_basePath; // The prefix to denote a path within the bundle
};

#endif // _BUNDLE_H_
// EOF =======================================================================
Loading

0 comments on commit 0541e4b

Please sign in to comment.