Skip to content

Commit ab7bdfc

Browse files
authored
[dd-trace] Improved GAC commands (#6995)
## Summary of changes This PR improves the support in the GAC command, by allowing us to install, uninstall and get info for multiple versions of an assembly. ## Reason for change The current support only checks if an assembly is installed in the GAC no matter version, causing an issue when a previous version of dd-trace installed an old version of Datadog.Trace in the GAC meaning that we bailout and don't install the new version, causing a crash when the application loads. <img width="1666" alt="image" src="https://github.com/user-attachments/assets/f4a8b940-6718-470d-bc8c-0a6fc84bb838" /> ## Implementation details Now we include more unmanaged Fusion apis to list the version of the assemblies installed in the GAC. ## Test coverage Added a new `GacWithMultipleVersions` test. ## Other details <!-- Fixes #{issue} --> <!-- ⚠️ Note: where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. -->
1 parent 1ceb375 commit ab7bdfc

20 files changed

+535
-75
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// <copyright file="AsmCacheFlags.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
namespace Datadog.Trace.Tools.Runner.Gac;
7+
8+
internal enum AsmCacheFlags
9+
{
10+
ASM_CACHE_ZAP = 0x01,
11+
ASM_CACHE_GAC = 0x02,
12+
ASM_CACHE_DOWNLOAD = 0x04,
13+
ASM_CACHE_ROOT = 0x08,
14+
ASM_CACHE_ROOT_EX = 0x80
15+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// <copyright file="AsmDisplayFlags.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
namespace Datadog.Trace.Tools.Runner.Gac;
7+
8+
internal enum AsmDisplayFlags
9+
{
10+
ASM_DISPLAYF_VERSION = 0x1,
11+
ASM_DISPLAYF_CULTURE = 0x2,
12+
ASM_DISPLAYF_PUBLIC_KEY_TOKEN = 0x4,
13+
ASM_DISPLAYF_PUBLIC_KEY = 0x8,
14+
ASM_DISPLAYF_CUSTOM = 0x10,
15+
ASM_DISPLAYF_PROCESSORARCHITECTURE = 0x20,
16+
ASM_DISPLAYF_LANGUAGEID = 0x40,
17+
ASM_DISPLAYF_RETARGET = 0x80,
18+
ASM_DISPLAYF_CONFIG_MASK = 0x100,
19+
ASM_DISPLAYF_MVID = 0x200,
20+
ASM_DISPLAYF_CONTENT_TYPE = 0x400,
21+
ASM_DISPLAYF_FULL = (((((ASM_DISPLAYF_VERSION | ASM_DISPLAYF_CULTURE) | ASM_DISPLAYF_PUBLIC_KEY_TOKEN) | ASM_DISPLAYF_RETARGET) | ASM_DISPLAYF_PROCESSORARCHITECTURE) | ASM_DISPLAYF_CONTENT_TYPE)
22+
}

tracer/src/Datadog.Trace.Tools.Runner/Gac/AssemblyCacheInstallFlags.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ namespace Datadog.Trace.Tools.Runner.Gac;
1313
internal enum AssemblyCacheInstallFlags
1414
{
1515
None = 0x0,
16+
17+
/// <summary>
18+
/// Replace existing files in the side-by-side store with the files in the assembly being installed
19+
/// if the version of the file in the assembly is greater than or equal to the version of the existing file.
20+
/// </summary>
1621
IASSEMBLYCACHE_INSTALL_FLAG_REFRESH = 0x1,
22+
23+
/// <summary>
24+
/// Replace existing files in the side-by-side store with the files in the assembly being installed.
25+
/// </summary>
1726
IASSEMBLYCACHE_INSTALL_FLAG_FORCE_REFRESH = 0x2,
1827
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// <copyright file="CreateAsmNameObjFlags.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using System;
7+
8+
namespace Datadog.Trace.Tools.Runner.Gac;
9+
10+
[Flags]
11+
internal enum CreateAsmNameObjFlags
12+
{
13+
/// <summary>
14+
/// If this flag is specified, the szAssemblyName parameter of CreateAssemblyNameObject is a fully-specified
15+
/// side-by-side assembly name and is parsed to the individual properties.
16+
/// </summary>
17+
CANOF_PARSE_DISPLAY_NAME = 0x1,
18+
19+
/// <summary>
20+
/// Reserved.
21+
/// </summary>
22+
CANOF_SET_DEFAULT_VALUES = 0x2,
23+
CANOF_VERIFY_FRIEND_ASSEMBLYNAME = 0x4,
24+
CANOF_PARSE_FRIEND_DISPLAY_NAME = (CANOF_PARSE_DISPLAY_NAME | CANOF_VERIFY_FRIEND_ASSEMBLYNAME)
25+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// <copyright file="GacNativeMethods.Impl.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.IO;
9+
using System.Reflection;
10+
using System.Runtime.InteropServices;
11+
using System.Text;
12+
using Microsoft.Win32;
13+
14+
namespace Datadog.Trace.Tools.Runner.Gac;
15+
16+
internal sealed partial class GacNativeMethods
17+
{
18+
private delegate Hresult CreateAssemblyCacheDelegate(out IAssemblyCache ppAsmCache, IntPtr reserved);
19+
20+
private delegate Hresult CreateAssemblyNameObjectDelegate(out IAssemblyName ppAssemblyNameObj, [MarshalAs(UnmanagedType.LPWStr)] string szAssemblyName, CreateAsmNameObjFlags flags, IntPtr pvReserved);
21+
22+
private delegate Hresult CreateAssemblyEnumDelegate(out IAssemblyEnum pEnum, IntPtr pUnkReserved, IAssemblyName pName, AsmCacheFlags dwFlags, IntPtr pvReserved);
23+
24+
internal IAssemblyCache CreateAssemblyCache()
25+
{
26+
var createAssemblyCachePointer = NativeLibrary.GetExport(GetPointer(), nameof(CreateAssemblyCache));
27+
var createAssemblyCache = Marshal.GetDelegateForFunctionPointer<CreateAssemblyCacheDelegate>(createAssemblyCachePointer);
28+
var hr = createAssemblyCache(out var ppAsmCache, IntPtr.Zero);
29+
if (hr != Hresult.S_OK)
30+
{
31+
throw new TargetInvocationException($"Error creating AssemblyCache. HRESULT = {hr}", null);
32+
}
33+
34+
return ppAsmCache;
35+
}
36+
37+
internal IAssemblyName CreateAssemblyName(string assemblyName)
38+
{
39+
var createAssemblyNameObjectPointer = NativeLibrary.GetExport(GetPointer(), "CreateAssemblyNameObject");
40+
var createAssemblyNameObject = Marshal.GetDelegateForFunctionPointer<CreateAssemblyNameObjectDelegate>(createAssemblyNameObjectPointer);
41+
var hr = createAssemblyNameObject(out var ppAsmName, assemblyName, 0, IntPtr.Zero);
42+
if (hr != Hresult.S_OK)
43+
{
44+
throw new TargetInvocationException($"Error creating AssemblyNameObject. HRESULT = {hr}", null);
45+
}
46+
47+
return ppAsmName;
48+
}
49+
50+
internal ICollection<AssemblyName> GetAssemblyNames(string assemblyName)
51+
{
52+
var createAssemblyEnumPointer = NativeLibrary.GetExport(GetPointer(), "CreateAssemblyEnum");
53+
var createAssemblyEnum = Marshal.GetDelegateForFunctionPointer<CreateAssemblyEnumDelegate>(createAssemblyEnumPointer);
54+
var pAssemblyName = CreateAssemblyName(assemblyName);
55+
var hr = createAssemblyEnum(out var pAssemblyEnum, IntPtr.Zero, pAssemblyName, AsmCacheFlags.ASM_CACHE_GAC, IntPtr.Zero);
56+
if (hr != Hresult.S_OK)
57+
{
58+
return [];
59+
}
60+
61+
var lstAsmName = new List<AssemblyName>();
62+
while (pAssemblyEnum.GetNextAssembly(IntPtr.Zero, out pAssemblyName, 0) == 0 && pAssemblyName != null)
63+
{
64+
var nSize = 260;
65+
var sbDisplayName = new StringBuilder(nSize);
66+
hr = pAssemblyName.GetDisplayName(sbDisplayName, ref nSize, AsmDisplayFlags.ASM_DISPLAYF_FULL);
67+
if (hr == Hresult.S_OK)
68+
{
69+
lstAsmName.Add(new AssemblyName(sbDisplayName.ToString()));
70+
}
71+
}
72+
73+
return lstAsmName;
74+
}
75+
}

tracer/src/Datadog.Trace.Tools.Runner/Gac/NativeMethods.cs renamed to tracer/src/Datadog.Trace.Tools.Runner/Gac/GacNativeMethods.cs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
// <copyright file="NativeMethods.cs" company="Datadog">
1+
// <copyright file="GacNativeMethods.cs" company="Datadog">
22
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
44
// </copyright>
55

66
using System;
77
using System.IO;
8-
using System.Reflection;
98
using System.Runtime.InteropServices;
109
using System.Runtime.Versioning;
1110
using Microsoft.Win32;
@@ -16,13 +15,43 @@ namespace Datadog.Trace.Tools.Runner.Gac;
1615
#if NET5_0_OR_GREATER
1716
[SupportedOSPlatform("windows")]
1817
#endif
19-
internal sealed class NativeMethods
18+
internal sealed partial class GacNativeMethods : IDisposable
2019
{
2120
private const string NetFrameworkSubKey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
2221

23-
private delegate int CreateAssemblyCacheDelegate(out IAssemblyCache ppAsmCache, int reserved);
22+
private IntPtr _libPointer;
2423

25-
internal static AssemblyCacheContainer CreateAssemblyCache()
24+
private GacNativeMethods(IntPtr libPointer)
25+
{
26+
_libPointer = libPointer;
27+
}
28+
29+
public static GacNativeMethods Create()
30+
{
31+
var fusionFullPath = GetFusionFullPath();
32+
var lPointer = NativeLibrary.Load(fusionFullPath);
33+
34+
if (lPointer == IntPtr.Zero)
35+
{
36+
throw new Exception($"Error loading fusion library.");
37+
}
38+
39+
return new GacNativeMethods(lPointer);
40+
}
41+
42+
public void Dispose()
43+
{
44+
var lPointer = _libPointer;
45+
if (lPointer == IntPtr.Zero)
46+
{
47+
return;
48+
}
49+
50+
_libPointer = IntPtr.Zero;
51+
NativeLibrary.Free(lPointer);
52+
}
53+
54+
private static string GetFusionFullPath()
2655
{
2756
string fusionFullPath;
2857
using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, Environment.Is64BitProcess ? RegistryView.Registry64 : RegistryView.Registry32).OpenSubKey(NetFrameworkSubKey))
@@ -41,16 +70,17 @@ internal static AssemblyCacheContainer CreateAssemblyCache()
4170
throw new FileNotFoundException($"{fusionFullPath} cannot be found.");
4271
}
4372

44-
var libPointer = NativeLibrary.Load(fusionFullPath);
45-
var createAssemblyCachePointer = NativeLibrary.GetExport(libPointer, nameof(CreateAssemblyCache));
46-
var createAssemblyCache = Marshal.GetDelegateForFunctionPointer<CreateAssemblyCacheDelegate>(createAssemblyCachePointer);
47-
var hr = createAssemblyCache(out var ppAsmCache, 0);
48-
if (hr != 0)
73+
return fusionFullPath;
74+
}
75+
76+
private IntPtr GetPointer()
77+
{
78+
var lPointer = _libPointer;
79+
if (lPointer == IntPtr.Zero)
4980
{
50-
NativeLibrary.Free(libPointer);
51-
throw new TargetInvocationException($"Error creating AssemblyCache. HRESULT = {hr}", null);
81+
throw new Exception("Fusion library has not been loaded.");
5282
}
5383

54-
return new AssemblyCacheContainer(libPointer, ppAsmCache);
84+
return lPointer;
5585
}
5686
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// <copyright file="Hresult.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
namespace Datadog.Trace.Tools.Runner.Gac;
7+
8+
internal enum Hresult : int
9+
{
10+
/// <summary>
11+
/// Operation successful
12+
/// </summary>
13+
S_OK = 0,
14+
S_FALSE = 1,
15+
16+
/// <summary>
17+
/// Not implemented
18+
/// </summary>
19+
E_NOTIMPL = unchecked((int)0x80004001),
20+
21+
/// <summary>
22+
/// No such interface supported
23+
/// </summary>
24+
E_NOINTERFACE = unchecked((int)0x80004002),
25+
26+
/// <summary>
27+
/// Pointer that is not valid
28+
/// </summary>
29+
E_POINTER = unchecked((int)0x80004003),
30+
31+
/// <summary>
32+
/// Operation aborted
33+
/// </summary>
34+
E_ABORT = unchecked((int)0x80004004),
35+
36+
/// <summary>
37+
/// Unspecified failure
38+
/// </summary>
39+
E_FAIL = unchecked((int)0x80004005),
40+
41+
/// <summary>
42+
/// Unexpected failure
43+
/// </summary>
44+
E_UNEXPECTED = unchecked((int)0x8000FFFF),
45+
46+
/// <summary>
47+
/// File not found error
48+
/// </summary>
49+
E_FILENOTFOUND = unchecked((int)0x80070002),
50+
51+
/// <summary>
52+
/// General access denied error
53+
/// </summary>
54+
E_ACCESSDENIED = unchecked((int)0x80070005),
55+
56+
/// <summary>
57+
/// Handle that is not valid
58+
/// </summary>
59+
E_HANDLE = unchecked((int)0x80070006),
60+
61+
/// <summary>
62+
/// Failed to allocate necessary memory
63+
/// </summary>
64+
E_OUTOFMEMORY = unchecked((int)0x8007000E),
65+
66+
/// <summary>
67+
/// One or more arguments are not valid
68+
/// </summary>
69+
E_INVALIDARG = unchecked((int)0x80070057),
70+
71+
/// <summary>
72+
/// Strong name signature could not be verified. The assembly may have been tampered with, or it was delay signed but not fully signed with the correct private key.
73+
/// </summary>
74+
E_INVALIDSTRONGNAME = unchecked((int)0x80131045),
75+
76+
/// <summary>
77+
/// The given assembly name or codebase was invalid.
78+
/// </summary>
79+
E_INVALIDASSEMBLY = unchecked((int)0x80131047)
80+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// <copyright file="HresultHelper.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
namespace Datadog.Trace.Tools.Runner.Gac;
7+
8+
internal static class HresultHelper
9+
{
10+
public static string ToStringOrHex(this Hresult hresult)
11+
{
12+
var str = hresult.ToString();
13+
if (int.TryParse(str, out _))
14+
{
15+
str = $"0x{((int)hresult):x8}";
16+
}
17+
else
18+
{
19+
str += $" [0x{(int)hresult:x8}]";
20+
}
21+
22+
return str;
23+
}
24+
}

tracer/src/Datadog.Trace.Tools.Runner/Gac/IAssemblyCache.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ namespace Datadog.Trace.Tools.Runner.Gac;
1616
internal interface IAssemblyCache
1717
{
1818
[PreserveSig]
19-
int UninstallAssembly(UninstallAssemblyFlags dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, IntPtr pvReserved, out UninstallDisposition pulDisposition);
19+
Hresult UninstallAssembly(UninstallAssemblyFlags dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, IntPtr pvReserved, out UninstallDisposition pulDisposition);
2020

2121
[PreserveSig]
22-
int QueryAssemblyInfo(QueryAssemblyInfoFlag dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, ref AssemblyInfo pAsmInfo);
22+
Hresult QueryAssemblyInfo(QueryAssemblyInfoFlag dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, ref AssemblyInfo pAsmInfo);
2323

2424
[PreserveSig]
25-
int CreateAssemblyCacheItem(uint dwFlags, IntPtr pvReserved, out IAssemblyCacheItem ppAsmItem, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName);
25+
Hresult CreateAssemblyCacheItem(uint dwFlags, IntPtr pvReserved, out IAssemblyCacheItem ppAsmItem, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName);
2626

2727
[PreserveSig]
28-
int CreateAssemblyScavenger(out object ppAsmScavenger);
28+
Hresult CreateAssemblyScavenger(out object ppAsmScavenger);
2929

3030
[PreserveSig]
31-
int InstallAssembly(AssemblyCacheInstallFlags dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszManifestFilePath, IntPtr pvReserved);
31+
Hresult InstallAssembly(AssemblyCacheInstallFlags dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszManifestFilePath, IntPtr pvReserved);
3232
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// <copyright file="IAssemblyEnum.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using System;
7+
using System.Runtime.InteropServices;
8+
9+
namespace Datadog.Trace.Tools.Runner.Gac;
10+
11+
[Guid("21b8916c-f28e-11d2-a473-00c04f8ef448")]
12+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
13+
internal interface IAssemblyEnum
14+
{
15+
Hresult GetNextAssembly(IntPtr pvReserved, out IAssemblyName ppName, int dwFlags);
16+
17+
Hresult Reset();
18+
19+
Hresult Clone(out IAssemblyEnum ppEnum);
20+
}

0 commit comments

Comments
 (0)