Home

Awesome

Debugging Masterclass

The following is a guide to using the native Visual Studio Code interface for the LLDB low-level debugger.


Prerequisite: C/C++ extension from Microsoft


Let's go ahead and open from the application menu bar at the top File->New Window (⇧⌘N) to work with.

Tip: Use ⌘↑ to navigate to the parent directory while selecting the directory in the Finder pop up.

Quick video of creating the following template configuration files

From the file we can notice the label for the task; command to run, specifying our compiler: clang; and the args list.

Tip: Use Control+Spacebar for IntelliSense to see all contextually proper variable names. Navigate your cursor with arrow keys to change context and select an item from the list.

<Image of IntelliSense contextual menu>

"args": [
	"-g",
	"-Wall",
	"-Wextra",
	"-Werror",
	"-Wconversion",
	"-I${workspaceFolder}/libft/includes",
	"-L${workspaceFolder}/libft",
	"-lft",
	"${workspaceFolder}/libft/ft_strcat.c",
	"${workspaceFolder}/libft/ft_strlen.c",
	"${workspaceFolder}/libft/ft_strcpy.c",
	"${workspaceFolder}/mains/main.c",
	"-o",
	"${workspaceFolder}/binaries/ft_strcat_debug_binary"
],

I like to use the ${workspaceFolder} variable which represents the root directory in the workspace. This is why we at first opened the directory.

Warning: The compilation may overwrite any file that has the same name as specified in the output flag.

It will now try to build your project and generate and launch it with a template launch.json. Check the status of the build from the temporary terminal that appears.
In case of any error popups appear just cancel all of them.


Let's now navigate to Explorer view and select the .vscode directory and from there choose launch.json.

Tip: Double click on a file (name or tab) to make it persist.

Tip: The debugger has been coded to be strict about the type of the currently active file that has to be open once you begin debugging. For our use case .c extension works fine.

Tip: Set the breakpoint by left clicking on the left hand-side of line numbers. A red circle should appear and stay.

<Footage of setting a breakpoint>

Tip: You might want to drag the Debug Console next to the terminal pane to view them both at the same time.

Tip: Once executing Run and Debug, even if Build finished with errors, the selected launch.json rule is ran. This creates oddities with the debugger interface given that a previous working version of the program exists.

Now your program should have been built successfully and the debugger should halt the program on the line 17 we specified.

Video about debug procedure

Now a hovering bar of five buttons should appear.

Tip: Drag the bar downwards from the left-hand side handle to prevent it from obstructing the view to the open tabs.

Tip: Hover over the buttons to see more.

Now choose Step into (F11) to follow the function call one level deeper.

Tip: As long as a variable is uninitialized, random values will appear.
Step over (F10) the lines you wish to execute within ft_strlen.

Tip: Observe from the left-hand side the interactive Call Stack, which shows every function call's stack frame in order. (Useful for inspecting recursion of putnbr.)

Tip: Variables on call stack change throughout, they aren't saved as snapshots of stack frames.

Tip: It isn't possible to retrace your steps so Restart (⇧⌘F5) the debugging whenever necessary, and after every change made to the sources.


Once the functionality has been verified, Step out (F12) from the function.

Now we are back at the line 17 the program first halted to. Step into (F11) once again now into the enclosing function call ft_strcpy.


Let's now concentrate on the Watch Expressions pane.

We can for example use our function to count the length of the string s2.

Tip: Beware of using watch expressions or calling functions that modify the memory of the program once running. (Watch expressions are evaluated everytime the program halts.)

We can use the C library functions to compare the results in the watch expressions. Notice we need to cast the return to the desired type (size_t)strlen(s2).

We can see strings (char *) from a specified location with &string[position].

Basically anything is possible with the watch expressions as they have access to all the memory the program reserves.

For example ft_test(src, dst, ref1) == ft_test(src, dst, ref2).

Test memory allocation failure handling by setting the recently successfully allocated pointer to NULL with word[w] = NULL.

Tip: Run this only once and then remove the expression.

Stop the debugging with Stop (⇧F5).


Random tips:

Tip: Infinite loops may run off and even though they disappear from the user interface they might stay in the background and slow down the computer. Relogging helps to solve this.

Tip: Even if the build fails, the debugger might appear to function, since it's been coded to assume the build is successful if there is a program with the correct name in the launch phase of the debugging.

Tip: When you need to give input from the stdin to the program you are debugging, use "externalConsole": true to achieve this.

Tip: Any file you wish to debug must be added as a source to the compilation phase. Use the wildcard *.c to select all .c files of a directory.

Tip: Attaching a shell script as a dependency to tasks will help you debug projects which have prerequisite libraries to them.