LLDB | Debugging like a pro

When developing programs, it is crucial to have the ability to analyse its functionality at runtime. Several tools exist to achieve this goal, but since we are working in an environment that comes with Clang, we can make use of LLDB.
If you come from environments like Visual Studio, you are familiar with UI functionality like the play button to start debugging a program, the visual and interactive breakpoints, and windows that show the call stack and frame variables. All those shiny things may prevent you to jump into a console world where you are only shown text, and all the interaction has to be done by writing commands. Don't be fooled tho, the same can be achieved in this text based workflow, and it is perhaps, even more convenient.
Prepare the program for debugging
In order to get the most out of the LLDB debugger we need to compile our program in a way that it stores useful data to inspect.
Orientative debugging can be achieved by placing
printf(and alike) function calls in key steps of our program.
The easiest way is to tell our compiler that we are going to debug the program via some flags:
CFLAGS += -g3 -O0 -fno-omit-frame-pointerThe
-gflag enables debug symbol generation. The extra3adds the debug level to use.-O0means that the compiler should reduce optimizations line inlining functions, removing variables or reordering code.-fno-omit-frame-pointerhelps stack traces to be accurate.
More and deeper information about the compiler flags can be found at the official CLANG docs
Run a program in debug mode
After compiling our program in debug mode, we need to call our binary through LLDB. We can pass arguments to our program if we need it too.
lldb ./test-program -- [args]Once we are inside the LLDB text interface, we can hit r and press return to run the program. If no errors appear, it will reach exit or loop condition and wait.
The official LLDB site has deeper information, refer there if needed.
Basic navigation in LLDB
As in most cli programs, navigation in LLDB works by entering commands using the keyboard.
Basic navigation gets us covered in running and stepping (over, into and out) through the execution timeline.
Run from start:
runorrin short form.Continue after stop:
cStep over one source line:
nStep into function call:
sStep out of current function:
f
Set breakpoints
Of course the main point is to observe our program behavior at certain points in the execution timeline, not just running the program until it fails or exits. To do so, we can instruct LLDB to set breakpoints.
Set a breakpoint by function:
breakpoint set -n mainorb mainSet a breakpoint by file and line:
breakpoint set -f main.c -l 27orb main.c:27.Set a breakpoint at a memory address:
breakpoint set -a 0x4008f1
Conditional breakpoints
We can also make a breakpoint raise only if certain conditions are met.
Set conditional breakpoint at function entry:
breakpoint set -n main -c "argc > 2"orb main -c "argc > 2".Set conditional breakpoint at file:line
breakpoint set -f main.c -l 27 -c "x > 100"orb main.c:27 -c "x > 100".Set multiple conditions to a breakpoint:
breakpoint set -n function_name -c "j == 5 || err > 0".
Some common patterns in conditional breakpoints can be string comparison, checking for null pointers, variable changes and array indexes or loop counter checks.
String comparison:
b func_name -c "(int)strcmp(buf, \"test\") == 0"Pointer not null:
b func_name -c "data != NULL && data->value ==1"Variable change:
watchpoint set variable fooLoop counter:
b func_name -c "i > 10 && i < 15"
Interacting with breakpoints
Now that we have set some breakpoints through the code, it should be useful to list, enable, disable and / or modify them when required.
List breakpoints:
breakpoint listorbr l.Enable breakpoints (N is breakpoint ID):
breakpoint enable N.Disable breakpoints (N is breakpoint ID):
breakpoint disable NDelete breakpoints (if we don't pass any breakpoint ID, it deletes all breakpoints):
breakpoint delete Norbr del N.Modify breakpoints:
breakpoint modify 1 -c "ptr_var != NULL"orbr m 1 -c "ptr_var != NULL".
Inspecting data
Once we are inside a breakpoint, the next logical thing is to inspect data. To do so we can move through the call stack or access variables' data.
We can inspect the current value for
[args]by running:settings show target.run-args.
Backtrace / call stack
Backtrace of current thread:
thread backtraceorbt.List threads:
thread list.Limit number of frames:
bt 3(top 3 frames).Select a frame:
frame select Norf NwhereNis frame number.Show current frame info:
frame info
Variables and expressions
Show all local variables and arguments in current frame:
frame variableorfr vShow selected variables:
frame variable x yPrint variable or expression:
print xorp xShow pointer value and what it points to:
frame variable ptrorexpr prtDump memory at an address:
memory read 0x4ffaabedc000
Summing Up
There is much more that can be achieved with LLDB and CLANG to further improve debugging, but in the same way the “sports” mode of your car is not what you use for everyday driving, and the car still delivers, this compact command cheat-sheet should be able to get you covered in most case scenarios when debugging C programs in the command line.