- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
本文聚焦于在 .NET runtime 仓库(runtime)中使用 Visual Studio Code 调试库(Libraries)测试的完整流程,覆盖两条核心路径:基于 CoreCLR 的本地启动调试(.NET Core Launch (console))与基于 Mono 运行时的远程附加调试(Attach to Mono)。读完本文,你将能够精准构造launch.json,把断点打在System.Net.Sockets等库的测试代码中,并理解测试运行命令exec ... xunit.console.dll ...背后的参数生成机制(见 xunit.console.targets)。
调试前的环境准备
开始之前,请确保本机满足以下条件:
- 安装 Visual Studio Code;
- 安装C# 扩展(
ms-dotnettools.csharp),它是 C# 语言服务与调试器的基础; - 可选安装C# Dev Kit(
ms-dotnettools.csdevkit),它提供更完整的项目管理与测试集成体验,非调试必需; - 本地已经能够构建 runtime 仓库并产出测试产物(
artifacts/bin目录下存在 testhost 与测试项目输出)。
调试的核心思路是:绕过常规的dotnet test启动方式,直接用调试器启动 testhost 中的dotnet可执行文件,把测试程序集作为参数传入。因此你不需要预先把断点绑定到 VS Code 的测试面板,只需要在测试代码里打断点并启动调试配置即可。
在 VS Code 中创建并配置调试会话
打开目标库源码目录
打开包含你想调试源码的文件夹。例如,如果你要调查System.Net.Sockets的测试失败,就打开:
runtime/src/libraries/System.Net.Sockets只打开与问题相关的库目录,能让调试器的符号加载与断点命中范围更聚焦,也便于使用该目录下的工作区配置。
生成 launch.json
- 按
Ctrl+Shift+D(macOS 为Cmd+Shift+D)打开调试窗口,或点击左侧活动栏的“运行和调试”按钮; - 点击create a launch.json file,在下拉菜单里选择包含
.NET Core的模板; - VS Code 会生成一个
.vscode/launch.json,里面默认包含.NET Core Launch (console)配置。
修改 .NET Core Launch (console) 配置
默认模板假设你要启动一个普通控制台应用,必须针对 runtime 的测试体系做三处关键修改:
1. 删除preLaunchTask属性
模板默认会先执行一个构建任务(preLaunchTask),这会让调试器每次启动前重新编译。在 runtime 仓库里,你应该先手动构建测试项目,然后纯粹用调试器启动已经构建好的产物,因此直接删除该属性,避免调试会话被构建任务干扰。
2. 设置program为 testhost 中的 dotnet 可执行文件
program指向真正承载测试运行的主机进程:
{full path to your dotnet/runtime directory}/artifacts/bin/testhost/net{Version}-{OS}-{Configuration}-{Architecture}/dotnet例如,Linux x64 的 Debug 构建对应路径大致为:
/path/to/runtime/artifacts/bin/testhost/net10.0-linux-Debug-x64/dotnet这个dotnet是仓库构建脚本产出、专用于测试的运行时主机,它内部带上了刚构建的 CoreCLR 与库程序集,直接使用它才能调试到仓库里的最新代码。
3. 设置cwd为测试项目的 bin 目录
cwd必须指向测试程序集所在的输出目录,这样测试依赖的程序集、runtimeconfig.json才能被正确解析。以System.Net.Sockets为例:
{full path to your dotnet/runtime directory}/artifacts/bin/System.Net.Sockets.Tests/Debug/net{Version}-{OS}不同库的目录命名可能略有差异(例如多目标框架会带上 TFM 后缀),以实际构建输出为准。如果拿不准,直接去artifacts/bin下找到对应测试项目的输出目录即可。
4. 设置args为测试运行参数
args是最核心、也最容易出错的部分。它的形态是:
[ "exec", "--runtimeconfig", "{TestProjectName}.runtimeconfig.json", "xunit.console.dll", "{TestProjectName}.dll", "-notrait", "category=failing" ]其中{TestProjectName}是测试项目名,比如System.Net.Sockets.Tests。
如何拿到这组参数(推荐做法):先在终端里跑一次你关心的测试命令,然后看输出中exec开头的片段,把它原样复制并改写成 JSON 数组填入args。
例如,在runtime/src/libraries/System.Net.Sockets/tests/FunctionalTests目录下执行:
dotnet build /t:Test终端输出中会出现类似:
exec --runtimeconfig System.Net.Sockets.Tests.runtimeconfig.json ... xunit.console.dll System.Net.Sockets.Tests.dll -notrait category=failing将其改写成:
[ "exec", "--runtimeconfig", "System.Net.Sockets.Tests.runtimeconfig.json", "xunit.console.dll", "System.Net.Sockets.Tests.dll", "-notrait", "category=failing" ]运行单个测试方法
如果只想调试某个具体测试,追加-method参数,格式为{类全名}.{方法名}:
[ "exec", "--runtimeconfig", "System.Net.Sockets.Tests.runtimeconfig.json", "xunit.console.dll", "System.Net.Sockets.Tests.dll", "-method", "System.Net.Sockets.Tests.{ClassName}.{TestMethodName}", "-notrait", "category=failing" ]同样,你可以先用 MSBuild 属性拿到精确参数再回填:
dotnet build /t:Test /p:xUnitMethodName=System.Net.Sockets.Tests.{ClassName}.{TestMethodName}参数生成机制的源码印证
args里出现的这些参数并不是魔法字符串,它们由仓库的 MSBuild 测试基础设施组装而成。在 xunit.console.targets 中可以看到:
<RunScriptCommand Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">"$(RunScriptHost)" exec --runtimeconfig $(AssemblyName).runtimeconfig.json $(_depsFileArgument) $(XunitConsolePath)</RunScriptCommand> ... <RunScriptCommand Condition="'$(XUnitMethodName)' != ''">$(RunScriptCommand) -method $(XUnitMethodName)</RunScriptCommand> <RunScriptCommand Condition="'$(XUnitClassName)' != ''">$(RunScriptCommand) -class $(XUnitClassName)</RunScriptCommand> <RunScriptCommand>$(RunScriptCommand)$(_withCategories.Replace(';', ' -trait category='))</RunScriptCommand> <RunScriptCommand>$(RunScriptCommand)$(_withoutCategories.Replace(';', ' -notrait category='))</RunScriptCommand>这解释了为什么-notrait category=failing会出现在每个测试命令行里:_withoutCategories把分号分隔的类别列表展开成多条-notrait category=...参数,用于默认排除标记为failing的用例。同时,/p:xUnitMethodName=...这个 MSBuild 属性最终通过-method落到运行命令上,这正是上面“先跑命令再抄参数”方案的底层依据。
其他可用的测试运行开关(同样来自 xunit.console.targets):
| 开关 | 来源属性 | 作用 |
|---|---|---|
-xml <file> | $(TestResultsName) | 输出测试结果 XML(默认testResults.xml) |
-nologo | 固定追加 | 关闭 xunit console 的启动横幅 |
-method <FQN> | $(XUnitMethodName) | 只运行指定方法 |
-class <FQN> | $(XUnitClassName) | 只运行指定类 |
-trait category=<x> | $(WithCategories) | 仅运行带某 category trait 的用例 |
-notrait category=<x> | $(WithoutCategories) | 排除带某 category trait 的用例 |
-maxthreads 1 | $(TestDisableParallelization) | 关闭并行,便于串行调试 |
-verbose | $(XUnitShowProgress) | 打印运行进度 |
测试项目的命名与框架也有一套约定:tests.props 中TestProjectName默认为$(MSBuildProjectName)(即项目文件名),TestFramework默认为xunit;xunit.props 则统一引入了xunit.core、xunit.assert、xunit.analyzers、Microsoft.DotNet.XUnitExtensions等包,保证所有库测试项目使用一致的测试栈。
断点调试与配置固化
完成以上修改后:
- 在测试代码中设置断点;
- 以
.NET Core Launch (console)配置启动调试; - 断点命中后,即可正常查看局部变量、监视表达式、调用堆栈,单步执行。
可选优化:把配置保存到工作区文件(workspace)。VS Code 的.code-workspace工作区文件可以放在仓库根目录之外,这样git clean -dfx之类的清理操作不会误删你的调试配置,也不受.vscode目录位置限制。对于经常在同一台机器上切换调试多个库的开发者,这是保持配置长期可用的推荐做法。
在 Mono 运行时上调试库(远程附加模式)
如果你需要在桌面平台(Linux / macOS / Windows)上,让库代码跑在Mono 运行时(而非默认的 CoreCLR)上进行调试,则使用“远程附加”模式。WebAssembly 与 Android/iOS 的 Mono 调试属于另一套工具链,分别见 Android 调试 与 WebAssembly 调试。
1. 安装 Mono Debugger 扩展
安装 VS Code 扩展Mono Debugger(ms-vscode.mono-debug),它提供mono类型的调试配置。
2. 编写 type=mono 的附加配置
在launch.json中新增一个type: "mono"的 attach 配置:
{ "version": "0.2.0", "configurations": [ { "name": "Attach to Mono", "type": "mono", "request": "attach", "address": "localhost", "port": 1235 } ] }该配置让调试器通过dt_socket传输协议,附加到本地1235端口上等待连接的 Mono 进程。
3. 用 MONO_ENV_OPTIONS 启动测试
在命令行启动测试,并通过MONO_ENV_OPTIONS环境变量把调试代理参数注入 Mono 运行时:
DOTNET_REMOTEEXECUTOR_SUPPORTED=0 MONO_ENV_OPTIONS="--debug --debugger-agent=transport=dt_socket,address=127.0.0.1:1235,server=y,suspend=y" ./dotnet.sh build /t:Test /p:RuntimeFlavor=Mono src/libraries/System.Buffers/tests拆解这条命令:
./dotnet.sh:仓库根目录下的构建脚本(见 dotnet.sh),负责调用仓库内置的 SDK;build /t:Test:构建并运行测试目标;/p:RuntimeFlavor=Mono:显式指定使用 Mono 运行时,这与构建脚本里PrimaryRuntimeFlavor=Mono的切换逻辑一致(见 build.sh);MONO_ENV_OPTIONS="--debug --debugger-agent=transport=dt_socket,address=127.0.0.1:1235,server=y,suspend=y":启动 Mono 调试代理并挂起,等待调试器附加;DOTNET_REMOTEEXECUTOR_SUPPORTED=0:必须设置。否则测试的 RemoteExecutor 会启动多个运行时实例,多个进程会同时尝试监听1235端口导致冲突。这一点在测试基础设施中也有体现:MONO_ENV_OPTIONS会被注入到测试运行脚本里(见 tests.props),而DOTNET_REMOTEEXECUTOR_SUPPORTED=0正是为了抑制这种多实例抢占端口的场景。
Windows 注意:在 Windows 上,不要在MONO_ENV_OPTIONS里传--debug,只需保留调试代理参数。
4. 附加调试器
保持命令行中的测试进程等待(suspend=y),在 VS Code 测试代码里设置断点,然后以Attach to Mono配置启动调试。调试器附加成功后,断点即可命中,变量与调用栈的查看方式和本地调试一致。
Mono 调试的两个已知限制
- Mono 不会在“首机会异常”(first chance exception)上暂停;
- xunit 会捕获所有异常。
因此,如果测试里抛出异常,调试器不会自动停在未捕获异常处。排查时应在可能抛异常的代码位置显式设置断点,而不是依赖“异常中断”行为。
调试流程速查与常见问题
典型流程回顾
- 打开目标库目录(如
src/libraries/System.Net.Sockets); - 用
/t:Test构建一次测试,从终端输出捕获exec ...参数; - 生成
launch.json(CoreCLR:删preLaunchTask、填program/cwd/args;Mono:写type: monoattach 配置); - 在测试源码打断点,启动对应调试配置;
- 用
/p:xUnitMethodName=...或-method缩小到单个用例,加快定位。
常见问题
| 现象 | 原因与处理 |
|---|---|
| 断点显示为“未绑定” | program指向的 dotnet 版本与当前构建不匹配,或cwd不在测试输出目录;确认路径与artifacts/bin实际产物一致 |
| 启动报“找不到 runtimeconfig” | args中--runtimeconfig文件名与测试项目名不一致(注意是TestProjectName而非目录名) |
| 调试器未命中但测试能跑 | args与真实测试命令不一致,尤其是-notrait/-method部分;重新跑命令核对exec输出 |
| Mono 附加时端口被占用 | 未设置DOTNET_REMOTEEXECUTOR_SUPPORTED=0,多个运行时实例抢占1235 |
| 想临时打印日志 | 调试库代码时避免使用System.Console.WriteLine,可参考 CoreLib 调试指南 中的专用日志通道 |
延伸阅读
- 在 Unix 上用 lldb 调试 core .NET 库:面向崩溃转储与 SOS 的底层调试路径;
- 调试 System.Private.CoreLib:CoreLib 内部调试时日志输出的特殊约定;
- Android Mono 调试 与 WebAssembly 调试:跨平台 Mono 场景的配套方案;
- xunit 测试基础设施:查看测试运行命令的完整生成规则,可据此构造任意自定义调试参数。
- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
相关推荐
Visual Studio Code调试配置完全指南:launch.json深度解析
Visual Studio Code的调试功能是开发者日常工作中不可或缺的工具,而launch.json文件则是调试配置的核心。无论你是前端开发者、后端工程师还
文档教程如何本地部署 WeKnora:离线 RAG 知识库搭建实战指南
如何本地部署 WeKnora:离线 RAG 知识库搭建实战指南 假设:财务那边压了几百份合同 PDF,合规要求数据不能出内网,但大家都希望能"问"这些文档。这是
语言运行时标准库JIT编译编译器.NET runtime Mono 调试指南:从加速构建、崩溃挂起到 JIT IR 可视化
.NET runtime Mono 调试指南:从加速构建、崩溃挂起到 JIT IR 可视化 本文基于 .NET runtime 仓库中 docs/design/
语言运行时标准库JIT编译编译器
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考