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

Runtime Contexts #537

Merged
merged 8 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
51 changes: 45 additions & 6 deletions src/AsmResolver.DotNet/AssemblyDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,72 @@ public class AssemblyDefinition : AssemblyDescriptor, IModuleProvider, IHasSecur
/// <param name="buffer">The raw contents of the executable file to load.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromBytes(byte[] buffer) =>
FromImage(PEImage.FromBytes(buffer));
public static AssemblyDefinition FromBytes(byte[] buffer) => FromBytes(buffer, new ModuleReaderParameters());

/// <summary>
/// Reads a .NET assembly from the provided input buffer.
/// </summary>
/// <param name="buffer">The raw contents of the executable file to load.</param>
/// <param name="readerParameters">The parameters to use while reading the assembly.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromBytes(byte[] buffer, ModuleReaderParameters readerParameters) =>
FromImage(PEImage.FromBytes(buffer, readerParameters.PEReaderParameters), readerParameters);

/// <summary>
/// Reads a .NET assembly from the provided input file.
/// </summary>
/// <param name="filePath">The file path to the input executable to load.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromFile(string filePath) =>
FromImage(PEImage.FromFile(filePath), new ModuleReaderParameters(Path.GetDirectoryName(filePath)));
public static AssemblyDefinition FromFile(string filePath) => FromFile(filePath, new ModuleReaderParameters(Path.GetDirectoryName(filePath)));

/// <summary>
/// Reads a .NET assembly from the provided input file.
/// </summary>
/// <param name="filePath">The file path to the input executable to load.</param>
/// <param name="readerParameters">The parameters to use while reading the assembly.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromFile(string filePath, ModuleReaderParameters readerParameters) =>
FromImage(PEImage.FromFile(filePath, readerParameters.PEReaderParameters), readerParameters);

/// <summary>
/// Reads a .NET assembly from the provided input file.
/// </summary>
/// <param name="file">The portable executable file to load.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromFile(PEFile file) => FromImage(PEImage.FromFile(file));
public static AssemblyDefinition FromFile(PEFile file) => FromFile(file, new ModuleReaderParameters());

/// <summary>
/// Reads a .NET assembly from the provided input file.
/// </summary>
/// <param name="file">The portable executable file to load.</param>
/// <param name="readerParameters">The parameters to use while reading the assembly.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromFile(PEFile file, ModuleReaderParameters readerParameters) =>
FromImage(PEImage.FromFile(file, readerParameters.PEReaderParameters), readerParameters);

/// <summary>
/// Reads a .NET assembly from the provided input file.
/// </summary>
/// <param name="file">The portable executable file to load.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromFile(IInputFile file) =>
FromFile(file, new ModuleReaderParameters(Path.GetDirectoryName(file.FilePath)));

/// <summary>
/// Reads a .NET assembly from the provided input file.
/// </summary>
/// <param name="file">The portable executable file to load.</param>
/// <param name="readerParameters">The parameters to use while reading the assembly.</param>
/// <returns>The module.</returns>
/// <exception cref="BadImageFormatException">Occurs when the image does not contain a valid .NET metadata directory.</exception>
public static AssemblyDefinition FromFile(IInputFile file) => FromImage(PEImage.FromFile(file));
public static AssemblyDefinition FromFile(IInputFile file, ModuleReaderParameters readerParameters) =>
FromImage(PEImage.FromFile(file, readerParameters.PEReaderParameters), readerParameters);

/// <summary>
/// Reads a .NET assembly from an input stream.
Expand Down
23 changes: 19 additions & 4 deletions src/AsmResolver.DotNet/AssemblyResolverBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using AsmResolver.DotNet.Serialized;
using AsmResolver.DotNet.Signatures;
using AsmResolver.IO;

Expand All @@ -16,21 +17,35 @@ public abstract class AssemblyResolverBase : IAssemblyResolver
private static readonly string[] BinaryFileExtensions = {".dll", ".exe"};
private static readonly SignatureComparer Comparer = new(SignatureComparisonFlags.AcceptNewerVersions);

private readonly ConcurrentDictionary<AssemblyDescriptor, AssemblyDefinition> _cache = new(new SignatureComparer());
private readonly ConcurrentDictionary<AssemblyDescriptor, AssemblyDefinition> _cache = new(SignatureComparer.Default);

/// <summary>
/// Initializes the base of an assembly resolver.
/// </summary>
/// <param name="fileService">The service to use for reading files from the disk.</param>
protected AssemblyResolverBase(IFileService fileService)
{
FileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
ReaderParameters = new ModuleReaderParameters(fileService);
}

/// <summary>
/// Initializes the base of an assembly resolver.
/// </summary>
/// <param name="readerParameters">The reader parameters used for reading new resolved assemblies.</param>
protected AssemblyResolverBase(ModuleReaderParameters readerParameters)
{
ReaderParameters = readerParameters;
}

