aryoda / R_CppDebugHelper

Diagnostics functions to be called in debuggers like 'gdb' to debug C and C++ based code called from R
GNU General Public License v3.0
0 stars 0 forks source link
c cpp debugging gdb r rcpp

CppDebugHelper

An R package with functions for debuggers like 'gdb' to inspect R and Rcpp specific types in C and C++ based code called from R

This packages provides functions that can be called from a debugger (eg. gdb) to easily inspect (print) the content of R and Rcpp specific data types like variables or environments on the console when debugging C or C++ called called from R. This can be done without modifying the source code of the debugged code.

This package is experimental and meant as a prove of concept

Status

Initiated in Oct, 2019 after some questions and discussions at Stackoverflow regarding debugging of Rcpp code with gdb

Currently work in (slow or no ;-) progress... Nothings works. A lot of ideas are collected in the RESEARCH_NOTES.txt...

Motivation

Debugging C and C++ code called from R using a debugger like gdb is often a pain because it is difficult to inspect the R and Rcpp specific data types (like variables or environments) with a debugger that does not know the internals of these data types.

This package is an attempt to improve this situation.

TODO

Goals

Functional requirements:

  1. Inspect variable values and structures of R and Rcpp variables during debugging

  2. Debugging support for C++ code

  3. Optional: Debugging support for plain C code

  4. Optional: Support for modifying variable values (to try to find bug fixes during debugging)

Non-functional requirements:

  1. No need to recompile R on Windows for debugging (unless you want to debug R itself and are resistent against build headaches ;-)

  2. Minimize preparation efforts for debugging

    • no need to add extra C/C++ code (eg. main.cpp with Rinside)
    • no need to modify existing C/C++ code
  3. Easy-to-use debugging helper functions

    • query R and Rcpp variables in the same debugging session
    • short function names
    • overloaded debugging helper functions to let gdb decide which function to call instead of forcing the user to find the right function name by knowing the data type
  4. Support for at least gdb and optionally the LLDB debugger

Functions and supported data types

You can call the debug functions dbg_* in gdb via the call command, eg.

(gdb) call dbg_ls()
(gdb) call dbg_str("myVar_in_global_env")
(gdb) call dbg_attributes(x)
(gdb) call dbg_print(x)
(gdb) call dbg_print(dbg_table(y))
...

The supported combination of functions and the data type of the first argument are marked with an x in the cell.

For non-obvious cases the meaning of the first argument for the function is described in the cell.

The last column (...) contains names of functions specialized for the data type.

TODO: Add row to show signatures to make this table more cheat-sheet alike. SEXP should always return an SEXP. Show example calls...

Input Data Type dbg_ls() dbg_str() dbg_print() dbg_attributes() dbg_table() dbg_get() dbg_subset() Others...
Function description List objects Print object structure Print object value Print attributes Create contingency table Find object in env Filter objects
Corresponding R function ls() str() print() attributes() table() get() myVar[begin:end]
Return type IntegerVector of class "table" SEXP same type as input object
Side effects print print print print
char * object name (in global env) object name (in global env) object name (in global env) object name (in global env) dbg_as_std_string(x)
std::string TODO (semantics?)
Rcpp::String prints the string content
SEXP x x x x x
Environment print names of all objects in the env str() of an object in the env prints names of all objects in the env prints attributes of an object in the env env to search a named object get object from the env
ComplexVector x x x (not supported in R) x
DataFrame x x x (not supported) x (filters rows like R: df[begin:end, ])
IntegerVector x x x x x
LogicalVector x x x x x
NumericVector x x x x x
DoubleVector x x x x x
RawVector x x x (not supported in R) x
CharacterVector x x x x x
StringVector x x x x x
ExpressionVector x x x (not supported in R) x
GenericVector x x x (not supported in Rcpp) x
Rcpp::List x x x (not supported by Rcpp) x
DateVector x x x x x
DatetimeVector x x x x x
... Matrices...

Note: This table was initially created using http://www.tablesgenerator.com/markdown_tables

