デバッグ

既にデバッグセッションの中に居ます。プログラムをデバッグしてみましょう。

loadコマンドの後、プログラムは、エントリポイントで停止しています。このことは、GDB出力の「Start address 0x8000XXX」という部分からわかります。 エントリポイントは、プロセッサ / CPUが最初に実行するプログラムの一部です。

スタータープロジェクトでは、main関数の前に実行する追加のコードを提供しています。 ここでは、「mainの前の」部分には興味がないので、main関数の直前までスキップします。ブレイクポイントを使って、これができます。

(gdb) break main
Breakpoint 1 at 0x800018c: file src/05-led-roulette/src/main.rs, line 10.

(gdb) continue
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10
10          let x = 42;

ブレイクポイントは、プログラムの通常フローを停止するために使います。continueコマンドは、ブレイクポイントに到達するまでプログラムを実行します。 今回の場合、main関数に到達するまでです。なぜなら、そこにブレイクポイントがあるからです。

GDBが「Breakpoint 1」と出力していることに留意して下さい。今回のプロセッサでは、6個のブレイクポイントしか使えないことを思い出して下さい。 これらのメッセージに注意を払うことは大事なことです。

より良いデバッグ経験のために、GDBのテキストユーザーインタフェース(TUI)を使います。 このモードに入るには、次のコマンドをGDBシェルに入力します。

(gdb) layout src

注記 Windowsユーザーの方はごめんなさい。GNU ARM Embedded Toolchainで配布されているGDBはTUIモードをサポートしていません:-(

GDB session

次のコマンドで、いつでもTUIモードから抜けることができます。

(gdb) tui disable

今、mainの最初に居ます。stepコマンドを使って、プログラムをステートメントごとに実行することができます。 _y = xステートメントに到達するために、コマンドを2回使います。一度、stepを入力すると、エンターを押すだけで、同じコマンドを再び実行できます。

(gdb) step
14           _y = x;

TUIモードを使っていない場合、各stepを呼ぶごとに、GDBは現在のステートメントを行番号と一緒に出力します。

現在、_y = xステートメントの「上」に居て、まだこのステートメントは実行されていません。 つまり、xは初期化されていますが、_yは初期化されていません。printコマンドを使って、スタック/ローカルな変数を調べてみましょう。

(gdb) print x
$1 = 42

(gdb) print &x
$2 = (i32 *) 0x10001ff4

(gdb) print _y
$3 = -536810104

(gdb) print &_y
$4 = (i32 *) 0x10001ff0

予想通り、x42という値を格納しています。しかし、_yは、-536810104 (?)という値を格納しています。 _yは、まだ初期化されていないため、ゴミが入っています。

print &xコマンドは、変数xのアドレスを出力します。ここで興味深いことは、GDBが参照の型を出力することです。i32*は、i32のポインタ値です。 他におもしろい点は、x_yのアドレスがお互いに非常に近いことです。これらのアドレスは、ちょうど4バイトだけ離れています。

ローカル変数を1つずつ出力する代わりに、info localsコマンドを使うこともできます。

(gdb) info locals
x = 42
_y = -536810104

では、もう一度stepを実行すると、loop {}ステートメントに到達します。

(gdb) step
17          loop {}

そして、_yは初期化されているはずです。

(gdb) print _y
$5 = 42

loop {}ステートメント上で再度stepを使うと、プログラムがそのステートメントを抜けることがないため、動けなくなります。 代わりに、layout asmコマンドで逆アセンブル画面に切り替えます。その上で、stepiを使って、1命令ずつ前に進めます。 layout srcコマンドを使うことで、いつでもRustソースコード画面に戻ることができます。

注記 間違ってstepを使ってしまい、GDBが動かなくなった場合、Ctrl+Cを打つことで、動けるようになります。

(gdb) layout asm

GDB session

TUIモードを使っていない場合、disassemble /mコマンドを使うことで、現在実行中の行周辺のプログラムを逆アセンブルできます。

(gdb) disassemble /m
Dump of assembler code for function main:
7       #[entry]
   0x08000188 <+0>:     sub     sp, #8
   0x0800018a <+2>:     movs    r0, #42 ; 0x2a

8       fn main() -> ! {
9           let _y;
10          let x = 42;
   0x0800018c <+4>:     str     r0, [sp, #4]

11          _y = x;
   0x0800018e <+6>:     ldr     r0, [sp, #4]
   0x08000190 <+8>:     str     r0, [sp, #0]

12
13          // 無限ループ。このスタックフレームから抜けないためのものです。
14          loop {}
=> 0x08000192 <+10>:    b.n     0x8000194 <main+12>
   0x08000194 <+12>:    b.n     0x8000194 <main+12>

End of assembler dump.

左側にある太矢印=>が見えますか?これは、プロセッサが次に実行する命令を示しています。

TUIモードではない場合、各stepiコマンドにより、GDBはステートメントと、行番号、およびプロセッサが次に実行する命令のアドレスを表示します。

(gdb) stepi
0x08000194      14          loop {}

(gdb) stepi
0x08000194      14          loop {}

さらに興味深いものに行く前に、最後のトリックがあります。GDBに次のコマンドを入力して下さい。

(gdb) monitor reset halt
Unable to match requested speed 1000 kHz, using 950 kHz
Unable to match requested speed 1000 kHz, using 950 kHz
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000196 msp: 0x10002000

(gdb) continue
Continuing.

Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10
10          let x = 42;

mainの最初に戻ってきます!

monitor reset haltは、マイクロコントローラをリセットし、プログラムのエントリポイントで停止します。 続くcontinueコマンドは、ブレイクポイントがあるmain関数に到達するまで、プログラムを実行します。

このコンボは、間違って調査の対象とするプログラムの一部をスキップしてしまったときに便利です。 プログラムの状態を、最初の状態に、簡単にロールバックすることができます。

ただし書きresetコマンドは、RAMをクリアしたり、触ったりしません。メモリは、前回実行した時の値を持ち続けています。 プログラムが、未定義動作の定義である初期化されていない変数の値に依存しない限り、このことは問題になりません。

このデバッグセッションは完了です。quitコマンドでデバッグセッションを終了できます。

(gdb) quit
A debugging session is active.

        Inferior 1 [Remote target] will be detached.

Quit anyway? (y or n) y
Detaching from program: $PWD/target/thumbv7em-none-eabihf/debug/led-roulette, Remote target
Ending remote debugging.

注記 デフォルトのGDBコマンドラインインタフェースが好みでない場合、gdb-dashboardを確認して下さい。 このツールは、Pythonを使用して、デフォルトのGDBコマンドラインインタフェースを、レジスタやソースコード、アセンブリなどを表示するダッシュボードに変換します。

ただし、OpenOCDは、終了しないで下さい!OpenOCDは、この後繰り返し使用します。 動作したままにしておくほうが良いです。

次は何でしょうか?約束した高レベルのAPIです。