深入探索 .NET 应用启动过程:通过主机跟踪揭秘引导流程

本文详细介绍了如何在现代 .NET 中启用主机跟踪来诊断应用程序启动问题,并通过一个简单应用的跟踪日志,逐步解析了从 dotnet muxer 到最终加载 coreclr 运行时的完整引导流程。

通过主机跟踪探索 .NET 引导过程

在本帖中,我们将了解如何为 .NET 主机本身启用诊断功能,以便用于调试运行 .NET 应用程序时遇到的问题。然后,我们将使用跟踪诊断来探索一个简单 .NET 应用程序的引导过程。

通过跟踪理解引导过程

本帖的主要重点是展示现代 .NET 中可用的主机跟踪功能。这不是像 OpenTelemetry 或 APM 解决方案那样带有活动和跨度的“跟踪”,这是老式的跟踪,即日志记录。😄

主机跟踪提供了关于 .NET 应用程序“引导”过程非常早期步骤的详细诊断信息。例如,如果您试图理解为什么应用程序使用了“错误”版本的 .NET,这会很有用。您不常需要它,但当事情不按预期工作时,它会非常宝贵!

在本文中,我将通过查看主机跟踪输出来探索一个简单 .NET 应用的启动过程。它会故意很冗长,但会让您了解可用的信息。

启用主机跟踪需要设置一个环境变量:COREHOST_TRACE=1。默认情况下,这会将跟踪写入 stderr,但您可以通过将 COREHOST_TRACEFILE 设置为以下两个值之一来将输出重定向到文件:

  • COREHOST_TRACEFILE=<file_path> 将日志追加到文件 <path>。如果文件不存在,则创建该文件,但其所在的目录必须存在。相对路径是相对于工作目录的。
  • COREHOST_TRACEFILE=<dir_path>(仅限 .NET 10+),如果目录 <dir_path> 存在,则将文件 <exe_name>.<pid>.log 追加到其中。

您还可以通过设置 COREHOST_TRACE_VERBOSITY=<level> 来控制日志的详细程度,其中 <level> 是 1 到 4 之间的值,4 最详细,1 仅错误。

为了测试它,我创建了一个简单的控制台应用程序,构建它,并启用跟踪后运行:

1
2
3
4
5
6
7
dotnet new console
dotnet build

# 启用跟踪
$env:COREHOST_TRACE=1
$env:COREHOST_TRACEFILE="host_trace.log"
dotnet bin\Debug\net9.0\MyApp.dll

考虑到这一点,让我们来探索 .NET 应用的引导过程。

使用现代 .NET 加载应用程序

当想到现代 .NET 应用程序时,我通常想到三个主要部分:

  • .NET 运行时,即 CoreCLR,它运行着 JIT 编译器、垃圾回收器以及构成 .NET 应用程序的一切。
  • .NET 基类库 (BCL),即作为 .NET 一部分交付的所有库。
  • 您的 .NET 应用程序,即您编写的代码,它可能引用其他 .NET 库以及构成 BCL 的库。

然而,要让 .NET 运行时运行起来,还有一整个“加载”过程需要发生!从高层次看,当您使用 dotnet myapp.dll 运行 .NET 应用程序时,您的应用程序会经过以下组件链:

  • dotnet 应用程序是一个“多路复用器”(muxer) 应用程序,用于确定您要运行什么。
  • hostfxr 是一个本机库,负责查找要加载的正确 .NET 运行时。
  • hostpolicy 是一个本机库,负责启动正确的 .NET 运行时。

我在本文中会更详细地探讨每个组件,但要深入了解(至少前两个),我推荐 Steve Gordon 关于内部原理的帖子。

Dotnet Muxer

dotnet muxer 是您作为 .NET 开发人员所做的大部分工作的入口点。无论是使用 dotnet builddotnet publish 进行开发,还是使用 dotnet MyApp.dll 实际运行应用程序,dotnet muxer 都是您的入口点。

在 Windows 上,dotnet muxer 是默认安装在 C:\Program Files\dotnet\dotnet.exe 的可执行文件。即使您的机器上安装了多个版本的 .NET 运行时或 .NET SDK,这里也只有一个入口点。当您安装新版本的 SDK 或运行时,通常会得到新版本的 muxer,但仍然只有一个。

muxer 实际上只负责一件事:加载 hostfxr 库并调用它。也就是说,它仍然会做一些初步验证。

