From 3857ce6cbdd9f4507612b38e8fec33ef2e3edb20 Mon Sep 17 00:00:00 2001
From: Elinor Fung <47805090+elinor-fung@users.noreply.github.com>
Date: Mon, 11 Nov 2019 19:55:30 -0800
Subject: [PATCH] Add tracing for Resolving/AssemblyResolve event handler
invocation (dotnet/coreclr#27788)
Commit migrated from https://github.com/dotnet/coreclr/commit/ba0ab50cccb1e04f4e209cb426fcf05952c4fad0
---
.../Runtime/Loader/AssemblyLoadContext.CoreCLR.cs | 6 +
src/coreclr/src/vm/ClrEtwAll.man | 53 ++++
src/coreclr/src/vm/assemblynative.cpp | 24 ++
src/coreclr/src/vm/assemblynative.hpp | 2 +
src/coreclr/src/vm/ecalllist.h | 2 +
.../Loader/binding/tracing/AssemblyToLoad.csproj | 2 +
.../Loader/binding/tracing/BinderEventListener.cs | 166 ++++++++--
.../tracing/BinderTracingTest.EventHandlers.cs | 334 +++++++++++++++++++++
.../Loader/binding/tracing/BinderTracingTest.cs | 82 ++++-
.../binding/tracing/BinderTracingTest.csproj | 24 ++
.../System/Runtime/Loader/AssemblyLoadContext.cs | 42 ++-
11 files changed, 683 insertions(+), 54 deletions(-)
create mode 100644 src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs
index c50cdc7..0700ec3 100644
--- a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs
@@ -35,6 +35,12 @@ namespace System.Runtime.Loader
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool IsTracingEnabled();
+ [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
+ internal static extern bool TraceResolvingHandlerInvoked(string assemblyName, string handlerName, string? alcName, string? resultAssemblyName, string? resultAssemblyPath);
+
+ [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
+ internal static extern bool TraceAssemblyResolveHandlerInvoked(string assemblyName, string handlerName, string? resultAssemblyName, string? resultAssemblyPath);
+
private Assembly InternalLoadFromPath(string? assemblyPath, string? nativeImagePath)
{
RuntimeAssembly? loadedAssembly = null;
diff --git a/src/coreclr/src/vm/ClrEtwAll.man b/src/coreclr/src/vm/ClrEtwAll.man
index 286575d..2ca30d1 100644
--- a/src/coreclr/src/vm/ClrEtwAll.man
+++ b/src/coreclr/src/vm/ClrEtwAll.man
@@ -394,6 +394,8 @@
value="32" eventGUID="{BCF2339E-B0A6-452D-966C-33AC9DD82573}"
message="$(string.RuntimePublisher.AssemblyLoaderTaskMessage)">
+
+
@@ -1705,6 +1707,42 @@
+
+
+
+
+
+
+
+
+
+ %1
+ %2
+ %3
+ %4
+ %5
+ %6
+
+
+
+
+
+
+
+
+
+
+
+
+ %1
+ %2
+ %3
+ %4
+ %5
+
+
+
+
@@ -3502,6 +3540,16 @@
task="AssemblyLoader"
symbol="AssemblyLoadStop" message="$(string.RuntimePublisher.AssemblyLoadStopEventMessage)"/>
+
+
+
+
@@ -6699,6 +6747,8 @@
+
+
@@ -7327,6 +7377,9 @@
+
+
+
diff --git a/src/coreclr/src/vm/assemblynative.cpp b/src/coreclr/src/vm/assemblynative.cpp
index 341de82..2b08568 100644
--- a/src/coreclr/src/vm/assemblynative.cpp
+++ b/src/coreclr/src/vm/assemblynative.cpp
@@ -1428,3 +1428,27 @@ FCIMPL0(FC_BOOL_RET, AssemblyNative::IsTracingEnabled)
FC_RETURN_BOOL(BinderTracing::IsEnabled());
}
FCIMPLEND
+
+// static
+void QCALLTYPE AssemblyNative::TraceResolvingHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR alcName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ FireEtwAssemblyLoadContextResolvingHandlerInvoked(GetClrInstanceId(), assemblyName, handlerName, alcName, resultAssemblyName, resultAssemblyPath);
+
+ END_QCALL;
+}
+
+// static
+void QCALLTYPE AssemblyNative::TraceAssemblyResolveHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ FireEtwAppDomainAssemblyResolveHandlerInvoked(GetClrInstanceId(), assemblyName, handlerName, resultAssemblyName, resultAssemblyPath);
+
+ END_QCALL;
+}
\ No newline at end of file
diff --git a/src/coreclr/src/vm/assemblynative.hpp b/src/coreclr/src/vm/assemblynative.hpp
index 8906813..7f54661 100644
--- a/src/coreclr/src/vm/assemblynative.hpp
+++ b/src/coreclr/src/vm/assemblynative.hpp
@@ -133,6 +133,8 @@ public:
static INT_PTR QCALLTYPE GetLoadContextForAssembly(QCall::AssemblyHandle pAssembly);
static BOOL QCALLTYPE InternalTryGetRawMetadata(QCall::AssemblyHandle assembly, UINT8 **blobRef, INT32 *lengthRef);
+ static void QCALLTYPE TraceResolvingHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR alcName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath);
+ static void QCALLTYPE TraceAssemblyResolveHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath);
};
#endif
diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h
index 278e429..6fedfc8 100644
--- a/src/coreclr/src/vm/ecalllist.h
+++ b/src/coreclr/src/vm/ecalllist.h
@@ -513,6 +513,8 @@ FCFuncStart(gAssemblyLoadContextFuncs)
QCFuncElement("InternalStartProfile", MultiCoreJITNative::InternalStartProfile)
#endif // defined(FEATURE_MULTICOREJIT)
FCFuncElement("IsTracingEnabled", AssemblyNative::IsTracingEnabled)
+ QCFuncElement("TraceResolvingHandlerInvoked", AssemblyNative::TraceResolvingHandlerInvoked)
+ QCFuncElement("TraceAssemblyResolveHandlerInvoked", AssemblyNative::TraceAssemblyResolveHandlerInvoked)
FCFuncEnd()
FCFuncStart(gAssemblyNameFuncs)
diff --git a/src/coreclr/tests/src/Loader/binding/tracing/AssemblyToLoad.csproj b/src/coreclr/tests/src/Loader/binding/tracing/AssemblyToLoad.csproj
index bf4c48e..278cdfe 100644
--- a/src/coreclr/tests/src/Loader/binding/tracing/AssemblyToLoad.csproj
+++ b/src/coreclr/tests/src/Loader/binding/tracing/AssemblyToLoad.csproj
@@ -2,6 +2,8 @@
Library
BuildOnly
+ $(AssemblyName)_$(AssemblyNameSuffix)
+ $(AssemblyName).FileListAbsolute.txt
diff --git a/src/coreclr/tests/src/Loader/binding/tracing/BinderEventListener.cs b/src/coreclr/tests/src/Loader/binding/tracing/BinderEventListener.cs
index 5a8e173..5d07fe7 100644
--- a/src/coreclr/tests/src/Loader/binding/tracing/BinderEventListener.cs
+++ b/src/coreclr/tests/src/Loader/binding/tracing/BinderEventListener.cs
@@ -15,22 +15,65 @@ namespace BinderTracingTests
{
internal class BindOperation
{
- internal AssemblyName AssemblyName;
- internal string AssemblyPath;
- internal AssemblyName RequestingAssembly;
- internal string AssemblyLoadContext;
- internal string RequestingAssemblyLoadContext;
-
- internal bool Success;
- internal AssemblyName ResultAssemblyName;
- internal string ResultAssemblyPath;
- internal bool Cached;
-
- internal Guid ActivityId;
- internal Guid ParentActivityId;
-
- internal bool Completed;
- internal bool Nested;
+ public AssemblyName AssemblyName { get; internal set; }
+ public string AssemblyPath { get; internal set; }
+ public AssemblyName RequestingAssembly { get; internal set; }
+ public string AssemblyLoadContext { get; internal set; }
+ public string RequestingAssemblyLoadContext { get; internal set; }
+
+ public bool Success { get; internal set; }
+ public AssemblyName ResultAssemblyName { get; internal set; }
+ public string ResultAssemblyPath { get; internal set; }
+ public bool Cached { get; internal set; }
+
+ public Guid ActivityId { get; internal set; }
+ public Guid ParentActivityId { get; internal set; }
+
+ public bool Completed { get; internal set; }
+ public bool Nested { get; internal set; }
+
+ public List AssemblyLoadContextResolvingHandlers { get; internal set; }
+ public List AppDomainAssemblyResolveHandlers { get; internal set; }
+
+ public List NestedBinds { get; internal set; }
+
+ public BindOperation()
+ {
+ AssemblyLoadContextResolvingHandlers = new List();
+ AppDomainAssemblyResolveHandlers = new List();
+ NestedBinds = new List();
+ }
+
+ public override string ToString()
+ {
+ var sb = new System.Text.StringBuilder();
+ sb.Append(AssemblyName);
+ sb.Append($" - Request: Path={AssemblyPath}, ALC={AssemblyLoadContext}, RequestingAssembly={RequestingAssembly}, RequestingALC={RequestingAssemblyLoadContext}");
+ sb.Append($" - Result: Success={Success}, Name={ResultAssemblyName}, Path={ResultAssemblyPath}, Cached={Cached}");
+ return sb.ToString();
+ }
+ }
+
+ internal class HandlerInvocation
+ {
+ public AssemblyName AssemblyName { get; internal set; }
+ public string HandlerName { get; internal set; }
+ public string AssemblyLoadContext { get; internal set; }
+
+ public AssemblyName ResultAssemblyName { get; internal set; }
+ public string ResultAssemblyPath { get; internal set; }
+
+ public override string ToString()
+ {
+ var sb = new System.Text.StringBuilder();
+ sb.Append($"{HandlerName} - ");
+ sb.Append($"Request: Name={AssemblyName.FullName}");
+ if (!string.IsNullOrEmpty(AssemblyLoadContext))
+ sb.Append($", ALC={AssemblyLoadContext}");
+
+ sb.Append($" - Result: Name={ResultAssemblyName?.FullName}, Path={ResultAssemblyPath}");
+ return sb.ToString();
+ }
}
internal sealed class BinderEventListener : EventListener
@@ -80,34 +123,32 @@ namespace BinderTracingTests
if (data.EventSource.Name != "Microsoft-Windows-DotNETRuntime")
return;
- object GetData(string name) => data.Payload[data.PayloadNames.IndexOf(name)];
- string GetDataString(string name) => GetData(name).ToString();
+ object GetData(string name)
+ {
+ int index = data.PayloadNames.IndexOf(name);
+ return index >= 0 ? data.Payload[index] : null;
+ };
+ string GetDataString(string name) => GetData(name) as string;
switch (data.EventName)
{
case "AssemblyLoadStart":
+ {
+ BindOperation bindOperation = ParseAssemblyLoadStartEvent(data, GetDataString);
lock (eventsLock)
{
Assert.IsTrue(!bindOperations.ContainsKey(data.ActivityId), "AssemblyLoadStart should not exist for same activity ID ");
- var bindOperation = new BindOperation()
- {
- AssemblyName = new AssemblyName(GetDataString("AssemblyName")),
- AssemblyPath = GetDataString("AssemblyPath"),
- AssemblyLoadContext = GetDataString("AssemblyLoadContext"),
- RequestingAssemblyLoadContext = GetDataString("RequestingAssemblyLoadContext"),
- ActivityId = data.ActivityId,
- ParentActivityId = data.RelatedActivityId,
- Nested = bindOperations.ContainsKey(data.RelatedActivityId)
- };
- string requestingAssembly = GetDataString("RequestingAssembly");
- if (!string.IsNullOrEmpty(requestingAssembly))
+ bindOperation.Nested = bindOperations.ContainsKey(data.RelatedActivityId);
+ bindOperations.Add(data.ActivityId, bindOperation);
+ if (bindOperation.Nested)
{
- bindOperation.RequestingAssembly = new AssemblyName(requestingAssembly);
+ bindOperations[data.RelatedActivityId].NestedBinds.Add(bindOperation);
}
- bindOperations.Add(data.ActivityId, bindOperation);
}
break;
+ }
case "AssemblyLoadStop":
+ {
lock (eventsLock)
{
Assert.IsTrue(bindOperations.ContainsKey(data.ActivityId), "AssemblyLoadStop should have a matching AssemblyBindStart");
@@ -123,7 +164,68 @@ namespace BinderTracingTests
bind.Completed = true;
}
break;
+ }
+ case "AssemblyLoadContextResolvingHandlerInvoked":
+ {
+ HandlerInvocation handlerInvocation = ParseHandlerInvokedEvent(GetDataString);
+ lock (eventsLock)
+ {
+ Assert.IsTrue(bindOperations.ContainsKey(data.ActivityId), "AssemblyLoadContextResolvingHandlerInvoked should have a matching AssemblyBindStart");
+ BindOperation bind = bindOperations[data.ActivityId];
+ bind.AssemblyLoadContextResolvingHandlers.Add(handlerInvocation);
+ }
+ break;
+ }
+ case "AppDomainAssemblyResolveHandlerInvoked":
+ {
+ HandlerInvocation handlerInvocation = ParseHandlerInvokedEvent(GetDataString);
+ lock (eventsLock)
+ {
+ Assert.IsTrue(bindOperations.ContainsKey(data.ActivityId), "AppDomainAssemblyResolveHandlerInvoked should have a matching AssemblyBindStart");
+ BindOperation bind = bindOperations[data.ActivityId];
+ bind.AppDomainAssemblyResolveHandlers.Add(handlerInvocation);
+ }
+ break;
+ }
}
}
+
+ private BindOperation ParseAssemblyLoadStartEvent(EventWrittenEventArgs data, Func getDataString)
+ {
+ var bindOperation = new BindOperation()
+ {
+ AssemblyName = new AssemblyName(getDataString("AssemblyName")),
+ AssemblyPath = getDataString("AssemblyPath"),
+ AssemblyLoadContext = getDataString("AssemblyLoadContext"),
+ RequestingAssemblyLoadContext = getDataString("RequestingAssemblyLoadContext"),
+ ActivityId = data.ActivityId,
+ ParentActivityId = data.RelatedActivityId,
+ };
+ string requestingAssembly = getDataString("RequestingAssembly");
+ if (!string.IsNullOrEmpty(requestingAssembly))
+ {
+ bindOperation.RequestingAssembly = new AssemblyName(requestingAssembly);
+ }
+
+ return bindOperation;
+ }
+
+ private HandlerInvocation ParseHandlerInvokedEvent(Func getDataString)
+ {
+ var handlerInvocation = new HandlerInvocation()
+ {
+ AssemblyName = new AssemblyName(getDataString("AssemblyName")),
+ HandlerName = getDataString("HandlerName"),
+ AssemblyLoadContext = getDataString("AssemblyLoadContext"),
+ ResultAssemblyPath = getDataString("ResultAssemblyPath")
+ };
+ string resultName = getDataString("ResultAssemblyName");
+ if (!string.IsNullOrEmpty(resultName))
+ {
+ handlerInvocation.ResultAssemblyName = new AssemblyName(resultName);
+ }
+
+ return handlerInvocation;
+ }
}
}
diff --git a/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs b/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs
new file mode 100644
index 0000000..bf97a43
--- /dev/null
+++ b/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs
@@ -0,0 +1,334 @@
+// 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.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+
+using TestLibrary;
+
+namespace BinderTracingTests
+{
+ partial class BinderTracingTest
+ {
+ [BinderTest]
+ public static BindOperation AssemblyLoadContextResolving_ReturnNull()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ using (var handlers = new Handlers(HandlerReturn.Null, AssemblyLoadContext.Default))
+ {
+ try
+ {
+ Assembly.Load(assemblyName);
+ }
+ catch { }
+
+ Assert.AreEqual(1, handlers.Invocations.Count);
+ Assert.AreEqual(0, handlers.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
+ RequestingAssemblyLoadContext = DefaultALC,
+ Success = false,
+ Cached = false,
+ AssemblyLoadContextResolvingHandlers = handlers.Invocations,
+ NestedBinds = handlers.Binds
+ };
+ }
+ }
+
+ [BinderTest]
+ public static BindOperation AssemblyLoadContextResolving_LoadAssembly()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ CustomALC alc = new CustomALC(nameof(AssemblyLoadContextResolving_LoadAssembly));
+ using (var handlers = new Handlers(HandlerReturn.RequestedAssembly, alc))
+ {
+ Assembly asm = alc.LoadFromAssemblyName(assemblyName);
+
+ Assert.AreEqual(1, handlers.Invocations.Count);
+ Assert.AreEqual(1, handlers.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ AssemblyLoadContextResolvingHandlers = handlers.Invocations,
+ NestedBinds = handlers.Binds
+ };
+ }
+ }
+
+ [BinderTest]
+ public static BindOperation AssemblyLoadContextResolving_NameMismatch()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ CustomALC alc = new CustomALC(nameof(AssemblyLoadContextResolving_NameMismatch));
+ using (var handlers = new Handlers(HandlerReturn.NameMismatch, alc))
+ {
+ Assert.Throws(() => alc.LoadFromAssemblyName(assemblyName));
+
+ Assert.AreEqual(1, handlers.Invocations.Count);
+ Assert.AreEqual(1, handlers.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = false,
+ Cached = false,
+ AssemblyLoadContextResolvingHandlers = handlers.Invocations,
+ NestedBinds = handlers.Binds
+ };
+ }
+ }
+
+ [BinderTest]
+ public static BindOperation AssemblyLoadContextResolving_MultipleHandlers()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ CustomALC alc = new CustomALC(nameof(AssemblyLoadContextResolving_NameMismatch));
+ using (var handlerNull = new Handlers(HandlerReturn.Null, alc))
+ using (var handlerLoad = new Handlers(HandlerReturn.RequestedAssembly, alc))
+ {
+ Assembly asm = alc.LoadFromAssemblyName(assemblyName);
+
+ Assert.AreEqual(1, handlerNull.Invocations.Count);
+ Assert.AreEqual(0, handlerNull.Binds.Count);
+ Assert.AreEqual(1, handlerLoad.Invocations.Count);
+ Assert.AreEqual(1, handlerLoad.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ AssemblyLoadContextResolvingHandlers = handlerNull.Invocations.Concat(handlerLoad.Invocations).ToList(),
+ NestedBinds = handlerNull.Binds.Concat(handlerLoad.Binds).ToList()
+ };
+ }
+ }
+
+ [BinderTest]
+ public static BindOperation AppDomainAssemblyResolve_ReturnNull()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ using (var handlers = new Handlers(HandlerReturn.Null))
+ {
+ try
+ {
+ Assembly.Load(assemblyName);
+ }
+ catch { }
+
+ Assert.AreEqual(1, handlers.Invocations.Count);
+ Assert.AreEqual(0, handlers.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
+ RequestingAssemblyLoadContext = DefaultALC,
+ Success = false,
+ Cached = false,
+ AppDomainAssemblyResolveHandlers = handlers.Invocations,
+ NestedBinds = handlers.Binds
+ };
+ }
+ }
+
+ [BinderTest]
+ public static BindOperation AppDomainAssemblyResolve_LoadAssembly()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ CustomALC alc = new CustomALC(nameof(AppDomainAssemblyResolve_LoadAssembly));
+ using (var handlers = new Handlers(HandlerReturn.RequestedAssembly))
+ {
+ Assembly asm = alc.LoadFromAssemblyName(assemblyName);
+
+ Assert.AreEqual(1, handlers.Invocations.Count);
+ Assert.AreEqual(1, handlers.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ AppDomainAssemblyResolveHandlers = handlers.Invocations,
+ NestedBinds = handlers.Binds
+ };
+ }
+ }
+
+ [BinderTest]
+ public static BindOperation AppDomainAssemblyResolve_NameMismatch()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ CustomALC alc = new CustomALC(nameof(AppDomainAssemblyResolve_NameMismatch));
+ using (var handlers = new Handlers(HandlerReturn.NameMismatch))
+ {
+ // Result of AssemblyResolve event does not get checked for name mismatch
+ Assembly asm = alc.LoadFromAssemblyName(assemblyName);
+
+ Assert.AreEqual(1, handlers.Invocations.Count);
+ Assert.AreEqual(1, handlers.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ AppDomainAssemblyResolveHandlers = handlers.Invocations,
+ NestedBinds = handlers.Binds
+ };
+ }
+ }
+
+ [BinderTest]
+ public static BindOperation AppDomainAssemblyResolve_MultipleHandlers()
+ {
+ var assemblyName = new AssemblyName(SubdirectoryAssemblyName);
+ CustomALC alc = new CustomALC(nameof(AppDomainAssemblyResolve_LoadAssembly));
+ using (var handlerNull = new Handlers(HandlerReturn.Null))
+ using (var handlerLoad = new Handlers(HandlerReturn.RequestedAssembly))
+ {
+ Assembly asm = alc.LoadFromAssemblyName(assemblyName);
+
+ Assert.AreEqual(1, handlerNull.Invocations.Count);
+ Assert.AreEqual(0, handlerNull.Binds.Count);
+ Assert.AreEqual(1, handlerLoad.Invocations.Count);
+ Assert.AreEqual(1, handlerLoad.Binds.Count);
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ AppDomainAssemblyResolveHandlers = handlerNull.Invocations.Concat(handlerLoad.Invocations).ToList(),
+ NestedBinds = handlerNull.Binds.Concat(handlerLoad.Binds).ToList()
+ };
+ }
+ }
+
+ private enum HandlerReturn
+ {
+ Null,
+ RequestedAssembly,
+ NameMismatch
+ }
+
+ private class Handlers : IDisposable
+ {
+ private HandlerReturn handlerReturn;
+ private AssemblyLoadContext alc;
+
+ internal readonly List Invocations = new List();
+ internal readonly List Binds = new List();
+
+ public Handlers(HandlerReturn handlerReturn)
+ {
+ this.handlerReturn = handlerReturn;
+ AppDomain.CurrentDomain.AssemblyResolve += OnAppDomainAssemblyResolve;
+ }
+
+ public Handlers(HandlerReturn handlerReturn, AssemblyLoadContext alc)
+ {
+ this.handlerReturn = handlerReturn;
+ this.alc = alc;
+ this.alc.Resolving += OnAssemblyLoadContextResolving;
+ }
+
+ public void Dispose()
+ {
+ AppDomain.CurrentDomain.AssemblyResolve -= OnAppDomainAssemblyResolve;
+ if (alc != null)
+ alc.Resolving -= OnAssemblyLoadContextResolving;
+ }
+
+ private Assembly OnAssemblyLoadContextResolving(AssemblyLoadContext context, AssemblyName assemblyName)
+ {
+ Assembly asm = ResolveAssembly(context, assemblyName);
+ var invocation = new HandlerInvocation()
+ {
+ AssemblyName = assemblyName,
+ HandlerName = nameof(OnAssemblyLoadContextResolving),
+ AssemblyLoadContext = context == AssemblyLoadContext.Default ? context.Name : context.ToString(),
+ };
+ if (asm != null)
+ {
+ invocation.ResultAssemblyName = asm.GetName();
+ invocation.ResultAssemblyPath = asm.Location;
+ }
+
+ Invocations.Add(invocation);
+ return asm;
+ }
+
+ private Assembly OnAppDomainAssemblyResolve(object sender, ResolveEventArgs args)
+ {
+ var assemblyName = new AssemblyName(args.Name);
+ var customContext = new CustomALC(nameof(OnAppDomainAssemblyResolve));
+ Assembly asm = ResolveAssembly(customContext, assemblyName);
+ var invocation = new HandlerInvocation()
+ {
+ AssemblyName = assemblyName,
+ HandlerName = nameof(OnAppDomainAssemblyResolve),
+ };
+ if (asm != null)
+ {
+ invocation.ResultAssemblyName = asm.GetName();
+ invocation.ResultAssemblyPath = asm.Location;
+ }
+
+ Invocations.Add(invocation);
+ return asm;
+ }
+
+ private Assembly ResolveAssembly(AssemblyLoadContext context, AssemblyName assemblyName)
+ {
+ if (handlerReturn == HandlerReturn.Null)
+ return null;
+
+ string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ string fileName = handlerReturn == HandlerReturn.RequestedAssembly ? $"{assemblyName.Name}.dll" : $"{assemblyName.Name}Mismatch.dll";
+ string assemblyPath = Path.Combine(appPath, "DependentAssemblies", fileName);
+
+ if (!File.Exists(assemblyPath))
+ return null;
+
+ Assembly asm = context.LoadFromAssemblyPath(assemblyPath);
+ var bind = new BindOperation()
+ {
+ AssemblyName = asm.GetName(),
+ AssemblyPath = assemblyPath,
+ AssemblyLoadContext = context == AssemblyLoadContext.Default ? context.Name : context.ToString(),
+ RequestingAssembly = CoreLibName,
+ RequestingAssemblyLoadContext = DefaultALC,
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false
+ };
+ Binds.Add(bind);
+ return asm;
+ }
+ }
+ }
+}
diff --git a/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.cs b/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.cs
index 9aee2c4..4373381 100644
--- a/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.cs
+++ b/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -25,7 +26,7 @@ namespace BinderTracingTests
}
}
- class BinderTracingTest
+ partial class BinderTracingTest
{
public class CustomALC : AssemblyLoadContext
{
@@ -35,6 +36,9 @@ namespace BinderTracingTests
private const string DefaultALC = "Default";
private const string DependentAssemblyName = "AssemblyToLoad";
+ private const string SubdirectoryAssemblyName = "AssemblyToLoad_Subdirectory";
+
+ private static readonly AssemblyName CoreLibName = typeof(object).Assembly.GetName();
[BinderTest]
public static BindOperation LoadFile()
@@ -47,6 +51,7 @@ namespace BinderTracingTests
AssemblyName = executingAssembly.GetName(),
AssemblyPath = executingAssembly.Location,
AssemblyLoadContext = AssemblyLoadContext.GetLoadContext(asm).ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
@@ -66,6 +71,7 @@ namespace BinderTracingTests
{
AssemblyName = executingAssembly.GetName(),
AssemblyLoadContext = AssemblyLoadContext.GetLoadContext(asm).ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
@@ -86,6 +92,7 @@ namespace BinderTracingTests
{
AssemblyName = executingAssembly.GetName(),
AssemblyLoadContext = alc.ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
@@ -106,6 +113,7 @@ namespace BinderTracingTests
AssemblyName = executingAssembly.GetName(),
AssemblyPath = executingAssembly.Location,
AssemblyLoadContext = alc.ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
@@ -143,6 +151,7 @@ namespace BinderTracingTests
AssemblyName = executingAssembly.GetName(),
AssemblyPath = executingAssembly.Location,
AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
@@ -154,13 +163,14 @@ namespace BinderTracingTests
[BinderTest(isolate: true)]
public static BindOperation PlatformAssembly()
{
- string assemblyName = "System.Xml";
+ var assemblyName = new AssemblyName("System.Xml");
Assembly asm = Assembly.Load(assemblyName);
return new BindOperation()
{
- AssemblyName = new AssemblyName(assemblyName),
+ AssemblyName = assemblyName,
AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
@@ -191,6 +201,7 @@ namespace BinderTracingTests
{
AssemblyName = new AssemblyName(assemblyName),
AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
RequestingAssemblyLoadContext = DefaultALC,
Success = false,
Cached = false
@@ -206,8 +217,8 @@ namespace BinderTracingTests
{
AssemblyName = new AssemblyName(DependentAssemblyName),
AssemblyLoadContext = DefaultALC,
- RequestingAssemblyLoadContext = DefaultALC,
RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
+ RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = t.Assembly.GetName(),
ResultAssemblyPath = t.Assembly.Location,
@@ -368,7 +379,7 @@ namespace BinderTracingTests
// Run specific test - first argument should be the test method name
MethodInfo method = typeof(BinderTracingTest)
.GetMethod(args[0], BindingFlags.Public | BindingFlags.Static);
- Assert.IsTrue(method != null && method.GetCustomAttribute() != null && method.ReturnType == typeof(BindOperation));
+ Assert.IsTrue(method != null && method.GetCustomAttribute() != null && method.ReturnType == typeof(BindOperation), "Invalid test muthod specified");
success = RunSingleTest(method);
}
}
@@ -459,32 +470,79 @@ namespace BinderTracingTests
Assert.IsTrue(binds.Length == 1, $"Bind event count for {assemblyName} - expected: 1, actual: {binds.Length}");
BindOperation actual = binds[0];
+ ValidateBindOperation(expected, actual);
+ }
+
+ private static void ValidateBindOperation(BindOperation expected, BindOperation actual)
+ {
ValidateAssemblyName(expected.AssemblyName, actual.AssemblyName, nameof(BindOperation.AssemblyName));
Assert.AreEqual(expected.AssemblyPath ?? string.Empty, actual.AssemblyPath, $"Unexpected value for {nameof(BindOperation.AssemblyPath)} on event");
Assert.AreEqual(expected.AssemblyLoadContext, actual.AssemblyLoadContext, $"Unexpected value for {nameof(BindOperation.AssemblyLoadContext)} on event");
- ValidateAssemblyName(expected.RequestingAssembly, actual.RequestingAssembly, nameof(BindOperation.RequestingAssembly));
Assert.AreEqual(expected.RequestingAssemblyLoadContext ?? string.Empty, actual.RequestingAssemblyLoadContext, $"Unexpected value for {nameof(BindOperation.RequestingAssemblyLoadContext)} on event");
+ ValidateAssemblyName(expected.RequestingAssembly, actual.RequestingAssembly, nameof(BindOperation.RequestingAssembly));
Assert.AreEqual(expected.Success, actual.Success, $"Unexpected value for {nameof(BindOperation.Success)} on event");
Assert.AreEqual(expected.ResultAssemblyPath ?? string.Empty, actual.ResultAssemblyPath, $"Unexpected value for {nameof(BindOperation.ResultAssemblyPath)} on event");
Assert.AreEqual(expected.Cached, actual.Cached, $"Unexpected value for {nameof(BindOperation.Cached)} on event");
ValidateAssemblyName(expected.ResultAssemblyName, actual.ResultAssemblyName, nameof(BindOperation.ResultAssemblyName));
+
+ ValidateHandlerInvocations(expected.AssemblyLoadContextResolvingHandlers, actual.AssemblyLoadContextResolvingHandlers, "AssemblyLoadContextResolving");
+ ValidateHandlerInvocations(expected.AppDomainAssemblyResolveHandlers, actual.AppDomainAssemblyResolveHandlers, "AppDomainAssemblyResolve");
+
+ ValidateNestedBinds(expected.NestedBinds, actual.NestedBinds);
+ }
+
+ private static bool AssemblyNamesMatch(AssemblyName name1, AssemblyName name2)
+ {
+ if (name1 == null || name2 == null)
+ return name1 == null && name2 == null;
+
+ return name1.Name == name2.Name
+ && ((name1.Version == null && name2.Version == null) || name1.Version == name2.Version)
+ && ((string.IsNullOrEmpty(name1.CultureName) && string.IsNullOrEmpty(name1.CultureName)) || name1.CultureName == name2.CultureName);
}
private static void ValidateAssemblyName(AssemblyName expected, AssemblyName actual, string propertyName)
{
- if (expected == null)
+ Assert.IsTrue(AssemblyNamesMatch(expected, actual), $"Unexpected value for {propertyName} on event - expected: {expected}, actual: {actual}");
+ }
+
+ private static void ValidateHandlerInvocations(List expected, List actual, string eventName)
+ {
+ Assert.AreEqual(expected.Count, actual.Count, $"Unexpected handler invocation count for {eventName}");
+
+ foreach (var match in expected)
{
- return;
+ Predicate pred = h =>
+ AssemblyNamesMatch(h.AssemblyName, match.AssemblyName)
+ && h.HandlerName == match.HandlerName
+ && h.AssemblyLoadContext == match.AssemblyLoadContext
+ && AssemblyNamesMatch(h.ResultAssemblyName, match.ResultAssemblyName)
+ && h.ResultAssemblyPath == match.ResultAssemblyPath;
+ Assert.IsTrue(actual.Exists(pred), $"Handler invocation not found: {match.ToString()}");
}
+ }
- if (expected.Version != null)
+ private static bool BindOperationsMatch(BindOperation bind1, BindOperation bind2)
+ {
+ try
{
- Assert.AreEqual(expected.FullName, actual.FullName, $"Unexpected value for {propertyName} on event");
+ ValidateBindOperation(bind1, bind2);
}
- else
+ catch (AssertTestException e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static void ValidateNestedBinds(List expected, List actual)
+ {
+ foreach (var match in expected)
{
- Assert.AreEqual(expected.Name, actual.Name, $"Unexpected value for {propertyName} on event");
+ Predicate pred = b => BindOperationsMatch(match, b);
+ Assert.IsTrue(actual.Exists(pred), $"Nested bind operation not found: {match.ToString()}");
}
}
}
diff --git a/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.csproj b/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.csproj
index f581bf1..0df6d4f 100644
--- a/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.csproj
+++ b/src/coreclr/tests/src/Loader/binding/tracing/BinderTracingTest.csproj
@@ -4,10 +4,34 @@
+
+
+
+
+ AssemblyNameSuffix=Subdirectory
+ false
+ Content
+ Always
+
+
+ AssemblyNameSuffix=SubdirectoryMismatch
+ false
+ Content
+ Always
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs
index ef54631..5ae3f8d 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs
@@ -582,18 +582,29 @@ namespace System.Runtime.Loader
return context.ResolveSatelliteAssembly(assemblyName);
}
- private Assembly? GetFirstResolvedAssembly(AssemblyName assemblyName)
+ private Assembly? GetFirstResolvedAssemblyFromResolvingEvent(AssemblyName assemblyName)
{
Assembly? resolvedAssembly = null;
- Func? assemblyResolveHandler = _resolving;
+ Func? resolvingHandler = _resolving;
- if (assemblyResolveHandler != null)
+ if (resolvingHandler != null)
{
// Loop through the event subscribers and return the first non-null Assembly instance
- foreach (Func handler in assemblyResolveHandler.GetInvocationList())
+ foreach (Func handler in resolvingHandler.GetInvocationList())
{
resolvedAssembly = handler(this, assemblyName);
+#if CORECLR
+ if (AssemblyLoadContext.IsTracingEnabled())
+ {
+ AssemblyLoadContext.TraceResolvingHandlerInvoked(
+ assemblyName.FullName,
+ handler.Method.Name,
+ this != AssemblyLoadContext.Default ? ToString() : Name,
+ resolvedAssembly?.FullName,
+ resolvedAssembly != null && !resolvedAssembly.IsDynamic ? resolvedAssembly.Location : null);
+ }
+#endif // CORECLR
if (resolvedAssembly != null)
{
return resolvedAssembly;
@@ -606,6 +617,11 @@ namespace System.Runtime.Loader
private static Assembly ValidateAssemblyNameWithSimpleName(Assembly assembly, string? requestedSimpleName)
{
+ if (string.IsNullOrEmpty(requestedSimpleName))
+ {
+ throw new ArgumentException(SR.ArgumentNull_AssemblyNameName);
+ }
+
// Get the name of the loaded assembly
string? loadedSimpleName = null;
@@ -619,10 +635,6 @@ namespace System.Runtime.Loader
}
// The simple names should match at the very least
- if (string.IsNullOrEmpty(requestedSimpleName))
- {
- throw new ArgumentException(SR.ArgumentNull_AssemblyNameName);
- }
if (string.IsNullOrEmpty(loadedSimpleName) || !requestedSimpleName.Equals(loadedSimpleName, StringComparison.InvariantCultureIgnoreCase))
{
throw new InvalidOperationException(SR.Argument_CustomAssemblyLoadContextRequestedNameMismatch);
@@ -648,8 +660,8 @@ namespace System.Runtime.Loader
{
string? simpleName = assemblyName.Name;
- // Invoke the AssemblyResolve event callbacks if wired up
- Assembly? assembly = GetFirstResolvedAssembly(assemblyName);
+ // Invoke the Resolving event callbacks if wired up
+ Assembly? assembly = GetFirstResolvedAssemblyFromResolvingEvent(assemblyName);
if (assembly != null)
{
assembly = ValidateAssemblyNameWithSimpleName(assembly, simpleName);
@@ -692,6 +704,16 @@ namespace System.Runtime.Loader
foreach (ResolveEventHandler handler in eventHandler.GetInvocationList())
{
Assembly? asm = handler(AppDomain.CurrentDomain, args);
+#if CORECLR
+ if (eventHandler == AssemblyResolve && AssemblyLoadContext.IsTracingEnabled())
+ {
+ AssemblyLoadContext.TraceAssemblyResolveHandlerInvoked(
+ name,
+ handler.Method.Name,
+ asm?.FullName,
+ asm != null && !asm.IsDynamic ? asm.Location : null);
+ }
+#endif // CORECLR
RuntimeAssembly? ret = GetRuntimeAssembly(asm);
if (ret != null)
return ret;
--
2.7.4