# Exploring the .NET boot process via host tracing

> Source: <https://andrewlock.net/exploring-the-dotnet-boot-process-via-host-tracing/>
> Published: 2025-11-25 10:00:00+00:00

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](#understanding-the-boot-process-with-tracing)

The main focus of this post is to show the [the host tracing feature](https://github.com/dotnet/runtime/blob/25cae043b11fa5e4fbda011376a7ad403438bd62/docs/design/features/host-tracing.md) 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>.log`

is 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](#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
`dotnet`

app is a "multiplexer" (muxer) application that decides what you're trying to run. `hostfxr`

is a native library responsible for finding the correct .NET runtime to load.`hostpolicy`

is 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](https://www.stevejgordon.co.uk/a-brief-introduction-to-the-dotnet-muxer).

[The ](#the-dotnet-muxer)`dotnet`

muxer

`dotnet`

muxerThe `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](https://github.com/dotnet/runtime/blob/f169b52556bc4769b4260b7a85c05c6f78911097/src/native/corehost/corehost.cpp#L187) 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](https://github.com/dotnet/runtime/blob/main/docs/design/features/hosting-layer-apis.md#net-core-21) 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.exe`

as well as`MyApp.dll`

. This executable is essentially a modified version of the`dotnet`

muxer, 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 ](#the-hostfxr-library)`hostfxr`

library

`hostfxr`

libraryThe `hostfxr`

library has several responsibilities:

- Parse the provided arguments to decide what to execute; is this a .NET SDK command like
`dotnet build`

and`dotnet publish`

, or is it an app execution like`dotnet 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
`hostpolicy`

library 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](#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](#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](#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](https://learn.microsoft.com/en-us/dotnet/core/versions/selection#control-roll-forward-behavior) 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 referenceotherframeworks, for example the`Microsoft.AspNetCore.App`

and`Microsoft.WindowsDesktop.App`

"frameworks" can reference the`Microsoft.NETCore.App`

framework. 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 ](#loading-hostpolicy)`hostpolicy`

`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 ](#the-hostpolicy-library)`hostpolicy`

library

`hostpolicy`

libraryThe 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](#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](https://github.com/dotnet/runtime/blob/1e09fc169a2c4d0c54c483967b845b03d11215d5/docs/project/glossary.md):

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](#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_ASSEMBLIES`

property as it's a list of paths toallthe 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](#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.
