Collect detailed assembly loading information
Starting with .NET 5, the runtime can emit events through EventPipe
with detailed information about managed assembly loading to aid in diagnosing assembly loading issues. These events are emitted by the Microsoft-Windows-DotNETRuntime
provider under the AssemblyLoader
keyword (0x4
).
Prerequisites
- .NET 5 SDK or later versions
dotnet-trace
tool
Note
The scope of dotnet-trace
capabilities is greater than collecting detailed assembly loading information. For more information on the usage of dotnet-trace
, see dotnet-trace
.
Collect a trace with assembly loading events
You can use dotnet-trace
to trace an existing process or to launch a child process and trace it from startup.
Trace an existing process
To enable assembly loading events in the runtime and collect a trace of them, use dotnet-trace
with the following command:
dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 --process-id <pid>
This command collects a trace of the specified <pid>
, enabling the AssemblyLoader
events in the Microsoft-Windows-DotNETRuntime
provider. The result is a .nettrace
file.
Use dotnet-trace to launch a child process and trace it from startup
Sometimes it may be useful to collect a trace of a process from its startup. For apps running .NET 5 or later, you can use dotnet-trace
to do this.
The following command launches hello.exe with arg1
and arg2
as its command line arguments and collects a trace from its runtime startup:
dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 -- hello.exe arg1 arg2
You can stop collecting the trace by pressing Enter or Ctrl + C. This also closes hello.exe.
Note
- Launching hello.exe via
dotnet-trace
redirects its input and output, and you won't be able to interact with it on the console by default. Use the--show-child-io
switch to interact with itsstdin
andstdout
. - Exiting the tool via Ctrl+C or
SIGTERM
safely ends both the tool and the child process. - If the child process exits before the tool, the tool exits as well and the trace should be safely viewable.
View a trace
The collected trace file can be viewed on Windows using the Events view in PerfView. All the assembly loading events will be prefixed with Microsoft-Windows-DotNETRuntime/AssemblyLoader
.
Example (on Windows)
This example uses the assembly loading extension points sample. The application attempts to load an assembly MyLibrary
- an assembly that is not referenced by the application and thus requires handling in an assembly loading extension point to be successfully loaded.
Collect the trace
Navigate to the directory with the downloaded sample. Build the application with:
dotnet build
Launch the application with arguments indicating that it should pause, waiting for a key press. On resuming, it will attempt to load the assembly in the default
AssemblyLoadContext
- without the handling necessary for a successful load. Navigate to the output directory and run:AssemblyLoading.exe /d default
Find the application's process ID.
dotnet-trace ps
The output will list the available processes. For example:
35832 AssemblyLoading C:\src\AssemblyLoading\bin\Debug\net5.0\AssemblyLoading.exe
Attach
dotnet-trace
to the running application.dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 --process-id 35832
In the window running the application, press any key to let the program continue. Tracing will automatically stop once the application exits.
View the trace
Open the collected trace in PerfView and open the Events view. Filter the events list to Microsoft-Windows-DotNETRuntime/AssemblyLoader
events.
All assembly loads that occurred in the application after tracing started will be shown. To inspect the load operation for the assembly of interest for this example - MyLibrary
, we can do some more filtering.
Assembly loads
Filter the view to the Start
and Stop
events under Microsoft-Windows-DotNETRuntime/AssemblyLoader
using the event list on the left. Add the columns AssemblyName
, ActivityID
, and Success
to the view. Filter to events containing MyLibrary
.
Event Name | AssemblyName | ActivityID | Success |
---|---|---|---|
AssemblyLoader/Start |
MyLibrary, Culture=neutral, PublicKeyToken=null |
//1/2/ | |
AssemblyLoader/Stop |
MyLibrary, Culture=neutral, PublicKeyToken=null |
//1/2/ | False |
You should see one Start
/Stop
pair with Success=False
on the Stop
event, indicating the load operation failed. Note that the two events have the same activity ID. The activity ID can be used to filter all the other assembly loader events to just the ones corresponding to this load operation.
Breakdown of attempt to load
For a more detailed breakdown of the load operation, filter the view to the ResolutionAttempted
events under Microsoft-Windows-DotNETRuntime/AssemblyLoader
using the event list on the left. Add the columns AssemblyName
, Stage
, and Result
to the view. Filter to events with the activity ID from the Start
/Stop
pair.
Event Name | AssemblyName | Stage | Result |
---|---|---|---|
AssemblyLoader/ResolutionAttempted |
MyLibrary, Culture=neutral, PublicKeyToken=null |
FindInLoadContext |
AssemblyNotFound |
AssemblyLoader/ResolutionAttempted |
MyLibrary, Culture=neutral, PublicKeyToken=null |
ApplicationAssemblies |
AssemblyNotFound |
AssemblyLoader/ResolutionAttempted |
MyLibrary, Culture=neutral, PublicKeyToken=null |
AssemblyLoadContextResolvingEvent |
AssemblyNotFound |
AssemblyLoader/ResolutionAttempted |
MyLibrary, Culture=neutral, PublicKeyToken=null |
AppDomainAssemblyResolveEvent |
AssemblyNotFound |
The events above indicate that the assembly loader attempted to resolve the assembly by looking in the current load context, running the default probing logic for managed application assemblies, invoking handlers for the AssemblyLoadContext.Resolving event, and invoking handlers for the AppDomain.AssemblyResolve. For all of these steps, the assembly was not found.
Extension points
To see which extension points were invoked, filter the view to the AssemblyLoadContextResolvingHandlerInvoked
and AppDomainAssemblyResolveHandlerInvoked
under Microsoft-Windows-DotNETRuntime/AssemblyLoader
using the event list on the left. Add the columns AssemblyName
and HandlerName
to the view. Filter to events with the activity ID from the Start
/Stop
pair.
Event Name | AssemblyName | HandlerName |
---|---|---|
AssemblyLoader/AssemblyLoadContextResolvingHandlerInvoked |
MyLibrary, Culture=neutral, PublicKeyToken=null |
OnAssemblyLoadContextResolving |
AssemblyLoader/AppDomainAssemblyResolveHandlerInvoked |
MyLibrary, Culture=neutral, PublicKeyToken=null |
OnAppDomainAssemblyResolve |
The events above indicate that a handler named OnAssemblyLoadContextResolving
was invoked for the AssemblyLoadContext.Resolving event and a handler named OnAppDomainAssemblyResolve
was invoked for the AppDomain.AssemblyResolve event.
Collect another trace
Run the application with arguments such that its handler for the AssemblyLoadContext.Resolving event will load the MyLibrary
assembly.
AssemblyLoading /d default alc-resolving
Collect and open another .nettrace
file using the steps from above.
Filter to the Start
and Stop
events for MyLibrary
again. You should see a Start
/Stop
pair with another Start
/Stop
between them. The inner load operation represents the load triggered by the handler for AssemblyLoadContext.Resolving when it called AssemblyLoadContext.LoadFromAssemblyPath. This time, you should see Success=True
on the Stop
event, indicating the load operation succeeded. The ResultAssemblyPath
field shows the path of the resulting assembly.
Event Name | AssemblyName | ActivityID | Success | ResultAssemblyPath |
---|---|---|---|---|
AssemblyLoader/Start |
MyLibrary, Culture=neutral, PublicKeyToken=null |
//1/2/ | ||
AssemblyLoader/Start |
MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null |
//1/2/1/ | ||
AssemblyLoader/Stop |
MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null |
//1/2/1/ | True | C:\src\AssemblyLoading\bin\Debug\net5.0\MyLibrary.dll |
AssemblyLoader/Stop |
MyLibrary, Culture=neutral, PublicKeyToken=null |
//1/2/ | True | C:\src\AssemblyLoading\bin\Debug\net5.0\MyLibrary.dll |
We can then look at the ResolutionAttempted
events with the activity ID from the outer load to determine the step at which the assembly was successfully resolved. This time, the events will show that the AssemblyLoadContextResolvingEvent
stage was successful. The ResultAssemblyPath
field shows the path of the resulting assembly.
Event Name | AssemblyName | Stage | Result | ResultAssemblyPath |
---|---|---|---|---|
AssemblyLoader/ResolutionAttempted |
MyLibrary, Culture=neutral, PublicKeyToken=null |
FindInLoadContext |
AssemblyNotFound |
|
AssemblyLoader/ResolutionAttempted |
MyLibrary, Culture=neutral, PublicKeyToken=null |
ApplicationAssemblies |
AssemblyNotFound |
|
AssemblyLoader/ResolutionAttempted |
MyLibrary, Culture=neutral, PublicKeyToken=null |
AssemblyLoadContextResolvingEvent |
Success |
C:\src\AssemblyLoading\bin\Debug\net5.0\MyLibrary.dll |
Looking at AssemblyLoadContextResolvingHandlerInvoked
events will show that the handler named OnAssemblyLoadContextResolving
was invoked. The ResultAssemblyPath
field shows the path of the assembly returned by the handler.
Event Name | AssemblyName | HandlerName | ResultAssemblyPath |
---|---|---|---|
AssemblyLoader/AssemblyLoadContextResolvingHandlerInvoked |
MyLibrary, Culture=neutral, PublicKeyToken=null |
OnAssemblyLoadContextResolving |
C:\src\AssemblyLoading\bin\Debug\net5.0\MyLibrary.dll |
Note that there is no longer a ResolutionAttempted
event with the AppDomainAssemblyResolveEvent
stage or any AppDomainAssemblyResolveHandlerInvoked
events, as the assembly was successfully loaded before reaching the step of the loading algorithm that raises the AppDomain.AssemblyResolve event.