Debugging: beyond printf

LLDB Cheatsheet

Introduction

This is my LLDB cheatsheet. There are many like it, but this one is mine.

A module on my University course teaches programming in C and C++ and a unit covers debugging on Windows using Visual Studio. I learned how to debug on Windows and then taught myself how to do the same on UNIX. This cheatsheet is my reference to help me remember some of what is available to me when debugging, namely that there’s more to debugging than printf.

Note: ‘(lldb)’ is the debugger prompt. ‘$’ is the shell prompt.

Load a program into lldb

$ lldb hello

Same in two steps:

$ lldb

(lldb) file /path/to/file/hello

Use a double dash to load a command with command line arguments:
$ lldb -- program -arg1 arg2

Attach lldb to a process

Attach to a process with Process ID 1337:
(lldb) attach -p 1337

Attach to a named process e.g. hello:
(lldb) process attach --name hello

You can also specify a Process ID instead of a name by using the --pid flag for a long-form version of the command we used above. And you can wait for the named process to launch by appending the --waitfor flag.

Get help

(lldb) help di

Breakpoints

(lldb) breakpoint set -n main

Note: For breakpoints to be set on line numbers, compile with debugging symbols.

$ c++ -std=c++11 -g -O0 -o case_study case_study.cc

  • We are compiling a C++ file (c++).
  • Using the C++ 11 standards from 2011 (-std=c++11).
  • Debugging symbols are added, optimisations removed (-g -O0).
  • Output file is case_study (-o case_study)
  • File being compiled is case_study.cc

We can now set a breakpoint on a line thusly:

(lldb) breakpoint set -f case_study.cc -l 39

  • We are setting a new breakpoint (breakpoint set).
  • The source file is case_study.cc (-f case_study.cc).
  • The breakpoint is set on line 39 of the source file (-l 39).

List breakpoints

(lldb) breakpoint list

Or simply:

(lldb) br l

Delete breakpoints

Delete a specific breakpoint by breakpoint number e.g. 4:

(lldb) breakpoint delete 4

Delete all breakpoints (short version)

(lldb) br del

Conditional breakpoints

(lldb) br mod -c "x < 0" 1

Note: This modifies the first breakpoint with the condition to break if the variable x is less than 0.

Remove the condition on breakpoint 1.

(lldb) br mod -c "" 1

References

1. Stack overflow

Watchpoints

Watch a variable with a watchpoint.

Set watchpoint

(lldb) watch set var global

List watchpoints

(lldb) wa l (short version)

Note: Don’t forget to run the application first. i.e. set a breakpoint at main, then set the watchpoint when you hit the breakpoint and continue. This is because the variable must exist in order to be watched. If the variable is declared in a later function, set the breakpoint there.

Running the application

Start the application

(lldb) r

Single Step

Step in (source level)

(lldb) s

Step over (source level)

(lldb) n

as in “next”

Step in (instruction level)

(lldb) si

Step over (instruction level)

(lldb) ni

Step out

(lldb) f

as in “finish”

Continue execution

Until next breakpoint or end of program (or crash):

(lldb) c

Examine memory

On the stack

(lldb) x -s4 -fx -c24 $rsp

LLDB now supports the GDB shorthand format syntax but there can’t be space after the command:

(lldb) x/24wx $rsp

(lldb) x/64gx $rsp

Examine the next 2 instructions

(lldb) x/2i $rip

References

1. Memory examination using gdb shorthand

Show the content of the registers

(lldb) register read

Examining the call stack

Show local variables

(lldb) frame variable

Short version:

(lldb) fr v

Optionally show a specific variable e.g. x

(lldb) fr v x

Print backtrace

(lldb) thread backtrace

(lldb) bt

List the threads

(lldb) thread list

References

1. Examining the call stack

Disassembly

Print disassembly for main function

(lldb) di -n main

Or with intel disassembly flavor

(lldb) di -F intel -n main

Core Dumps

lldb can generate core dumps with the process save-core command:

(lldb) process save-core /path/to/corefile

The core dump can then be opened with lldb:

$ lldb -c /path/to/corefile

Finding memory leaks

The leaks command line tool

For usage instructions, refer to the man page. Basic usage is quite simple, leaks [pid]

$ leaks 661

You may need to use lldb to run the program and set a breakpoint before your program exits. ps aux | grep “program_name” can be used to find the pid of the target program.

When you run leaks, it tells you if it found any memory leaks e.g.

Process 661: 4 leaks for 4032 total leaked bytes.

That’s bad news bears, it found 4 leaks. In this case a function may return early without freeing one of the pointers we allocated. If you don’t know where your leak is, leaks has got your back again, it will give you a list of the memory addresses to the allocated memory so we can examine it.

(lldb) x/4wx 0x10badc0de

I can see hex values in the range of ASCII characters because in my case the memory leak is a string. What even was that string though? Find out by examining the characters. 24 should be more than enough to tell me what the string is and then find the exact function which returns without freeing the memory.

(lldb) x/24bc 0x10badc0de

We are examining 24 bytes as chars from the memory address which was identified as the culprit. That’s enough for me to find the line of code causing problems and fix it. If you need to, refer to the debugging cheatsheet to pinpoint the problem.

Process 883: 0 leaks for 0 total leaked bytes.

That’s better, no more leaks. The leaks tool and LLDB are amazing! It is recommended to use leaks during unit testing to get the most out of it.

References:

Finding leaks

Further reading

1. LLDB website

2. FreeBSD man page

3. GDB and LLDB command examples

4. LLDB tutorial

Bonus clang cheatsheet

Create dynamic library

$ cc -dynamiclib -o student.dylib student.c second_file.c third_file.c

References:

dynamiclib command line option

Apple Developer tools article

Compile app using dynamic library

$ cc -o dyn_student_app student.dylib student_app.c

Compile to assembly

Generate assembly using -S:

$ cc -S -o app.s app.c

or with intel assembler:

$ cc -S -masm=intel -o hello.intel.s hello.c

Reference: clang command guide

Turn on all warnings

$ cc -Wall -o app app.c

Reference: Diagnostics reference

And add -pedantic for even more warnings.

Compiling a C++ file with the C++ 11 standards

$ c++ -std=c++11 -o quite_modern quite_modern.cc

Debugging symbols

Use the -g option. To customise the optimisation use -O e.g. -O0.

From the man page: -O0 Means “no optimization”: this level compiles the fastest and generates the most debuggable code.

Compiling with debugging symbols creates a .dSYM directory.

Cross Compiling (update)

Since this post post first came out, Apple introduced the M1 chip for macs, meaning they now run on an ARM processor. If you are running an Intel-based mac and would like to create an application for an ARM-based mac, you can cross compile using the -target option.

For ARM:

cc -o arm_executable arm_executable.c -target arm64-apple-macos11

For Intel:

cc -o intel_executable intel_executable.c -target x86_64-apple-macos10.12

Suggested posts