ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

使用 Visual Studio Code 调试 .NET runtime 库:从 launch.json 到 Mono 远程附加的完整指南

使用 Visual Studio Code 调试 .NET runtime 库:从 launch.json 到 Mono 远程附加的完整指南 语言运行时标准库JIT编译编译器【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载本文聚焦于在 .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 Kitms-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按CtrlShiftDmacOS 为CmdShiftD打开调试窗口或点击左侧活动栏的“运行和调试”按钮点击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, categoryfailing ]其中{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 categoryfailing将其改写成[ exec, --runtimeconfig, System.Net.Sockets.Tests.runtimeconfig.json, xunit.console.dll, System.Net.Sockets.Tests.dll, -notrait, categoryfailing ]运行单个测试方法如果只想调试某个具体测试追加-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, categoryfailing ]同样你可以先用 MSBuild 属性拿到精确参数再回填dotnet build /t:Test /p:xUnitMethodNameSystem.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 categoryfailing会出现在每个测试命令行里_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 categoryx$(WithCategories)仅运行带某 category trait 的用例-notrait categoryx$(WithoutCategories)排除带某 category trait 的用例-maxthreads 1$(TestDisableParallelization)关闭并行便于串行调试-verbose$(XUnitShowProgress)打印运行进度测试项目的命名与框架也有一套约定tests.props 中TestProjectName默认为$(MSBuildProjectName)即项目文件名TestFramework默认为xunitxunit.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 Debuggerms-vscode.mono-debug它提供mono类型的调试配置。2. 编写 typemono 的附加配置在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_SUPPORTED0 MONO_ENV_OPTIONS--debug --debugger-agenttransportdt_socket,address127.0.0.1:1235,servery,suspendy ./dotnet.sh build /t:Test /p:RuntimeFlavorMono src/libraries/System.Buffers/tests拆解这条命令./dotnet.sh仓库根目录下的构建脚本见 dotnet.sh负责调用仓库内置的 SDKbuild /t:Test构建并运行测试目标/p:RuntimeFlavorMono显式指定使用 Mono 运行时这与构建脚本里PrimaryRuntimeFlavorMono的切换逻辑一致见 build.shMONO_ENV_OPTIONS--debug --debugger-agenttransportdt_socket,address127.0.0.1:1235,servery,suspendy启动 Mono 调试代理并挂起等待调试器附加DOTNET_REMOTEEXECUTOR_SUPPORTED0必须设置。否则测试的 RemoteExecutor 会启动多个运行时实例多个进程会同时尝试监听1235端口导致冲突。这一点在测试基础设施中也有体现MONO_ENV_OPTIONS会被注入到测试运行脚本里见 tests.props而DOTNET_REMOTEEXECUTOR_SUPPORTED0正是为了抑制这种多实例抢占端口的场景。Windows 注意在 Windows 上不要在MONO_ENV_OPTIONS里传--debug只需保留调试代理参数。4. 附加调试器保持命令行中的测试进程等待suspendy在 VS Code 测试代码里设置断点然后以Attach to Mono配置启动调试。调试器附加成功后断点即可命中变量与调用栈的查看方式和本地调试一致。Mono 调试的两个已知限制Mono 不会在“首机会异常”first chance exception上暂停xunit 会捕获所有异常。因此如果测试里抛出异常调试器不会自动停在未捕获异常处。排查时应在可能抛异常的代码位置显式设置断点而不是依赖“异常中断”行为。调试流程速查与常见问题典型流程回顾打开目标库目录如src/libraries/System.Net.Sockets用/t:Test构建一次测试从终端输出捕获exec ...参数生成launch.jsonCoreCLR删preLaunchTask、填program/cwd/argsMono写type: monoattach 配置在测试源码打断点启动对应调试配置用/p:xUnitMethodName...或-method缩小到单个用例加快定位。常见问题现象原因与处理断点显示为“未绑定”program指向的 dotnet 版本与当前构建不匹配或cwd不在测试输出目录确认路径与artifacts/bin实际产物一致启动报“找不到 runtimeconfig”args中--runtimeconfig文件名与测试项目名不一致注意是TestProjectName而非目录名调试器未命中但测试能跑args与真实测试命令不一致尤其是-notrait/-method部分重新跑命令核对exec输出Mono 附加时端口被占用未设置DOTNET_REMOTEEXECUTOR_SUPPORTED0多个运行时实例抢占1235想临时打印日志调试库代码时避免使用System.Console.WriteLine可参考 CoreLib 调试指南 中的专用日志通道延伸阅读在 Unix 上用 lldb 调试 core .NET 库面向崩溃转储与 SOS 的底层调试路径调试 System.Private.CoreLibCoreLib 内部调试时日志输出的特殊约定Android Mono 调试 与 WebAssembly 调试跨平台 Mono 场景的配套方案xunit 测试基础设施查看测试运行命令的完整生成规则可据此构造任意自定义调试参数。赞分享语言运行时标准库JIT编译编译器【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载相关推荐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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进