Debugging C with Clang compiler and LLDB

Just an easy and quick tutorial about how to debug C code using clang. I am using Mac OS X 10.11.1 “El Capitan” and Clang compiler is already installed.

If you have OS X and Clang is not installed, you can get it using homebrew:

brew install llvm --with-clang --with-asan

Create C file

First we will create a small C program called test01.c:

#include <stdio.h>

  int main()
  {
    int x = 10;
    int y = 5;
    char *name = "Jimmy";

    x += 1;
    x += y; 
    printf("Name is %s\n", name);
    printf("Number x is %d\n", x);
  }

Compiling the file

For adding the debugger information to our compiled file, we just need to add the -g flag to the Clang compiler:

clang -g test01.c -o test01

The -o flag is just to define the name of the compiled file, which in this case is test01.

Debugging with LLDB

LLDB is a high-performance debugger that supports C, Objective-C and C++. We just need to open a console an use the command lldb plus the name of our compiled file:

lldb test01

Getting the following response:

(lldb) target create "test01"
Current executable set to 'test01' (x86_64).

Adding breakpoints

Now if we want to add some break points, we just need to use the b (Breakpoint) command followed by a line number or a function name.
Example using line number:

b 10

Example using function name:

b main

The name of the source file could also be added before the line or function name (separated by a colon).

b test01.c:10
b test01.c:main

Running the file in debug mode

Now we just need to use the command run [optional params] for start debugging. Remember that you can add all the optional params that you need for running the app.

run

output:

Process 67445 launched: '/Users/anderustarroz/Sites/c/test2' (x86_64)
Process 67445 stopped
* thread #1: tid = 0x47b22f, 0x0000000100000f06 test2`main + 22 at test2.c:5, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000f06 test2`main + 22 at test2.c:5
   2
   3   	  int main()
   4   	  {
-> 5   	    int x = 10;
   6   	    int y = 5;
   7   	    char *name = "Jimmy";
   8

The debugging stops at the very beginning of the main function cause we defined a breakpoint at b main. Now we can use one of the following options to keep debugging:

  • next or n: Steps to the next line.
  • step or s: Steps into current’s line function.
  • continue or c: Continue until the next breakpoint.
  • finish: Finishes executing the current function and then pauses.

Deleting breakpoints

List all existing breakpoints:

br list

output:

Current breakpoints:
1: name = 'main', locations = 1
  1.1: where = test01`main + 22 at test2.c:5, address = test01[0x0000000100000f06], unresolved, hit count = 0

2: file = '/examples/c/test01.c', line = 10, locations = 1
  2.1: where = test01`main + 52 at test01.c:10, address = test01[0x0000000100000f24], unresolved, hit count = 0

For deleting specific breakpoints we just need to to use br del + the breakpoint id number. The breakpoints ids are displayed using br list (previous example).
Removing main function breakpoint:

br del 1

Deleting all breakpoints:

br del

Showing variables

For displaying specific variables use print or p followed by the variable name:

p x

Show all arguments and local variables:

frame variable

Modifying variables

The variables can be updated using expr [variable name] = [value]. For example, to modify x just try the following:

expr x = 5

Other useful commands

Display current line

You can use the command f to see the current line where the debugger is stopped in.
f

Using backtrace

Your program may have died deep inside a function that has been called more than once in your program, and you need to know which sequence of nested functions calls led to the failure. The answer is bt (backtrace), which is probably one of the most important tools for debugging.
bt

Moving up and down within the frames

In some occasions you may need to move up to a higher frame using the up command to debug there. The down command moves you back down a frame towards where you started. The up and down commands let you move up and down the calling stack of nested functions so you can issue debug commands about the function that’s “active” at each level.
up
down

Comments 3

  1. The beauty of your cheat sheet is that it still works perfectly under macOS Big Sur, in 2022! Thank you so much for sticking to the essentials, for an old programmer who has not used a command-line debugger in the past 25 years… lol

Leave a Reply to mutatedi Cancel reply

Your email address will not be published. Required fields are marked *