不带任何参数调用 dotnet 没有意义,因此如果您直接运行 dotnet.exe,muxer 本身会打印一些基本的用法信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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.

下一步是 muxer 尝试查找并加载 .NET Host Framework Resolver (hostfxr)。它会搜索 dotnet 可执行文件旁边的 host\fxr 子文件夹,并读取那里列出的所有文件夹版本。如果像我一样安装了多个运行时,您会有很多条目:

muxer 读取所有这些文件夹,进行 SemVer 比较,并选择最高的一个。在文件夹内,您将找到 hostfxr 库(Windows 上是 hostfxr.dll,mac 上是 libhostfxr.dylib,Linux 上是 libhostfxr.so)。muxer 将 hostfxr 库加载到进程中。

Steve Gordon 在他关于 hostfxr 库的帖子中详细介绍了 muxer 用于执行此搜索和加载的代码,如果您想查看细节的话!

一旦 muxer 加载了 hostfxr,它会解析 hostfxr_main_startupinfo 函数并调用它。现在,如果我们查看跟踪日志,我们可以看到这一切都在发生:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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]

这些日志清楚地显示了 muxer 搜索 host\fxr 目录,找到最高版本,加载 hostfxr.dll 库,并调用 hostfxr_main_startupinfo 函数。

“muxer”作为标准入口点有一个变体,即“apphost”模型。当您发布 .NET 应用程序时,通常还会在应用程序的 dll 旁边生成一个可执行文件,例如 MyApp.exe 以及 MyApp.dll。这个可执行文件本质上是 dotnet muxer 的修改版本,带有各种调整。在本文中我不打算探讨 apphost,只知道它存在!

我们已经加载了 hostfxr 库,是时候看看它的作用了。

Hostfxr 库

hostfxr 库有几个职责:

  1. 解析提供的参数以决定执行什么;这是像 dotnet builddotnet publish 这样的 .NET SDK 命令,还是像 dotnet MyApp.dll 这样的应用程序执行。
  2. 如果是 SDK 命令,则找到要使用的正确 SDK。
  3. 决定加载哪个版本的 .NET 运行时。
  4. 为选定的运行时加载 hostpolicy 库。

我们将在下面的跟踪日志中看看这些步骤是如何体现的。

解析参数并决定行为

第一步在概念上是 muxer 的一部分,因为它涉及确定调用者的意图。他们是试图执行 SDK 命令,还是试图执行应用程序?如果我们运行像 dotnet --info 这样的 SDK 命令,最容易在跟踪日志中看到这一点:

1
2
3
4
--- 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]

在上面的日志中,您可以看到 hostfxr 已经确定 --info 不是要运行的应用程序,因此它重定向到 .NET SDK。另一方面,如果我们使用 dotnet myapp.dll 运行我们的应用程序,我们会看到类似这样的内容:

1
2
3
4
--- 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]

我们稍后会回到应用程序的情况,现在我们先坚持 SDK 场景:

查找 SDK

一旦 hostfxr 确定执行了 SDK 命令,下一步就是通过读取路径中的任何 global.json 文件来确定要加载哪个 .NET SDK:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
--- 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

在这些日志中,我们可以看到 hostfxr 在父路径中找到了一个 global.json 文件夹,并解析了加载 SDK 的规则。现在它可以搜索可用的 SDK 并选择要运行的那个:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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]

如您所见,它解析到了 9.0.306 版本的 SDK 并正在执行 dotnet.dll SDK 应用程序。最后三行日志也很有趣,以“Using the provided arguments”开始——它们基本上与我们运行 dotnet myapp.dll 时看到的日志相同。唯一的区别是,在这种情况下,我们要运行的 .NET 应用是 dotnet.dll,即 .NET SDK。我们现在将切换回控制台应用程序,继续加载过程。

选择要加载的 .NET 运行时

此时 hostfxr 知道要加载哪个 .NET 应用程序,但不知道要加载哪个 .NET 运行时。它通过检查应用程序的 runtimeconfig.json 来确定这一点。该文件位于应用程序旁边,其中包含(除其他外)要使用的运行时版本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "runtimeOptions": {
    "tfm": "net9.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "9.0.0"
    },
    "configProperties": {
      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
    }
  }
}

如果我们检查跟踪日志,可以看到 hostfxr 探测并找到了该文件,并读取了指定的框架详细信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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'.

随着请求的版本确定,hostfxr 开始通过查看 C:\Program Files\dotnet\shared\Microsoft.NETCore.App 来搜索哪些版本的运行时可用。它应用为应用程序配置的任何前滚策略(除非另有规定,否则为 Minor)并选择最佳匹配:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
--- 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]

