Visual Studio Codeを用いてChoreonoidをデバッグする方法

Visual Studio Codeを用いてChoreonoidのプログラムをデバッグする方法を解説します。

本ドキュメントでは、GNU Debugger (GDB) と Microsoft の C/C++ 拡張機能を組み合わせたデバッグ方法を説明します。Linux 上で gcc でビルドした Choreonoid をデバッグする場合、この組み合わせが最も安定しており、機能も豊富です。

Visual Studio Code と拡張機能のインストール

まず Visual Studio Code 本体をインストールし、続けて C/C++ 拡張機能をインストールします。これらは一度行えば以降のデバッグ作業で繰り返す必要はありません。

Visual Studio Code のインストール

既に Visual Studio Code を使用している方は、それを使用して頂いて構いません。

ここでは Ubuntu 24.04 LTS (64bit) 上に Visual Studio Code をインストールする場合を例に説明します。

Visual Studio Code のホームページ https://azure.microsoft.com/ja-jp/products/visual-studio-code を開いて、 VS Codeをダウンロードする ボタンを押します。

../../_images/01_install_vscode.png

表示されたダウンロードページの .deb Debian, Ubuntu ボタンを押して、.debファイル(例: code_1.102.0-1752099874_amd64.deb )をダウンロードします。

../../_images/02_install_vscode.png

.debファイルが保存されたディレクトリで端末を開いて、

sudo dpkg -i code_1.102.0-1752099874_amd64.deb

を実行すると、インストールが開始します(ファイル名はダウンロードしたバージョンに合わせて読み替えてください)。 後はインストーラの指示に従ってください。インストール中に以下の画面が表示されたときは、 <はい>/<いいえ> のいずれかを選択してインストールを続けてください。

../../_images/03_install_vscode.png

C/C++ 拡張機能のインストール

Visual Studio Code を起動します。端末を起動して、

code

を実行すると、Visual Studio Code が起動します。

設定の変更が不要な場合は、 Mark Done ボタンを押して、初期設定を完了してください。

../../_images/04_launch_vscode.png

続けて、Visual Studio Code に C/C++ 拡張機能をインストールします。ウィンドウ左の Extensions (Ctrl+Shift+K) ボタンを押して、EXTENSIONSパネル上部の検索バーで C/C++ を検索し、Microsoft 提供の拡張機能をインストールしてください。

../../_images/06_install_c_cpp.png

この拡張機能には、GDB を Visual Studio Code から利用するためのデバッガ(cppdbg)が含まれています。

注釈

Visual Studio Code でC/C++のデバッグを行うための拡張機能は他にも多数存在します。代表的なものとしては、LLDB を利用する CodeLLDB や、GDB を軽量に扱う Native DebugGDB Debugger - Beyond などがあり、それぞれに特徴があります。本ドキュメントでは、Linux 上で gcc でビルドした Choreonoid をデバッグするにあたって最も安定して動作する Microsoft の C/C++ 拡張機能を採用しており、他の拡張機能の設定方法や使い方については扱いません。拡張機能ごとに launch.json の記法や使い勝手は異なりますので、他の選択肢を利用する際は各拡張機能のドキュメントを参照してください。

以上で、Visual Studio Code と拡張機能のインストールは完了です。

Choreonoid のデバッグビルドを用意する

デバッグを行うには、デバッグ情報を含めてビルドされた Choreonoid の実行ファイルが必要です。

デバッグビルドは、通常のリリースビルドとは別のビルドディレクトリで行うことをおすすめします。同一のディレクトリをリリースビルドとデバッグビルドで使い回すと、設定を切り替えるたびに全てのソースを再ビルドすることになり、効率が悪くなります。ディレクトリを分けておけば、デバッグと通常の使用を切り替えながら作業でき、必要になったときにすぐデバッグを開始できます。

本ドキュメントでは、Choreonoid のトップディレクトリ直下に build-debug というディレクトリを作成し、そこでデバッグビルドを行う例で説明します。以降の設定やコマンドの例もすべてこのディレクトリ名を前提とします。別の名前を使う場合は、適宜読み替えてください。

