VS2017 Debugging Tips

Collecting VS2017 Debugging Tips from diverse resources.

Change the execution flow

Change the Execution Flow

A typical debugging situation is to analyze why a function call failed by stepping through. When another function that finds a function call returns an error and does not restart, use the yellow statement to identify the statement you want to execute next, thus changing the code location where the thread will execute next.

Grab the yellow arrow (the execution pointer) on the left and move the yellow arrow to the line above the method you want to step into. Be careful Moving the pointer cannot revert your application to an earlier app state.

Watch Windows

Pseudo-variables in Watch Window

The Watch windows or the QuickWatch dialog support some special (debugger-recognized) variables called pseudo variables. The documented ones refer to MS Docs

Pseudo-variables

If you want to display only the Dynamic View for an object, you can use the dynamic format specifier:

  • C#: ObjectName, dynamic
  • Visual Basic:: $dynamic, ObjectName

The Dynamic View also enhances the debugging experience for COM objects. When the debugger encounters a COM object wrapped in System.__ComObject, it adds a Dynamic View node for the object.

Format Specifiers

If you have the following code:

1
2
3
4
5
int main() {
int my_var1 = 0x0065;
int my_var2 = 0x0066;
int my_var3 = 0x0067;
}

Add the my_var1 variable to the Watch window (while debugging, Debug > Windows > Watch > Watch 1), and then set the display to hexadecimal (in the Watch window, right-click the variable and select Hexadecimal Display). Now the Watch window shows that it contains the value 0x0065. To see this value expressed as a character instead of an integer, in the Name column, after the variable name, add the character format specifier, c.

The Value column now appears with 101 ‘e’.

Format Specifiers
Here lists all the specifiers.

String Visualizer

String Visualizer

View Return Values for Functions

To view return values for your functions, look at the functions that appear in the Autos window while you are stepping through your code. To see the return value for a function, make sure that the function you are interested in has already executed (press F10 once if you are currently stopped on the function call). If the window is closed, use Debug > Windows > Autos to open the Autos window.

Return Values

In addition, you can enter functions in the Immediate window to view return values. (Open it using Debug > Windows > Immediate.)

Immediate Window

Use the context operator in C++ to qualify a breakpoint location, variable name, or expression.

Disassembly Code

Disassembly Code is created by Just-in-Time (JIT) compiler, not the Microsoft intermediate language (MSIL) generated by the Visual Studio compiler.

Disassembly Code

optional information:

  • Memory address where each instruction is located. For native applications, this is the actual memory address. For Visual Basic, C#, or managed code, it is an offset from the beginning of the function.
  • Source code from which the assembly code derives.
  • Code bytes—byte representations of the actual machine or MSIL instructions.
  • Symbol names for the memory addresses.
  • Line numbers corresponding to the source code.

Breakpoints

conditions (T/F; changed); hit count; filter (Expression & Logical Operators; Particular Thread); actions; tags (for sort); labels; export/import; etc.

Run to Cursor

Right-click a line of code in your app and choose Run to Cursor. This command starts debugging and sets a temporary breakpoint on the current line of code.

Run to Cursor

If you have set breakpoints, the debugger pauses on the first breakpoint that it hits.

Press F5 until you reach the line of code where you selected Run to Cursor.

You can use Run to Cursor in the Call Stack window while you are debugging.

Run to a Function Specified by Name

You can tell the debugger to run your application until it reaches a specified function. You can specify the function by name or you can choose it from the call stack.

To specify a function by name, choose Debug, New Breakpoint, Break at Function, then enter the name of the function and other identifying information.

To narrow the function specification:

  1. Use the fully qualified function name. Example: Namespace1.ClassX.MethodA()
  2. Add the parameter types of an overloaded function. Example: MethodA(int, string)
  3. Use the ‘!’ symbol to specify the module. Example: App1.dll!MethodA
  4. Use the context operator in native C++. {function, , [module]} [+<line offset from start of method>] Example: {MethodA, , App1.dll}+2
  5. Use the address of an object to set a breakpoint on a method called on a specific instance of a class. Native C++ Only. Example: ((my_class *) 0xcccccccc)->my_method

Specification Break

If the function is overloaded or is in multiple namespace, you can choose the functions that you want in the Choose Breakpoints dialog box.

Specification Break

Other Situation

You can also set breakpoints in the call stack, in the Disassembly window, and, in native C++ code, at a data condition or a memory address. Click here for more information.

To set breakpoint conditions:
https://docs.microsoft.com/en-us/visualstudio/debugger/using-breakpoints?view=vs-2017#breakpoint-conditions.

Log a message to Output window with special keywords:
https://docs.microsoft.com/en-us/visualstudio/debugger/using-breakpoints?view=vs-2017#BKMK_Print_to_the_Output_window_with_tracepoints

Memory

Memory

Some bugs are caused by incorrect struct definitions, ignored alignment properties, etc.

Multithread

Thread Markder

Threads Window (Debug) with Callstack:

Disassembly Code

To discover the thread marker:

  1. In the Debug Toolbar, click the Show Threads in Source button button.

  2. Press F11 once to advance the debugger one line of code.

  3. Look at the gutter on the left side of the window. On this line, you will see a thread marker icon icon that resembles two cloth threads. The thread marker indicates that a thread is stopped at this location. Notice that a thread marker may be partially concealed by a breakpoint.

  4. Hover the pointer over the thread marker. A DataTip appears. The DataTip tells you the name and thread ID number for each stopped thread. In this case, the name is probably <noname>.

  5. Right-click the thread marker to see the available options on the shortcut menu.

View the Location of Threads

In the Parallel Stacks window, you can switch between a Threads view and (for task-based programming) Tasks view, and you can view call stack information for each thread.

