Skip to content

Offroad Debugging of .NET Core on Linux OSX from Visual Studio

Andrew Wang edited this page Mar 29, 2023 · 38 revisions

Microsoft and the .NET community have created a new version of .NET, .NET Core, which is designed to be cross-platform, modular, and optimized for the cloud.

If you want to debug on the target device itself, you can use Visual Studio Code by following these instructions. But it is also possible to stay in Visual Studio and still debug to Linux or OSX. For day-to-day development, especially if you intend to deploy to a Linux Docker container, Visual Studio 2017+ included tools for publishing and debugging to a Docker container. You can also remotely attach to a process over SSH. As of Visual Studio 2019 version 16.3, Docker attach support is also included with Visual Studio.

In the future, more scenarios are likely to get a more 'on-road' experience from Visual Studio. But for now, for folks who want to play around with non-Docker scenarios launch, or non-SSH/Docker attach or just want to get under the hood and see how things work, this wiki page should walk you through how to get things to work in an offroad manner. Pay attention as this will be a bit bumpy.

The debugger platform has been expanded between Visual Studio 2017 15.3 and previous versions of Visual Studio. Visual Studio can now communicate using the debug adapter protocol which is used by Visual Studio, Visual Studio Code and Visual Studio for Mac. This protocol is now used for debugging cross-platform .NET Core applications. The following instructions apply to Visual Studio 15.3 (and newer). If you are constrained to an older version of Visual Studio refer to the legacy instructions.

Your feedback

File bugs and feature requests here. Many issues are likely to be in common with Visual Studio Code, so please also look in the VS Code OmniSharp C# extension GitHub to see if the issue is already filed.

Machine setup

Visual Studio Computer

Install Visual Studio 2017 or newer and select the .NET Core workload. If you already have Visual Studio, you can do this from within the installer.

Linux Computer

The Linux system needs to have the .NET CLI tools, dotnet, and a vsdbg debugger installed.

  1. Install the .NET Core Command line tools (CLI).
  2. Install VSDBG by running the following command. Replace '~/vsdbg' with wherever you want vsdbg installed to.
# Using cURL
curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v vs2019 -l ~/vsdbg
# Alternatively, wget
wget https://aka.ms/getvsdbgsh -O - 2>/dev/null | /bin/sh /dev/stdin -v vs2019 -l ~/vsdbg
Copying vsdbg from Windows to Linux using PowerShell

If you want to download vsdbg on Windows and then copy it to your Linux/Mac computer/container, you can use the .ps1 script with this one-liner. Other supported RuntimeID values are linux-musl-x64, linux-arm and osx.

powershell -NoProfile -ExecutionPolicy unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -useb 'https://aka.ms/getvsdbgps1'))) -Version vs2019 -RuntimeID linux-x64 -InstallPath c:\vsdbg\linux-x64"

Setting up a transport

Visual Studio relies on another executable to take care of remoting stdin/out between the Windows computer and the target computer. The nice side of this is that you can debug as long as you have some way to exchange messages between your two computers; The downside is that this will take a bit of work to setup. Then again, you are reading the offroad instructions :). Here are some hints for getting things setup with Docker or SSH, but you can bring anything you want.

SSH

For SSH support, you will first want to enable SSH in your Linux server. For example, on Ubuntu you can do that by running:

sudo apt-get install openssh-server

Then, on Windows you need an SSH client designed to be used programmatically. Plink.exe from PuTTY fits the bill, though you can certainly use other tools too. If you are running on a recent version of Windows 10, you can use the built-in ssh.exe (C:\Windows\sysnative\OpenSSH\ssh.exe). Note: Using C:\Windows\System32\ will not work as expected.

Next, you need a scriptable way to authenticate. One option is to provide the password on the command line, but obviously there are some security concerns there. A more secure option is to use SSH keys --

  1. Download puttygen.exe from PuTTY.
  2. Run the tool and click 'Generate' and follow the instructions. Note: leave 'Key passphrase' empty, otherwise plink.exe will fail to open the key.
  3. Save the generated private key to a file.
  4. Copy the public key's text from the top part of the PuTTY Key Generator's Window.
  5. Add this to the ~/.ssh/authorized_keys file on your server.
  6. Test your connection from the command line. This will also allow you to accept the server's key on the client, which must be done the first time.

Example:

c:\mytools\plink.exe -i c:\users\greggm\ssh-key.ppk greggm@mylinuxbox -batch -T echo "hello world"
# 'hello world' should print

Sharing sources and compiled binaries

Building on Windows

If you are developing and compiling your app in Visual Studio, you need some way of getting the following on your Linux target machine:

  • The PDB files for any module you want to debug. Note that only Portable PDBs are supported.
  • The app itself and any runtime dependencies it might have
  • The '<proj-name>.deps.json' file which is used by the 'dotnet' host executable to determine runtime dependencies

For simple projects, you can find these files in <SolutionDir>\artifacts\src\<ProjectName>\bin\Debug\<TargetFramework>. For projects with dependencies, you can use 'dotnet publish' to assemble the files you are likely to need.

Building on Linux

If you are compiling your app on Linux, you need some way of sharing sources back to Windows so that Visual Studio can open them.

Transferring file

Obviously there any many options to transfer files between Windows and Linux. This document will not try and list all of them, but for those just trying to kick the tires, here are a few commands you might find useful:

Connect to a Windows share from Ubuntu:

