Debugging is one of the most important activities in software development. Bugs in software occur inadvertantly. When a user reports an issue with your software, how do you fix it?

The first step is to reproduce the issue. After that debugging tools help to diagnose the issue and to figure out its root cause.

Nim does many things to make debugging as easy as possible. For example it ensures that detailed and easy to understand stack traces are reported whenever your application crashes. Consider the following code in listing 1.6 .

This code is fairly simple. It reads a line of text from the standard input, converts this line into an integer, adds the number 5 to it and displays the result. Save this code as adder.nim and compile it by executing nim c adder.nim , then execute the resulting binary. The program will wait for your input, once you type in a number you will see the sum of 5 and the number you typed in. But what happens when you don’t type in a number? Type in some text and observe the results. You should see something similar to the output in listing 1.7 below.

The program crashed because an exception was raised and it was not caught by any try statements. This resulted in a stack trace being displayed and the program exiting. The stack trace in listing 1.7 is very informative, it leads directly to the line which caused the crash. After the adder.nim module name, the number 3 points to the line number in the adder module. This line is highlighted in listing 1.8 below.

The parseInt procedure cannot convert strings containing only letters into a number because no number exists in that string. The exception message shown at the bottom of the stack trace informs us of this. It includes the string value that parseInt attempted to parse which gives further hints about what went wrong.

You may not think it but program crashes are a good thing when it comes to debugging. The truly horrible bugs are the ones which produce no crashes, but instead result in your program producing incorrect results. In such cases advanced debugging techniques need to be used. Debugging also comes in handy when a stack trace does not give enough information about the issue.

The primary purpose of debugging is to investigate the state of memory at a particular point in the execution of your program. You may for example want to find out what the value of the line variable is just before the parseInt procedure is called. This can be done in many ways.

Compiling the code in listing 1.11 will fail with an error: "'add' can have side effects". Thankfully the solution is simple. Nim provides a side effect free echo for this very purpose, it is called debugEcho so all you need to do is replace echo with debugEcho and the code will compile.

This creates a problem whenever you want to debug such procedures with the echo procedure. The echo procedure is not side effect free because it accesses a global stdout variable. So the following code will not compile.

A procedure marked with the {.noSideEffect.} pragma is said to have no side effect. This means that the procedure does not modify or read any external state, such as changing global variables or writing to a file. Marking a procedure as having no side effects is useful when you want this to be enforced by the compiler, that way the code will not compile unless the procedure remains side effect free. For example consider the following add procedure, it is said to contain no side effects because passing the same inputs to this procedure will always produce the same output.

The exception message just shows some whitespace which is how Tab characters are shown in normal text. But you have no way of distinguishing whether that whitespace is just normal space characters or whether it is in fact a multiple Tab characters. The repr procedure solves this ambiguity by showing \9\9\9 , the number 9 is the ASCII number code for the tab character. The memory address of the line variable is also shown.

The repr procedure is useful because it shows non-printable characters in their escaped form. It also shows extra information about many types of data. Running the example in listing 1.9 and typing in 3 Tab characters results in the following output.

Using the repr procedure and echo , let’s investigate the value of the line variable just before the call to parseInt .

By far the simplest and most common approach is to use the echo procedure. The echo procedure allows you to display the value of most variables, as long as the type of the variable implements the $ procedure it can be displayed. For other variables the repr procedure can be used, you can pass any type of variable to it and get a textual representation of that variable’s value.

The a procedure is called first on line 7, followed by a1 at line 5, and finally the writeStackTrace procedure is called on line 2.

An unhandled exception is not the only way for a stack trace to be displayed. You may find it useful to display the current stack trace anywhere in your program for debugging purposes. This can give you vital information, especially in larger programs with many procedures, where it can show you the path through those procedures and how your program’s execution ended in a certain procedure.

3.3. Using GDB/LLDB

Sometimes a proper debugging tool is necessary for the truly complicated issues. As with profiling tools in the previous section, Nim programs can be debugged using most C debuggers. One of the most popular debugging tools is the GNU Debugger, its often known by the acronym GDB.

The GNU debugger should be included with your distribution of gcc which you should already have as part of your Nim installation. Unfortunately on the latest versions of Mac OS X installation of gdb is problematic, but you can use a similar debugger called LLDB. LLDB is a much newer debugger, but it functions in almost exactly the same way.

Let’s try to use GDB (or LLDB if you’re on Mac OS X) to debug the small adder.nim example introduced in listing 1.8. I will repeat the example below.

