ArduPilot シミュレーターを gdb でデバッグする方法 (中編)

Stabilize モードを gdb でデバッグするというシナリオに沿って、よく利用する gdb コマンドを紹介します。Stabilize モードはプロポの操作通りに動く非常にシンプルなモードなので、飛行モードのコード解析をしたい人は最初に見ることをお勧めします。

今回は、ArduCopter の Stabilize モードである stabilize_run() という関数の解析を行っていきます。

Stabilize モードのソースコード
https://github.com/ArduPilot/ardupilot/blob/master/ArduCopter/control_stabilize.cpp

※ 本記事は gdb の使い方の説明を目的にしており、Stabilize モードの解説には焦点をあてていません。

gdb の概要は前編に記載しています。

 

解析の流れ

これから行う解析の、ざっくりとした流れをご紹介します。

1. stabilize_run() 関数でブレークする

2. スタックを表示し、stabilize_run() 関数が呼び出されるまでの流れを確認する

3. ステップ実行により、ソースコードの行単位で順番に処理を見る

4. 変数の値を参照する

ブレークしてステップ実行しつつ変数の値を確認するという流れはデバッグの基本となります。

 

紹介する gdb コマンド一覧

下記はこれから紹介する gdb コマンド一覧です。(登場順)

説明 コマンド 短縮コマンド
ブレークポイントの設定 break b
継続実行 (続行) continue c
スタックバックトレースの確認
(関数呼び出し履歴)
backtrace bt
ソースコードの表示 layout src la src
ソースコード上の次の行まで実行
(ステップ オーバー)
next n
変数の参照、設定 print p
ステップ実行ごとに変数を参照 display d
ブレークポイントの一覧を表示 info breakpoints i b
ブレークポイントの削除 delete d
ブレークポイントの無効化 disable disab
ブレークポイントの有効化 enable ena
ソースコード上の次の行まで実行
実行対象が関数の場合には関数に入る
(ステップ イン)
step s
現在の関数が終了するまで実行
(ステップ アウト)
finish fin
gdb の終了 quit q

短縮コマンドは慣れれば非常に早く打てるようになります。
本記事では分かりやすさの観点から短縮せずに記載します。

 

解析手順

1. SITL を gdb にアタッチした状態で起動

ArduCopter のディレクトリに移動して、-D と -G オプションを付けて SITL を起動します。
sim_vehicle.py --console -D -G
SITL の実行環境構築については ArduPilot 入門第2回をご参照ください。

2. 割り込みを発生させる

gdb コンソール上で「Ctrl + c」(Ctrl キーを押しながら c を押す) によりプログラムの処理を中断して、gdb のコマンドを入力できる状態にします。

3. ブレークポイントの設定

Stabilize モードの関数が呼び出されるタイミングでブレークポイントを設定します。
break Copter::stabilize_run()

gdb は Tab 補完ができるので活用していきましょう。
例えば「break Copter::stab」まで入力している状態で Tab キーを押してみてください。

「break Copter::stab」⇒「break Copter::stabilize_」

上記のように補完されます。run() まで補完されなかったのは stabilize_ から始まる関数が他にもあったからです。
Tab キーを2回押してみましょう。

(gdb) break Copter::stabilize_
stabilize_init(bool) stabilize_run()

stabilize_init と stabilize_run が候補として表示されました。
「break Copter::stabilize_r」まで入力して、Tab キーを利用すると完全に補完してくれます。

4. プログラムの実行

ブレークポイントに到達するまでプログラムを実行します。
continue

stabilize_run は繰り返し何度も呼ばれるため、continue した後すぐにブレークします。

5. スタックを確認

backtrace

(gdb) backtrace
#0 Copter::stabilize_run (this=0x7ab6e0 ) at ../../ArduCopter/control_stabilize.cpp:24
#1 0x0000000000430249 in Copter::update_flight_mode (this=0x7ab6e0 ) at ../../ArduCopter/flight_mode.cpp:177
#2 0x00000000004060bb in Copter::fast_loop (this=0x7ab6e0 ) at ../../ArduCopter/ArduCopter.cpp:281
#3 0x0000000000405ffd in Copter::loop (this=0x7ab6e0 ) at ../../ArduCopter/ArduCopter.cpp:240
#4 0x00000000004d4e56 in HAL_SITL::run (this=0x7b2e40 <AP_HAL::get_HAL()::hal>, argc=11,
argv=0x7fffffffdba8, callbacks=0x7ab6e0 ) at ../../libraries/AP_HAL_SITL/HAL_SITL_Class.cpp:87
#5 0x0000000000407149 in main (argc=11, argv=0x7fffffffdba8) at ../../ArduCopter/ArduCopter.cpp:645

スタックは下から順番に呼び出されているため、Copter::loop -> Copter::fast_loop -> Copter::update_flight_mode -> Copter::stabilize_run の順番に関数が呼び出されていることが分かります。

fast_loop 関数が ArduCopter の main のループ関数であり、1 秒間に 400 回呼び出されます。
そして、fast_loop 関数から飛行モードの制御を行うため update_flight_mode 関数が呼び出され、最終的に現在の飛行モードである stabilize_run 関数が呼び出されていることが分かります。