Choreonoid をソースコードからビルドする手順の全般については ソースコードからのビルドとインストール (Ubuntu Linux編) を参照してください。ここではデバッグビルド特有の手順のみを示します。

まず、Choreonoid のトップディレクトリ内に build-debug ディレクトリを作成します。

mkdir build-debug
cd build-debug

次に、このディレクトリで CMake を実行してビルド設定を行います。デバッグビルドにするためには CMAKE_BUILD_TYPEDebug に設定する必要があります。コマンドラインで指定する場合は、

cmake -DCMAKE_BUILD_TYPE=Debug ..

のように -DCMAKE_BUILD_TYPE=Debug オプションを付けて cmake を実行します。

あるいは ccmake を使って対話的に設定することもできます。

ccmake ..

を実行し、 CMAKE_BUILD_TYPE の値を Debug に変更し、configure , generate して終了します。

CMake の設定が完了したら、build-debug ディレクトリ内で

cmake --build .

を実行して Choreonoid をビルドします。並列ビルドを行う場合は

cmake --build . --parallel 並列数

のように --parallel オプションで並列数を指定できます。ビルドの詳細や他のビルド方法については Choreonoidのビルド を参照してください。

以上で、デバッグビルドの準備は完了です。

デバッグ対象のサンプル

ここでは、Choreonoid に付属するサンプル sample/SimpleController/SampleCrawler.cnoid をデバッグ対象として説明します。このプロジェクトは SampleCrawlerController クラスによって制御されており、そのソースコードは sample/SimpleController/SampleCrawlerController.cpp にあります。以降ではこのコントローラのコードにブレークポイントを設定し、変数の値を確認しながらプログラムの動作を追っていきます。

launch.json の作成

Visual Studio Code でデバッグを行うには、デバッグ対象の実行ファイルや起動時の引数などを記述した launch.json というファイルを作成する必要があります。

まず Visual Studio Code で Choreonoid を開きます。 Open Folder... を押して、Choreonoidのトップディレクトリを指定します。質問が表示された場合は、適宜回答してください。ここでは、 Trust the authors of all files in the parent folder '<username>' にチェックを入れて、 Yes, I trust the authors と回答しておきます。

次に launch.json を作成します。ウィンドウ左の Run and Debug (Ctrl+Shift+D) ボタンを押して、RUN AND DEBUG: RUNパネル内の create a launch json file を押します。

../../_images/07_run_and_debug.png

ウィンドウ上に表示された Select debugger のメニューから C/C++: (gdb) Launch を選択すると、launch.json ファイルが作成されます。

../../_images/09_open_json.png

作成された launch.json ファイルは Choreonoid のトップディレクトリ内に以下のように保存されます。

- choreonoid
  +- .vscode
    +- launch.json

続けて、launch.json ファイルを以下のように編集します。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build-debug/bin/choreonoid",
            "args": [ "${workspaceFolder}/sample/SimpleController/SampleCrawler.cnoid" ],
            "cwd": "${workspaceFolder}",
            "MIMode": "gdb"
        }
    ]
}

ここで、 program にはデバッグ対象とする Choreonoid の実行ファイルまでのパスを記述します。また、 args は Choreonoid を起動するときに与える引数を記述します。上の例では、先ほど説明したサンプルプロジェクトを起動時に読み込むように指定しています。

以上で、launch.json の設定は完了です。なお、ここで作成した最小限の設定に加えて、デバッグをより快適に行うための追加設定がいくつかあります。これらについては、次の「補足:デバッグの追加設定」で説明します。

補足:デバッグの追加設定

ここまでで作成した launch.json は、デバッグを行うための最小限の設定です。これに加えて、デバッグ実行時の gdb の挙動を調整しておくと、デバッグをより快適に行えるようになる場合があります。ここでは、そうした設定と、その指定方法について説明します。

設定の指定方法

