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

Feature/GAC Directory Priorities #242

Merged
merged 3 commits into from
Dec 29, 2021
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
4 changes: 3 additions & 1 deletion src/AsmResolver.DotNet/AssemblyDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace AsmResolver.DotNet
/// <summary>
/// Represents an assembly of self-describing modules of an executable file hosted by a common language runtime (CLR).
/// </summary>
public class AssemblyDefinition : AssemblyDescriptor, IHasSecurityDeclaration
public class AssemblyDefinition : AssemblyDescriptor, IModuleProvider, IHasSecurityDeclaration
{
private IList<ModuleDefinition>? _modules;
private IList<SecurityDeclaration>? _securityDeclarations;
Expand Down Expand Up @@ -133,6 +133,8 @@ public AssemblyHashAlgorithm HashAlgorithm
/// </summary>
public ModuleDefinition? ManifestModule => Modules.Count > 0 ? Modules[0] : null;

ModuleDefinition? IModuleProvider.Module => ManifestModule;

/// <summary>
/// Gets a collection of modules that this .NET assembly defines.
/// </summary>
Expand Down
88 changes: 75 additions & 13 deletions src/AsmResolver.DotNet/DotNetFrameworkAssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,28 @@ public DotNetFrameworkAssemblyResolver(IFileService fileService)
}

/// <summary>
/// Gets a collection of global assembly cache (GAC) directories that are probed upon resolving a reference
/// to an assembly.
/// Gets a collection of 32-bit global assembly cache (GAC_32) directories that are probed upon resolving a
/// reference to an assembly.
/// </summary>
public IList<GacDirectory> GacDirectories
public IList<GacDirectory> Gac32Directories
{
get;
} = new List<GacDirectory>();

/// <summary>
/// Gets a collection of 64-bit global assembly cache (GAC_64) directories that are probed upon resolving a
/// reference to an assembly.
/// </summary>
public IList<GacDirectory> Gac64Directories
{
get;
} = new List<GacDirectory>();

/// <summary>
/// Gets a collection of MSIL global assembly cache (GAC_MSIL) directories that are probed upon resolving a
/// reference to an assembly.
/// </summary>
public IList<GacDirectory> GacMsilDirectories
{
get;
} = new List<GacDirectory>();
Expand Down Expand Up @@ -65,7 +83,7 @@ private void DetectWindowsGacDirectories()
private void DetectMonoGacDirectories()
{
if (Directory.Exists("/usr/lib/mono/gac"))
GacDirectories.Add(new GacDirectory("/usr/lib/mono/gac"));
GacMsilDirectories.Add(new GacDirectory("/usr/lib/mono/gac"));

string? mostRecentMonoDirectory = Directory
.EnumerateDirectories("/usr/lib/mono")
Expand All @@ -83,24 +101,68 @@ private void AddGacDirectories(string windowsGac, string? prefix)
return;

foreach (string directory in Directory.EnumerateDirectories(windowsGac))
GetGacDirectoryCollection(directory).Add(new GacDirectory(directory, prefix));

IList<GacDirectory> GetGacDirectoryCollection(string directory) => Path.GetFileName(directory) switch
{
string name = Path.GetFileName(directory);
if (name.StartsWith("GAC"))
GacDirectories.Add(new GacDirectory(directory, prefix));
}
"GAC_32" => Gac32Directories,
"GAC_64" => Gac64Directories,
_ => GacMsilDirectories
};
}

/// <inheritdoc />
protected override string? ProbeRuntimeDirectories(AssemblyDescriptor assembly)
{
for (int i = 0; i < GacDirectories.Count; i++)
bool is32BitPreferred;
bool is32BitRequired;

// Try infer from declaring module which GAC directory would be preferred.
if (assembly is IModuleProvider {Module: { } module})
{
string? path = GacDirectories[i].Probe(assembly);
if (!string.IsNullOrEmpty(path))
return path;
is32BitPreferred = module.IsBit32Preferred;
is32BitRequired = module.IsBit32Required;
}
else
{
// If declaring module could not be obtained, assume AnyCPU since it is the most common case.
is32BitPreferred = false;
is32BitRequired = false;
}

string? path;

if (is32BitRequired)
{
// If this assembly only runs on 32-bit, then we should only try resolve from GAC_32 or GAC_MSIL.
path = ProbeGacDirectories(Gac32Directories);
}
else if (is32BitPreferred)
{
// If this assembly can run on 64-bit but prefers 32-bit, then prefer GAC_32 over GAC_64.
path = ProbeGacDirectories(Gac32Directories);
path ??= ProbeGacDirectories(Gac64Directories);
}
else
{
// Otherwise assume a 64-bit environment first.
path = ProbeGacDirectories(Gac64Directories);
path ??= ProbeGacDirectories(Gac32Directories);
}

// Fallback: probe GAC_MSIL.
return path ?? ProbeGacDirectories(GacMsilDirectories);

return null;
string? ProbeGacDirectories(IList<GacDirectory> directories)
{
for (int i = 0; i < directories.Count; i++)
{
if (directories[i].Probe(assembly) is { } p)
return p;
}

return null;
}
}
}
}
63 changes: 53 additions & 10 deletions test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@
using AsmResolver.DotNet.Signatures;
using AsmResolver.DotNet.TestCases.NestedClasses;
using AsmResolver.IO;
using AsmResolver.PE.File.Headers;
using Xunit;

