Debugging tips and tools

 
 
  • Gérald Barré

Here are some tips and tools to help you debug your .NET applications. The goal is not to be exhaustive, but to give you some ideas on how to debug your applications.

#Random tips

  • Talk to a rubber duck
  • Take a walk, sleep, play a game, etc. and come back to your code later with a fresh mind
  • Read the documentation carefully
  • Before changing the code, make sure you understand why it doesn't behave the way you expect it to behave
  • Write an automated test that reproduces the bug, so you can quickly iterate on the fix
  • Delete/Comment all the code that is not needed to reproduce the bug. In the end, you should have a small piece of code that reproduces the bug. (be sure the code is versioned, so you can always go back to the original code)
  • You can do the opposite, create a new application and add code little by little until you reproduce the bug.
  • Time spent using a debugger is barely reusable for the next issues. Improving logs and error messages is reusable for the next issues. So, this can save you, or your coworkers, time later.

Also, fixing issues is not the only part of the job. You also need to prevent issues from happening in the first place. You can check this blog post to understand why fixing bugs is not enough.

#Is the code executed?

If you are not sure your code is actually executed, add a throw statement at the beginning of your code. You can also write to the console, just in case the exception is catch and swallowed.

C#
public void MethodToDebug()
{
    Console.WriteLine("Debugging");
    throw new Exception("Debugging");
    // Your code here
}

#Starting the debugger from the code

A debugger is how you get a view into a system's that too complicated to understand by just looking at the code. It is a tool that allows you to step through the code, inspect variables, and set breakpoints. It is a tool that allows you to understand what is going on in your application.

If you cannot start the process with a debugger and can't find a way to attach the debugger to an existing process, you can use System.Diagnostics.Debugger.Launch() or System.Diagnostics.Debugger.Break(); where you want to set a breakpoint.

C#
public void MethodToDebug()
{
    Debugger.Launch();
    for (int i = 0; i < 100; i++)
    {
        if (i == 50)
        {
            Debugger.Break();
        }

        Console.WriteLine(i);
    }
}

You can also use the Task Manager to start debugging a process:

#Debugging more than one process at a time

Visual Studio can debug multiple processes at the same time. For instance, you can debug a web application and a console application at the same time.

You can also attach to many running processes:

#Observing the value of a variable without a debugger

If you can't attach a debugger, you can log values using Console.WriteLine(), File.WriteAllText() or Logger.LogDebug() to output the values you want to observe.

C#
public void MethodToDebug()
{
    for (int i = 0; i < 100; i++)
    {
        if (i == 50)
        {
            // Temporary code
            File.WriteAllText("debug.txt", $"{DateTime.UtcNow}: i = {i}");
        }

        Console.WriteLine(i);
    }
}

#Loading assembly symbols

Sometimes Visual Studio cannot find the symbols for an assembly. When it happens, you cannot use breakpoints to debug the application. You can try to load the symbols manually by opening the Modules window in Visual Studio (Debug > Windows > Modules), find the module you want to debug and click Load Symbols:

If it can't find the symbols, you may need to enable symbols servers in the options:

If you still can't find symbols for the module, you can disassemble it and debug using the disassembled code.

#Different kinds of Breakpoints

There are many ways to break into the code:

#Adding assertions

Breakpoints are useful to debug your code, but they are not always easy to use. You can use assertions to check the state of your code.

C#
public void MethodToDebug()
{
    for (int i = 0; i < 100; i++)
    {
        Debug.Assert(i != 50, "i should not be 50");
        Console.WriteLine(i);
    }
}

#Observing the application without a debugger

  • You can observe http calls using a proxy such as Fiddler

    If you can see the requests and responses, you can manually configure the proxy in the code:

    C#
    using var handler = new HttpClientHandler();
    handler.Proxy = new WebProxy("http://localhost", 8888)
    {
        BypassProxyOnLocal = false,
        UseDefaultCredentials = true,
    };
    using var httpClient = new HttpClient(handler);
  • Chromium-based browsers: about://net-internals/, about://net-export/, Network tab in Developer Tools

  • You can observe network traffic using WireShark

  • You can observe file system, registry and process/thread activity using procmon (Process Monitor) or wtrace

  • You can use dotnet-counter to quickly monitor things like the CPU usage or the rate of exceptions being thrown in your .NET Core application.

  • You can use dotnet-trace or PerfView to collect ETW events and stacks for your .NET Core application. In a .NET application, ETW events allow to get lots of information about an application such as database queries, http calls, dns queries, lock contention, thread pool info, Garbage Collector, JIT, Assembly loading, Activity, etc.

  • Windows Performance Analyzer creates graphs and data tables of Event Tracing for Windows (ETW) events.

  • You can take a dump of the application using dotnet-dump and analyze it later using Visual Studio. If you need to create a dump when certain conditions are met, you can check ProcDump. There are various other ways to create a dump file, see my previous post about How to generate a dump file of a .NET application for more information.

  • You can create a crash dump file when an application crashes by setting a registry key or an environment variable

  • You can list handles (opened files, registry keys, ports, synchronization primitives) using Handle or ProcessExplorer

  • You can see memory allocations using Visual Studio

Visual Studio provides many tools to debug an application. If you need more advanced tools, you can use WinDbg and dotnet-sos. Sysinternals tools are also very useful.

#Viewing values in the debugger

  • Watch windows (Debug > Windows > Watch > Watch 1) allow you to evaluate multiple expressions. All expressions are recomputed when execution is suspended. Note that you can use commands such as nq (no quote) or nse (no side-effect), h (hexa).

  • Object ID allows to create a globally accessible id for an object. So, even if the value is not accessible in the current context, you can still reference the value using $N where N is the object ID.

  • Data tips provide a convenient way to view information about variables in your program during debugging. The value is in red when the value changed since the last evaluation, so you can quickly view what changed.

  • Custom visualizers allow you to show formatted values instead of raw values. There are visualizers for string (documentation) or IEnumerable (documentation) values.

  • The Immediate Window allows you to execute arbitrary code and see the result

#XAML Binding Errors

You can view XAML binding errors in Visual Studio. When running the application, all binding errors are shown in this window.

#Debugging multi-threaded applications

  • Getting the list of running threads (Debug > Windows > Threads). You can double-click on a thread to switch to it.

  • Getting the list of .NET Tasks (Debug > Windows > Tasks)

  • Show threads in source

  • Parallel watch allows to evaluate the value of an object for all threads

  • View threads and tasks in the Parallel Stacks window (documentation). This view is amazing! It combines all similar stacks/tasks together and shows the number of threads/tasks that are running the same code. This give you a good idea of what is happening in your application.

  • Detect deadlocks using the parallel stacks window:

#Other tools

##Check used ports on Windows

PowerShell
$Port = 5000
Get-Process -Id (Get-NetTCPConnection -LocalPort $port).OwningProcess
Get-Process -Id (Get-NetUDPEndpoint -LocalPort $port).OwningProcess

netstat -a -b

##Check DNS configuration

PowerShell
Resolve-DnsName -Name www.meziantou.net
Resolve-DnsName -Type A -Name meziantou.net

You can also use an online service, such as https://dnslookup.online/, to check the DNS configuration.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub