Debugging Tools: lldb, gdb, strace, ltrace, rr Reverse Debugging


Introduction





Debugging is a core developer skill. Beyond print statements and basic breakpoints, advanced debugging tools provide deep insight into program behavior. This article covers lldb and gdb for native code debugging, strace for system call tracing, ltrace for library call analysis, and rr for the revolutionary capability of reverse execution.





lldb (LLVM Debugger)





The modern debugger from the LLVM project, preferred for Clang-compiled code:






# Start debugging


lldb ./myapp


lldb -p 12345 # Attach to process


lldb -c core.dump ./myapp # Analyze core dump




# Breakpoints


(lldb) breakpoint set --name main


(lldb) breakpoint set --file app.c --line 42


(lldb) breakpoint set --selector viewDidLoad


(lldb) breakpoint set --func-regex ".*alloc.*"


(lldb) breakpoint command add 1


> frame variable


> continue


> DONE




# Execution control


(lldb) run


(lldb) continue


(lldb) next # Step over


(lldb) step # Step into


(lldb) finish # Step out




# Variable inspection


(lldb) frame variable


(lldb) frame variable --regex ".*error.*"


(lldb) expression myVar


(lldb) expression self->name


(lldb) expression --objc -- print [NSString stringWithFormat:@"%@", myString]




# Thread and backtrace


(lldb) thread backtrace


(lldb) thread backtrace all


(lldb) thread select 2




# Watchpoints (break on data access)


(lldb) watchpoint set variable myVar


(lldb) watchpoint set expression -- myPointer[5]







gdb (GNU Debugger)





The traditional Unix debugger:






# Compile with debug symbols


gcc -g -O0 myapp.c -o myapp




# Start debugging


gdb ./myapp


gdb -p 12345


gdb ./myapp core




# Common commands


(gdb) break main


(gdb) break app.c:42 if x > 5


(gdb) run arg1 arg2


(gdb) bt # Backtrace


(gdb) info locals # Show local variables


(gdb) print x


(gdb) display x # Auto-display every stop


(gdb) watch y # Break when y changes


(gdb) x/20x ptr # Examine memory as hex


(gdb) disassemble # Show assembly




# Scripted debugging


(gdb) set pagination off


(gdb) set logging on


(gdb) commands


> print x


> continue


> end




# TUI mode (split view)


gdb -tui ./myapp







strace (System Call Trace)





Trace all system calls a program makes:






# Trace a command


strace -o trace.log ./myapp


strace -f ./myapp # Follow forks




# Common filters


strace -e open,read,write ./myapp # Specific syscalls


strace -e network ./myapp # Network-related calls


strace -e trace=file ./myapp # File operations


strace -e trace=process ./myapp # Process management


strace -e trace=signal ./myapp # Signal handling




# Performance analysis


strace -c ./myapp # Syscall count and time summary


strace -T ./myapp # Time spent in each syscall


strace -r ./myapp # Relative timestamps




# Useful patterns


strace -p 12345 -e write -s 1000 # See what a process writes


strace -f -e openat -p $(pgrep nginx) # File opens by nginx workers


strace -e read -s 10000 python3 app.py # Show read content




# PID tracking and timing


strace -fy -t -T -o trace.log -p 12345







**What to look for**: `ENOENT` (file not found), `EACCES` (permission denied), `ECONNREFUSED` (connection refused), slow syscalls (high `-T` values), unexpected file opens, excessive context switching.





ltrace (Library Call Trace)





Trace library function calls:






# Basic usage


ltrace ./myapp


ltrace -o libcalls.log ./myapp




# Filter specific libraries


ltrace -e malloc+free ./myapp # Memory allocation calls


ltrace -e str* ./myapp # String functions


ltrace -e 'libc.*' ./myapp # All libc functions




# Count library calls


ltrace -c ./myapp




# With timing


ltrace -T ./myapp # Time per call


ltrace -S ./myapp # Show syscalls too




# Suppress repetitive calls


ltrace -n 2 ./myapp # Indent by call depth







rr (Reverse Debugger)





Record and replay debugging with reverse execution:






# Record execution


rr record ./myapp


rr record -- ./myapp arg1 arg2




# Replay (deterministic)


rr replay


rr replay -p 12345




# Reverse debugging commands


(gdb) reverse-continue # Go back to most recent event


(gdb) reverse-step # Step back


(gdb) reverse-next # Step over back


(gdb) reverse-finish # Go back to function entry




# Find when a variable changed


(gdb) watch -l myVar


(gdb) reverse-continue # Stops when myVar last changed




# Go to specific event


rr replay -M 12345 # Replay to event number 12345




# Chaos mode (test concurrency)


rr record --chaos ./myapp # Randomize thread scheduling







**Key strength**: Debug intermittent bugs by recording once, then replaying infinitely. When you miss the bug, just restart replay and be more prepared. Chaos mode helps find concurrency bugs.





Debugging Workflow






# 1. Program crashes — get a core dump


ulimit -c unlimited


./myapp


gdb ./myapp core




# 2. Use strace to see what the program is doing


strace -f -o trace.log ./myapp


less trace.log # Look for error syscalls




# 3. For tricky bugs, use rr


rr record ./myapp


rr replay


# Now you can step forward AND backward




# 4. Library call issues


ltrace -c ./myapp # Which library calls are most frequent?







Comparison





| Tool | Best For | Overhead | Learning Curve |


|------|----------|----------|----------------|


| lldb | Native debugging (LLVM) | Low | Medium |


| gdb | Native debugging (GCC) | Low | High (many commands) |


| strace | System call tracing | Medium | Low |


| ltrace | Library call tracing | Medium | Low |


| rr | Reverse debugging | High (record) | Medium |





Recommendations




* **General debugging**: lldb for new projects, gdb for legacy code. Both support the same core debugging workflow.

* **File/permission issues**: strace is the fastest way to find "file not found" or "permission denied" problems.

* **Performance debugging**: strace -c shows where your program spends time in syscalls.

* **Intermittent bugs**: rr is revolutionary — record once, replay infinitely with reverse execution.

* **Library comprehension**: ltrace shows which library functions are called and how often.




Start with strace for quick diagnostics. Use lldb/gdb for step-through debugging. Deploy rr for your hardest bugs — the reverse execution capability makes "oops, I missed that" a thing of the past.