Debugging Synergy .NET Code
This topic includes the following sections:
- Remote debugging
- Local variables and the Locals window
- Determining whether a system option is set
- Examining the contents of a dynamic memory handle
- Synergy types in the Visual Studio Memory Usage Tool
- Watch and Quickwatch windows
- SET WATCH functionality in Visual Studio
The Visual Studio debugger is used for debugging Synergy .NET applications on Windows and on Linux (via remote debugging). See Visual Studio documentation for more information on debugging, and note the following:
- The active configuration/platform combination determines which build will be debugged. The configuration/platform combination or the profile (for SDK-style projects) determines which debug settings are used. See Configurations, platforms, and profiles.
- When an assembly is built, the compiler creates a program database file (.pdb) for the assembly. Program database files store debugging and project state information. If you want to obtain traceback information for a distributed assembly, you must distribute the program database file for the assembly. Note that only module traceback information is provided unless the “Debug/Optimize code” option on the Build page of project properties is set to “Debug” or “Debug (full)” when the assembly is compiled.
- The _DEBUG system define enables you to include code based on whether a program is compiled in debug mode. See Compile-time defines for more information
- No information displays for defines when you hover over them. This is true for any language that supports defines (e.g., C++).
- If a breakpoint doesn’t work, use Visual Studio’s Modules window to load necessary symbols, or at least find out why the symbols weren’t loaded. Select Debug > Windows > Modules from the Visual Studio menu (this opens the Modules window), and then find the entry for the missing symbols. If the path and filename are correct, but the status column indicates that the symbols aren’t loaded, you can instruct Visual Studio to load them by right-clicking the entry and selecting Load Symbols from the context menu. If the Modules window indicates that the path or file isn’t correct, you’ll need to figure out why the file couldn’t be located and resolve the issue.
Remote debugging
With Visual Studio’s “Attach to Process” feature, you can use Visual Studio to debug the following:
-
A Synergy .NET Framework or Synergy .NET 6 or higher program running on a remote Windows system
-
A .NET 6 Harmony Core application running on a remote Linux machine or on Windows Subsystem for Linux (WSL 2). See the Harmony Core wiki topic Deploying on Linux with .NET 6 for information on deploying a Harmony Core application to a Linux machine or a WSL 2 system.
The development machine must have the following:
-
Synergy DBL Integration for Visual Studio (SDI). See www.synergex.com/synergy-dbl-integration for information on system requirements for SDI.
-
The solution, project, or source files you want to step through for the program you want to debug. (Generally, in Visual Studio you’ll open the Synergy .NET solution or project for the remote program before you attach to a remote process so you can step through code. But if necessary, you can open only source files.)
-
Network access to the remote system.
The remote system must have the following:
-
Synergy/DE 12 or higher or the version 12 REV11 licensing upgrade package (LUP) to provide REV11 licensing for the system.
-
A Synergy .NET application built in debug mode. For a remote Linux machine or WSL 2 system, this must be a .NET 6 Harmony Core application.
-
Remote Tools for Visual Studio. Note that this is automatically downloaded and installed the first time you use Visual Studio to attach to a process on the remote machine.
-
One of the following:
-
To examine Synergy variables as you debug an application on a remote Windows system that does not have Visual Studio, you must install the “Synergy DBL Integration - Remote Debugger” on the remote Windows system. This is a set of libraries that enable Visual Studio remote debugging to work with Synergy types. It can be downloaded from the SDI downloads page of the Synergex Resource Center.
-
To enable the debugger to use expression evaluation (e.g., to examine Synergy variables) while debugging an application running on Linux, the Linux system must have the SynergyUnixExpressionEvaluator. See Setting up the SynergyUnixExpressionEvaluator below.
The following is a brief summary of the steps for remote debugging Synergy .NET programs. See Microsoft documentation on attaching to running processes for details on this process. (Note that Microsoft’s documentation on attaching to processes includes information on features that are not supported for SDI, features such as attaching to a process running on Azure App Service, attaching to a process running in a Docker container, and reattaching to a process.)
1. | In Visual Studio on the development machine, open the solution, project, or source file(s). Set breakpoints as needed. |
2. | If you are debugging a problem that occurs when the service starts, set the WAIT_FOR_DEBUGGER environment variable on the Linux system. Set it to any value. |
3. | Start the program on the remote machine or WSL 2 system. |
4. | In Visual Studio on the development machine, select Debug > Attach to Process. |
5. | In the “Attach to Process” dialog, select the connection type, connection target, and the remote process. (See Visual Studio documentation for details.) And for a .NET 6 or higher program on a Linux system or WSL 2, click the Select button to open the “Attach to...Select Code Type” dialog. In this dialog, select “Managed (.NET Core for Unix)” and click OK. Additionally, |
- for a program on a remote WSL 2 system, select “Windows Subsystem for Linux (WSL)” or SSH in the “Connect type” field.
- for a program on a remote Linux machine, select SSH in the “Connect type” field.
6. | In the “Available Processes” area of the “Attach to Process” dialog, select the name of the process you want to debug. |
7. | Click Attach and then start debugging (e.g., select Debug > Start Debugging from the Visual Studio menu). |
Setting up the SynergyUnixExpressionEvaluator
In order for expression evaluation to be available when debugging a .NET 6 Harmony Core application on a remote Linux system or WSL 2, SynergyUnixExpressionEvaluator must be set up on the remote system. Without expression evaluation, the debugger will support only the most basic operations (e.g., basic breakpoints and stepping).
To set up the SynergyUnixExpressionEvaluator,
1. | Ensure that the remote Linux machine or WSL 2 system has the folder $HOME/.vs-debugger/vs2022. This is created automatically the first time Visual Studio is used to attach to a process on the system. If this folder does not exist on the system, use Visual Studio to remotely attach to a process on that system. For information on attaching to processes, see Microsoft documentation on attaching to running processes with the Visual Studio debugger (e.g., learn.microsoft.com/en-us/visualstudio/debugger/attach-to-running-processes-with-the-visual-studio-debugger). |
2. | Clone or copy the SynergyUnixExpressionEvaluator repository (github.com/Synergex/SynergyUnixExpressionEvaluator) to the Linux machine or WSL 2 system. |
3. | On the Linux machine or WSL 2 system, run the CopyDebuggerFiles.sh script, which is in the SynergyUnixExpressionEvaluator repository. This adds Synergy DBL expression evaluation support for remote debugging. |
Local variables and the Locals window
Note the following for local variables and the Locals window:
- Make sure both of the following Visual Studio debugging options (Tools > Options > Debugging > General) are selected: “Enable property evaluation and other implicit function calls” and “Call string-conversion function on objects in variables windows.” If either is unselected, local variables will be displayed with internal names.
- If the “Unwind the call stack on unhandled exceptions debugging” option is cleared (Tools > Options > Debugging), the pseudovariable $exception is displayed in the Locals window for an uncaught exception. Expand $exception to see more information.
- Local fields are not initialized until you step past the PROC statement.
- If the debugger is running slowly, it may be that too many variables are displayed in the Locals window (rather than being hidden in collapsed nodes of the Locals window). The debugger continually evaluates displayed variables, but doesn’t evaluate variables in collapsed nodes. For example, if the Globals node is expanded, the debugger may need to evaluate scores of variables, especially if there are a large number of unnamed common variables. (These are displayed directly under the Globals node rather than in subnodes, which could be collapsed.) To prevent this, keep Locals window nodes collapsed, and instead use the Watch or QuickWatch window or hover over a variable to get information about it.
- C# literal suffixes are supported. If you click on a local variable in the Locals window and its value is one of the following types, you’ll see the suffix (e.g., .m). Do not remove these suffixes.
Literal
Suffix
float
.f
double
.d
decimal
.m
unsigned
.u
long
.l
Determining whether a system option is set
To determine whether a system option is set for a Synergy .NET program, enter the following in the Visual Studio Immediate window, where OPTION is the number of the system option (see System Options):
Synergex.SynergyDE.SysRoutines.f_option(Synergex.SynergyDE.IntegerDesc.CreateLiteral(OPTION), Synergex.SynergyDE.VariantDesc.notPassedInteger);*
If the option is set, this returns 1. If the option is not set, it returns 0.
Examining the contents of a dynamic memory handle
1. | Open the Locals window, expand the dm variable, locate the name of the dynamic memory handle you want to inspect, and note the integer value assigned to it. |
2. | In the Immediate window, run the following command, where ## is the number assigned to the dynamic memory handle: |
Synergex.SynergyDE.SysRoutines.hatml(##).ToString()
The value for the dynamic memory handle will be displayed below the command you entered in the Immediate window.
If your dynamic memory handle contains null values, you will need to write the results to a file. For example:
System.IO.File.WriteAllText("filename", Synergex.SynergyDE.SysRoutines.hatml(##).ToString())
where filename is the path and name of the file you want to create and ## is the number assigned to the dynamic memory handle.
Synergy types in the Visual Studio Memory Usage Tool
The Visual Studio Memory Usage Tool displays information on Synergy types, but it uses different names for these types:
- A Synergy alpha, integer, decimal, or implied decimal will have a name with one of the following formats (where Type is Alpha, Integer, Decimal, or ImpliedDecimal):
Synergex.SynergyDE.TypeDesc
Synergex.SynergyDE.PinTypeDesc
Synergex.SynergyDE.RefTypeDesc
For example, a decimal will appear as either Synergex.SynergyDE.DecimalDesc, Synergex.SynergyDE.PinDecimalDesc, or Synergex.SynergyDE.RefDecimalDesc.
- Structures appear as GenBox<string1,string2> or GenCouple<string1,string2>, where string1 and string2 are strings that include the structure name. For example, the following is for a boxed structure named struct1 in namespace ns1:
Synergex.SynergyDE.Gen_Box<ns1.$$_struct1, ns1.$$_wrap_struct1>
The next example is for a boxed structure named struct2 in namespace ns1:
Synergex.SynergyDE.Gen_Couple<ns1.$$_struct2, ns1.$$_wrap_struct2>
Watch and Quickwatch windows
Synergy DBL Integration includes several Synergy-specific commands for use in the Visual Studio Watch and QuickWatch windows:
Command |
Information displayed |
---|---|
^ADDR(expression) |
The address for a descriptor type variable specified by expression. This works only for .NET types in Synergy records and i, d, and a Synergy types. It is used in a procedure that determines what changed a variable. See SET WATCH functionality in Visual Studio below. |
Show channels |
Channel information |
Show handles |
Handle information |
Note the following:
- If you set conditional breakpoints in a Watch window, only the == operator and value comparisons (not reference comparisons) are supported.
- If a path to a static type contains a mix of imported and specified (manually entered) namespaces, it may not be evaluated in the Watch and QuickWatch windows. For example, it may not be evaluated if a namespace is defined in a file because the namespace is assumed to be imported for the entire file.
- If you use the unsupported Synergy.SynergyDE.tklib.dll library, you will not be able to directly examine UI Toolkit globals such as g_select in the debugger. Instead, find the property name for the .define in tools.def, and enter that property name (case-sensitive) in the Watch or QuickWatch window. For example, the property name entry in tools.def for g_select is
.define g_select Synergex.SynergyDE.UIToolkit.GlobalState.t_selection
So, to examine g_select you would enter the following in the Watch or QuickWatch window: Synergex.SynergyDE.UIToolkit.GlobalState.t_selection.
For information on the Synergy.SynergyDE.tklib.dll library, see Using UI Toolkit with the .NET Framework.
SET WATCH functionality in Visual Studio
In traditional Synergy, if a variable’s value changes, you can find out what changed it by using WATCH variable or SET WATCH variable. But for Synergy .NET, this functionality is not available as the default in the Visual Studio debugger. Instead, you must use the SOS debugging extension (which is installed with Visual Studio) as outlined in the following procedure. The SOS command !CLRStack dumps current stack traces for all threads at the point the variable changes. By examining these stack traces, you can find out which routine is writing to the variable.
1. | Open your Synergy .NET project in Visual Studio, and locate or create a programmatic break or pause in execution (e.g., a point where the program waits for user input) that happens before the variable’s value is changed. You’ll need a programmatic break or pause to give you time to select Break All in step 7. |
2. | Set a Visual Studio breakpoint at or before the programmatic break. |
3. | Start debugging the program (Debug > Start Debugging). |
^ADDR(my_alpha)
Note (or copy) the address in the Value column of the Watch or QuickWatch window. You will use the hexadecimal form of this address to set a data breakpoint in step 8. (If a decimal value is displayed in the Value column, you can right-click on the value and select Hexadecimal Display from the context menu.)
5. | Detach from the process (Debug > Detach All). |
6. | Reattach to the process in native mode: Select Debug > Attach to Process, select the process for your program in the Available Processes area of the Attach to Process window, and then click Select. In the Select Code Type window, select Native and clear all other options. Then click OK to close the Select Code Type window, and click Attach in the Attach to Process window. |
Detaching and reattaching is necessary because the debugger runs in managed mode by default but must run in native mode for the data breakpoint set in step 8 to work correctly.
8. | Set a data breakpoint (Debug > New Breakpoint > New Data Breakpoint) using the hexadecimal address you got in step 4. For example, if the hexadecimal address from that step is 0x0288F0C0, enter this in the Address field of the New Breakpoint window. |
|
9. | When the breakpoint is set, continue program execution (Debug > Continue). |
10. | When the debugger breaks at the breakpoint, you’ll see the message “The following breakpoint was hit....” Click OK in this message box. |
11. | Select Debug > Save Dump As, and then use the Save Dump As window to save a .dmp file. |
12. | Stop the debugging session (Debug > Stop Debugging). |
13. | In Visual Studio, select File > Open > File, and then in the Open File window, select the file you saved in the previous step and click Open. |
14. | A window opens with the title of the .dmp file. In the Actions section of this window, select Debug with Mixed. |
15. | Enter the following in the Immediate Window: |
.load sos
This loads the SOS debugger extension.
16. | Enter the following case-sensitive SOS command in the Immediate Window: |
!CLRStack
This writes the stack traces to the Immediate Window.
Note that the program’s debugging session is over, so you won’t be able to continue debugging it.