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

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 its stdin and stdout.
  • 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

  1. Navigate to the directory with the downloaded sample. Build the application with:

    dotnet build
    
  2. 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
    
  3. 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
    
  4. Attach dotnet-trace to the running application.

    dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 --process-id 35832
    
  5. 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.

PerfView assembly loader filter image

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.

PerfView Start and Stop events image

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.

PerfView ResolutionAttempted events image

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.

PerfView extension point events image

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.

PerfView successful Start and Stop events image

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.

PerfView successful ResolutionAttempted events image

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.

PerfView successful extension point events image

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.

See also