November 25, 2025 ~17 min read
Share on:
In this post we take a look a look at how you can enable diagnostics for the .NET host itself that you can use to debug issues running your .NET applications. We then use the tracing diagnostics to explore the boot process of a simple .NET application.
Understanding the boot process with tracing
The main focus of this post is to show the the host tracing feature available in modern .NET. This isn’t "tracing" like OpenTelemetry or APM solutions with activities and spans, this is old school tracing, i.e. logging.😄
Host tracing provides you detailed diagnostic infor…
November 25, 2025 ~17 min read
Share on:
In this post we take a look a look at how you can enable diagnostics for the .NET host itself that you can use to debug issues running your .NET applications. We then use the tracing diagnostics to explore the boot process of a simple .NET application.
Understanding the boot process with tracing
The main focus of this post is to show the the host tracing feature available in modern .NET. This isn’t "tracing" like OpenTelemetry or APM solutions with activities and spans, this is old school tracing, i.e. logging.😄
Host tracing provides you detailed diagnostic information about the very early steps of a .NET application’s "boot" process. This can be useful if you’re trying to understand why your application is using the "wrong" version of .NET, for example. You won’t need it often, but it can be invaluable when things aren’t working the way you expect!
In this post I’m going to explore the startup process for a simple .NET app by looking at the host tracing output. It’s going to be intentionally verbose, but it will give you an idea of what’s available.
Enabling host tracing requires setting a single environment variable: COREHOST_TRACE=1. By default this writes the traces to stderr, but you can redirect that output to a file by setting COREHOST_TRACEFILE to one of two values:
COREHOST_TRACEFILE=<file_path>appends the logs to the file<path>. The file is created if it doesn’t already exist, but the directory it’s in must exist. Relative paths are relative to the working directory.COREHOST_TRACEFILE=<dir_path>(.NET 10+ only), if the directory<dir_path>exists, The file<exe_name>.<pid>.logis appended to.
You can also control the verbosity of the logs by setting COREHOST_TRACE_VERBOSITY=<level> where <level> is a value from 1 to 4, 4 being the most verbose, and 1 being only errors.
To test it out, I created a simple console app, built it, and ran it with tracing enabled:
dotnet new console
dotnet build
# Enable tracing
$env:COREHOST_TRACE=1
$env:COREHOST_TRACEFILE="host_trace.log"
dotnet bin\Debug\net9.0\MyApp.dll
With that in mind, let’s explore the boot process of a .NET app.
Loading applications with modern .NET
When I think about modern .NET applications, I often think of three main divisions:
- The .NET runtime, the CoreCLR, which is running the JIT compiler, the garbage collector, and everything that make up a .NET application.
- The .NET base class libraries (BCL), which are all the libraries shipped as part of .NET.
- Your .NET application, which is the code written by you, which may reference other .NET libraries, as well as libraries that make up the BCL.
However, there’s also a whole "loading" process that has to happen to get the .NET runtime running!
At a high level, when you run a .NET application using dotnet myapp.dll, your app goes through the following chain of components:
- The
dotnetapp is a "multiplexer" (muxer) application that decides what you’re trying to run. hostfxris a native library responsible for finding the correct .NET runtime to load.hostpolicyis a native library responsible for starting the correct .NET runtime.
I explore each of these components in a little more detail in this post, but for a deeper dive (on the first two at least), I recommend Steve Gordon’s posts looking at the internals.
The dotnet muxer
The dotnet muxer is the entrypoint for most of the work you do as a .NET developer. Whether you’re doing development with dotnet build and dotnet publish, or actually running an application using dotnet MyApp.dll, the dotnet muxer is your entrypoint.
On Windows, the dotnet muxer is the executable that’s installed by default at C:\Program Files\dotnet\dotnet.exe. There’s a single entrypoint here, even if you have multiple versions of the .NET runtime or .NET SDK installed on your machine.
When you install a new version of the SDK or runtime, you’ll typically get a new version of the muxer, but there’s still only one.
The muxer is really just responsible for one thing: loading the hostfxr library and invoking it. That said, it still does a bit of preliminary validation. Calling dotnet without any arguments doesn’t make any sense, so if you simply run dotnet.exe, the muxer itself prints some basic usage information:
Usage: dotnet [path-to-application]
Usage: dotnet [commands]
path-to-application:
The path to an application .dll file to execute.
commands:
-h|--help Display help.
--info Display .NET information.
--list-runtimes [--arch <arch>] Display the installed runtimes matching the host or specified architecture. Example architectures: arm64, x64, x86.
--list-sdks [--arch <arch>] Display the installed SDKs matching the host or specified architecture. Example architectures: arm64, x64, x86.
The next step is for the muxer to try to find and load the .NET Host Framework Resolver (hostfxr). This searches a subfolder host\fxr next to the dotnet executable, and reads all the folder versions listed there. If, like me, you have lots of runtimes installed, you’ll have lots of entries:

