How to debug dotnet processes using the terminal (on linux)

I live in neovim and the terminal. So it is natural to want to use the terminal to quickly attach, run and debug some C#

If you want the most control (and power I guess), gdb and lldb (a comparison for more details), have long been the goto solution. But the don't really support the dotnet runtime. Enter netcoredb

In fact many debugger GUI's are build to leverage gdb or lldb which have automation modes built-in. netcoredb is used by neovim to provide debugger functionality, but you can just use it directly.

Prerequisites

# install https://github.com/Samsung/netcoredbg
yay -S netcoredbg

Debugging a console app

per the docs, it is straight-forward to debug a console app

# assuming some simple exaple code (like https://github.com/guylangston/simple-cs)
netcoredbg --interpreter=cli -- /home/guy/.dotnet/dotnet  ./bin/Debug/net8.0/simple-console.dll
ncdb> break Program.cs:11
 Breakpoint 1 at Program.cs:11 --pending, warning: No executable code of the debugger's target code type is associated with this line.
ncdb> run
^running

library loaded: /home/guy/.dotnet/shared/Microsoft.NETCore.App/8.0.11/System.Private.CoreLib.dll
no symbols loaded, base address: 0x7fdfa6870000, size: 13100544(0xc7e600)

thread created, id: 32990

library loaded: /home/guy/repo/simple-cs/simple-console/bin/Debug/net8.0/simple-console.dll
symbols loaded, base address: 0x7fe026832000, size: 7680(0x1e00)
breakpoint modified,  Breakpoint 1 at /home/guy/repo/simple-cs/simple-console/Program.cs:11

<snip..../>

In
32990

stopped, reason: breakpoint 1 hit, thread id: 32990, stopped threads: all, times= 1, frame={Program.<Main>$() at /home/guy/repo/simple-cs/simple-console/Program.cs:11}
ncdb> bt
#0: 0x00007fdfa7631a65 simple-console.dll` Program.<Main>$() at /home/guy/repo/simple-cs/simple-console/Program.cs:11

ncdb> list
   6        Console.ReadLine();
   7    }
   8    for(int cc=2; cc<20; cc++)
   9    {
   10       var factors = Factorize(cc).ToArray(); // for easy debugger attach
 > 11       Console.WriteLine($"Num: {cc} has Factors: {string.Join(',', factors.Select(x=>x.ToString()))}");
   12   }
   13   Console.WriteLine("Out");
   14   return 0;
   15
ncdb> print cc
cc = 2

Debugging a unit test (xunit)

$ VSTEST_HOST_DEBUG=1 dotnet test --filter CanDebug | grep "Process Id: "
Process Id: 34659, Name: dotnet
$ netcoredbg --attach 34659

Note: Adding VSTEST_HOST_DEBUG=1 as a prefix will set the environment variable, which will pause the running at the start of the matching test and display the PID to allow attaching.

$ netcoredbg --attach 34995

library loaded: /home/guy/.dotnet/shared/Microsoft.NETCore.App/8.0.11/System.Private.CoreLib.dll
no symbols loaded, base address: 0x7fefcac60000, size: 13100544(0xc7e600)
You are debugging a Release build of testhost.dll. Using Just My Code with Release builds using compiler optimizations 

<snip.../>

stopped, reason: interrupted, thread id: 34995, stopped threads: all, frame={System.Private.CoreLib.dll` System.Threading.Monitor.Wait()}
ncdb> bt
#0: 0x00007fefcae675be System.Private.CoreLib.dll` System.Threading.Monitor.Wait()
#1: 0x00007fefcae72292 System.Private.CoreLib.dll` System.Threading.ManualResetEventSlim.Wait()
#2: 0x00007fefcae87fd1 System.Private.CoreLib.dll` System.Threading.Tasks.Task.SpinThenBlockingWait()
#3: 0x00007fefcae87d98 System.Private.CoreLib.dll` System.Threading.Tasks.Task.InternalWaitCore()
#4: 0x00007fefcaed5da8 System.Private.CoreLib.dll` System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification()
#5: 0x00007fefcba22abc testhost.dll` Microsoft.VisualStudio.TestPlatform.TestHost.Program.Run() at /_/src/testhost.x86/Program.cs:61
#6: 0x00007fefcba227cf testhost.dll` Microsoft.VisualStudio.TestPlatform.TestHost.Program.Run() at /_/src/testhost.x86/Program.cs:54
#7: 0x00007fefcba21970 testhost.dll` Microsoft.VisualStudio.TestPlatform.TestHost.Program.Main() at /_/src/testhost.x86/Program.cs:37

ncdb> break ExampleXunit.cs:30
 Breakpoint 1 at ExampleXunit.cs:30 --pending, warning: No executable code of the debugger's target code type is associated with this line.
ncdb> c
^running
ncdb>
thread exited, id: 35004

thread exited, id: 35017

thread created, id: 35487
You are debugging a Release build of Microsoft.TestPlatform.CrossPlatEngine.dll. Using Just My Code with Release builds using compiler optimizations results in a degraded debugging experience (e.g. breakpoints will not be hit).
library loaded: /home/guy/repo/simple-cs/simple-test/bin/Debug/net8.0/Microsoft.TestPlatform.CrossPlatEngine.dll
symbols loaded, base address: 0x7fefc8166000, size: 315328(0x4cfc0)

<snip..../>

You are debugging a Release build of xunit.assert.dll. Using Just My Code with Release builds using compiler optimizations results in a degraded debugging experience (e.g. breakpoints will not be hit).
library loaded: /home/guy/repo/simple-cs/simple-test/bin/Debug/net8.0/xunit.assert.dll
symbols loaded, base address: 0x7faf14020000, size: 136224(0x21420)

stopped, reason: breakpoint 1 hit, thread id: 35503, stopped threads: all, times= 1, frame={simple_test.ExampleXunit.CanDebug() at /home/guy/repo/simple-cs/simple-test/ExampleXunit.cs:30}
ncdb> print a
a = 10
ncdb> print res
res = 60
ncdb> step

Comments, suggestions, improvements all welcome. Either via mastadon or comment section below.

Post by @guylangston@dotnet.social
View on Mastodon