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.
-
R - d lldb
-
(lldb) run -e "library(<package.name>)"
-
(lldb) b <c-call-name>
-
(lldb) run -f inst/debug.R # (or run -e "...")
Step-by-step debug process
-
Start R with the debugger:
R -d lldb
-
Run the program with inside lldb:
run
-
As new R process is created, load the package you want to debug:
library(<package.name>)
-
Exit R by CTRL+C that returns back to lldb
-
Set breakpoints with
b <c-call-name>
. Alternatively, you can also set breakpoints at line numbers:breakpoint set --file test.c --line 12
(orb test.c:12
in short) -
Continue the program with
c
orcontinue
that puts you back in R -
Call the R function having the underlying C function you want to step into with, e.g.
testFun(rnorm(10))
-
We are again back to lldb and all the breakpoints are set.
-
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
orprint
- Print to the terminal (obviously) -
fr v
orframe variable
to get the current local variable names and their values.
Continue execution
-
Go to next line with
n
orthread step-over
. -
You can step in a function with
s
orthread 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, addCFLAGS=-g -O0
for C, andCXXFLAGS=-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
-
Using gdb to debug R packages with native code https://vimeo.com/11937905
-
Matloff, N. (2011). The Art of R programming: A tour of statistical software design. No Starch Press. Debugging C code in R.
-
Wickham, H. (2015). R packages: organize, test, document, and share your code. O'Reilly Media, Inc. Debugging compiled code. https://r-pkgs.org/src.html#src-debugging
-
Debugging C/C++ code. Bioconductor.org https://www.bioconductor.org/developers/how-to/c-debugging/