The muxer reads all these folders, does a SemVer comparison, and selects the highest one. Inside the folder you’ll find the hostfxr library (hostfxr.dll on Windows, libhostfxr.dylib on mac, and libhostfxr.so on Linux). The muxer loads the hostfxr library into the process.
Steve Gordon walks through the code the muxer uses to do this search and loading in his post on the hostfxr library if you want to see the details!
Once the muxer has loaded hostfxr, it resolves the hostfxr_main_startupinfo function and invokes it.
Now, if we take a look at the tracing logs, we can see this all playing out:
Tracing enabled @ Thu Oct 23 18:33:26 2025 GMT
--- Invoked dotnet [version: 10.0.0-rc.2.25502.107 @Commit: 89c8f6a112d37d2ea8b77821e56d170a1bccdc5a] main = {
C:\Program Files\dotnet\dotnet.exe
bin\Debug\net9.0\myapp.dll
}
.NET root search location options: 0
Reading fx resolver directory=[C:\Program Files\dotnet\host\fxr]
Considering fxr version=[10.0.0-rc.2.25502.107]...
Considering fxr version=[2.1.30]...
Considering fxr version=[3.1.32]...
Considering fxr version=[5.0.17]...
Considering fxr version=[6.0.36]...
Considering fxr version=[7.0.20]...
Considering fxr version=[9.0.10]...
Considering fxr version=[9.0.6]...
Detected latest fxr version=[C:\Program Files\dotnet\host\fxr\10.0.0-rc.2.25502.107]...
Resolved fxr [C:\Program Files\dotnet\host\fxr\10.0.0-rc.2.25502.107\hostfxr.dll]...
Loaded library from C:\Program Files\dotnet\host\fxr\10.0.0-rc.2.25502.107\hostfxr.dll
Invoking fx resolver [C:\Program Files\dotnet\host\fxr\10.0.0-rc.2.25502.107\hostfxr.dll] hostfxr_main_startupinfo
Host path: [C:\Program Files\dotnet\dotnet.exe]
Dotnet path: [C:\Program Files\dotnet\]
App path: [C:\Program Files\dotnet\dotnet.dll]
These logs clearly show the muxer searching the host\fxr directory, finding the highest version, loading the hostfxr.dll library, and invoking the hostfxr_main_startupinfo function.
There’s a variation on the "muxer" as the standard entrypoint, which is the "apphost" model. When you publish your .NET application, you typically also get an executable produced next to your app’s dll, e.g.
MyApp.exeas well asMyApp.dll. This executable is essentially a modified version of thedotnetmuxer, with various tweaks. I’m not going to look into the apphost in this post, just know that it exists!
We’ve loaded the hostfxr library, so it’s time to see what that does.
The hostfxr library
The hostfxr library has several responsibilities:
- Parse the provided arguments to decide what to execute; is this a .NET SDK command like
dotnet buildanddotnet publish, or is it an app execution likedotnet MyApp.dll. - If it’s an SDK command, find the correct SDK to use.
- Decide which version of the .NET runtime to load.
- Load the
hostpolicylibrary for the selected runtime.
We’ll look at how each of those steps shows up in the tracing logs below.
Parse the arguments and decide behaviour
The first step is conceptually part of the muxer in that it’s about deciding the intention of the caller. Are they trying to execute SDK commands, or are they trying to execute an application? It’s easiest to see this playing out in the tracing logs if we run an SDK command like dotnet --info:
--- Executing in muxer mode...
Using the provided arguments to determine the application to execute.
Application '--info' is not a managed executable.
--- Resolving .NET SDK with working dir [D:\repos\temp\MyApp]
In the above logs, you can see that hostfxr has established that --info is not an app to run, so it redirects to the .NET SDK. On the other hand, if we had run our app using dotnet myapp.dll we’d see something like this instead:
--- Executing in muxer mode...
Using the provided arguments to determine the application to execute.
Using dotnet root path [C:\Program Files\dotnet\]
App runtimeconfig.json from [D:\repos\temp\myapp\bin\Debug\net9.0\myapp.dll]
We’ll come back to the application case in a second, for now we’ll stick to the SDK scenario:
Finding the SDK
Once hostfxr has decided that an SDK command was executed the next step is to work out which .NET SDK to load by reading any global.json files in the path:
--- Resolving .NET SDK with working dir [D:\repos\temp\MyApp]
Probing path [D:\repos\temp\MyApp\global.json] for global.json
Probing path [D:\repos\temp\global.json] for global.json
Probing path [D:\repos\global.json] for global.json
Found global.json [D:\repos\global.json]
--- Resolving SDK information from global.json [D:\repos\global.json]
Value 'sdk/version' is missing or null in [D:\repos\global.json]
Value 'sdk/rollForward' is missing or null in [D:\repos\global.json]
Resolving SDKs with version = 'latest', rollForward = 'latestMajor', allowPrerelease = false
In these logs we can see that hostfxr found a global.json folder in a parent path and parsed the rules for loading an SDK. Now it can search for the available SDKs and pick the one to run:
Searching for SDK versions in [C:\Program Files\dotnet\sdk]
Ignoring version [10.0.100-preview.6.25358.103] because it does not match the roll-forward policy
Ignoring version [10.0.100-rc.2.25502.107] because it does not match the roll-forward policy
Version [9.0.301] is a better match than [none]
Version [9.0.306] is a better match than [9.0.301]
SDK path resolved to [C:\Program Files\dotnet\sdk\9.0.306]
Using .NET SDK dll=[C:\Program Files\dotnet\sdk\9.0.306\dotnet.dll]
Using the provided arguments to determine the application to execute.
Using dotnet root path [C:\Program Files\dotnet\]
App runtimeconfig.json from [C:\Program Files\dotnet\sdk\9.0.306\dotnet.dll]
As you can see, it’s resolved to the 9.0.306 version of the SDK and is executing the dotnet.dll SDK application. It’s also interesting to see the final three logs, starting with "Using the provided arguments"—they’re essentially the same logs we saw when we ran dotnet myapp.dll. The only difference is that in this case, the .NET app we’re running is dotnet.dll, the .NET SDK.
We’ll switch back to the console app again now, and continue with the load process.
Choosing a .NET runtime to load
At this point hostfxr knows which .NET app to load but it doesn’t know which .NET runtime to load. It determines this by inspecting the runtimeconfig.json of the app. This file lives alongside the app and includes, among other things, the version of the runtime to use:
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
},
"configProperties": {
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
If we check the tracing logs, we can see hostfxr probes for and finds this file, and reads the specified framework details:
Using the provided arguments to determine the application to execute.
Using dotnet root path [C:\Program Files\dotnet\]
App runtimeconfig.json from [D:\repos\temp\myapp\bin\Debug\net9.0\myapp.dll]
Runtime config is cfg=D:\repos\temp\myapp\bin\Debug\net9.0\myapp.runtimeconfig.json dev=D:\repos\temp\myapp\bin\Debug\net9.0\myapp.runtimeconfig.dev.json
Attempting to read dev runtime config: D:\repos\temp\myapp\bin\Debug\net9.0\myapp.runtimeconfig.dev.json
Attempting to read runtime config: D:\repos\temp\myapp\bin\Debug\net9.0\myapp.runtimeconfig.json
Runtime config [D:\repos\temp\myapp\bin\Debug\net9.0\myapp.runtimeconfig.json] is valid=[1]
--- The specified framework 'Microsoft.NETCore.App', version '9.0.0', apply_patches=1, version_compatibility_range=minor is compatible with the previously referenced version '9.0.0'.
With the requested version established, hostfxr sets about searching for which versions of the runtime are available by looking in C:\Program Files\dotnet\shared\Microsoft.NETCore.App. It applies whatever roll forward policies are configured for the app (Minor unless otherwise specified) and chooses the best match:
--- Resolving FX directory, name 'Microsoft.NETCore.App' version '9.0.0'
Searching FX directory in [C:\Program Files\dotnet]
Attempting FX roll forward starting from version='[9.0.0]', apply_patches=1, version_compatibility_range=minor, roll_to_highest_version=0, prefer_release=1
'Roll forward' enabled with version_compatibility_range [minor]. Looking for the lowest release greater than or equal version to [9.0.0]
Found version [9.0.6]
Applying patch roll forward from [9.0.6] on release only
Inspecting version... [10.0.0-rc.2.25502.107]
Inspecting version... [2.1.30]
Inspecting version... [3.1.32]
Inspecting version... [5.0.17]
Inspecting version... [6.0.36]
Inspecting version... [7.0.20]
Inspecting version... [8.0.17]
Inspecting version... [8.0.21]
Inspecting version... [9.0.10]
Inspecting version... [9.0.6]
Changing Selected FX version from [] to [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10]
Chose FX version [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10]
As you can see above, hostfxr found that 9.0.10 was the best version match for the app. If it couldn’t find a match for some reason, you’d see a message something like this:
No match greater than or equal to [10.0.0] found.
Framework reference didn't resolve to any available version.
It was not possible to find any compatible framework version
You must install or update .NET to run this application.
Once a valid runtime version is found, hostfxr attempts to load the runtimeconfig.json for the runtime. This indicates if any other runtimes need to be resolved.
The runtime is actually a shared "framework", called
Microsoft.NETCore.App. Frameworks can reference other frameworks, for example theMicrosoft.AspNetCore.AppandMicrosoft.WindowsDesktop.App"frameworks" can reference theMicrosoft.NETCore.Appframework. You can also create your own frameworks if you want! Everything is resolved recursively at this point.
Runtime config is cfg=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.runtimeconfig.json dev=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.runtimeconfig.dev.json
Attempting to read dev runtime config: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.runtimeconfig.dev.json
Attempting to read runtime config: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.runtimeconfig.json
Runtime config [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.runtimeconfig.json] is valid=[1]
--- Summary of all frameworks:
framework:'Microsoft.NETCore.App', lowest requested version='9.0.0', found version='9.0.10', effective reference version='9.0.0' apply_patches=1, version_compatibility_range=minor, roll_to_highest_version=0, folder=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10
Executing as a framework-dependent app as per config file [D:\repos\temp\myapp\bin\Debug\net9.0\myapp.runtimeconfig.json]
Once all the frameworks are loaded (just the Microsoft.NETCore.App runtime in this case) we move onto the final responsibility of hostfxr, loading hostpolicy.
Loading hostpolicy
Once the .NET runtime is resolved, hostfxr needs to load the hostpolicy library for the specific chosen version of the runtime. It does this by reading the deps.json file of the chosen runtime and looking for a library called something like runtime.win-x64.Microsoft.NETCore.DotNetHostPolicy. If it doesn’t find that entry (it didn’t in the example below) then it just looks for it in the root framework folder:
--- Resolving hostpolicy.dll version from deps json [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.deps.json]
Dependency manifest C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.deps.json does not contain an entry for runtime.win-x64.Microsoft.NETCore.DotNetHostPolicy
The expected hostpolicy.dll directory is [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10]
Loaded library from C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\hostpolicy.dll
And as you can see from the final line, hostfxr found hostpolicy.dll and loaded it, so it’s time to look at the hostpolicy behaviour.
The hostpolicy library
The main responsibilities of hostpolicy are:
- Building the Trusted Platform Assemblies list based on the application and framework deps.json.
- Setting up the context switches to run the application.
- Launching the .NET runtime to run your application.
Building the Trusted Platform Assemblies list
After printing a few logs that I’m going to skip over for the purposes of this post, we start to get a lot of logs printed. I truncate them to just a few entries below, just enough to give a taste of what’s going on:
Loading deps file... [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.deps.json]: is_framework_dependent=0, use_fallback_graph=0
Processing package Microsoft.NETCore.App.Runtime.win-x64/9.0.10
Adding runtime assets
System.Private.CoreLib.dll assemblyVersion=9.0.0.0 fileVersion=9.0.1025.47515
Microsoft.VisualBasic.dll assemblyVersion=10.0.0.0 fileVersion=9.0.1025.47515
Microsoft.Win32.Primitives.dll assemblyVersion=9.0.0.0 fileVersion=9.0.1025.47515
mscorlib.dll assemblyVersion=4.0.0.0 fileVersion=9.0.1025.47515
netstandard.dll assemblyVersion=2.1.0.0 fileVersion=9.0.1025.47515
System.AppContext.dll assemblyVersion=9.0.0.0 fileVersion=9.0.1025.47515
System.Buffers.dll assemblyVersion=9.0.0.0 fileVersion=9.0.1025.47515
System.ComponentModel.DataAnnotations.dll assemblyVersion=4.0.0.0 fileVersion=9.0.1025.47515
# ...
Adding native assets
clrjit.dll assemblyVersion= fileVersion=9.0.1025.47515
coreclr.dll assemblyVersion= fileVersion=9.0.1025.47515
createdump.exe assemblyVersion= fileVersion=9.0.1025.47515
System.IO.Compression.Native.dll assemblyVersion= fileVersion=9.0.1025.47515
# ...
Reconciling library Microsoft.NETCore.App.Runtime.win-x64/9.0.10
package: Microsoft.NETCore.App.Runtime.win-x64, version: 9.0.10
Adding runtime assets
Entry 0 for asset name: System.Private.CoreLib, relpath: System.Private.CoreLib.dll, assemblyVersion 9.0.0.0, fileVersion 9.0.1025.47515
Entry 1 for asset name: Microsoft.VisualBasic, relpath: Microsoft.VisualBasic.dll, assemblyVersion 10.0.0.0, fileVersion 9.0.1025.47515
Entry 2 for asset name: Microsoft.Win32.Primitives, relpath: Microsoft.Win32.Primitives.dll, assemblyVersion 9.0.0.0, fileVersion 9.0.1025.47515
Entry 3 for asset name: mscorlib, relpath: mscorlib.dll, assemblyVersion 4.0.0.0, fileVersion 9.0.1025.47515
# ...
Adding native assets
Entry 0 for asset name: clretwrc, relpath: clretwrc.dll, assemblyVersion , fileVersion 9.0.1025.47515
Entry 1 for asset name: clrgc, relpath: clrgc.dll, assemblyVersion , fileVersion 9.0.1025.47515
Entry 2 for asset name: clrgcexp, relpath: clrgcexp.dll, assemblyVersion , fileVersion 9.0.1025.47515
#...
In the above logs, hostpolicy has read the deps.json file for the chosen runtime, and is loading all the libraries it lists. You can see these files all listed if you open the deps.json file yourself, for example:
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v9.0/win-x64",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v9.0": {},
".NETCoreApp,Version=v9.0/win-x64": {
"Microsoft.NETCore.App.Runtime.win-x64/9.0.10": {
"runtime": {
"System.Private.CoreLib.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1025.47515"
},
"Microsoft.VisualBasic.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "9.0.1025.47515"
},
//...
After processing the framework deps.json file, hostpolicy moves onto your apps deps.json file, which is likely much simpler. In the simple console app case it will only contain a reference to the app dll itself:
Processing package myapp/1.0.0
Adding runtime assets
myapp.dll assemblyVersion= fileVersion=
Reconciling library myapp/1.0.0
project: myapp, version: 1.0.0
Adding runtime assets
Entry 0 for asset name: myapp, relpath: myapp.dll, assemblyVersion , fileVersion
With the list of assets created, hostpolicy sets about building up the Trusted Platform Assemblies (TPA) list. As per this glossary:
Trusted Platform Assemblies used to be a special set of assemblies that comprised the platform assemblies, when it was originally designed. As of today, it is simply the set of assemblies known to constitute the application.
So hostpolicy simply walks through all those assemblies it discovered, and adds them to the TPA:
-- Probe configurations:
probe type=app
probe type=framework dir=[C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10] fx_level=1
Adding tpa entry: D:\repos\temp\myapp\bin\Debug\net9.0\myapp.dll, AssemblyVersion: , FileVersion:
Processing TPA for deps entry [myapp, 1.0.0, myapp.dll] with fx level: 0
Using probe config: type=app
Local path query D:\repos\temp\myapp\bin\Debug\net9.0\myapp.dll (skipped file existence check)
Probed deps dir and matched 'D:\repos\temp\myapp\bin\Debug\net9.0\myapp.dll'
Processing TPA for deps entry [Microsoft.NETCore.App.Runtime.win-x64, 9.0.10, System.Private.CoreLib.dll] with fx level: 1
Using probe config: type=app
Skipping... not app asset
Using probe config: type=framework dir=[C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10] fx_level=1
Local path query C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\System.Private.CoreLib.dll (skipped file existence check)
Probed deps json and matched 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\System.Private.CoreLib.dll'
Adding tpa entry: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\System.Private.CoreLib.dll, AssemblyVersion: 9.0.0.0, FileVersion: 9.0.1025.47515
#...
That goes on for another 1000 lines, even in a basic console app, so we’ll skip ahead 😅
Creating the context switches
The next lines written by hostpolicy in the trace log are:
Property FX_DEPS_FILE = C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.deps.json
Property TRUSTED_PLATFORM_ASSEMBLIES = C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\System.Security.Cryptography.X509Certificates.dll;C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.CSharp.dll; # TRUNCATED!
Property NATIVE_DLL_SEARCH_DIRECTORIES = C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\;
Property PLATFORM_RESOURCE_ROOTS =
Property APP_CONTEXT_BASE_DIRECTORY = D:\repos\temp\myapp\bin\Debug\net9.0\
Property APP_CONTEXT_DEPS_FILES = D:\repos\temp\myapp\bin\Debug\net9.0\myapp.deps.json;C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\Microsoft.NETCore.App.deps.json
Property PROBING_DIRECTORIES =
Property RUNTIME_IDENTIFIER = win-x64
Property System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization = false
Property HOST_RUNTIME_CONTRACT = 0x1cd836084d8
This shows the context properties which will be passed to the runtime when it’s loaded. As you can see, it’s primarily a set of configuration values loaded from the the environment, containing various details about paths to files used to initialize the runtime. It also contains the configProperties from the app’s runtimeconfig.json, such as the EnableUnsafeBinaryFormatterSerialization setting.
Note that I truncated the
TRUSTED_PLATFORM_ASSEMBLIESproperty as it’s a list of paths to all the assemblies in the TPA
And finally hostpolicy loads the coreclr.dll .NET runtime and launches it!
CoreCLR path = 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\coreclr.dll', CoreCLR dir = 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\'
Loaded library from C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.10\coreclr.dll
Launch host: C:\Program Files\dotnet\dotnet.exe, app: D:\repos\temp\myapp\bin\Debug\net9.0\myapp.dll, argc: 0, args:
And there we have it, from muxer, to hostfxr, to hostpolicy.dll to coreclr.dll and a running app! If you’re running into difficulties early in the NET app booting process, then consider enabling tracing to see exactly what’s going on.
Summary
In this post I showed how you can enable host tracing by setting COREHOST_TRACE=1 and setting COREHOST_TRACEFILE to a file path. I then ran a very simple app and explored the host tracing logs it produces. We then saw how the dotnet muxer is the entrypoint for the app, which locates and loads hostfxr. hostfxr is then responsible for finding the correct .NET runtime to load and for loading hostpolicy.dll. Finally hostpolicy.dll boots the .NET runtime and runs your application.
Previous Companies complaining .NET moves too fast should just pay for post-EOL supportNext Recent updates to NetEscapades.EnumGenerators: [EnumMember] support, analyzers, and bug fixes