Подтвердить что ты не робот

Отладка (строка за строкой) Rcpp-сгенерированной DLL под Windows

Недавно я экспериментировал с Rcpp (inline) для генерации библиотек DLL, которые выполняют различные задачи на поставляемых R входах. Я хотел бы иметь возможность отлаживать код в этих DLL строк за строкой, учитывая определенный набор R-входов. (я работаю под Windows.)

Чтобы проиллюстрировать, рассмотрим конкретный пример того, что любой человек должен иметь возможность запускать...

Код ниже - это действительно простая функция cxx, которая просто удваивает входной вектор. Однако обратите внимание, что есть дополнительная переменная 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()

выводит список DLL в сеансе R. Последний, указанный в списке, должен быть DLL, созданный вышеуказанным процессом, - он имеет случайное временное имя, которое в моем случае

file7e61645c

В столбце "Filename" указано, что 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

Но мы можем, конечно, также вызвать DLL напрямую по имени с помощью команды .Call:

> .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 и код внутри нее получали пользовательское имя и находились в пользовательском каталоге, а не временное имя и местоположение. Но это не влияет на суть вопроса. Я упоминаю это только для того, чтобы подчеркнуть, что довольно легко изменить настройки компиляции, если кто-то дает мне толчок:)

Для полноты установки verbose = TRUE в исходном вызове cxxfunction выше аргумент компиляции имеет следующий вид:

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" ) и соответствующими файлами, скопированными в более постоянное место.

Заранее спасибо за помощь ребятам:)

4b9b3361

Ответ 1

Я немного ошеломлен навязчивой идеей Rcpp у пользователей есть inline и cxxfunction(). Да, это действительно очень полезно, и это, несомненно, привело к принятию Rcpp, поскольку это упрощает эксперименты. Да, это позволило нам использовать более 700 единичных тестов в источниках. Да, я использую его все время, чтобы продемонстрировать примеры здесь, в rcpp-devel list или даже жить в presentations.

Но значит ли это, что мы должны использовать его для каждой задачи? Означает ли это, что у него нет "затрат", таких как рандомизированные имена файлов во временном каталоге и т.д. Pp? В нашей документации Ромен и я утверждали иначе.

Наконец, отладка динамически загруженных R-модулей затруднена. Существует целый раздел (обязательный) Написание R-расширений об этом, и Дуг Бэйтс однажды или дважды разместил учебник о том, как это сделать через ESS и Emacs (хотя я всегда забываю, где он разместил его, когда-то был IIRC на rcpp-devel list).

Редактировать 2012-июль-07:

Вот ваш шаг за шагом:

  • (Преамбула: я использовал gcc и g++ уже много лет, и даже когда добавляю -g, я не всегда поворачиваю -O2 в -O0. Я действительно не уверен, что вам это нужно, но как вы просите об этом...)

  • Установите переменную среды CXXFLAGS в значение -g -O0 -Wall. Существуют многочисленные способы сделать это, некоторые из них зависят от платформы (например, панель управления Windows) и, следовательно, менее универсальны и интересны. Я использую ~/.R/Makevars для Windows и Unix. Вы могли бы использовать это, или вы могли бы переопределить R-системный RHOME/etc/Makeconf в масштабе всей системы, или вы могли бы использовать Makeconf.site или... См. Полный docs --- но, как я уже сказал, ~/.R/Makevars является моим предпочтительным способом, как он НЕ мешает компиляции вне R.

  • Теперь выполняется всякая компиляция R через R CMD SHLIB, R CMD COMPILE, R CMD INSTALL,.... Поэтому вам больше не нужно использовать встроенный или локальный пакет. Продолжение с помощью inline...

  • В остальном мы в основном следуем "Раздел 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")
  • Теперь приостановите R, отправив сигнал разрыва (в Emacs, простой выбор из раскрывающегося списка).

  • В 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() может написать для вас, так как вам не нужно иметь дело со случайными каталогами и именами файлов. Но каждый к своим...