sudo apt-get install cifs-utils
sudo mkdir /mnt/myshare
sudo mount -t cifs //my-windows-computer/myshare /mnt/myshare -o domain=my-windows-domain,username=myalias,uid=$USER,gid=$USER
# /mnt/myshare should now be mapped

To copy files using scp (SSH-based secure copy):

# NOTE: greggm is an example account name
c:\mytools\pscp.exe -i c:\users\greggm\my-ssh-key.ppk c:\MyProject\artifacts\src\MyProject\bin\Debug\netcoreapp1.0\* greggm@mylinuxbox:/home/greggm/myproject

Create launch configuration file:

Next, you need to create an launch.json file that will tell Visual Studio how to debug. Here is an example launch.json file which uses plink.exe to connect to the target over SSH and launch a project called 'clicon':

{
  "version": "0.2.0",
  "adapter": "c:\\mytools\\plink.exe",
  "adapterArgs": "-i c:\\users\\greggm\\ssh-key.ppk greggm@mylinuxbox -batch -T ~/vsdbg/vsdbg --interpreter=vscode",
  "configurations": [
    {
      "name": ".NET Core Launch",
      "type": "coreclr",
      "cwd": "~/clicon",
      "program": "bin/Debug/netcoreapp1.0/clicon.dll",
      "request": "launch"
    }
  ]
}

Let's look at how this works:

  • Adapter: This is the path to the executable that VSCodeDebugAdapterHost will launch that will connect to the target computer, launch vsdbg, and establish that stdin/out connection. Any tools that forward the stdin/stdout from vsdbg on a remote machine can be used.
  • AdapterArgs: Any arguments that the executable specified in 'Adapter' takes. In my example, I am telling plink.exe the path to my private SSH key, telling it to connect to my SSH Linux box, and telling it to run executable vsdbg from the '~/vsdbg' directory that I installed it to. If you not using plink, make sure to set any flags that preserve whitespace since the debug adapter protocol relies on the exact byte length of messages. The -T flags disables PTY allocation, which is important for byte length. Be sure to use -T and not -t.
  • Program: The path, on the Linux computer, to the executable I want to run. Since the debuggee is a .NET Core assembly, vsdbg will automatically run the program with the dotnet host executable.

Turn off Just My Code if you are retail debugging

If you are attempting to debug retail code, you will want to turn off Just My Code through Tools->Options->Debugging in Visual Studio.

Start debugging

  1. Start Visual Studio
  2. View->Other Windows->Command Window
  3. DebugAdapterHost.Launch /LaunchJson:"<path-to-the-launch.json-file-you-saved>" /EngineGuid:541B8A8A-6081-4506-9F0A-1CE771DEBC04

Troubleshooting

  • If you run into problems getting this to work, you can turn on logging by opening the VS Command Window (View->Other Windows->Command Window), and running: DebugAdapterHost.Logging /On /OutputWindow. This will then send logging messages to the output window ('Debug Adapter Host Log' pane). You can also add /Verbosity:debug for more information.
  • The error message 'The debug adapter exited unexpectedly' means that the transport executable (plink.exe in the above example) exited unexpectedly. A few notes in troubleshooting this --
    • As noted in the first troubleshooting step, the logs are the first place to look. The transport may have written errors before it exited (Debug adapter error output: lines).
    • If the target process is running in a Docker container, this could indicate that the container shutdown. For example because the additional memory used by the debugger caused the container to hit its memory limit.
    • This could also indicate that vsdbg running in the container is crashing or being aborted. You can confirm or deny this by modifying the transport command line to run a script that would run vsdbg and then output vsdbg's exit code.

Detaching

Use 'Debug->Detach All' to detach from the process. Stop debugging will terminate the process.

Attaching

Instead of launching, it is also possible to attach in a similar way. Here is an example launch.json file that attaches to a process running in a Docker container:

{
  // NOTE: replace 'my_container_name' with the name of the container you want to connect to
  "version": "0.2.0",
  "adapter": "docker.exe",
  "adapterArgs": "exec -i my_container_name /remote_debugger/vsdbg --interpreter=vscode",
  "configurations": [
    {
      "name": ".NET Core Docker Attach",
      "type": "coreclr",
      "request": "attach",
      // replace with the process id you want to attach to. You can find this by running 'pidof' in the container
      // ex: `docker exec -it my_container_name pidof dotnet`
      "processId": 93
    }
  ]
}

Notes:

  1. Docker support is now built in to the Attach To Process dialog, so use this as a starting point if you need to use a custom transport (example: kubernetes). But please don't actually use this for Docker any longer.
  2. This requires that vsdbg is installed to the container. In this case it assumes it is installed to the /remote_debugger folder (see 'adapterArgs' line above). See the Linux Computer section for more information on obtaining vsdbg.
{
  "version": "0.2.0",
  "adapter": "c:\\mytools\\plink.exe",
  "adapterArgs": "-i \"c:\\users\\greggm\\ssh-key.ppk\" greggm@mylinuxbox -batch -T docker exec -i my_container_name /remote_debugger/vsdbg --interpreter=vscode",
  "configurations": [
      {
          "name": ".NET Core Remote SSH Docker Attach",
          "type": "coreclr",
          "request": "attach",
          // NOTE: This assumes the Docker process is PID 1. Use `top` or `pidof` to check
          "processId": 1
      }
  ]
}