2012-07-05 51 views
17

最近我一直在试用Rcpp(内联)来生成在提供的R输入上执行各种任务的DLL。 我希望能够逐行调试这些DLL中的代码,给定一组特定的R输入。(我在Windows下工作。)在Windows下调试(逐行)Rcpp生成的DLL

为了说明这一点,让我们考虑一个具体的例子,任何人都应该能够运行......

下面的代码是一个非常简单的cxxfunction它只是双打输入向量。但请注意,还有一个额外的变量myvar可以将值更改几次,但不会影响输出 - 已添加该变量,以便我们能够看到调试过程何时正确运行。

library(inline) 
library(Rcpp) 

f0 <- cxxfunction(signature(a="numeric"), plugin="Rcpp", body=' 
    Rcpp::NumericVector xa(a); 
    int myvar = 19; 
    int na = xa.size(); 
    myvar = 27; 
    Rcpp::NumericVector out1(na); 
    for(int i=0; i < na; i++) { 
     out1[i] = 2*xa[i]; 
     myvar++; 
    } 
    myvar = 101; 
    return(Rcpp::List::create(_["out1"] = out1)); 
') 

后,我们运行上面,键入命令

getLoadedDLLs() 

带来了在R对话的DLL列表。最后一个上市的应该是上述过程中创建的DLL - 它有一个随机的临时名称,这在我的情况是

file7e61645c 

“文件名”栏中显示cxxfunction已经把这个DLL中的位置tempdir(),这对我来说是目前

C:/Users/TimP/AppData/Local/Temp/RtmpXuxtpa/file7e61645c.dll 

现在,明显的方式来调用DLL是通过f0,如下

> f0(c(-7,0.7,77)) 

$out1 
[1] -14.0 1.4 154.0 

但是,我们当然可以还名称使用.Call命令直接调用DLL:

> .Call("file7e61645c",c(-7,0.7,77)) 

$out1 
[1] -14.0 1.4 154.0 

所以,我已经到达了我直接调用DLL独立与R输入(这里,向量c(-7,0.7,77))点,并让它回归答案正确地R.

我真正需要的,不过,是行由行调试工具(用gdb,我相信),让我观察到的myvar值被设置为19, 27,28,29,30,最后101代码进行。以上示例是故意设置的,因此调用DLL不会告诉我们关于myvar的任何信息。

为了澄清,这里的“胜利条件”能够观察到myvar的变化(看到myvar = 19的值将是第一步!),而不添加任何其他内容到代码体中。这显然可能需要修改代码的编译方式(是否有打开调试模式的设置?),或者调用R的方式 - 但我不知道从哪里开始。如上所述,所有这些都是基于Windows的。

最后说明:在我的实验中,我实际上对cxxfunction的副本进行了一些小的修改,以便输出DLL及其内的代码接收用户定义的名称并位于用户定义的目录中比临时名称和位置。但这并不影响问题的实质。我提到这只是为了强调,如果有人给我一个微调,应该很容易改变编译设置:)

为了完整起见,在上面的原始cxxfunction调用中设置verbose = TRUE显示编译参数为下面的表格:

C:/R/R-2.13.2/bin/i386/R CMD SHLIB file7e61645c.cpp 2> file7e61645c.cpp.err.txt 
g++ -I"C:/R/R-213~1.2/include" -I"C:/R/R-2.13.2/library/Rcpp/include"  -O2 -Wall -c file7e61645c.cpp -o file7e61645c.o 
g++ -shared -s -static-libgcc -o file7e61645c.dll tmp.def file7e61645c.o C:/R/R-2.13.2/library/Rcpp/lib/i386/libRcpp.a -LC:/R/R-213~1.2/bin/i386 -lR 

我的改编版本有一个编译参数与上述相同,除了字符串“file7e61645c”是由用户选择的名字代替无处不在(如“testdll”)及有关文件复制到更加永久的位置。

预先感谢您的帮助球员:)

+0

