If you’ve ever used the debugger, chances are you’ve used a file and line number to set a breakpoint. Most of the time this provides enough granularity.

Sometimes, though, more fine grained control would be helpful. Consider the following example:

 1 int foo() {
 2   return 1;
 3 }
 4 int bar() {
 5   return 2;
 6 }
 7 int baz() {
 8   return 3;
 9 }
10 int main(int argc, char** argv) {
11   return foo() + bar() + baz();
12 }

Line Breakpoints

Let’s say we want to step into the function baz on line 11 and can’t set a breakpoint on baz itself. This might sound a bit contrived, but it’s not that uncommon for a function to get called an unknown number of times, maybe even asynchronously from another thread.

To set a breakpoint on line 11 in example.c, we can use the breakpoint set command or the b alias.

Let’s run our little example.

(lldb) b example.c:11
Breakpoint 1: where = example`main + 22 at example.c:11:10, address = 0x0000000100000f86
(lldb) r
Process 27166 launched: 'example' (x86_64)
Process 27166 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000f86 example`main(argc=1, argv=0x00007ffeefbff4f8) at example.c:11:10
   8      return 3;
   9    }
   10   int main(int argc, char** argv) {
-> 11     return foo() + bar() + baz();
   12   }

The debugger hits the breakpoint and is stopped at line 11. The step command allows us to step into the function calls on that line. To get to baz, we need to step through foo and bar first. The functions are pretty short, so we can step through them with next or use finish to step out of the current frame. I prefer the latter because it prints the function’s return value, if any.

(lldb) step
Process 27166 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x0000000100000f44 example`foo at example.c:2:3
   1    int foo() {
-> 2      return 1;
   3    }
   4    int bar() {
   5      return 2;
   6    }
   7    int baz() {
(lldb) fin
Process 27166 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step out

Return value: (int) $0 = 1

    frame #0: 0x0000000100000f8b example`main(argc=1, argv=0x00007ffeefbff4f8) at example.c:11:10
   8      return 3;
   9    }
   10   int main(int argc, char** argv) {
-> 11     return foo() + bar() + baz();
   12   }

Column Breakpoints

Column breakpoints address the issue of having to step in and out of functions by making it possible to be more precise. With a column breakpoint we can skip over the calls to foo and bar altogether. For our example, the call to baz is located at column 26. To set a column breakpoint, we can either use the b command with a line and column number, or spell it out as breakpoint set -f example.c -l 11 -u 26.

(lldb) b example.c:11:26
Breakpoint 1: where = example`main + 43 at example.c:11:26, address = 0x0000000100000f9b
(lldb) r
Process 27166 launched: 'example' (x86_64)
Process 27166 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000f9b example`main(argc=1, argv=0x00007ffeefbff4f8) at example.c:11:26
   8      return 3;
   9    }
   10   int main(int argc, char** argv) {
-> 11     return foo() + bar() + baz();
   12   }

We still stop at line 11, but notice how the location of frame #0 is example.c:11:26 instead of example.c:11:10. As intended, we now step straight into baz.

(lldb) step
Process 27166 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x0000000100000f64 example`baz at example.c:8:3
   5      return 2;
   6    }
   7    int baz() {
-> 8      return 3;
   9    }
   10   int main(int argc, char** argv) {
   11     return foo() + bar() + baz();

Just as with line numbers, we need to make sure to pass the correct column. For example, setting a breakpoint on column 34, which corresponds to the ;, will not be resolved.

(lldb) b example.c:11:34
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

Column breakpoints have been available in LLDB for a while. You can already set them from the SB API in Python. Support for columns in the breakpoint command lagged a bit behind. To use the b file:line:column syntax or to pass -u to breakpoint set, you’ll need the latest LLDB from llvm.org.


Column breakpoints are especially convenient in IDEs where you visually select where to set the breakpoint. Line numbers are usually easy enough to spot in an editor, but the column can be a bit more tedious. If you’re using Vim, I recommend adding a key binding or leader mapping to yank the current file, line and column number, which you can then paste straight into the debugger.