/// <summary>
/// Gets the file service that is used for reading files from the disk.
/// </summary>
public IFileService FileService
public IFileService FileService => ReaderParameters.PEReaderParameters.FileService;

/// <summary>
/// Gets the reader parameters used for reading new resolved assemblies.
/// </summary>
public ModuleReaderParameters ReaderParameters
{
get;
}
Expand Down Expand Up @@ -128,7 +143,7 @@ public void AddToCache(AssemblyDescriptor descriptor, AssemblyDefinition definit
/// <returns>The assembly.</returns>
protected virtual AssemblyDefinition LoadAssemblyFromFile(string path)
{
return AssemblyDefinition.FromFile(FileService.OpenFile(path));
return AssemblyDefinition.FromFile(FileService.OpenFile(path), ReaderParameters);
}

/// <summary>
Expand Down
90 changes: 90 additions & 0 deletions src/AsmResolver.DotNet/Bundles/BundleAssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Collections.Concurrent;
using System.IO;
using AsmResolver.DotNet.Serialized;
using AsmResolver.DotNet.Signatures;

namespace AsmResolver.DotNet.Bundles;

/// <summary>
/// Provides an implementation of an assembly resolver that prefers assemblies embedded in single-file-host executable.
/// </summary>
public class BundleAssemblyResolver : IAssemblyResolver
{
private readonly BundleManifest _manifest;
private readonly DotNetCoreAssemblyResolver _baseResolver;
private readonly ConcurrentDictionary<AssemblyDescriptor, AssemblyDefinition> _embeddedFilesCache = new(SignatureComparer.Default);

internal BundleAssemblyResolver(BundleManifest manifest, ModuleReaderParameters readerParameters)
{
_manifest = manifest;

// Bundles are .NET core 3.1+ only -> we can always default to .NET Core assembly resolution.
_baseResolver = new DotNetCoreAssemblyResolver(readerParameters, manifest.GetTargetRuntime().Version);
}

/// <inheritdoc />
public AssemblyDefinition? Resolve(AssemblyDescriptor assembly)
{
// Prefer embedded files before we forward to the default assembly resolution algorithm.
if (TryResolveFromEmbeddedFiles(assembly, out var resolved))
return resolved;

return _baseResolver.Resolve(assembly);
}

private bool TryResolveFromEmbeddedFiles(AssemblyDescriptor assembly, out AssemblyDefinition? resolved)
{
if (_embeddedFilesCache.TryGetValue(assembly, out resolved))
return true;

try
{
for (int i = 0; i < _manifest.Files.Count; i++)
{
var file = _manifest.Files[i];
if (file.Type != BundleFileType.Assembly)
continue;

if (Path.GetFileNameWithoutExtension(file.RelativePath) == assembly.Name)
{
resolved = AssemblyDefinition.FromBytes(file.GetData(), _baseResolver.ReaderParameters);
_embeddedFilesCache.TryAdd(assembly, resolved);
return true;
}
}
}
catch
{
// Ignore any reader errors.
}

resolved = null;
return false;
}

/// <inheritdoc />
public void AddToCache(AssemblyDescriptor descriptor, AssemblyDefinition definition)
{
_baseResolver.AddToCache(descriptor, definition);
}

/// <inheritdoc />
public bool RemoveFromCache(AssemblyDescriptor descriptor)
{
// Note: This is intentionally not an or-else (||) construction.
return _embeddedFilesCache.TryRemove(descriptor, out _) | _baseResolver.RemoveFromCache(descriptor);
}

/// <inheritdoc />
public bool HasCached(AssemblyDescriptor descriptor)
{
return _embeddedFilesCache.ContainsKey(descriptor) || _baseResolver.HasCached(descriptor);
}

/// <inheritdoc />
public void ClearCache()
{
_embeddedFilesCache.Clear();
_baseResolver.ClearCache();
}
}
56 changes: 55 additions & 1 deletion src/AsmResolver.DotNet/Bundles/BundleManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
using System.Text;
using System.Threading;
using AsmResolver.Collections;
using AsmResolver.DotNet.Config.Json;
using AsmResolver.IO;
using AsmResolver.PE.File;
using AsmResolver.PE.File.Headers;
using AsmResolver.PE.Win32Resources;
using AsmResolver.PE.Win32Resources.Builder;

namespace AsmResolver.DotNet.Bundles
Expand Down Expand Up @@ -287,6 +287,60 @@ public string GenerateDeterministicBundleID()
.Replace('/', '_');
}

/// <summary>
/// Determines the runtime that the assemblies in the bundle are targeting.
/// </summary>
/// <returns>The runtime.</returns>
/// <exception cref="ArgumentException">Occurs when the runtime could not be determined.</exception>
public DotNetRuntimeInfo GetTargetRuntime()
{
return TryGetTargetRuntime(out var runtime)
? runtime
: throw new ArgumentException("Could not determine the target runtime for the bundle");
}

