2012-07-05 54 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