Debugging R package with LLDB
Metin Yazici
2020-02-02 - 2 years ago
4 min read

This post gives you a quick walkthrough how to debug an R package calling native C/C++ code with LLDB.

TL;DR

Jump into this recipe if you feel comfortable enough with lldb. Otherwise, scroll a bit more.

  1. R - d lldb

  2. (lldb) run -e "library(<package.name>)"

  3. (lldb) b <c-call-name>

  4. (lldb) run -f inst/debug.R # (or run -e "...")

Step-by-step debug process

  1. Start R with the debugger:R -d lldb

  2. Run the program with inside lldb: run

  3. As new R process is created, load the package you want to debug: library(<package.name>)

  4. Exit R by CTRL+C that returns back to lldb

  5. Set breakpoints with b <c-call-name>. Alternatively, you can also set breakpoints at line numbers: breakpoint set --file test.c --line 12 (or b test.c:12 in short)

  6. Continue the program with c or continue that puts you back in R

  7. Call the R function having the underlying C function you want to step into with, e.g. testFun(rnorm(10))

  8. We are again back to lldb and all the breakpoints are set.

  9. You can now examine the environment. If you like, type command gui to start the "magical" terminal interface TUI (or sometimes it's called "LLDB curses GUI")

Examine R objects

At some point, you would like to inspect the variables with R structure. In the newer versions, you can receive an error like ...has unknown return type; cast the call to its declared return type.

This means that you need to cast the return value of the variables into a base C type so lldb can know what they exactly return. As far as I know, in earlier versions of lldb, that wasn't a problem but it's the case for now and I found it quite annoying sigh.

Print and inspect R structures interactively: (assuming that you want to inspect a R numeric vector named x)

(lldb) p (double)Rf_PrintValue(x)
(lldb) p (bool)Rf_isReal(x)
(lldb) p (double*)REAL(x) # returns the address
(lldb) p ((double*)REAL(x))[2] # returns the value

Print the whole data.frame (casting doesn't matter so can do any e.g. char, double etc.):

(lldb) p (char)Rf_PrintValue(df)

Set debug variables

For R objects:

(lldb) p REAL(VECTOR_ELT(df, 1))
(double *) $0 = 0x0e30..

(lldb) p REAL(VECTOR_ELT(df, 1))[0]
(double) $1 = 3.1415..

(lldb) e double $var=REAL(VECTOR_ELT(df, 1))[0]
(lldb) p $var
(double) $var = 3.1415

Basic LLDB commands

Help

Help for everything

  • help <command>

  • help print

Breakpoints

  • breakpoint list

Examine objects

  • p or print - Print to the terminal (obviously)

  • fr v or frame variable to get the current local variable names and their values.

Continue execution

  • Go to next line with n or thread step-over.

  • You can step in a function with s or thread step-in.

Watch variables

  • watch set var <varname> Set watchpoints to stop the execution when the value of a variable changes. watchpoint list list all the watchpoints.

Run shell commands

  • Possible to run Shell commands inside the LLDB (I've almost never needed this feature though): platform shell pwd

Some troubleshooting

  • LLDB prints expressions starting with dollar sign $. This is useful as we can set conditions or breakpoints, when we want to refer back to that variable.

  • "... was compiled with optimization - stepping may behave oddly; variables may not be available." LLDB cannot do one-to-one mapping when the compiler optimization is on. To overcome this, you should have separate 'debug' builds with the optimization turned off For the R packages, add CFLAGS=-g -O0 for C, and CXXFLAGS=-g -O0 for C++, in the ~/.R/Makevars file.

  • Most of the instructions here can also be used for debugging R packages with GDB. Check GDB to LLDB command map if use GDB.

Resources