デバッグ時の gdb の挙動は、主に次の方法で調整できます。設定の種類によって、適した方法や、確実に反映される方法が異なります。

  • launch.json の setupCommands に記述する : launch.json の configuration に setupCommands という項目を設けると、デバッグ開始時に gdb に対して自動的に実行させるコマンドを指定できます。この方法は、その configuration にのみ適用されます。以下のように記述します。

    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "(gdb) Launch",
                ...
                "MIMode": "gdb",
                "setupCommands": [
                    {
                        "description": "コマンドの説明",
                        "text": "実行する gdb コマンド",
                        "ignoreFailures": true
                    }
                ]
            }
        ]
    }
    

    各コマンドは text に記述し、 description にはその説明を任意で記述します。 ignoreFailurestrue にしておくと、そのコマンドが失敗してもデバッグを継続します。

  • 環境変数で指定する : gdb がプロセスの起動時に参照する設定(後述の debuginfod の取得先など)は、環境変数で制御できます。VS Code から起動される gdb は、 VS Code 自体を起動した環境(シェル)の環境変数を引き継ぎます 。このため、シェルの設定ファイル(~/.bashrc~/.profile など)に環境変数を記述し、その環境から VS Code を起動します。

  • ~/.gdbinit に記述する : ~/.gdbinit は、gdb をコマンドラインで単体起動したときに最初に読み込まれる設定ファイルです。記述した設定はシステム上のすべての gdb に適用されます。

注釈

VS Code の C/C++ 拡張機能 (cppdbg) 経由のデバッグでは、デバッガ初期化の順序の都合で、setupCommands で送ったコマンドや ~/.gdbinit に記述した設定が、効果を持つべきタイミングに間に合わないことがあります。特に、後述の debuginfod に関する設定は、これらの方法では確実には無効化できないことが報告されています。そのような設定を VS Code 上で確実に反映させたい場合は、環境変数による方法を利用してください。

以下では、設定しておくとよい代表的な項目について説明します。

初回のデバッグ実行が非常に遅い場合(debuginfod について)

何も設定していない状態で初めてデバッグ実行を行うと、Choreonoid のウィンドウが表示されるまでに非常に長い時間がかかることがあります。このとき、Visual Studio Code の DEBUG CONSOLE には、次のようなメッセージが大量に表示されます。

Downloading separate debug info for /lib/x86_64-linux-gnu/libQt6Core.so.6...
Downloading separate debug info for /lib/x86_64-linux-gnu/libstdc++.so.6...
Downloading separate debug info for /lib/x86_64-linux-gnu/libX11.so.6...
(以下、多数のライブラリについて同様のメッセージが続く)

これは、gdb が debuginfod という仕組みを使って、デバッグ対象が依存する共有ライブラリのデバッグ情報をネットワーク経由で取得している様子です。

