[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;
value="32" eventGUID="{BCF2339E-B0A6-452D-966C-33AC9DD82573}"
message="$(string.RuntimePublisher.AssemblyLoaderTaskMessage)">
<opcodes>
+ <opcode name="AssemblyLoadContextResolvingHandlerInvoked" message="$(string.RuntimePublisher.AssemblyLoadContextResolvingHandlerInvokedOpcodeMessage)" symbol="CLR_ALC_RESOLVING_HANDLER_INVOKED_OPCODE" value="12"/>
+ <opcode name="AppDomainAssemblyResolveHandlerInvoked" message="$(string.RuntimePublisher.AppDomainAssemblyResolveHandlerInvokedOpcodeMessage)" symbol="CLR_APPDOMAIN_ASSEMBLY_RESOLVE_HANDLER_INVOKED_OPCODE" value="13"/>
</opcodes>
</task>
<!--Next available ID is 33-->
</UserData>
</template>
+ <template tid="AssemblyLoadContextResolvingHandlerInvoked">
+ <data name="ClrInstanceID" inType="win:UInt16" />
+ <data name="AssemblyName" inType="win:UnicodeString" />
+ <data name="HandlerName" inType="win:UnicodeString" />
+ <data name="AssemblyLoadContext" inType="win:UnicodeString" />
+ <data name="ResultAssemblyName" inType="win:UnicodeString" />
+ <data name="ResultAssemblyPath" inType="win:UnicodeString" />
+ <UserData>
+ <AssemblyLoadContextResolvingHandlerInvoked xmlns="myNs">
+ <ClrInstanceID> %1 </ClrInstanceID>
+ <AssemblyName> %2 </AssemblyName>
+ <HandlerName> %3 </HandlerName>
+ <AssemblyLoadContext> %4 </AssemblyLoadContext>
+ <ResultAssemblyName> %5 </ResultAssemblyName>
+ <ResultAssemblyPath> %6 </ResultAssemblyPath>
+ </AssemblyLoadContextResolvingHandlerInvoked>
+ </UserData>
+ </template>
+
+ <template tid="AppDomainAssemblyResolveHandlerInvoked">
+ <data name="ClrInstanceID" inType="win:UInt16" />
+ <data name="AssemblyName" inType="win:UnicodeString" />
+ <data name="HandlerName" inType="win:UnicodeString" />
+ <data name="ResultAssemblyName" inType="win:UnicodeString" />
+ <data name="ResultAssemblyPath" inType="win:UnicodeString" />
+ <UserData>
+ <AppDomainAssemblyResolveHandlerInvoked xmlns="myNs">
+ <ClrInstanceID> %1 </ClrInstanceID>
+ <AssemblyName> %2 </AssemblyName>
+ <HandlerName> %3 </HandlerName>
+ <ResultAssemblyName> %4 </ResultAssemblyName>
+ <ResultAssemblyPath> %5 </ResultAssemblyPath>
+ </AppDomainAssemblyResolveHandlerInvoked>
+ </UserData>
+ </template>
+
<template tid="MethodLoadUnload">
<data name="MethodID" inType="win:UInt64" outType="win:HexInt64" />
<data name="ModuleID" inType="win:UInt64" outType="win:HexInt64" />
task="AssemblyLoader"
symbol="AssemblyLoadStop" message="$(string.RuntimePublisher.AssemblyLoadStopEventMessage)"/>
+ <event value="293" version="0" level="win:Informational" template="AssemblyLoadContextResolvingHandlerInvoked"
+ keywords ="AssemblyLoaderKeyword" opcode="AssemblyLoadContextResolvingHandlerInvoked"
+ task="AssemblyLoader"
+ symbol="AssemblyLoadContextResolvingHandlerInvoked" message="$(string.RuntimePublisher.AssemblyLoadContextResolvingHandlerInvokedEventMessage)"/>
+
+ <event value="294" version="0" level="win:Informational" template="AppDomainAssemblyResolveHandlerInvoked"
+ keywords ="AssemblyLoaderKeyword" opcode="AppDomainAssemblyResolveHandlerInvoked"
+ task="AssemblyLoader"
+ symbol="AppDomainAssemblyResolveHandlerInvoked" message="$(string.RuntimePublisher.AppDomainAssemblyResolveHandlerInvokedEventMessage)"/>
+
</events>
</provider>
<string id="RuntimePublisher.AppDomainUnload_V1EventMessage" value="AppDomainID=%1;%nAppDomainFlags=%2;%nAppDomainName=%3;%nAppDomainIndex=%4;%nClrInstanceID=%5" />
<string id="RuntimePublisher.AssemblyLoadStartEventMessage" value="ClrInstanceID=%1;%nAssemblyName=%2;%nAssemblyPath=%3;%nRequestingAssembly=%4;%nAssemblyLoadContext=%5;%nRequestingAssemblyLoadContext=%6" />
<string id="RuntimePublisher.AssemblyLoadStopEventMessage" value="ClrInstanceID=%1;%nAssemblyName=%2;%nAssemblyPath=%3;%nRequestingAssembly=%4;%nAssemblyLoadContext=%5;%nRequestingAssemblyLoadContext=%6;%nSuccess=%7;%nResultAssemblyName=%8;%nResultAssemblyPath=%9;%nCached=%10" />
+ <string id="RuntimePublisher.AssemblyLoadContextResolvingHandlerInvokedEventMessage" value="ClrInstanceID=%1;%nAssemblyName=%2;%nHandlerName=%3;%nAssemblyLoadContext=%4;%nResultAssemblyName=%5;%nResultAssemblyPath=%6" />
+ <string id="RuntimePublisher.AppDomainAssemblyResolveHandlerInvokedEventMessage" value="ClrInstanceID=%1;%nAssemblyName=%2;%nHandlerName=%3;%nResultAssemblyName=%4;%nResultAssemblyPath=%5" />
<string id="RuntimePublisher.StackEventMessage" value="ClrInstanceID=%1;%nReserved1=%2;%nReserved2=%3;%nFrameCount=%4;%nStack=%5" />
<string id="RuntimePublisher.AppDomainMemAllocatedEventMessage" value="AppDomainID=%1;%nAllocated=%2;%nClrInstanceID=%3" />
<string id="RuntimePublisher.AppDomainMemSurvivedEventMessage" value="AppDomainID=%1;%nSurvived=%2;%nProcessSurvived=%3;%nClrInstanceID=%4" />
<string id="RuntimePublisher.TieredCompilationPauseOpcodeMessage" value="Pause" />
<string id="RuntimePublisher.TieredCompilationResumeOpcodeMessage" value="Resume" />
+ <string id="RuntimePublisher.AssemblyLoadContextResolvingHandlerInvokedOpcodeMessage" value="AssemblyLoadContextResolvingHandlerInvoked" />
+ <string id="RuntimePublisher.AppDomainAssemblyResolveHandlerInvokedOpcodeMessage" value="AppDomainAssemblyResolveHandlerInvoked" />
+
<string id="RundownPublisher.MethodDCStartOpcodeMessage" value="DCStart" />
<string id="RundownPublisher.MethodDCEndOpcodeMessage" value="DCStop" />
<string id="RundownPublisher.MethodDCStartVerboseOpcodeMessage" value="DCStartVerbose" />
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
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
QCFuncElement("InternalStartProfile", MultiCoreJITNative::InternalStartProfile)
#endif // defined(FEATURE_MULTICOREJIT)
FCFuncElement("IsTracingEnabled", AssemblyNative::IsTracingEnabled)
+ QCFuncElement("TraceResolvingHandlerInvoked", AssemblyNative::TraceResolvingHandlerInvoked)
+ QCFuncElement("TraceAssemblyResolveHandlerInvoked", AssemblyNative::TraceAssemblyResolveHandlerInvoked)
FCFuncEnd()
FCFuncStart(gAssemblyNameFuncs)
<PropertyGroup>
<OutputType>Library</OutputType>
<CLRTestKind>BuildOnly</CLRTestKind>
+ <AssemblyName Condition="'$(AssemblyNameSuffix)'!=''">$(AssemblyName)_$(AssemblyNameSuffix)</AssemblyName>
+ <CleanFile>$(AssemblyName).FileListAbsolute.txt</CleanFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="AssemblyToLoad.cs" />
{
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<HandlerInvocation> AssemblyLoadContextResolvingHandlers { get; internal set; }
+ public List<HandlerInvocation> AppDomainAssemblyResolveHandlers { get; internal set; }
+
+ public List<BindOperation> NestedBinds { get; internal set; }
+
+ public BindOperation()
+ {
+ AssemblyLoadContextResolvingHandlers = new List<HandlerInvocation>();
+ AppDomainAssemblyResolveHandlers = new List<HandlerInvocation>();
+ NestedBinds = new List<BindOperation>();
+ }
+
+ 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
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");
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<string, string> 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<string, string> 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;
+ }
}
}
--- /dev/null
+// 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<FileLoadException>(() => 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<HandlerInvocation> Invocations = new List<HandlerInvocation>();
+ internal readonly List<BindOperation> Binds = new List<BindOperation>();
+
+ 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;
+ }
+ }
+ }
+}
// 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;
}
}
- class BinderTracingTest
+ partial class BinderTracingTest
{
public class CustomALC : AssemblyLoadContext
{
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()
AssemblyName = executingAssembly.GetName(),
AssemblyPath = executingAssembly.Location,
AssemblyLoadContext = AssemblyLoadContext.GetLoadContext(asm).ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
{
AssemblyName = executingAssembly.GetName(),
AssemblyLoadContext = AssemblyLoadContext.GetLoadContext(asm).ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
{
AssemblyName = executingAssembly.GetName(),
AssemblyLoadContext = alc.ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
AssemblyName = executingAssembly.GetName(),
AssemblyPath = executingAssembly.Location,
AssemblyLoadContext = alc.ToString(),
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
AssemblyName = executingAssembly.GetName(),
AssemblyPath = executingAssembly.Location,
AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = CoreLibName,
RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = asm.GetName(),
[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(),
{
AssemblyName = new AssemblyName(assemblyName),
AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
RequestingAssemblyLoadContext = DefaultALC,
Success = false,
Cached = false
{
AssemblyName = new AssemblyName(DependentAssemblyName),
AssemblyLoadContext = DefaultALC,
- RequestingAssemblyLoadContext = DefaultALC,
RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
+ RequestingAssemblyLoadContext = DefaultALC,
Success = true,
ResultAssemblyName = t.Assembly.GetName(),
ResultAssemblyPath = t.Assembly.Location,
// 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<BinderTestAttribute>() != null && method.ReturnType == typeof(BindOperation));
+ Assert.IsTrue(method != null && method.GetCustomAttribute<BinderTestAttribute>() != null && method.ReturnType == typeof(BindOperation), "Invalid test muthod specified");
success = RunSingleTest(method);
}
}
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<HandlerInvocation> expected, List<HandlerInvocation> actual, string eventName)
+ {
+ Assert.AreEqual(expected.Count, actual.Count, $"Unexpected handler invocation count for {eventName}");
+
+ foreach (var match in expected)
{
- return;
+ Predicate<HandlerInvocation> 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<BindOperation> expected, List<BindOperation> actual)
+ {
+ foreach (var match in expected)
{
- Assert.AreEqual(expected.Name, actual.Name, $"Unexpected value for {propertyName} on event");
+ Predicate<BindOperation> pred = b => BindOperationsMatch(match, b);
+ Assert.IsTrue(actual.Exists(pred), $"Nested bind operation not found: {match.ToString()}");
}
}
}
</PropertyGroup>
<ItemGroup>
<Compile Include="BinderTracingTest.cs" />
+ <Compile Include="BinderTracingTest.EventHandlers.cs" />
<Compile Include="BinderEventListener.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
<ProjectReference Include="AssemblyToLoad.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <!-- Build the same AssemblyToLoad project with a different assembly name. These will be moved to a subdirectory
+ by the MoveDependentAssembliesToSubdirectory target, such that they will not be on the test's app path -->
+ <ProjectReference Include="AssemblyToLoad.csproj">
+ <Properties>AssemblyNameSuffix=Subdirectory</Properties>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ <OutputItemType>Content</OutputItemType>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </ProjectReference>
+ <ProjectReference Include="AssemblyToLoad.csproj">
+ <Properties>AssemblyNameSuffix=SubdirectoryMismatch</Properties>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ <OutputItemType>Content</OutputItemType>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </ProjectReference>
+ </ItemGroup>
+
+ <Target Name="MoveDependentAssembliesToSubdirectory" AfterTargets="CopyFilesToOutputDirectory">
+ <ItemGroup>
+ <AssembliesToMove Include="$(OutDir)/AssemblyToLoad_*.*" />
+ </ItemGroup>
+ <Move SourceFiles="@(AssembliesToMove)" DestinationFolder="$(OutDir)/DependentAssemblies" />
+ </Target>
</Project>
return context.ResolveSatelliteAssembly(assemblyName);
}
- private Assembly? GetFirstResolvedAssembly(AssemblyName assemblyName)
+ private Assembly? GetFirstResolvedAssemblyFromResolvingEvent(AssemblyName assemblyName)
{
Assembly? resolvedAssembly = null;
- Func<AssemblyLoadContext, AssemblyName, Assembly>? assemblyResolveHandler = _resolving;
+ Func<AssemblyLoadContext, AssemblyName, Assembly>? resolvingHandler = _resolving;
- if (assemblyResolveHandler != null)
+ if (resolvingHandler != null)
{
// Loop through the event subscribers and return the first non-null Assembly instance
- foreach (Func<AssemblyLoadContext, AssemblyName, Assembly> handler in assemblyResolveHandler.GetInvocationList())
+ foreach (Func<AssemblyLoadContext, AssemblyName, Assembly> 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;
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;
}
// 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);
{
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);
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;