Getting started

To debug your own package or R code that uses C/C++ (eg. via Rcpp) you have to follow these steps:

  1. Install and compile this package if not already done (currently only from github, not CRAN):
# install.packages("devtools")
devtools::install_github("aryoda/CppDebugHelper")
  1. Build your package or C/C++ library called from R with debugging information

    • For R packages modify the Makevars file via usethis::edit_r_makevars() and add (or edit) the line CXXFLAGS = -g3 -O0 -Wall (for Linux only). For Windows you have to add CXXFLAGS = -g3 -std=c++11. Save the file.

      Don't forget to remove or comment the line later or you may slow down your R or newly installed packages!

    • Clean and build your package (or C/C++ libary)

  2. Open a command shell ("terminal") and cd into the location where your C/C++ source code was compiled

    If you have multiple locations you can add more locations in gdb later via the directory command (see help directory in gdb).

  3. Start the debugger:

    # open a command shell ("terminal")
    # on Linux use:
    R -d gdb
    # on Windows use
    gdb /path/to/R-3.x.x/bin/x64/Rgui.exe

    Note: All examples here are based on gdb. lldb does not work so far (for the current status see issue #5), but perhaps you are the lucky one ;-) You can use this GDB to LLDB command map to "translate" the example gdb commands to lldb.

  4. Start R in gdb

    (gdb) run # or short: just an "r"

    The R command prompt appears.

  5. Load the CppDebugHelper package to "inject" the debug functions

    library(CppDebugHelper)
  6. Load all your packages and libraries to be able to set breakpoints in gdb

    • The R package to be debugged must be loaded via library.

    • C/C++ libraries you are calling directly via .C or .Call must be loaded via dyn.load.

  7. Interrupt R to put you back to the gdb debugger to set breakpoints

    • On Linux: Press CTRL+C
    • On Windows: In RGui select the Misc > Break to debugger menu item

    In gdb set breakpoints, eg. in the CppDebugHelper test function:

    TODO
  8. TODO

Expected deliverables when this package gets released one day

  1. An R package that offers low-level C/C++ functions to painlessly inspect R/Rcpp specific data types from a debugger
  2. Document the options you have to debug
  3. Provide a tutorial using gdb (incl. test functions as a separate R package to learn debugging)

Planned debugging API functions

Offer public C/C++-level functions to

Note: Each print function should respect getOption("max.print") and cut the output with [ reached getOption("max.print") -- omitted 9000 entries ]

gdb pretty printers

For

A function for automatic installation or at least instructions would be helpful.

Open issues

TODO

Known limitations

General prerequisites

A debugger is no compiler

So gdb cannot

See the gdb documentation of C++ Expressions for what is possible and which limitations apply:

Limitations of gdb regarding the R and Rcpp API

FAQs

Where are the divers R config files like Makevars stored?

# Where is my home folder?
path.expand("~")
# Where is R's default Makefile configuration?
file.path(R.home("etc"), "Makeconf")
[1] "/usr/lib/R/etc/Makeconf"
# In this file you will find the build flags (variables) like: PKG_CXXFLAGS, PKG_LIBS...
# Where is user Makevars file (which overrides the default make file generated by R)?
usethis::edit_r_makevars()

I have compiler errors or warning when building this package on Windows

If you get errors or warnings like

the used CPP compiler uses an older C++ standard as default.

To build successfully you have to enable a newer C++ standard (at least C++11) in the Makevars file:

CXXFLAGS = -g3 -O0 -Wall -std=c++11
# CXXFLAGS = -g3 -O0 -Wall -std=c++14   # or this for C++14

For details see:

How can I debug on Windows (R has no -d switch)?

Precondition: The code under inspection has been compiled for debugging (TODO: add link to howto instructions)

On Linux you can debug an R script or R package with gdb via R -d gdb. This start R and attaches gdb as debugger.

The gdb version delivered on Windows via the Rtools does not support the -d switch (perhaps because you cannot send a signal to running processes to stop the execution for debugging like Linux does eg. with the shortcut Ctrl+C).

But: To set a breakpoint in Windows DLL it must be loaded first and this requires R to be started.

Once you have started R you cannot pause R (with out-of-the-box Windows ways) to call the debugger.

The solution is to "debug" the RGui.exe which has a built-in menu item Misc > Break to debugger. This allows you the pause R and work in gdb:

gdb /path/to/R/bin/x64/Rgui.exe

To make the source code visible in gdb you also

For details see the R for Windows FAQ

During debugging with gdb on Windows I see a lot of warnings containing attribute.cpp(92)\dwmapi.dll

You can see a lot of warning similar to this one:

windows\dwm\dwmapi\attribute.cpp(92)\dwmapi.dll!00007FFFAD51594E: (caller: 00007FFFABCA071A) ReturnHr(30) tid(10a0) 80070006 The handle is invalid.

This is a Windows 10 bug. It seems Microsoft has forgotten to disable tracing code before releasing this DLL.

You can ignore these warnings (even though it is annoying to be flooded with them).

For details see: https://social.msdn.microsoft.com/Forums/en-US/3a5a145a-c13d-4898-bb61-a5baadc9332f/why-am-i-getting-hundreds-of-weird-messages-in-debug-output-window

Debugging with gdb on Windows fails with error "cannot execute this command while the selected thread is running"

Sometimes during debugging it is no longer possible to step (via the next command) or continue the execution.

The error message is:

(gdb) c
Continuing.
Cannot execute this command while the selected thread is running.

This problem was observed with gdb --version GNU gdb (GDB) 7.9.1 as installed with Rtools v3.5 and is not easily reproducible.

This seems to be bug but the exact reason is unclear:

Suppress noisy startup print of gdb when debugging with R

R -d gdb --debugger-args=--quiet

How can I pass additional arguments to gdb?

You can use the R argument --debugger-args=ARGS where ARGS are arguments to the debugger.

R -d gdb --debugger-args=--quiet

How can I use the debugging helper functions in gdb directly without R?

The easiest way to load the debugging helper functions is via R's library(CppDebugHelper) command but if you want to debug an application that does use R directly you can load the underlying shared library using the LD_PRELOAD environment variable:

gdb myApp
(gdb) set environment LD_PRELOAD ./CppDebugHelper.so
(gdb) run
(gdb) # Press Ctrl+C to break R into gdb
(gdb) # verify that the library has been loaded by gdb
(gdb) info sharedlibrary 
From                To                  Syms Read   Shared Object Library
0x00007ffff7bbc040  0x00007ffff7bca2e0  Yes         ./CppDebugHelper.so
...
(gdb) # The debug functions are now available, eg.
(gdb) ptype dbg_as_std_string("hello world")

See:

Calling a debug function (dbg_*) in gdb fails with: Attempt to take address of value not located in memory.

When you call a debug function gdb you will get an error message like

Attempt to take address of value not located in memory.

if an function argument is a variable that is not stored in memory but in a CPU register:

(gdb) call dbg_print(x)
Attempt to take address of value not located in memory.

This most probably occurs because you have enabled code optimization during compilation. Check if in the Makevars file the -O flag is set to -O0 ("optimization = zero"). Then clean-up the binaries of your code and recompile (Build > Clean & Rebuild in RStudio).

Can I use the lldb debugger instead of gdb?

lldb is the default debugger in Xcode on macOS/OS X for C++ and typically used with the Clang compiler.

All examples here are based on gdb but it should (= not yet tested!) be possible to use lldb instead of gdb because this packages does not depend on any special debugger.

You can use this GDB to LLDB command map to "translate" the example gdb commands to lldb commands.

In practice it did not work so far (for the current status see issue #5).

See also: Is it possible to debug a gcc-compiled program using lldb, or debug a clang-compiled program using gdb?

License

GPL-3 (see file LICENSE)

Links

gdb

lldb

Rcpp

R API