Open the Parallel Stacks window by choosing Debug > Windows > Parallel Stacks. You should see something similar to this (the exact information will be different depending on the current location of each thread, your hardware, and your programming language).

Threads

Switch

Threads View shows call stack information for all the threads in your application. It lets you navigate between threads and stack frames on those threads. In managed code, the Tasks View shows call stacks of System.Threading.Tasks.Task objects. In native code, the Tasks View shows call stacks of task groups, parallel algorithms, asynchronous agents, and lightweight tasks.

Tasks View

Method View

Method View

Flagging and Unflagging Threads

You can flag threads that you want to give special attention. Flagging threads is a good way to keep track of important threads and to ignore threads that you do not care about.

To flag threads:

  1. In the Parallel Watch window, hold down the SHIFT key and select multiple rows.
  2. Right-click and choose Flag. Now, all the selected threads are flagged. Now, you can filter to show only flagged threads.
  3. In the Parallel Watch window, find the Show Only Flagged Threads buttonflag button
  4. Click the Show Only Flagged Threads button.

Flagged Threads

Depending on the thread ID, thread name, process ID, process name, machine name, etc., it is determined whether the thread is broken. Logical operations can be performed between the conditions; Right-click another thread and then we can Switch to Thread. Thread name can be set in both native code and managed code.

Freezing and Thawing Thread Execution

You can freeze and thaw (suspend and resume) threads to control the order in which threads perform work. This can help you resolve concurrency issues such as deadlocks and race conditions.

To freeze and unfreeze threads:

  1. In the Parallel Watch window, with all the rows selected, right-click and select Freeze. In the second column, a pause icon now appears for each row. The pause icon indicates that the thread is frozen.
  2. Show the Suspended Count column by selecting it in the Columns list.
  3. Deselect the rows by clicking one row only.
  4. Right-click a row and select Thaw. The pause icon goes away on this row, indicating that the thread is no longer frozen.
  5. Switch to the code editor and click F11. Only the unfrozen thread runs.
The app may also instantiate some new threads. Notice that any new threads are unflagged and are not frozen.

Switch Between Processes

You can attach to multiple processes when you are debugging, but only one process is active in the debugger at any given time. You can set the active or current process in the Debug Location toolbar or in the Processes window. To switch between processes, both processes must be in break mode.

To set the current process:

  1. On the Debug Location toolbar, choose Process to view the Process list box. Select the process that you want to designate as current process.
    Switch

  2. If the Debug Location toolbar is not visible, choose Tools, Customize. On the Toolbars tab, choose Debug Location.
    Open the Processes window (shortcut Ctrl+Alt+Z), find the process that you want to set as the current process, and double-click it.
    Switch

Set a Breakpoint on a Specific Process

  1. Get the process name or process ID number from the Processes window.
  2. Select a breakpoint, and open the Breakpoint Filter dialog box as in the first procedure.
  3. In the Breakpoint Filter dialog box, type: ProcessName = yourprocessname or ProcessID = yourprocessIDnumber. To create a more complex filter, you can combine clauses using &, the AND operator, ||, the OR operator, !, the NOT operator, and parentheses.
  4. Click OK.

Set a Breakpoint on a Specific Thread

  1. Get the thread name or thread ID number from the Threads window.
  2. Select a breakpoint, and open the Breakpoint Filter dialog box as described in the first procedure.
  3. In the Breakpoint Filter dialog box, type: ThreadName = yourthreadname or ThreadID = yourthreadIDnumber. To create a more complex filter, you can combine clauses using &, the AND operator, ||, the OR operator, !, the NOT operator, and parentheses.
  4. Click OK.

More Reading:

Walkthrough: Debugging a Parallel Application in Visual Studio

Step into Specific

Step into

It can be widely used!

Snapshot Debugger

To debug live ASP.NET apps in Azure App Service:
Snapshot

For more information, see Debug live ASP.NET apps using the Snapshot Debugger.

View snapshots with IntelliTrace Step-back:

IntelliTrace

For more information, see the View snapshots using IntelliTrace step-back and IntelliTrace page.

Exception

Break into Code on Handled Exceptions

The debugger breaks into your code on unhandled exceptions. However, handled exceptions (such as exceptions that occur within a try/catch block) can also be a source of bugs and you may want to investigate when they occur. You can configure the debugger to break into code for handled exceptions as well by configuring options in the Exception Settingsdialog box. Open this dialog box by choosing Debug > Windows > Exception Settings.

The Exception Settings dialog box allows you to tell the debugger to break into code on specific exceptions. In the illustration below, the debugger breaks into your code whenever a System.NullReferenceException occurs. For more information, see Managing exceptions.

Handled Exceptions

Add Conditions to an Exception

You can set conditions on exceptions in the Exception Settings dialog box. Currently supported conditions include the module name(s) to include or exclude for the exception. By setting module names as conditions, you can choose to break for the exception only on particular code modules, or you can avoid breaking on particular modules.
Note

Adding conditions to an exception is new in Visual Studio 2017
To add conditional exceptions, choose the Edit condition icon in the Exception Settings dialog box or right-click the exception and choose Edit Conditions.

Condtional Exception

GPU Debugging

Configuring GPU Debugging

The debugger cannot break on both CPU code and GPU code in the same app execution. By default, the debugger breaks on CPU code. To debug GPU code, use one of these two steps:

  1. In the Debug Type list on the Standard toolbar, choose GPU Only.
  2. In Solution Explorer, on the shortcut menu for the project, choose Properties. In the Property Pages dialog box, select Debugging, and then select GPU Only in the Debugger Type list.

    NOTE: Independent hardware vendors who want to support GPU debugging in Visual Studio must create a DLL that implements the VSD3DDebug interface and targets their own drivers.

Other Tips

Reference

More Reading