Debug: Difference between revisions

From 太極
Jump to navigation Jump to search
Line 6: Line 6:


=== debug(), debugonce() and isdebugged() functions ===
=== debug(), debugonce() and isdebugged() functions ===
=== trace() and browser(), findLineNum() and setBreakpoint() ===
* [https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/trace trace(what, tracer, exit, at, print, signature, where = topenv(parent.frame()), edit = FALSE)]
* [https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/browser browser(text = "", condition = NULL, expr = TRUE, skipCalls = 0L)]
* as.list(body(myFun)).
* [https://www.rdocumentation.org/packages/utils/versions/3.6.2/topics/findLineNum findLineNum() and setBreakpoint()] if we use '''source()''' to get the functions. Or [https://zhanxw.com/blog/2011/05/r代码除错-how-to-debug-r-code/ eval(parse(text=x))].
More complicated than '''debug()'''. To insert a browser() function, we don't need to modify the original function.
[https://stackoverflow.com/a/8980282 browser() debug statement R]
<pre>
myFun <- function() {
    x <- 8:1
    y <- 1:8
    plot(y~x)
    lines(y~x)
    text(x,y, letters[1:8], pos=3)
}
as.list(body(myFun))
# [[1]]
# `{`
#
# [[2]]
# x <- 8:1
#
# [[3]]
# y <- 1:8
#
# [[4]]
# plot(y ~ x)
#
# ... More ...
trace(myFun, browser, at = 4)
myFun() 
untrace(myFun)
</pre>
There are several examples in ?trace().
<pre>
##  Very simple use. Not useful
trace(sum)
hist(rnorm(100)) # shows about 3-4 calls to sum()
untrace(sum)
## Show how pt() is called from inside power.t.test():
trace(pt) ## would show ~20 calls, but we want to see more:
trace(pt, tracer = quote(cat(sprintf("tracing pt(*, ncp = %.15g)\n", ncp))),
      print = FALSE) # <- not showing typical extra
power.t.test(20, 1, power=0.8, sd=NULL)  ##--> showing the ncp root finding:
untrace(pt)
f <- function(x, y) {
    y <- pmax(y, 0.001)
    if (x > 0) x ^ y else stop("x must be positive")
}
## arrange to call the browser on entering and exiting
## function f
trace("f", quote(browser(skipCalls = 4)),
      exit = quote(browser(skipCalls = 4)))
trace("f", quote(if(any(y < 0)) yOrig <- y),
      exit = quote(if(exists("yOrig")) browser(skipCalls = 4)),
      print = FALSE)
## Enter the browser just before stop() is called.  First, find
## the step numbers
untrace(f) # (as it has changed f's body !)
as.list(body(f))
as.list(body(f)[[3]]) # -> stop(..) is [[4]]
trace("f", quote(browser(skipCalls = 4)), at = list(c(3,4)))
f(-1,2)
## trace a utility function, with recover so we
## can browse in the calling functions as well.
trace("as.matrix", recover)
## turn off the tracing (that happened above)
untrace(c("f", "as.matrix"))
</pre>


=== How to quit debugging ===
=== How to quit debugging ===

Revision as of 17:17, 8 September 2020

R

How to debug an R code

debug(), debugonce() and isdebugged() functions

trace() and browser(), findLineNum() and setBreakpoint()

More complicated than debug(). To insert a browser() function, we don't need to modify the original function.

browser() debug statement R

myFun <- function() {
    x <- 8:1
    y <- 1:8
    plot(y~x)
    lines(y~x)
    text(x,y, letters[1:8], pos=3)
}
as.list(body(myFun)) 
# [[1]]
# `{`
# 
# [[2]]
# x <- 8:1
# 
# [[3]]
# y <- 1:8
# 
# [[4]]
# plot(y ~ x)
# 
# ... More ... 
trace(myFun, browser, at = 4)
myFun()   
untrace(myFun)

There are several examples in ?trace().

##  Very simple use. Not useful
trace(sum)
hist(rnorm(100)) # shows about 3-4 calls to sum()
untrace(sum)

## Show how pt() is called from inside power.t.test():
trace(pt) ## would show ~20 calls, but we want to see more:
trace(pt, tracer = quote(cat(sprintf("tracing pt(*, ncp = %.15g)\n", ncp))),
      print = FALSE) # <- not showing typical extra
power.t.test(20, 1, power=0.8, sd=NULL)  ##--> showing the ncp root finding:
untrace(pt)

f <- function(x, y) {
    y <- pmax(y, 0.001)
    if (x > 0) x ^ y else stop("x must be positive")
}
## arrange to call the browser on entering and exiting
## function f
trace("f", quote(browser(skipCalls = 4)),
      exit = quote(browser(skipCalls = 4)))
trace("f", quote(if(any(y < 0)) yOrig <- y),
      exit = quote(if(exists("yOrig")) browser(skipCalls = 4)),
      print = FALSE)

## Enter the browser just before stop() is called.  First, find
## the step numbers
untrace(f) # (as it has changed f's body !)
as.list(body(f))
as.list(body(f)[[3]]) # -> stop(..) is [[4]]
trace("f", quote(browser(skipCalls = 4)), at = list(c(3,4)))
f(-1,2)

## trace a utility function, with recover so we
## can browse in the calling functions as well.
trace("as.matrix", recover)

## turn off the tracing (that happened above)
untrace(c("f", "as.matrix"))

How to quit debugging

press Q and then ENTER

list or undebug all debugged functions

https://stackoverflow.com/a/12807637

Using assign() in functions

For example, insert the following line to your function

 assign(envir=globalenv(), "GlobalVar", localvar)

options(error)

options(
  error = rlang::entrace, 
  rlang_backtrace_on_error = "branch")

Debug lapply()/sapply()

Debugging with RStudio

Debug R source code

Build R with debug information

$ ./configure --help
$ ./configure --enable-R-shlib --with-valgrind-instrumentation=2 \
                               --with-system-valgrind-headers \
               CFLAGS='-g -O0 -fPIC' \
               FFLAGS='-g -O0 -fPIC' \
               CXXFLAGS='-g -O0 -fPIC' \
               FCFLAGS='-g -O0 -fPIC' 
$ make -j4
$ sudo make install
# Make sure to create a file <src/Makevars> with something like: CFLAGS=-ggdb -O0
# Or more generally
# CFLAGS=-Wall -Wextra -pedantic -O0 -ggdb
# CXXFLAGS=-Wall -Wextra -pedantic -O0 -ggdb
# FFLAGS=-Wall -Wextra -pedantic -O0 -ggdb

$ tree nidemo
$ R CMD INSTALL nidemo
$ cat bug.R
$ R -f bug.R 
$ R -d gdb
(gdb) r
> library(nidemo)
> Ctrl+C
(gdb) b nid_buggy_freq
(gdb) c  # continue
> buggy_freq("nidemo/DESCRIPTION") # stop at breakpoint 1
(gdb) list
(gdb) n # step through
(gdb) # press RETURN a few times until you see the bug
(gdb) d 1 # delete the first break point
(gdb) b Rf_error # R's C entry point for the error function
(gdb) c
> buggy_freq("nidemo/DESCRIPTION")
(gdb) bt 5 # last 5 stack frames
(gdb) frame 2
(gdb) list
(gdb) p freq_data
(gdb) p ans
(gdb) call Rf_PrintValues(ans)
(gdb) call Rf_PrintValues(fname)
(gdb) q
# Edit buggy.c

$ R CMD INSTALL nidemo # re-install the package
$ R -f bug.R
$ R -d gdb
(gdb) run
> source("bug.R") # error happened
(gdb) bt 5 # show the last 5 frames
(gdb) frame 2
(gdb) list
(gdb) frame 1
(gdb) list
(gdb) p file
(gdb) p fh
(gdb) q
# Edit buggy.c

$ R CMD INSTALL nidemo
$ R -f bug.R

.Call

Registering native routines

https://cran.rstudio.com/doc/manuals/r-release/R-exts.html#Registering-native-routines

Pay attention to the prefix argument .fixes (eg .fixes = "C_") in useDynLib() function in the NAMESPACE file.

Example of debugging cor() function

Note that R's cor() function called a C function cor().

stats::cor
....
 .Call(C_cor, x, y, na.method, method == "kendall")

A step-by-step screenshot of debugging using the GNU debugger gdb can be found on my Github repository https://github.com/arraytools/r-debug.