Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// <copyright file="ManagedProfilerAssemblyResolver.NetFramework.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#if NETFRAMEWORK

#nullable enable

using System;
using System.IO;
using System.Reflection;

namespace Datadog.Trace.ClrProfiler.Managed.Loader;

// Owns the AppDomain.AssemblyResolve callback on .NET Framework. Kept on a
// separate class from Startup so the handler can be dispatched without
// forcing Startup's type-initializer to have finished. If the handler were
// a method on Startup, a configBuilder attached to <appSettings> that
// issues sync-over-async work during Startup..cctor could deadlock: the
// main thread would be blocked inside the .cctor waiting for a Task, whose
// continuation runs on a ThreadPool thread that probes Type.GetType and
// fires AssemblyResolve, whose handler would then wait on Startup..cctor.
internal static class ManagedProfilerAssemblyResolver
{
// Seeded by Startup..cctor before the handler is subscribed.
internal static string? ManagedProfilerDirectory { get; set; }

internal static Assembly? OnAssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
return ResolveAssembly(args.Name);
}
catch (Exception ex)
{
StartupLogger.Log(ex, "Error resolving assembly: {0}", args.Name);
}

return null;
}

internal static Assembly? ResolveAssembly(string name)
{
var assemblyName = new AssemblyName(name);

// On .NET Framework, having a non-US locale can cause mscorlib
// to enter the AssemblyResolve event when searching for resources
// in its satellite assemblies. Exit early so we don't cause
// infinite recursion.
if (string.Equals(assemblyName.Name, "mscorlib.resources", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName.Name, "System.Net.Http", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName.Name, "vstest.console.resources", StringComparison.OrdinalIgnoreCase))
{
return null;
}

// WARNING: Logs must not be added _before_ we check for the above bail-out conditions
var path = string.IsNullOrEmpty(ManagedProfilerDirectory) ? $"{assemblyName.Name}.dll" : Path.Combine(ManagedProfilerDirectory, $"{assemblyName.Name}.dll");
StartupLogger.Debug("Assembly Resolve event received for: {0}. Looking for: {1}", name, path);

if (File.Exists(path))
{
if (name.StartsWith("Datadog.Trace, Version=", StringComparison.Ordinal) && name != Startup.AssemblyName)
{
StartupLogger.Debug(" Trying to load '{0}' which does not match the expected version ('{1}'). [Path={2}]", name, Startup.AssemblyName, path);
return null;
}

StartupLogger.Debug("Calling Assembly.LoadFrom(\"{0}\")", path);
var assembly = Assembly.LoadFrom(path);
StartupLogger.Debug("Assembly loaded: {0}", assembly.FullName);
return assembly;
}

StartupLogger.Debug("Assembly not found in path: {0}", path);
return null;
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@

#nullable enable

using System;
using System.IO;
using System.Reflection;

namespace Datadog.Trace.ClrProfiler.Managed.Loader
{
Expand All @@ -22,57 +20,6 @@ internal static string ComputeTfmDirectory(string tracerHomeDirectory)
{
return Path.Combine(Path.GetFullPath(tracerHomeDirectory), "net461");
}

private static Assembly? AssemblyResolve_ManagedProfilerDependencies(object sender, ResolveEventArgs args)
{
try
{
return ResolveAssembly(args.Name);
}
catch (Exception ex)
{
StartupLogger.Log(ex, "Error resolving assembly: {0}", args.Name);
}

return null;
}

private static Assembly? ResolveAssembly(string name)
{
var assemblyName = new AssemblyName(name);

// On .NET Framework, having a non-US locale can cause mscorlib
// to enter the AssemblyResolve event when searching for resources
// in its satellite assemblies. Exit early so we don't cause
// infinite recursion.
if (string.Equals(assemblyName.Name, "mscorlib.resources", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName.Name, "System.Net.Http", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName.Name, "vstest.console.resources", StringComparison.OrdinalIgnoreCase))
{
return null;
}

// WARNING: Logs must not be added _before_ we check for the above bail-out conditions
var path = string.IsNullOrEmpty(ManagedProfilerDirectory) ? $"{assemblyName.Name}.dll" : Path.Combine(ManagedProfilerDirectory, $"{assemblyName.Name}.dll");
StartupLogger.Debug("Assembly Resolve event received for: {0}. Looking for: {1}", name, path);

if (File.Exists(path))
{
if (name.StartsWith("Datadog.Trace, Version=", StringComparison.Ordinal) && name != AssemblyName)
{
StartupLogger.Debug(" Trying to load '{0}' which does not match the expected version ('{1}'). [Path={2}]", name, AssemblyName, path);
return null;
}

StartupLogger.Debug("Calling Assembly.LoadFrom(\"{0}\")", path);
var assembly = Assembly.LoadFrom(path);
StartupLogger.Debug("Assembly loaded: {0}", assembly.FullName);
return assembly;
}

StartupLogger.Debug("Assembly not found in path: {0}", path);
return null;
}
}
}

Expand Down
18 changes: 17 additions & 1 deletion tracer/src/Datadog.Trace.ClrProfiler.Managed.Loader/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ namespace Datadog.Trace.ClrProfiler.Managed.Loader
/// </summary>
public sealed partial class Startup
{
private const string AssemblyName = "Datadog.Trace, Version=3.43.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb";
// internal so ManagedProfilerAssemblyResolver can reference it. Safe because const strings are inlined at compile time.
// Do not add non-const static members on Startup that the resolver needs - that would re-introduce the .cctor deadlock.
internal const string AssemblyName = "Datadog.Trace, Version=3.43.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb";
private const string AzureAppServicesSiteExtensionKey = "DD_AZURE_APP_SERVICES"; // only set when using the AAS site extension
private const string TracerHomePathKey = "DD_DOTNET_TRACER_HOME";

Expand Down Expand Up @@ -78,7 +80,17 @@ static Startup()

try
{
#if NETFRAMEWORK
// On .NET Framework, route AssemblyResolve through a class other than Startup so
// the handler doesn't require Startup..cctor to have finished. If a configBuilder
// on <appSettings> issues sync-over-async work during Startup..cctor, the async
// continuation may fire AssemblyResolve on a ThreadPool thread; a handler on
// Startup itself would deadlock waiting on Startup..cctor.
ManagedProfilerAssemblyResolver.ManagedProfilerDirectory = ManagedProfilerDirectory;
AppDomain.CurrentDomain.AssemblyResolve += ManagedProfilerAssemblyResolver.OnAssemblyResolve;
#else
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve_ManagedProfilerDependencies;
#endif
}
catch (Exception ex)
{
Expand Down Expand Up @@ -181,7 +193,11 @@ private static void TryInvokeManagedMethod(string typeName, string methodName, s
// We will try to resolve it manually as a last chance.
StartupLogger.Log(ex, "Error on assembly load: {0}, Trying to solve it manually...", assemblyString);

#if NETFRAMEWORK
var assembly = ManagedProfilerAssemblyResolver.ResolveAssembly(assemblyString);
#else
var assembly = ResolveAssembly(assemblyString);
#endif
if (assembly is not null)
{
StartupLogger.Log("Assembly '{0}' was resolved manually.", assemblyString);
Expand Down
Loading