如上所示,hostfxr 发现 9.0.10 是应用程序的最佳版本匹配。如果由于某种原因找不到匹配项,您会看到类似这样的消息:

1
2
3
4
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.

一旦找到有效的运行时版本,hostfxr 会尝试加载该运行时的 runtimeconfig.json。这表明是否还需要解析其他运行时。

运行时实际上是一个共享的“框架”,称为 Microsoft.NETCore.App。框架可以引用其他框架,例如 Microsoft.AspNetCore.AppMicrosoft.WindowsDesktop.App “框架”可以引用 Microsoft.NETCore.App 框架。您也可以创建自己的框架!此时所有内容都会被递归解析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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]

一旦所有框架都加载完毕(在本例中只有 Microsoft.NETCore.App 运行时),我们就进入 hostfxr 的最后一项职责,加载 hostpolicy

加载 Hostpolicy

一旦 .NET 运行时被解析,hostfxr 需要为特定选定的运行时版本加载 hostpolicy 库。它通过读取选定运行时的 deps.json 文件并查找名为类似 runtime.win-x64.Microsoft.NETCore.DotNetHostPolicy 的库来实现。如果找不到该条目(如下例所示),则它只在根框架文件夹中查找:

1
2
3
4
5
--- 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

正如您从最后一行看到的,hostfxr 找到了 hostpolicy.dll 并加载了它,现在是时候查看 hostpolicy 的行为了。

Hostpolicy 库

hostpolicy 的主要职责是:

  1. 基于应用程序和框架的 deps.json 构建受信任的平台程序集列表。
  2. 设置上下文开关以运行应用程序。
  3. 启动 .NET 运行时来运行您的应用程序。

构建受信任的平台程序集列表

在打印了一些为了本文目的我将跳过的日志之后,我们开始获得大量打印的日志。我在下面将它们截断为几个条目,足以让人了解正在发生的事情:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
#...

在上面的日志中,hostpolicy 已经读取了所选运行时的 deps.json 文件,并且正在加载其中列出的所有库。如果您自己打开 deps.json 文件,您可以看到这些文件都列在其中,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "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"
          },
//...          

处理完框架的 deps.json 文件后,hostpolicy 会转到您的应用的 deps.json 文件,这可能要简单得多。在简单的控制台应用情况下,它只包含对应用 dll 本身的引用:

1
2
3
4
5
6
7
8
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 

创建资产列表后,hostpolicy 开始构建受信任的平台程序集 (TPA) 列表。根据此词汇表:

受信任的平台程序集曾经是构成平台程序集的一组特殊程序集,这是最初设计时的概念。到今天,它只是构成应用程序的一组已知程序集。

因此,hostpolicy 只是遍历它发现的所有那些程序集,并将它们添加到 TPA:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
-- 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
#...

即使是一个基本的控制台应用程序,这也将继续进行 1000 行,所以我们跳过它 😅

创建上下文开关

hostpolicy 在跟踪日志中写入的下一行是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

这显示了运行时加载时将传递给它的上下文属性。正如您所看到的,它主要是一组从环境加载的配置值,包含用于初始化运行时的各种文件路径详细信息。它还包含来自应用程序 runtimeconfig.jsonconfigProperties,例如 EnableUnsafeBinaryFormatterSerialization 设置。

请注意,我截断了 TRUSTED_PLATFORM_ASSEMBLIES 属性,因为它是 TPA 中所有程序集路径的列表。

最后,hostpolicy 加载 coreclr.dll .NET 运行时并启动它!

1
2
3
4
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: 

就是这样,从 muxer 到 hostfxr,再到 hostpolicy.dll,再到 coreclr.dll 和一个运行中的应用程序!如果您在 NET 应用程序启动过程的早期遇到困难,请考虑启用跟踪以准确查看正在发生的情况。

总结

在本文中,我展示了如何通过设置 COREHOST_TRACE=1 并将 COREHOST_TRACEFILE 设置为文件路径来启用主机跟踪。然后,我运行了一个非常简单的应用程序并探索了它产生的主机跟踪日志。

我们看到 dotnet muxer 是应用程序的入口点,它定位并加载 hostfxrhostfxr 负责查找要加载的正确 .NET 运行时并加载 hostpolicy.dll。最后,hostpolicy.dll 启动 .NET 运行时并运行您的应用程序。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计