- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
导读
本文面向正在为 .NET 运行时仓库(runtime repo)贡献代码、需要快速验证 CoreCLR 修改效果的开发者,系统讲解如何使用自己构建的 CoreCLR 产物来运行 .NET 应用程序:包括以corerun作为宿主启动本地运行时、借助CORE_ROOT/CORE_LIBRARIES环境变量与命令行参数定位运行时与类库、以及通过测试构建脚本生成一整套Core_Root产物用于联调测试。读完本文,你将掌握在不安装任何额外 SDK、不修改系统全局环境的前提下,用最快的方式持续迭代测试自己编译出的 CoreCLR 与类库,并理解corerun底层发现程序集、初始化运行时(coreclr_initialize)和传递运行时属性的完整原理。
为什么需要 CoreRun:运行本地运行时的三种方式
要使用你自己构建的运行时来运行 .NET 应用,除了托管应用程序本身之外,还必须有一个能够加载运行时的*宿主(host)*程序,并准备好应用依赖的全部 .NET 类库。在 runtime 仓库中,官方文档归纳了三种主要方式:
- 使用机器上已安装的 .NET SDK,并替换自包含应用中的必要二进制:适合希望以接近真实发布形态验证的场景,详见 使用已安装 SDK 运行你的构建。
- 使用你的构建产出的开发版 Shipping 包(Dev Shipping Packages):这是最接近最终用户使用方式、但流程也最长的方案,通过
clr+libs+host+packs子集构建出 NuGet 包与可分发运行时,详见 使用构建的 Shipping 包。 - 使用构建产物中生成的 CoreRun 宿主:这也是本文的主角。
corerun是一个平台无关的轻量级宿主工具,专门用于快速测试本地构建的 .NET 运行时,能够大幅加速运行时开发与测试失败问题的调查。当你处于"频繁修改、持续测试调试"的内循环中时,官方推荐优先使用这种方式——因为它不需要打包、不需要安装,每次重新构建后直接重新执行即可应用最新的改动。
阅读本文之前,请确保你已经至少完成了仓库的clr子集构建,并且产物位于artifacts/bin/coreclr/<OS>.<arch>.<configuration>目录。如果尚未完成构建,请先参考 CoreCLR 构建指南 完成环境准备与编译。
认识 CoreRun 宿主
它是什么、不知道什么
corerun二进制是构建clr子集的产物之一,位于<仓库根目录>/artifacts/bin/coreclr/<OS>.<Arch>.<Configuration>目录下(Windows 上名为corerun.exe)。从 CoreCLR 构建指南 的"Build Results"一节可以确认,同一目录下还会产出coreclr(运行时本体:Windows 为coreclr.dll,macOS 为libcoreclr.dylib,Linux 为libcoreclr.so)以及System.Private.CoreLib.dll等核心托管库。
关键设计点是:corerun完全不理解 NuGet。它只需要两样东西:
- 平台对应的运行时动态库(
coreclr.dll/libcoreclr.dylib/libcoreclr.so); - 应用运行所需的类库程序集,例如
System.Runtime.dll、System.IO.dll等。
这一点在 corerun 源码 中体现得很直接:平台抽象层(pal)针对不同平台分别定义了运行时库文件名常量——Windows 为coreclr+.dll,macOS 为libcoreclr+.dylib,Linux 及其他类 Unix 系统为libcoreclr+.so,加载时统一拼成<core_root>/coreclr.dll(或对应平台名称)后调用try_load_coreclr动态加载。
运行时与类库的发现启发式
corerun通过以下顺序的启发式规则来定位运行时二进制(源码中run()函数与帮助文本均明确记录了该顺序):
- 检查用户是否通过命令行传入了
--clr-path参数; - 检查
CORE_ROOT环境变量是否已定义; - 检查 .NET 运行时二进制是否与
corerun二进制位于同一目录。
无论通过哪种方式定位到运行时二进制,其所在目录都会被同时用来查找全部基类库(BCL)程序集。此外,你还可以通过定义CORE_LIBRARIES环境变量,把额外的目录纳入类库程序集的搜索集合。
从 corerun.cpp 的源码可以进一步确认环境变量的完整定义:
| 环境变量 | 作用 |
|---|---|
CORE_ROOT | 指向包含 CoreCLR 运行时二进制的目录 |
CORE_LIBRARIES | 指向包含附加平台程序集的目录,用于覆盖/补充框架程序集 |
APP_ASSEMBLIES | 控制应用程序集如何提供给运行时:PROPERTY(默认,通过TRUSTED_PLATFORM_ASSEMBLIES属性传入路径列表)、EXTERNAL(通过外部程序集探测回调提供)、或直接给出一份平台分隔符分隔的路径列表 |
MOCK_HOSTPOLICY | 测试用:预加载一个 mock hostpolicy 动态库 |
PLATFORM_NATIVE_R2R | 置为1时向运行时提供平台原生 R2R(ReadyToRun)镜像的回调支持(仅 Windows 与 macOS) |
TPA 列表的构建细节:CORE_LIBRARIES 如何"覆盖"框架程序集
corerun最终通过TRUSTED_PLATFORM_ASSEMBLIES(TPA)属性把可信平台程序集清单交给运行时。源码中的build_tpa()函数(见 corerun.cpp)揭示了一个实用的细节:
- 它先按
.dll、.exe两类扩展名遍历; - 对每个扩展名,依次遍历
core_libraries(来自CORE_LIBRARIES)与core_root(来自--clr-path/CORE_ROOT/corerun 所在目录)这两个目录; - 用一个
std::set对简单程序集名去重,同一个简单名称只保留第一个实例。
源码注释明确指出:由于 CoreCLR 并不总是优先采用 TPA 列表中的第一个实例(例如 NI 原生镜像可能被优先于 IL 选择),因此构建 TPA 时只保留每个简单程序集名的首个实例,从而让用户可以通过把 dll 放进%CORE_LIBRARIES%目录来覆盖框架程序集。这正是"用CORE_LIBRARIES指向系统共享类库目录即可复用已安装 .NET 的类库"这一做法得以成立的底层机制。
用 CoreRun 运行应用程序
下面以经典的 Hello World 为例,展示如何用你自己构建的运行时取代机器上安装的运行时来运行应用。
使用系统级 .NET 安装中的共享类库
首先创建并构建一个普通控制台应用:
mkdir HelloWorld && cd HelloWorld dotnet new console dotnet build注意,这里我们仍然用机器上的 SDK 完成编译(编译只依赖 SDK 的编译能力),关键区别在于运行时执行阶段交给corerun。接下来按以下步骤操作:
- 把
corerun所在目录加入PATH环境变量以方便调用(也可以跳过此步,始终使用完整路径);- 以下示例假设你以Debug配置、x64架构构建,请根据你自己的构建参数调整路径。
- 由于我们只构建了运行时(clr 子集)而没有构建类库,需要通过
CORE_LIBRARIES告诉corerun使用机器上 .NET 默认安装自带的类库;- 以下示例假设你机器上默认 .NET 安装的版本名为
7.0.0,请替换为你实际安装的版本。
- 以下示例假设你机器上默认 .NET 安装的版本名为
- 最后执行
corerun运行应用。
Windows 命令提示符(CMD):
set PATH=%PATH%;<repo_root>\artifacts\bin\coreclr\windows.x64.Debug set CORE_LIBRARIES=%ProgramFiles%\dotnet\shared\Microsoft.NETCore.App\7.0.0 corerun HelloWorld.dllmacOS 与 Linux:
# 如果你在 Linux 上,把 osx 改为 linux。 export PATH="$PATH:<repo_root>/artifacts/bin/coreclr/osx.x64.Debug" export CORE_LIBRARIES="/usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0" corerun HelloWorld.dllPowerShell:
# 注意这里用的是 '+=',因为我们要追加到已有的 PATH 变量。 # 另外,在 Linux 或 macOS 上请把 ';' 换成 ':'。 $Env:PATH += ';<repo_root>\artifacts\bin\coreclr\windows.x64.Debug' $Env:CORE_LIBRARIES = %ProgramFiles%\dotnet\shared\Microsoft.NETCore.App\7.0.0 corerun HelloWorld.dll设置好PATH与CORE_LIBRARIES之后,corerun HelloWorld.dll就知道去哪里获取它所需的程序集了。这套设置只需在同一个终端实例内做一次:之后即使你重新构建并修改了运行时,也可以直接再次执行corerun来运行应用——修改立即生效,无需重复配置。这正是它适合"改代码 → 重编译 → 立即测试"内循环的原因。
执行发布为自包含的应用程序
当应用以自包含方式发布(dotnet publish --self-contained)时,发布目录中已经包含应用运行所需的全部类库。因此,只需要把上一节中CORE_LIBRARIES的值改为指向该发布目录,corerun就会从你部署的应用中获取所有这些库的代码:
set CORE_LIBRARIES=<path\to\publish\output> corerun HelloWorld.dll这样你就用本地构建的 CoreCLR 运行了一个"自包含布局"的应用,类库代码来自你的发布目录而非系统共享目录。
认识 Core_Root:类库 + 运行时的一站式测试目录
什么是 Core_Root
前文通过CORE_LIBRARIES借用系统类库的做法有一个局限:你无法同时测试自己修改的类库。为了解决这个问题,测试构建脚本会为你汇集一套完整的测试运行环境——Core_Root。
Core_Root由测试构建脚本(Windows 为src/tests/build.cmd,macOS/Linux 为src/tests/build.sh)创建,它会把你刚刚构建好的 CoreCLR 与测试所需的类库片段集中放置到一个目录中,产出位置为:
artifacts/tests/coreclr/<OS>.<Arch>.<Configuration>/Tests/Core_Root从 CoreCLR 构建指南 可以看到,完整的 Core_Root 不仅包含类库与 CLR,还打包了Crossgen2、R2RDump、ILC 编译器以及corerun等工具,是 CI 管道中运行 CLR 测试的方式,也是最可靠的测试运行时改动与运行外部应用的途径之一。
如何生成 Core_Root
由于测试构建过程相当漫长,官方建议只在需要时通过-generatelayoutonly标志生成 Core_Root 布局,然后按需单独构建个别测试或测试树。
重要前提:要生成 Core_Root,你必须先用-subset libs构建过类库。测试构建脚本默认以Release模式搜索类库,无论你为运行时指定了什么配置。如果你用其他配置构建的类库,必须传入/p:LibrariesConfiguration=<your_config>标志。更详细说明见 CoreCLR 测试文档。
典型的生成命令(假设 x64 机器的 Checked 配置 CLR 构建,来自 CoreCLR 构建指南):
./src/tests/build.sh -arch x64 -checked -generatelayoutonly而在准备阶段,构建指南推荐用如下命令同时构建 clr 与 libs(clr 用 Debug、类库用 Release 是常见组合):
./build.sh -subset clr+libs -runtimeConfiguration Debug -librariesConfiguration Release使用 Core_Root 运行应用
拿到 Core_Root 之后,直接调用其中的corerun,或者把 Core_Root 目录加入PATH,即可用这套"运行时 + 类库"的组合运行应用:
Windows 命令提示符:
set PATH=%PATH%;<repo_root>\artifacts\tests\coreclr\windows.x64.Debug\Tests\Core_Root corerun HelloWorld.dllmacOS 与 Linux:
# 如果你在 macOS 上,把 linux 改为 osx。 export PATH="$PATH:<repo_root>/artifacts/tests/coreclr/linux.x64.Debug/Tests/Core_Root" corerun HelloWorld.dllPowerShell:
# 注意这里用的是 '+=',因为我们要追加到已有的 PATH 变量。 # 另外,在 Linux 或 macOS 上请把 ';' 换成 ':'。 $Env:PATH += ';<repo_root>\artifacts\tests\coreclr\windows.x64.Debug\Tests\Core_Root' corerun HelloWorld.dll相比仅使用 clr 构建产出的corerun,生成 Core_Root 的优势在于:你可以同时测试和调试类库与运行时——因为CORE_ROOT(或--clr-path)指向的 Core_Root 目录里既包含你构建的libcoreclr运行时,也包含你构建的类库程序集,二者是配套的同一套产物。
CoreRun 的完整命令行选项
corerun支持若干可选命令行参数,执行corerun --help可查看完整帮助。以下选项在源码的display_usage()与parse_args()中均有对应实现(见 corerun.cpp),短选项与长选项等价:
| 选项 | 说明 | 示例 |
|---|---|---|
-c, --clr-path <PATH> | 在命令行直接指定 Core_Root 位置。如果你的corerun就在 Core_Root 目录内,或者已通过CORE_ROOT环境变量设置了路径,则可以省略此参数 | corerun --clr-path /path/to/core_root HelloWorld.dll |
-p, --property <PROPERTY> | 在运行时初始化期间向其传递一个属性,格式为<key>=<value>,可多次指定;属性值含空格时请为整个参数加引号 | corerun --property System.GC.Concurrent=true HelloWorld.dll |
-l, --preload <PATH> | 在加载 CLR 之前先加载指定的共享库(原生库) | corerun --preload /path/to/libfoo.so HelloWorld.dll |
-d, --debug | 在加载 .NET 运行时之前等待调试器附加 | corerun --debug HelloWorld.dll |
-e, --env <PATH> | 指定一个.env文件路径,为本次测试运行设置环境变量(格式兼容 python-dotenv 项目的 dotenv 规范,仓库内实现见 dotenv.cpp) | corerun --env gcstress.env HelloWorld.dll |
-?, -h, --help | 显示帮助信息 | corerun --help |
-st(源码内置) | 执行 corerun 自检(self-test),验证参数解析与工具函数行为 | corerun -st |
帮助文本中还给出了一个综合示例,同时演示了调试等待、传递两个运行时属性以及向托管程序集传参:
corerun -d -p System.GC.Concurrent=true -p "FancyProp=/usr/first last/root" HelloWorld.dll arg1源码视角:这些选项底层做了什么
从 corerun.cpp 的parse_args()与run()实现可以进一步理解各选项的语义:
--clr-path的优先级:run()中先看config.clr_path(命令行传入)是否为空,为空才回退到CORE_ROOT环境变量,再为空则使用corerun可执行文件所在目录。这与文档描述的启发式顺序完全一致,也与display_usage()中 "The runtime binary is searched for in --clr-path, CORE_ROOT environment variable, then in the directory the corerun binary is located." 的说明一一对应。--property的传递链:解析时按=拆分键值,运行时通过get_runtime_property回调(host runtime contract)把用户自定义属性提供给运行时初始化;若属性格式缺少=会直接报错。--debug的行为:wait_for_debugger()会打印进程 PID 并提示 "Waiting for the debugger to attach (PID: ...). Press any key to continue ...",阻塞等待调试器附加,附加成功后打印 "Debugger is attached."。- 运行时初始化流程:
run()依次加载 CoreCLR 动态库、解析coreclr_initialize/coreclr_execute_assembly/coreclr_shutdown_2(以及可选的coreclr_set_error_writer)导出符号,然后构造初始化属性,其中除用户属性外,还固定设置三项基础属性:TRUSTED_PLATFORM_ASSEMBLIES:全部受信程序集的完整路径清单(即前文 TPA 列表);APP_PATHS:程序集加载器将探测的路径列表(即入口程序集所在目录);NATIVE_DLL_SEARCH_DIRECTORIES:P/Invoke 调用原生 DLL 时探测的路径列表(包含应用目录、CORE_LIBRARIES目录与 core root 目录)。 随后调用coreclr_initialize创建运行时实例与应用域,再调用coreclr_execute_assembly执行托管程序集,最后通过coreclr_shutdown_2关闭运行时并取回退出码。
--preload:在加载 CLR 之前通过dlopen/LoadLibraryEx预加载指定共享库,可用于注入原生依赖或 mock 库。--env的 dotenv 解析:仓库内 dotenv.hpp 说明其实现了基于 python-dotenv 项目格式的.env文件解析,并在load_into_current_process()中把键值写入当前进程环境,供运行时与测试使用;此外 corerun 自带的self_test()还会对 dotenv 解析逻辑做自检。
常见问题与排查思路
corerun找不到运行时:请确认产物目录中确实存在coreclr.dll/libcoreclr.so/libcoreclr.dylib,并检查--clr-path、CORE_ROOT是否指向该目录。源码中运行时加载失败会打印 "Failed to load: '<路径>'" 与具体错误码,可据此定位路径问题。- 类库版本不匹配:使用
CORE_LIBRARIES指向系统共享类库时,务必确认其版本与你构建的运行时兼容;如果需要同时测试类库改动,请改用 Core_Root 方案。 - 无法加载托管程序集或 P/Invoke 失败:检查
APP_PATHS是否包含入口程序集所在目录、NATIVE_DLL_SEARCH_DIRECTORIES是否包含原生依赖所在目录;这些目录在run()中会自动汇集应用目录、CORE_LIBRARIES与 core root,若自定义布局较特殊,可通过--env注入辅助环境变量辅助排查。 - Core_Root 生成报类库配置错误:回顾测试构建前提——必须先以
-subset libs构建类库,且默认按 Release 搜索;若你的类库是其他配置,请携带/p:LibrariesConfiguration=<your_config>。
延伸阅读
- CoreCLR 构建指南:了解 clr/lib 子集构建参数、产物布局与 Core_Root 生成命令的完整上下文。
- CoreCLR 测试文档:测试构建脚本的详细用法、配置参数与测试运行方式。
- 使用已安装 SDK 运行你的构建 与 使用构建的 Shipping 包:另外两种测试自构建运行时的方案。
- 源码参考:corerun.cpp、corerun.hpp、dotenv.cpp。
- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
相关推荐
dotnet/runtime 如何用 corerun 与 Core_Root 运行自己构建的托管应用?
dotnet/runtime 如何用 corerun 与 Core_Root 运行自己构建的托管应用? 你在 dotnet/runtime 仓库里改动了 Cor
语言运行时标准库JIT编译编译器.NET Runtime CoreCLR 测试构建与运行完全指南:从 src/tests 到 Core_Root
.NET Runtime CoreCLR 测试构建与运行完全指南:从 src/tests 到 Core_Root 本篇技术指南围绕 dotnet/runtime
语言运行时标准库JIT编译编译器在 Cloudflare Worker 中运行 ECMAScript 模块:基于 worker-javascript 示例构建 Workspace 运行时
在 Cloudflare Worker 中运行 ECMAScript 模块:基于 worker javascript 示例构建 Workspace 运行时 本文
后端云原生存储
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考