/// <summary>
/// Attempts to determine the runtime that the assemblies in the bundle are targeting.
/// </summary>
/// <param name="targetRuntime">When the method returns <c>true</c>, contains the target runtime.</param>
/// <returns><c>true</c> if the runtime could be determined, <c>false</c> otherwise.</returns>
public bool TryGetTargetRuntime(out DotNetRuntimeInfo targetRuntime)
{
// Try find the runtimeconfig.json file.
for (int i = 0; i < Files.Count; i++)
{
var file = Files[i];
if (file.Type == BundleFileType.RuntimeConfigJson)
{
var config = RuntimeConfiguration.FromJson(Encoding.UTF8.GetString(file.GetData()));
if (config is not {RuntimeOptions.TargetFrameworkMoniker: { } tfm})
continue;

if (DotNetRuntimeInfo.TryParseMoniker(tfm, out targetRuntime))
return true;
}
}

// If it is not present, make a best effort guess based on the bundle file format version.
switch (MajorVersion)
{
case 1:
targetRuntime = new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(3, 1));
return true;

case 2:
targetRuntime = new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(5, 0));
return true;

case 6:
targetRuntime = new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(6, 0));
return true;
}

targetRuntime = default;
return false;
}

/// <summary>
/// Constructs a new application host file based on the bundle manifest.
/// </summary>
Expand Down
30 changes: 29 additions & 1 deletion src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using AsmResolver.DotNet.Config.Json;
using AsmResolver.DotNet.Serialized;
using AsmResolver.IO;

namespace AsmResolver.DotNet
Expand Down Expand Up @@ -35,6 +36,17 @@ public DotNetCoreAssemblyResolver(IFileService fileService, Version runtimeVersi
{
}

/// <summary>
/// Creates a new .NET Core assembly resolver, by attempting to autodetect the current .NET or .NET Core
/// installation directory.
/// </summary>
/// <param name="readerParameters">The parameters to use while reading the assembly.</param>
/// <param name="runtimeVersion">The version of .NET to target.</param>
public DotNetCoreAssemblyResolver(ModuleReaderParameters readerParameters, Version runtimeVersion)
: this(readerParameters, null, runtimeVersion, DotNetCorePathProvider.Default)
{
}

/// <summary>
/// Creates a new .NET Core assembly resolver, by attempting to autodetect the current .NET or .NET Core
/// installation directory.
Expand Down Expand Up @@ -81,7 +93,23 @@ public DotNetCoreAssemblyResolver(
RuntimeConfiguration? configuration,
Version? fallbackVersion,
DotNetCorePathProvider pathProvider)
: base(fileService)
: this(new ModuleReaderParameters(fileService), configuration, fallbackVersion, pathProvider)
{
}

/// <summary>
/// Creates a new .NET Core assembly resolver.
/// </summary>
/// <param name="readerParameters">The parameters to use while reading the assembly.</param>
/// <param name="configuration">The runtime configuration to use, or <c>null</c> if no configuration is available.</param>
/// <param name="fallbackVersion">The version of .NET or .NET Core to use when no (valid) configuration is provided.</param>
/// <param name="pathProvider">The installation directory of .NET Core.</param>
public DotNetCoreAssemblyResolver(
ModuleReaderParameters readerParameters,
RuntimeConfiguration? configuration,
Version? fallbackVersion,
DotNetCorePathProvider pathProvider)
: base(readerParameters)
{
if (fallbackVersion is null)
throw new ArgumentNullException(nameof(fallbackVersion));
Expand Down
16 changes: 15 additions & 1 deletion src/AsmResolver.DotNet/DotNetFrameworkAssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AsmResolver.DotNet.Serialized;
using AsmResolver.IO;

namespace AsmResolver.DotNet
Expand All @@ -25,7 +26,15 @@ public DotNetFrameworkAssemblyResolver()
/// </summary>
/// <param name="fileService">The service to use for reading files from the disk.</param>
public DotNetFrameworkAssemblyResolver(IFileService fileService)
: base(fileService)
: this(new ModuleReaderParameters(fileService))
{
}

/// <summary>
/// Creates a new default assembly resolver.
/// </summary>
public DotNetFrameworkAssemblyResolver(ModuleReaderParameters readerParameters)
: base(readerParameters)
{
DetectGacDirectories();
}
Expand Down Expand Up @@ -92,7 +101,12 @@ private void DetectMonoGacDirectories()
.FirstOrDefault();

if (mostRecentMonoDirectory is not null)
{
SearchDirectories.Add(mostRecentMonoDirectory);
string facadesDirectory = Path.Combine(mostRecentMonoDirectory, "Facades");
if (Directory.Exists(facadesDirectory))
SearchDirectories.Add(facadesDirectory);
}
}

private void AddGacDirectories(string windowsGac, string? prefix)
Expand Down
Loading