Listing 1. 13. The adder.nim example import strutils let line = stdin.readLine() let result = line.parseInt + 5 echo(line, " + 5 = " , result)

In order to use these debugging tools you will need to compile adder.nim with two additional flags. The --debuginfo flag, which will instruct the compiler to add extra debugging information to the resulting binary. The debugging information will be used by GDB and LLDB to read procedure names and line numbers of the currently executed code. And also the --linedir:on flag which will include Nim-specific debug information such as module names and Nim source code lines. GDB and LLDB will use the information added by the --linedir:on flag to report Nim-specific module names and line numbers.

Putting both of these together you should compile the adder module using the following command: nim c --debuginfo --linedir:on adder.nim .

The --debugger:native flag Newer versions of Nim support the --debugger:native flag which is equivalent to specifying the --linedir:on and --debuginfo flags.

The next step is to launch the debugging tool. The usage of both of these tools is very similar. To launch the adder executable in GDB execute gdb adder and to launch it in LLDB execute lldb adder . GDB or LLDB should launch and you should see something similar to figure 1.4 or figure 1.5.

Figure 1. 4. GDB on Windows

Figure 1. 5. LLDB on Mac OS X

Once these tools are launched they will wait for input from the user. The input is in the form of a command. Both of these tools support a range of different commands for controlling the execution of the program, to watch the values of specific variables, to set breakpoints and much more. To get a full list of supported commands type in help and press enter.

The aim for this debugging session is to find out the value of the line variable, just like in the previous sections. To do this we need to set a breakpoint at line 3 in the adder.nim file. Thankfully, both GDB and LLDB share the same command syntax for creating breakpoints. Simply type in b adder.nim:3 into the terminal and press enter. A breakpoint should be successfully created, the debugger will confirm this by displaying a message that is similar to Listing 5.23.

Listing 1. 14. This message is shown when a breakpoint is successfully created in LLDB. Breakpoint 1: where = adder`adderInit000 + 119 at adder.nim:3, address = 0x0000000100020f17

Once the breakpoint is created, you can instruct the debugger to run the adder program by using the run command. Type in run into the terminal and press enter. The program won’t hit the breakpoint because it will first read a line from standard input, so after you use the run command you will need to type something else into the terminal. This time the adder program will read it.

The debugger will then stop the execution of the program at line 3. Figures 1.6 and 1.7 show what that will look like.

Figure 1. 6. Execution paused at line 3 in GDB

Figure 1. 7. Execution paused at line 3 in LLDB

At this point in the execution of the program, we should be able to display the value of the line variable. Displaying the value of a variable is the same in both GDB and LLDB. One can use the p (or print ) command to display the value of any variable. Unfortunately you cannot simply type in print line and get the result. This is because of name mangling which I mentioned in the profiling section. Before you can print out the value of the line variable you will need to find out what the new name of it is. In almost all cases the variable name will only have an underscore followed by a randomised number appended to it. This makes finding the name rather trivial, but the process differs between GDB and LLDB.

In GDB it is simple to find out the name of the line variable, you can simply type in print line_ and press the Tab button. GDB will then auto-complete the name for you, or give you a list of choices.

As for LLDB, because it does not support auto-complete via the Tab key, this is a bit more complicated. You need to find the name of the variable by looking at the list of local and global variables in the current scope. You can get a list of local variables by using the fr v -a (or frame variable --no-args ) command, and a list of global variables by using the ta v (or target variable ) command. The line variable is a global variable so type in ta v to get a list of the global variables. You should see something similar to the screenshot in figure 1.8.

Figure 1. 8. The list of global variables in LLDB

You can see the line variable at the bottom of the list as line_106004 .

Now print the line variable by using the print <var_name_here> command, make sure to replace the <var_name_here> with the name of the line variable that you found from the previous step. Figures 1.9 and 1.10 show what you may see.

Figure 1. 9. Printing the value of the line variable in GDB

Figure 1. 10. Printing the value of the line variable in LLDB

This unfortunately tells us nothing about the value of the line variable. We are in the land of low-level C, so the line variable is a pointer to a NimStringDesc type. We can dereference this pointer by appending an asterisk to the beginning of the variable name: print *line_106004 .

Doing this will show values of each of the fields in the NimStringDesc type. Unfortunately in LLDB this does not show the value of the data field, so we must explicitly access it: print (char*)line_106004->data . The (char*) is required to cast the data field into something which LLDB can display. Figures 1.11 and 1.12 show what this looks like in GDB and LLDB respectively.

Figure 1. 11. Displaying the value of the line variable in GDB

Figure 1. 12. Displaying the value of the line variable in LLDB