我不能直接帮助,但我知道德克等总是有帮助的。但他们通常在[Rcpp电子邮件列表]上做生意(http://lists.r-forge.r-project.org/mailman/listinfo/rcpp-devel) – 2012-07-05 15:15:21

+0

已经在这方面取得了一些温和的进展,所以简短的更新。使用inline ::: compileCode(在cxxfunction中被调用)时,我发现在'R CMD SHLIB'的末尾添加'--debug'使我可以通过组合gdb和R来检查DLL内部发生了什么。HOWEVER ,这不是一个完整的解决方案,因为有些变量是不可访问的(例如'i',在'i'上的循环中);消息传来,他们被“优化了”。因此,我认为我需要在编译参数中将“-O2”替换为“-O0”......但我没有如何做到这一点的想法...... – 2012-07-05 16:18:42

+0

我收集了很多证据,我所需要做的就是将R CMD SHLIB编译器标志从-O2更改为类似-g -O0的内容......例如请参阅https://stat.ethz.ch/pipermail/r-devel/2008-November/051390.html中的文章 - 但我缺乏关于需要指定和如何的精确声明。一些在线资源提到在'/.R/Makevars.win'创建一个文件,但是它们没有描述该文件,并且由于字母R之前的点,这不是Windows中的有效位置。 – 2012-07-05 17:22:21

回答

18

我有点被迷恋一些Rcpp用户有与inline包及其cxxfunction()目瞪口呆。是的,这确实非常有帮助,它进一步推动了Rcpp的采用,因为它使得快速实验变得更容易。是的,它允许我们在源代码中使用700多个单元测试。是的,我一直用它来演示这里的例子,在rcpp-devel list甚至住在presentations

但这是否意味着我们应该将它用于每项任务?这是否意味着它没有“成本”,例如临时目录中的随机文件名等pp?罗曼和我在我们的文档中另有争辩。

最后,动态加载的R模块的调试很困难。关于它的(强制性的)Writing R Extensions有一整段内容,Doug Bates曾经发布过一次或两次有关如何通过ESS和Emacs这样做的教程(尽管我总是忘记他发布它的位置;曾经是rcpp-devel list上的IIRC)。

编辑2012年07月07:

这是你一步一步:

  • (序言:我用gcc和g ++多年,甚至当我添加-g我并不总是把-O2变成-O0,我真的不确定你需要这个,但是当你问它时...)

  • 将你的环境变量CXXFLAGS设置为“-g -O0 - 壁”。有许多方法可以做到这一点,有些是依赖于平台(例如Windows控制面板),因此不太普遍和有趣。我在Windows和Unix上使用~/.R/Makevars。你可以使用它,或者你可以覆盖R的系统范围的$ RHOME/etc/Makeconf,或者你可以使用Makeconf.site或...查看完整的文档---但正如我所说,~/.R/Makevars是我喜欢的方式,因为它不妨碍R以外的汇编。

  • 现在每个编译R都通过R CMD SHLIB,R CMD COMPILE,R CMD INSTALL ...来使用。所以它不再重要你使用内联或本地包。内联继续...

  • 对于剩下的,我们主要遵循“第4.4.1节中动态加载代码查找入口点”“写作R附加”的:

  • 开始有R另一个R对话 - d gdb。

  • 编译你的代码。对于

fun <- cxxfunction(signature(), plugin="Rcpp", verbose=TRUE, body=' 
    int theAnswer = 42; 
    return wrap(theAnswer); 
') 

我得到

[...] 
Compilation argument: 
/usr/lib/R/bin/R CMD SHLIB file11673f928501.cpp 2> file11673f928501.cpp.err.txt 
ccache g++-4.6 -I/usr/share/R/include -DNDEBUG -I"/usr/local/lib/R/site- library/Rcpp/include" -fpic -g -O0 -Wall -c file11673f928501.cpp -o file11673f928501.o 
g++-4.6 -shared -o file11673f928501.so file11673f928501.o -L/usr/local/lib/R/site-library/Rcpp/lib -lRcpp -Wl,-rpath,/usr/local/lib/R/site-library/Rcpp/lib -L/usr/lib/R/lib -lR 
  • 调用如tempdir()看到临时目录,cd到上面,并dyn.load()的文件中使用这个临时目录再建:
dyn.load("file11673f928501.so") 
  • 现在通过发送一个中断信号(在Emacs,从下拉一个简单的选择)暂停R上。

  • 在gdb中,设置一个断点。上述分配一次成为行32对我来说,这样

break file11673f928501.cpp 32 
cont 
  • 回到R,调用函数:

    乐趣()

  • Presto,在调试中蒙古包在破发点,我们希望:

R> fun() 

Breakpoint 1, file11673f928501() at file11673f928501.cpp:32 
32  int theAnswer = 42; 
(gdb) 
  • 现在它是 “只是” 你来上班GDB到它的魔力

现在,就像我说的在我的第一次尝试中,通过一个简单的包(Rcpp.package.skeleton()可以为您编写),所有这些都会更容易(在我的眼中),因为您不必处理随机目录和文件名。但每个人都有自己的...

+0

如果有人在里面使用包含Rcpp的包,那么这只是一个改变CXXFLAGS的问题,用R -d gdb启动R,打破R,用gdb在cpp文件中设置断点并恢复?难道不可能附加到R进程并设置中断,而不必中断R?有关使用Rcpp调试软件包的任何指南?许多很多感谢Rcpp BTW! – Juancentro 2013-10-30 19:34:05

+1

使用RInside,您的C++程序会为您启动R。您可以尝试从gdb连接到R进程 - 或者在外部C++程序中使用gdb。 – 2013-10-30 19:59:54

+0

对不起,如果我不清楚,我不是在谈论RInside。我正在讨论一个依赖于Rcpp的R包,因为它包含C++代码。 – Juancentro 2013-10-30 20:38:37