Edited at

R本体Cの関数をデバッグする(その1)

More than 3 years have passed since last update.


RとC言語

R本体はC言語でコーディングされています。またRの基本関数の多くはCで実装されています.たとえば「vector」関数と「length」関数をコンソールで丸括弧なしで実行すると,関数本体のコードが確認できます.

すると,以下のように出力されます.

> vector

function (mode = "logical", length = 0L)
.Internal(vector(mode, length))
<bytecode: 0x15d6830>
<environment: namespace:base>
> length
function (x) .Primitive("length")

ここには関数の実体がありません.表示されている 「.Internal」と「.Primitives」は,これらの関数がC言語で実装された関数を呼び出していることを表わしています.


C 関数の実装を確認

R本体のソースをダウンロードして,該当ファイルを探すか,あるいは「pryr」パッケージを使います.


ダウンロードしたRのソース

解凍したフォルダのsrc/main にある names.c を開きます.

ここには「.Internal」と「.Primitives」として分類される関数の一覧があります.


「pryr」パッケージ

Rのソースをさくっと眺めるのに便利なパッケージです.

> library(pryr)

> show_c_source(.Internal(length(x)))
length is implemented by do_length with op = 0

実行するとブラウザが起動し,Github上に公開されているRのソースから 該当するCの関数 が表示されます.


「.Internal」と「.Primitives」

プリミティブ関数である「length」を確認すると以下のように記述されています.

{"length",  do_length,  0,  1,  1,  {PP_FUNCALL, PREC_FN,   0}},

これはRの関数名とCの関数名を対応させた表(配列)であり,Rで「length」を実行するとCの「do_length」が呼び出されることを意味しています.

そして「do_length」という関数の本体は array.c という別のソースファイルで以下のように実装されています.

SEXP attribute_hidden do_length(SEXP call, SEXP op, SEXP args, SEXP rho)

ちなみに「.Primitives」は直接Cの関数を呼び出すのに対して,「.Internal」は引数名およびデフォルト値を指定してCの関数を呼び出す形式になっています.

「.Internal」そのものは「Primitives」関数として定義されています.


デバッグツール GDB

こうした関数のデバッグにはGDBなどのツールを使う必要があります.以下の説明はLinuxないしMacでの操作を想定しています.CRANに公開されているWindows用バイナリでは動作しません(ソースからビルドしたRを使う必要があります).


R -d gdb

デバッグ作業はターミナルにて行ないます.以下のように 「R -d gdb」を実行します.

~$ R -d gdb

GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/local/lib/R/bin/exec/R...done.
(gdb)


run

「run」コマンドを入力するとRのコンソールに変わります.

(gdb) run

Starting program: /usr/local/lib/R/bin/exec/R
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

R version 3.3.0 (2016-05-03) -- "Supposedly Educational"
Copyright (C) 2016 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R は、自由なソフトウェアであり、「完全に無保証」です。
一定の条件に従えば、自由にこれを再配布することができます。
配布条件の詳細に関しては、'license()' あるいは 'licence()' と入力してください。

R は多くの貢献者による共同プロジェクトです。
詳しくは 'contributors()' と入力してください。
また、R や R のパッケージを出版物で引用する際の形式については
'citation()' と入力してください。

'demo()' と入力すればデモをみることができます。
'help()' とすればオンラインヘルプが出ます。
'help.start()' で HTML ブラウザによるヘルプがみられます。
'q()' と入力すれば R を終了します。

> x <- 1:10
> length(x)
[1] 10

ここでプリミティブである「length」関数をデバッグしたい場合,CtrlとCを同時押しして,いったんGDBに戻ります.

Program received signal SIGINT, Interrupt.

0x00007ffff72cc6d3 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: そのようなファイルやディレクトリはありません.


ブイレークポイント

「length」関数は,Cでは「do_length」として定義されていましたので,GDBの「b」コマンドでブレークポイントを設定します.

(gdb) b do_length

Breakpoint 1 at 0x7ffff7245600: do_length. (2 locations)

「c」でRに戻り,Rの「length」関数を実行します.