debuginfod は、Linux ディストリビューションが提供するサーバー(Ubuntu の場合は https://debuginfod.ubuntu.com )から、ライブラリのデバッグシンボルを必要に応じてダウンロードする仕組みです。Choreonoid は Qt や標準C++ライブラリ (libstdc++)、X11 関連ライブラリなど数多くの共有ライブラリに依存しており、これらすべてについて順番にデバッグ情報を取得しようとするため、初回は大きな待ち時間が発生します。debuginfod によってシステムライブラリのデバッグ情報が揃うと、それらライブラリの内部までステップ実行したり、スタックトレースに関数名や行番号を表示したりできるようになります。

一度ダウンロードされたデバッグ情報は ~/.cache/debuginfod_client/ 以下にキャッシュされます。このため、 2回目以降のデバッグ実行ではキャッシュから読み込まれ、初回ほどの待ち時間は発生しなくなります 。これは正常な動作です。ただし、2回目以降の起動にかかる時間は一定ではなく、ほとんど待たされない場合もあれば、ある程度待たされる場合もあります。また、 apt upgrade などでライブラリが更新されると、更新された分については再びダウンロードが発生し、その回の起動は遅くなります。

通常、自分が開発している Choreonoid 本体やプラグインのコードをデバッグするだけであれば、システムライブラリ内部のデバッグ情報は必要ありません。そのような場合は、debuginfod によるダウンロードそのものを無効にしておくことで、初回から素早く起動できるようになります。

VS Code 上のデバッグで確実に無効化するには、 環境変数 ``DEBUGINFOD_URLS`` を空にする 方法を用います。debuginfod はこの変数で指定されたサーバーからデバッグ情報を取得するため、空にしておけば取得先が無くなり、ダウンロードは一切行われません。シェルの設定ファイル(~/.bashrc など)に次の記述を追加します。

export DEBUGINFOD_URLS=""

前述のとおり、VS Code から起動される gdb は VS Code 自体を起動した環境の環境変数を引き継ぎます。このため、上記を追加した後は、 設定を反映した新しい端末から ``code`` コマンドで VS Code を起動し直す 必要があります(GUI のアイコンから起動している場合は、~/.profile に記述してログインし直してください)。

gdb を単体で使う場合に限れば、 set debuginfod enabled off という設定を ~/.gdbinit や setupCommands に記述する方法でも無効化できます。ただし、前述のとおり VS Code 経由のデバッグではこれらの方法は確実には効かないため、環境変数による方法を推奨します。

なお、いずれの方法でもダウンロードが行われなくなるだけで、自分のコードのデバッグには何の支障もありません。

STLコンテナの表示と起動時間(-enable-pretty-printing について)

Visual Studio Code が launch.json を自動生成する際には、setupCommands に -enable-pretty-printing というコマンドが含まれることがあります。この設定を有効にすると、 std::vectorstd::string などのSTLコンテナが VARIABLES パネルで見やすく表示されるという利点があります。

しかし、Choreonoid のようにプラグインを多数読み込むアプリケーションでは、この設定を有効にすると起動時間が大幅に長くなり、また基底クラスの内容が変数パネルで展開できなくなるという副作用があります。このため、本ドキュメントの設定例ではこれを含めていません。STLコンテナの内容を確認したい場合は、後述する STLコンテナの内容を表示する の方法を利用することを推奨します。

以上で、launch.json の追加設定についての説明は完了です。

デバッグの実行

実際にサンプルをデバッグしてみましょう。ここでは、

  • ブレークポイントの設定

  • デバッグの開始

  • 変数の表示

  • ステップ実行

について説明します。

ブレークポイントの設定

デバッグを行うファイルを開き、ブレークポイントとする行番号左のところをクリックします。ブレークポイントが設定されると赤い●が付きます。ここでは、 sample/SimpleController/SampleCrawlerController.cpp の 40 行目にブレークポイントを設定しています。

../../_images/10_check_breakpoint.png

デバッグの開始

Debug モードで Choreonoid を起動します。

ウィンドウ左の Run and Debug (Ctrl+Shift+D) ボタンを押して、RUN AND DEBUG パネル上部の▶ボタンを押すと、launch.json の設定に従って Choreonoid が起動します。Debug モードで Choreonoid を起動している間、Visual Studio Code のウィンドウ上部には、以下のようなデバッグ用のボタン群が表示されます。

../../_images/11_button_group.png

ボタンは左から順に Continue (F5)Step Over (F10)Step Into (F11)Step Out (Shift+F11)Restart (Ctrl+Shift+F5)Stop (Shift+F5) です。

Choreonoid が起動できたら、通常どおりシミュレーションバーからシミュレーションを実行します。

シミュレーションを実行すると、ブレークポイント(40 行目)でプログラムが一時停止します。

変数の表示

プログラムがブレークポイントで停止すると、以下のような画面となり、Visual Studio Code の左側に VARIABLES パネルが表示されます。

../../_images/12_input_var.png

VARIABLES パネルには、ブレークポイントで停止した時点でアクセス可能な変数(ローカル変数や this ポインタのメンバなど)が一覧表示されます。オブジェクトは展開して中身を確認することができ、基底クラスを持つオブジェクトの場合、基底クラスもノードとして表示されてそのメンバを展開できます。

ここでは、40 行目に出てくる crawlerL の値を確認してみましょう。 crawlerLSampleCrawlerController クラスのメンバ変数なので、 this を展開した中に含まれています。VARIABLES パネルの this の左にある ▸ をクリックして展開し、さらにその中の crawlerL を展開すると、以下のように crawlerL のメンバ変数を確認することができます。この中の dq_target_ が、コード中の crawlerL->dq_target() で参照される値で、この時点では 0 となっています。

../../_images/13_show_var.png

ステップ実行

ブレークポイントで停止した状態から、プログラムを一行ずつ実行してみましょう。

Visual Studio Code のウィンドウ上部にあるデバッグ用のボタン群から Step Over (F10) を押すと、40 行目が実行された後、次の行で再度停止します。

ここで、VARIABLES パネル上で先ほどと同じ手順で crawlerLdq_target_ の値を確認してみると、その値が 1.5 となっており、40 行目の処理によって値が更新されたことがわかります。

../../_images/14_show_var.png

変数表示のヒント

以上の基本操作に加えて、より便利に変数を確認する機能や、特殊なケースでの工夫について説明します。

WATCH パネルを利用する

特定の式の値を継続的に監視したい場合は、 WATCH パネルに式を登録しておくと便利です。WATCH パネルの + ボタンを押して式を入力すると、プログラムが停止するたびにその式が評価され、値が表示されます。

例えば、先ほど VARIABLES パネルから辿って確認した crawlerLdq_target_ は、WATCH パネルに

this->crawlerL->dq_target()

と登録しておけば、アクセサ関数 dq_target() の戻り値として直接表示させることができます。毎回 VARIABLES パネルで this を展開して辿る必要がなくなり、頻繁に監視したい値がある場合に効果的です。

特に、以下で説明するような VARIABLES パネルでは直接確認しにくい値についても、WATCH パネルを活用することで簡単に参照できるようになります。

STLコンテナの内容を表示する

-enable-pretty-printing を有効にしていない場合、std::stringstd::vector などのSTLコンテナは内部構造のまま表示され、VARIABLES パネルで展開しても中身が直感的には読めません。これらは WATCH パネルに以下のような式を登録することで、内容を確認することができます。

std::string の文字列内容を表示する例

str.c_str()

std::vector の全要素を表示する例(gdbの配列表示記法を利用)

vec._M_impl._M_start@vec.size()

std::shared_ptrstd::unique_ptr の指す先を表示する例

ptr.get()

Pimpl イディオムで実装されたクラスのメンバ

Choreonoid のいくつかのクラスは、実装の詳細を隠蔽するために Pimpl イディオムを採用しており、メンバ変数を Impl クラスに分離しています。 Impl クラスはヘッダファイルでは前方宣言のみ行われ、実装は .cpp ファイル内で定義されているため、他の翻訳単位からは Impl の型情報が見えず、変数パネルでは impl ポインタを展開しても中身を確認できません。

この制約は、デバッグ情報の形式(Linux では DWARF が翻訳単位ごとに分かれている)に起因するもので、デバッガを変えても解消しません。対処方法としては、以下のいずれかがあります。

  • 該当クラスの .cpp ファイル内にブレークポイントを置き、そこで停止させて変数を確認する

  • クラスがアクセサ関数を提供している場合、 WATCH パネルにその関数呼び出しを登録する(例: item->name()

オブジェクトのアクセサ関数を呼び出す

WATCH パネルに登録する式では、デバッグ対象のプロセス内で関数を呼び出して結果を表示することもできます。これを利用して、オブジェクトの状態をアクセサ関数経由で確認することができます。

item->name()
item->filePath()
body->link(0)->p()

ただし、呼び出す関数に副作用がある場合(メンバを変更する、ロックを取る、例外を投げるなど)は、デバッグ対象のプロセス状態に影響を与える可能性があるため注意が必要です。基本的には状態を変更しない const な関数のみを呼び出すのが安全です。