namespace AsmResolver.DotNet.Tests
{
public class AssemblyResolverTest
{
private const string NonWindowsPlatform = "Test checks for the presence of the Microsoft.WindowsDesktop.App runtime, which is only available on Windows.";
private const string NonWindowsPlatform = "Test checks for the presence of Windows specific runtime libraries.";

private readonly SignatureComparer _comparer = new SignatureComparer();
private readonly SignatureComparer _comparer = new();

[Fact]
public void ResolveCorLib()
{
var assemblyName = typeof(object).Assembly.GetName();
var assemblyRef = new AssemblyReference(
assemblyName.Name,
assemblyName.Version,
assemblyName.Version!,
false,
assemblyName.GetPublicKeyToken());

Expand All @@ -30,7 +31,7 @@ public void ResolveCorLib()

Assert.NotNull(assemblyDef);
Assert.Equal(assemblyName.Name, assemblyDef.Name);
Assert.NotNull(assemblyDef.ManifestModule.FilePath);
Assert.NotNull(assemblyDef.ManifestModule!.FilePath);
}

[Fact]
Expand All @@ -41,7 +42,7 @@ public void ResolveCorLibUsingFileService()
var assemblyName = typeof(object).Assembly.GetName();
var assemblyRef = new AssemblyReference(
assemblyName.Name,
assemblyName.Version,
assemblyName.Version!,
false,
assemblyName.GetPublicKeyToken());

Expand All @@ -61,7 +62,7 @@ public void ResolveLocalLibrary()
var assemblyRef = new AssemblyReference(assemblyDef);

Assert.Equal(assemblyDef, resolver.Resolve(assemblyRef), _comparer);
Assert.NotNull(assemblyDef.ManifestModule.FilePath);
Assert.NotNull(assemblyDef.ManifestModule!.FilePath);

resolver.ClearCache();
Assert.False(resolver.HasCached(assemblyRef));
Expand All @@ -80,7 +81,7 @@ public void ResolveWithConfiguredRuntime()
var assemblyName = typeof(object).Assembly.GetName();
var assemblyRef = new AssemblyReference(
assemblyName.Name,
assemblyName.Version,
assemblyName.Version!,
false,
assemblyName.GetPublicKeyToken());

Expand All @@ -98,7 +99,7 @@ public void ResolveWithConfiguredRuntime()

Assert.NotNull(assemblyDef);
Assert.Equal(assemblyName.Name, assemblyDef.Name);
Assert.NotNull(assemblyDef.ManifestModule.FilePath);
Assert.NotNull(assemblyDef.ManifestModule!.FilePath);
Assert.Contains("Microsoft.NETCore.App", assemblyDef.ManifestModule.FilePath);
}

Expand Down Expand Up @@ -128,7 +129,7 @@ public void ResolveWithConfiguredRuntimeWindowsCanStillResolveCorLib()

Assert.NotNull(assemblyDef);
Assert.Equal(assemblyName.Name, assemblyDef.Name);
Assert.NotNull(assemblyDef.ManifestModule.FilePath);
Assert.NotNull(assemblyDef.ManifestModule!.FilePath);
}

[SkippableFact]
Expand Down Expand Up @@ -156,8 +157,50 @@ public void ResolveWithConfiguredRuntimeWindowsResolvesRightWindowsBase()

Assert.NotNull(assemblyDef);
Assert.Equal("WindowsBase", assemblyDef.Name);
Assert.NotNull(assemblyDef.ManifestModule.FilePath);
Assert.NotNull(assemblyDef.ManifestModule!.FilePath);
Assert.Contains("Microsoft.WindowsDesktop.App", assemblyDef.ManifestModule.FilePath);
}

[SkippableTheory]
[InlineData(false)]
[InlineData(true)]
public void PreferResolveFromGac32If32BitAssembly(bool legacy)
{
Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), NonWindowsPlatform);

var module = new ModuleDefinition("SomeAssembly", legacy
? KnownCorLibs.MsCorLib_v2_0_0_0
: KnownCorLibs.MsCorLib_v4_0_0_0);

module.IsBit32Preferred = true;
module.IsBit32Required = true;
module.MachineType = MachineType.I386;
module.PEKind = OptionalHeaderMagic.Pe32;

var resolved = module.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve();
Assert.NotNull(resolved);
Assert.Contains("GAC_32", resolved.ManifestModule!.FilePath!);
}

[SkippableTheory]
[InlineData(false)]
[InlineData(true)]
public void PreferResolveFromGac64If64BitAssembly(bool legacy)
{
Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), NonWindowsPlatform);

var module = new ModuleDefinition("SomeAssembly", legacy
? KnownCorLibs.MsCorLib_v2_0_0_0
: KnownCorLibs.MsCorLib_v4_0_0_0);

module.IsBit32Preferred = false;
module.IsBit32Required = false;
module.MachineType = MachineType.Amd64;
module.PEKind = OptionalHeaderMagic.Pe32Plus;

var resolved = module.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve();
Assert.NotNull(resolved);
Assert.Contains("GAC_64", resolved.ManifestModule!.FilePath!);
}
}
}