(gdb) c

Continuing.

> length(x)

実行すると直ちにGDBに切り替わります.処理がCの「do_length」関数の内部に入っています.

GDBの通常のコマンドでデバッグを実行することができます(たとえば次のコードに移る「n」や変数を表示する「p」).

ただしRのオブジェクト(SEXP型)を確認するには,Rで用意されている関数を使います.以下,いくつか例を紹介します.


オブジェクトを確認

「length」関数で指定したベクトル「x」は仮引数の「args」に渡されています.


Breakpoint 1, do_length (call=0x1b18128, op=0x636248, args=0x1b17fd8, rho=0x65c708) at array.c:426
426 {
(gdb) n
427 checkArity(op, args);
(gdb)

(gdb) p args
$1 = (SEXP) 0x1b18a58

SEXP 型のオブジェクト(より厳密には,引数のペアリストに使われる「LISTSXP」型だが後述)とそのアドレスが表示されます.型の詳細については R の公式ドキュメントである R Internalの1.1 SEXPs を参照してください.


オブジェクトの構造 「R_inspect」

「args」の中身(構造)を確認するには「R_inspect」関数を使います.

(gdb) p R_inspect(args)

@1b18a58 02 LISTSXP g0c0 []
@1ad7ca8 13 INTSXP g0c4 [NAM(1)] (len=10, tl=0) 1,2,3,4,5,...
$2 = (struct SEXPREC *) 0x1b18a58
(gdb) c
Continuing.
[1] 10

引数はペアリストである「LISTSXP」型であり、具体的には「INTSXP」型を要素とするベクトルであることが確認できます.「@」はアドレスです。

より詳細な情報を確認するには次のように実行します

(可読性を高めるため,改行やスペースを加えています).型の定義を見てみましょう.


型の定義

(gdb) p args

$3 = (SEXP) 0x1b18cb8
(gdb) ptype args
type = struct SEXPREC {
struct sxpinfo_struct sxpinfo;
struct SEXPREC *attrib;
struct SEXPREC *gengc_next_node;
struct SEXPREC *gengc_prev_node;
union {
struct primsxp_struct primsxp;
struct symsxp_struct symsxp;
struct listsxp_struct listsxp;
struct envsxp_struct envsxp;
struct closxp_struct closxp;
struct promsxp_struct promsxp;
} u;
} *

「u」というのがデータのノードになります。


実際の構造

引数として与えられた「x」の具体的な構造をみます.

(gdb) p *args

$5 = {sxpinfo = {type = 2, obj = 0, named = 0, gp = 0, mark = 0, debug = 0,
trace = 0, spare = 0, gcgen = 0, gccls = 0},
attrib = 0x616a68,
gengc_next_node = 0x1b18df8,
gengc_prev_node = 0x1b17f00,
u = {primsxp = {offset = 16417320},
symsxp = {pname = 0xfa8228, value = 0x616a68, internal = 0x616a68},
listsxp = {carval = 0xfa8228, cdrval = 0x616a68, tagval = 0x616a68},
envsxp = {frame = 0xfa8228, enclos = 0x616a68, hashtab = 0x616a68},
closxp = {formals = 0xfa8228, body = 0x616a68, env = 0x616a68},
promsxp = {value = 0xfa8228, expr = 0x616a68, env = 0x616a68}}}
(gdb)

「u」の部分がデータノードです。引数として渡された「x」つまり「args」の最初の要素を確認するにはこうします。

(gdb) p INTEGER(args->u)[0]

$4 = 1


データを確認 「Rf_PrintValue」

中身を確認してみましょう.これには「Rf_PrintValue」関数を使います.

(gdb) p Rf_PrintValue(args)

[[1]]
[1] 1 2 3 4 5 6 7 8 9 10

$6 = void

最後の「$6 = void」というのは「Rf_PrintValue」の返り値にすぎません.


Rinternal.h を確認しよう

Rf_PrintValue などの関数はRのソースに含まれるヘッダファイル Rinternal.h に一覧があります.

続く(はず)