Coreファイルを用いて落ちた箇所を探る方法

Choreonoidが落ちるという報告をいただく際に、公開できないプラグインやコントローラを使用していて、Choreonoidの開発者がその状況を再現できない場合があります。その場合落ちる原因を特定したり修正したりすることは基本的に困難ですが、せめてプログラム内のどの箇所で落ちたかの情報があると、検討をつけることができるかもしれません。

プログラムがどの箇所で落ちたかの情報は、Linuxにおいては、Coreファイル(Coreダンプ)という仕組みを用いることで得ることができます。ここではその概要を紹介します。

Coreダンプの有効化

Coreファイルはプログラムが落ちた時にカレントディレクトリに出力される "core" というファイル名のファイルです。この中にはプログラムの実行に関する情報が格納されており、これをデバッガで読み込むことで、プログラムが落ちるに至った過程をある程度解析することが可能です。Coreファイルが出力されることは「コアダンプ」と呼ばれており、日本語では「コアを吐く」といった表現もされます。この詳細については、「Coreファイル」「コアダンプ」といった用語で検索をかけるといろいろとヒットするので、そちらをご参照ください。

Coreファイルのよいところは、対象のプログラムについて特にデバッグのための準備や操作を行わなくても、どこで落ちたかのヒントを得ることができる点にあります。通常のビルドを行ったプログラムを通常の方法で実行して、それが落ちた場合に、その場である程度の情報を得ることができるのです。

ここではUbuntu Linux上でCoreダンプを有効にするための手順について簡単に紹介します。Ubunt Linuxでは、デフォルトではプログラムが落ちる際にもCoreが出力されない設定となっています。これを出力されるようにするには、/etc/default/apport というファイルにおいて

enabled=0

としておきます。(Coreを出力するための設定方法はこれ以外にもあるようですが、ここでは詳細を省きます。)

この設定に切り替えた直後には、設定を反映させるため、Ubuntuを再起動するようにします。

そして、シェルの設定として、:

ulimit -c unlimited

というコマンドを実行しておく必要があります。これを毎回実行するのが面倒な場合は、ホームの .profile ファイルなどに記述しておきます。

注釈

Ubuntu 20.04 以降では、上記コマンドを .profile に記述してもCoreファイル出力が有効にならないようです。この場合は .bashrc の方に記述すれば有効になることを確認しています。

以上の設定を行っていれば、プログラムが落ちる際に、カレントディレクトリに"core"という名前のファイルが出力されます。

なお、coreファイルは以前出力されたものが残っている場合があります。その時で出力されたファイルであるかどうかは、coreファイルのタイムスタンプをみることで確認できます。落ちた時刻と思われるタイムスタンになっていれば、そこで出力されたcoreファイルであるとみなして間違いありません。

GDBへの読み込み

Coreファイルはデバッガである"GDB"で読み込むことで、その中に格納されている情報を確認することができます。この場合、GDBを以下のようにして起動します。

gdb 実行ファイル coreファイル

「実行ファイル」の部分には、実行していて落ちたプログラムの実行ファイルを指定します。実行ファイルが特定できるよう、ファイルへのパスも含めて記述するようにします。

coreファイルの部分には、対応するcoreファイル(通常"core")を指定します。

例えばChoreonoidのソースディレクトリ上で、ビルドディレクトリ"build"以下に生成されたChoreonoidの実行ファイルを実行し、その後Choreonoidが落ちてcoreファイルが出力された場合は、そのソースディレクトリ上で、

gdb ./build/bin/choreooid core

などとします。

バックトレース(呼び出し履歴)の表示

GDBへの読み込みに成功すると、GDBのコマンドプロンプトが以下のように表示されます。:

(gdb)

ここにGDBのコマンドを入力することで、いろいろな操作ができます。

ここで紹介するのは、backtrace というコマンドです。これはCoreファイル内に格納されているスタックの情報を参照して、落ちた箇所までの関数の呼び出し履歴を表示してくれるというものです。

このコマンドを実行するには、GDBのコマンドプロンプトで backtrace もしくは bt と入力してリターンキーを押します。

(gdb) bt

すると以下のようなテキストが画面にずらずらっと表示されます。

#0  0x00007f93cd754078 in cnoid::Item::insertChildItem(cnoid::Item*, cnoid::Item*, bool) ()
  from /home/nakaoka/choreonoid/build/lib/libCnoidBase.so.1.8
#1  0x00007f93cd74cec9 in cnoid::ItemFileIO::Impl::loadItem(cnoid::ItemFileIO::InvocationType, cnoid::Item*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, cnoid::Item*, bool, cnoid::Item*, cnoid::Mapping const*) () from /home/nakaoka/choreonoid/build/lib/libCnoidBase.so.1.8
#2  0x00007f93cd74d1a2 in cnoid::ItemFileIO::loadItem(cnoid::Item*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, cnoid::Item*, bool, cnoid::Item*, cnoid::Mapping const*) ()
  from /home/nakaoka/choreonoid/build/lib/libCnoidBase.so.1.8
#3  0x00007f93cd74722f in cnoid::ItemManager::load(cnoid::Item*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, cnoid::Item*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, cnoid::Mapping const*) () from /home/nakaoka/choreonoid/build/lib/libCnoidBase.so.1.8

これは関数の呼び出し履歴の分だけ表示されます。落ちた箇所までの関数の呼び出しが深ければ、その分表示されるテキストの量(行数)も多くなります。画面に一度に表示しきれないときは、

---Type <return> to continue, or q <return> to quit---

という表示がでてきますので、ここでリターンを押すと続きを表示することができます。

このバックトレースにおいて一番上に表示されている、 #0 と表示されている部分が、落ちた箇所の関数です。上記の例では、ChoreonoidのItemクラスのinsertChildItemという関数が表示されており、最終的にこの関数で落ちたことが分かります。そしてそれ以下に続く行をみることで、どのような関数呼び出しを経てこの関数まで至ったかも分かります。

なお、プログラムを通常のビルド方法でビルドすると、ソースコードのコンパイルにおいて最適化が施されることになりますが、その場合、関数の呼び出しがインライン展開されるなどの理由で、実際のソースコード上で落ちた関数を正確に反映していない場合もあります。ただその場合でも落ちた関数の呼び出し元のどこかの関数が表示されるので、何も情報がない場合よりは、手がかりをえることができます。

Choreonoidが落ちる場合の報告については、この情報を提示していただくと、開発者としても何が問題か検討をつけられることがありますので、ぜひご活用いただければと思います。(なお、この情報をフォーラム等で提示していただいたからと言って、Choreonoidの開発者が必ず対処方法を示すというものではありませんので、その点は予めご了承ください。)