6. ソースコードとの同期

ここからは stabilize のソースコードを見ていきましょう。
gdb はソースファイルとの連携もできます。この機能があれば、GUI でデバッグする感覚に近くになりますね。

layout src

上記は、次に 24 行目を実行することを示します。

ソース表示モードをやめる場合には、Ctrl + x, a と順番に入力します。

7. ステップ実行

次の行に進みます。
next

if 文に到達しました。もう一度 next コマンドを入力してみましょう。

if 文の中に入りましたね。この if 文では下記をチェックしています。

!motors->armed() arming していない状態
ap.throttle_zero スロットルが 0 (スロットルを下げた状態)
!motors->get_interlock() モーターが動作していない状態

これらの条件のうちどれか一つでも true であれば、飛び立てる状態ではないので return で関数を終了しています。

8. 変数や関数の戻り値の参照

if 文の中の各変数や関数の戻り値は何だったのかを確認したい場合には print コマンドを使用します。

print <変数名>

(gdb) print !motors->armed()
$1 = true
(gdb) print ap.throttle_zero
$2 = 1 '\001'
(gdb) print !motors->get_interlock()
$3 = true

if 文の各条件はすべて true (1) であることが分かります。

[補足] print 文の便利な使い方として、 「print <変数>=<任意の値>」とすれば変数を書き換えることができます。
また、「print 関数名」と入力した場合、戻り値を表示するだけではなく関数呼び出しが行われていることに注意してください。

9. 参照設定

何度も表示する変数を毎回 print コマンドで表示するのは大変です。
display コマンドを用いると、ステップ実行ごとに変数を表示することができます。
display !motors->armed()
display ap.throttle_zero
display !motors->get_interlock()

10. ブレークポイントの無効化

コプターを飛ばしている状態で再度 break をして、変数や関数の戻り値が変化しているのかを確認してみましょう。
ここで continue を実行すると、すぐに stabilize_run 関数の先頭でブレークされてしまい、コプターを飛行状態にすることができないので、一時的にブレークポイントを無効にします。

10-1. ブレークポイントの番号を確認します。
info breakpoints

10-2. ブレークポイントを無効にします。
disable 1
※ 10-1 で確認した番号に合わせて、ブレークポイントを無効にします。

10-3. ブレークポイントが無効になっているか確認します。
info breakpoints

ここでは、ブレークポイントを無効にしましたが、ブレークポイントが不要になった場合には delete コマンドによりブレークポイントを削除することもできます。

11. コプターを飛ばしている状態にする

MAV Proxy コンソールで以下のコマンドを実行します。
arm throttle
rc 3 1700

gdb コンソールに戻り、Ctrl + c でブレークします。

12. ブレークポイントの有効化

enable 1
※ 10-1 で確認した番号に合わせて、ブレークポイントを有効にします。

13. プログラムの実行

continue を行うと、stabilize_run でブレークし、display コマンドで設定した変数や関数の戻り値が表示されることを確認します。

(gdb) continue
Continuing.

Breakpoint 1, Copter::stabilize_run (this=0x7ab6e0 ) at ../../ArduCopter/control_stabilize.cpp:24
1: !motors->armed() = false
2: ap.throttle_zero = 0 '\000'
3: !motors->get_interlock() = false

全て false (0) の状態になりました。
next を実行していけば、先ほどの if 文には入らないことを確認できます。

さて、ここからが、stabilize モードの中心部分のコードになります。
ただ、このペースでこの後のコードも説明すると、とんでもなく長い記事になってしまうので、この先は皆さんで解析してみてください。

14. 関数の中を参照

関数の中を参照したいときには「step」コマンドを、関数を抜け出したところまで進めたいときには「finish」コマンドを利用します。

15. gdb の終了

quit

コマンドの紹介は以上で終了です。
gdb には今回説明したコマンド以外にも便利なコマンドが様々あります。
「gdb コマンド」などの検索キーワードで調べると多くの情報がヒットするので気になる方は調べてみてください。

また、ArduPilot のソースコードを解析するためには、基本的な構造や各種ライブラリを把握しておく必要があります。それらの情報は Wiki にまとまっていますので、是非ご参考ください。

 

参考情報

Debugging with GDB
http://ardupilot.org/dev/docs/debugging-with-gdb.html#debugging-with-gdb

GDB Cheat Sheet
http://darkdust.net/files/GDB%20Cheat%20Sheet.pdf

GDB 英語マニュアル
https://sourceware.org/gdb/current/onlinedocs/gdb/

非公式ですが日本語に翻訳されたマニュアルもあります (GDB version 5.0 を翻訳)
http://www.asahi-net.or.jp/~wg5k-ickw/html/online/gdb-5.0/gdb-ja_toc.html

Learning ArduPilot — Introduction
http://ardupilot.org/dev/docs/learning-ardupilot-introduction.html

 

 

著者紹介

yamaguchi_picture山口達也
ドローンソフトウェアエンジニア養成塾 第1期卒業生。

記事の誤りを見つけた方は下記のメールアドレスまでご連絡ください。
techblog@drone-j.com
(週末にメールを確認していますので、対応が遅くなること、休みの日にメールを返信させていただくこと、ご了承ください。)