WSL2 通过 vscode 调试 native(ELF) 格式的 dotnet 程序(瞎折腾)

先说结论:参见 launch.json 中的注释部分,引用了 cef 的 dotnet 程序在 Linux 下面是 vscode 调试不了的,不引用 cef 的话 VS2022 在 WSL 模式下就能调试,所以不用折腾了!

默认情况下,VS2022 调试运行是通过 dotnet MyApp.dll 启动目标程序的,当调试对象是包含引用 cef 之类需要多进程执行的应用程序时,VS2022 能自动附加到 CEF 子进程(Windows 特有机制),

1. CEF 启动时会创建多个子进程:
   Browser 进程(你的主程序)
   Renderer 进程(渲染网页内容)
   GPU / Utility / Plugin 等进程

2. VS2022 默认启用“子进程调试”
   项目属性 → Debug → 勾选了 "Enable native code debugging""Debug child processes"
   当主进程启动 CEF 子进程时,VS2022 会自动附加调试器到所有子进程
   即使子进程是 chrome.exe 或 MyApp.exe --type=renderer,也能被监控

3. Windows 的调试 API 支持进程继承
   Windows 提供 DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS 标志
   VS2022 启动主进程时使用 DEBUG_PROCESS,自动继承调试权限到子进程
   所有子进程的异常、断点、模块加载都会通知调试器

但是 WSL2 下的 Linux 调试工具链没有这种支持。当子进程命令是 "./MyApp --type=renderer" 时,后台启动的"dotnet --type=renderer ..." 会被视作非法命令,导致目标进程崩溃,即使禁用 CEF 多进程:--single-process 也不行。
这就导致明明引用了 CEF 的 dotnet 应用程序发布成 linux-x64 后可以运行,但 VS2022 却无法在 WSL 模式下调试启动它。以往的解决办法是发布成 linux-x64 以后,通过 VS2022 + SSH 在 Linux 主机上远程调试,比较麻烦,官方已经不推荐了。如今更主流的做法,是使用 vscode 在 WSL2 中实现 dotnet 程序的 native 调试。

1. 在 WSL2 中进入解决方案或工程项目的目录,启动 vscode (注意,要在宿主 Windows 系统上安装了 vscode,不要在 WSL2 里直接安装)
code .
在 WSL 中首次运行 vscode 时,会自动下载安装 vscode Server,稍候片刻等 vscode 窗口弹出就好了。安装启用必要的 vscode 插件,比如 C# Dev Kit 和 vscode-solution-explorer。当然 dotnet-sdk 也是事先需要安装好的。

2. 手动创建 .vscode/launch.json 或者在 vscode 中“添加配置”
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Native App",
      "type": "coreclr",
      "request": "launch",
      "program": "${workspaceFolder}/${input:projectName}/bin/Debug/net8.0/${input:projectName}",
      "cwd": "${workspaceFolder}/${input:projectName}",
      "console": "integratedTerminal"
      // ==== 以下内容,建议先看完再动手,因为其实不需要动手 ====
      // 直接运行 native 程序,代替 dotnet *.dll 方式启动调试
      //"console": "integratedTerminal",
      //"stopAtEntry": true,
      // "env": {
      //   "DISPLAY": ":0",
      //   "WAYLAND_DISPLAY": "",              // 清空(Win10 WSLg 不支持 Wayland)
      //   "XDG_SESSION_TYPE": "x11",          // 显式指定 X11
      //   "GDK_BACKEND": "x11",               // 强制 GTK3 使用 X11
      //   "XDG_RUNTIME_DIR": "/run/user/0",   // 替换为 vscode 终端运行的 UID, 用 id -u 查询,以 root 运行时是 0,普通用户运行一般是 1000
      //   "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/0/bus"  // UID 要与 XDG_RUNTIME_DIR 设置一致
      // }
      //
      // 但是由于 vsdbg 在 integratedTermina 模式下会直接 fork + 注入调试代理,可能干扰 X11/GTK 初始化
      // 让 vscode 调用系统终端(如 xterm -e ...),通过终端再启动你的程序,确保 vsdbg 只 attach,不控制启动上下文
      // 需要手动安装 xterm 程序:apt install xterm
      //"console": "externalTerminal",
      //
      // 结果用 xterm 直接运行可以,但是调试启动也不灵,
      // 尝试手动从内置控制台启动进程后,在 vscode 里面 Attach to Process 附加到进程调试
      // 最好在 Program.Main() 中启动时通过 Debugger.IsAttached 判断并等待调试器附加
      //
      // 然而不幸的是,cef 在 linux 下调试器一附加,就 Trace/breakpoint trap (core dumped) 退出了
      // 结论:尽量在 Windows 下调试,Linux 下还是死心踏地祭出 Console.WriteLine 大法,放弃挣扎吧
    }
  ],
   "inputs": [
    {
      "id": "projectName",
      "type": "pickString",
      "description": "请指定要以 Native 模式调试的工程",
      "options": ["MyApp"],
      "default": "MyApp"
    }
  ]
}

因为 dotnet build 在 WSL2 中除了生成 MyApp.dll 之外,还同时生成了 MyApp 这个 ELF 格式的可执行程序(对应 Windows 下生成 PE 格式的 MyApp.exe),所以直接指定运行这个可执行文件,可以代替执行默认的 dotnet MyApp.dll。注意这里将 .vscode 目录创建在解决方案目录下的,所以增加了一个选择调试目标工程的步骤。如果是在工程目录下创建的,那么可以去掉 program 和 cwd 路径中的 /${input:projectName},把 program 中的第二个 /${input:projectName} 换成实际的 MyApp 即可。

3. 保存 launch.json 后,在 vscode 中菜单“运行”——“启动调试(F5)” ,你会发现应用程序如愿停在了 vscode 中打了断点的位置。

综上所述,当你在 Linux 或 WSL2 下开发 dotnet + CEF 这类需要调试运行 native executable 的场景,vscode 是更灵活、更合适的选择。

折腾半天,被 DeepSeek 和 Qwen 耍得团团转,有道是“AI得来终觉浅,绝知此事要躬行”

 

补充说明:修改 csproj 文件,在 PropertyGroup 中添加以下内容,可以让 WSL 下通过控制台运行更方便一点

<!-- 指定工程编译 linux-x64 自包含应用,即除了生成默认的 .NET 程序集(assembly)VMP.Container.dll 以外,
     Debug/Release 直接生成 Linux ELF 格式的可执行文件,而非 Windows 可执行的 PE 格式 .exe 文件,
     这个文件是可以直接在 WSL 的控制台中启动运行的,免去了每次要发布成 linux-x64 才能在 Linux 运行的麻烦。
-->
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<!-- 是否自动在 OutputPath 中加入 RuntimeIdentifier
     false 不将 RID 表示添加到输出路径中, 将生成文件到 ${BaseOutputPath}\$(Configuration)\$(TargetFramework)\, 
           即生成 bin\GtkForm.Linux\Debug\net8.0\VMP.Container
     true  将生成文件到 ${BaseOutputPath}\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier),
           即生成 bin\GtkForm.Linux\Debug\net8.0\linux-x64\VMP.Container
-->
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>

 

posted @ 2026-02-09 01:08  树欲静·而风不止  阅读(5)  评论(0)    收藏  举报