GDBを使用したデバッグ手法(基本編)

C++やC言語で開発したプログラムをGDBでデバッグする方法をご紹介したいと思います。

GDBはプログラムの不具合解析や一時的に値を変更して動作をテストする際に使用するツールです。
組込ソフト開発ではLinux環境での開発が多く、Window環境で用意されているような統合開発環境(IDE)が使用できないケースが多くあります。
そのような場合のデバッグ方法としてGDBは強力なデバッグツールとなり得ます。

GDBでできることは以下の4点です。(GDB公式マニュアルの記載より)

●ユーザ・プログラムをその動作に影響を与える可能性のある様々なことを指定して起動する
●指定された条件が成立したときにユーザ・プログラムを停止する
●停止したときにユーザ・プログラムが何を行っていたかを調べる
●ユーザ・プログラムの内部を変更することによって、1つのバグの影響を試験的に修正して、ほかのバグについて調べる

 

GDBの起動と終了方法

GDBの起動方法

linux等のコンソールより以下のコマンド入力で起動します。

 

GDBの終了方法

以下のコマンドを入力、またはCtrl+dキー押下(C-d)で終了します。

 

GDBを利用してプログラムをデバッグするには

実際にデバッグするにはプログラムにアタッチする必要があります。
ここで対象となるプログラムはデバッグ情報を持つプログラム(gccで-gオプションを付加してビルドした実行ファイル)を指定します。
(デバッグ情報を持たないプログラムにもアタッチできるが、ソースコードやシンボルの定義名が参照できない)
また、アタッチ方法は起動時に指定する方法と起動後に指定する方法の2通りあって、それぞれ手順が異なります。
前者はプログラムの出力とGDBのI/Oバッファが混在して表示されるため、見難いですが起動直後のデバッグが可能です。
後者はプログラムとGDBを異なる端末から起動できるため出力が混在することがありません。

GDB起動時にプログラムを指定する方法

1. 以下のコマンドを入力。(programは対象となる実行ファイル)

 

GDB起動後にプログラムをアタッチする方法

1. 端末Aからプログラムを実行

2. 端末BからGDBを実行

3. gdbのコンソールにてプログラムにアタッチする

シンボルのロードが完了したらcontinue

 

 

基本的な使い方

問題のあるプログラムを調査する際、プログラムを途中で停止させて変数の値を調べたり問題が発生するパスを調査したりします。
この章ではプログラムの停止から再開、ステップ実行、コールスタックの調査等の基本的なデバッグ手順について記載します。

ブレイクポイントの指定

プログラムの停止は、ユーザーが指定した位置で停止する場合と、シグナルの受信による停止等がありますが、ここではユーザーが意図的に停止したい位置を指定する方法について記載します。
特定の関数がコールされるタイミングが知りたいなど、関数でブレイクさせたい場合はbreakコマンドにより関数を指定したブレイクポイントが指定可能です。

関数functionのエントリにブレイクポイントを設定します。
更に細かい特定のラインでブレイクさせたい場合はファイルの特定の行を指定したブレイクポイントが以下のコマンドで指定可能です。

filenameで指定されるソースファイルのlinenumで指定される番号の行に、 ブレイクポイントを設定します。
GDBはCUIによるコマンド入力が基本となりますが、Eclipse等のGUIベースのデバッグ環境では、画面からソースコードを選択することによりブレイクポイントを設定することが可能です。以降、GUIの例はEclipseのものを使用しています。EclipseはCUIに比べ動きがもっさりであまり使う機会はないですが。。。
GUI(Eclipse)でブレイクポイントを設定した場合の例
gdb01

ブレイクポイントの確認

設定したブレイクポイントの確認を行います。ブレイクポイント番号はブレイクポイントの削除や有効/無効の設定に利用します。

出力例は以下

列の意味は以下のようになっています。

表記 説明
Num ブレイクポイント番号
Type ブレイクポイント、ウォッチポイント、または、キャッチポイント
Disp ブレイクポイントに次に到達したときに、無効化または削除されるべくマークされているか否かを示します。
End 有効なブレイクポイントをy'、有効でないブレイクポイントをn’で示します。
Address ユーザ・プログラム内のブレイクポイントの位置をメモリ・アドレスとして示します。
What ユーザ・プログラムのソースコード内におけるブレイクポイントの位置を、ファイル名および行番号で示します。

GUIでブレイクポイントの一覧を表示した例は以下
gdb02

ブレイクポイントの削除

不要となったブレイクポイントはdeleteコマンドにより削除することが出来ます。また、clearコマンドでもbreakポイントを設定した時の関数等を指定することにより削除することが出来ます。
ブレイクポイント番号を指定して削除する場合は

引数が指定されない場合、 すべてのブレイクポイントを削除します。

関数等を指定して削除する場合は

 

ブレイクポイントの無効化

一時的にプログラムを停止させたくない場合にブレイクポイントを削除せず無効にすることが可能です。
ブレイクポイントを無効に設定するには

指定されたブレイクポイントを無効化します。 番号が1つも指定されない場合は、すべてのブレイクポイントが無効化されます。

ブレイクポイントを再度有効に設定するには

指定されたブレイクポイント(または、すべての定義済みブレイクポイント)を有効化します。
GUIでブレイクポイントの有効/無効を設定する例は以下
gdb03

継続実行

停止したプログラムを再開するにはcontinueコマンドを使用します。継続実行はプログラムが終了または再度ブレイクポイントに到達するまで停止しません。

「c」はcontinueコマンドと同義です。

ステップ実行

ステップ実行は処理を1行ずつ実行することができます。問題の原因を解析する際に、1ステップずつ値の変化を確認するのに役立ちます。
ステップ実行には関数の呼出しが発生した場合に関数に入るステップ実行のコマンドとその関数を停止することなく最後まで実行するステップ実行のコマンドが存在します。
関数の呼出しが発生した場合に対象の関数に入るには

異なるソースファイル行に到達するまでユーザ・プログラムを継続実行した後、 プログラムを停止させ、GDBに制御を戻します。このコマンドの省略形は「s」です。

・stepコマンドのイメージ
gdb04
対象の関数を最後まで実行させるには(関数に入らないようにするには)

カレントな (最下位の) スタック・フレーム上において、ソースコード上の次の行まで実行します。 これはstepコマンドと似ていますが、nextコマンドは、ソースコード上に関数呼び出しが存在すると、その関数を停止することなく最後まで実行します。このコマンドの省略形は「n」です。

・nextコマンドのイメージ
gdb05
現在の関数を最後まで実行させるには

選択されているスタック・フレーム上の関数が復帰するまで、実行を継続します。戻り値があれば、それを表示します。このコマンドの省略型は「fin」です。

・finishコマンドのイメージ
gdb06
nextコマンドやstepコマンドはループの処理をステップ実行した場合に、ループが終了するまで複数回ステップ実行する必要がある。ループを抜けた次の行にブレイクポイントを設定しそこまで継続実行させ抜けることもできるが、コマンドによってループを抜けることもできる。
ループを抜けるには

カレントなスタック・フレーム上において、カレント行よりも後ろにある行に到達するまで実行を継続します。このコマンドの省略型は「u」です。

・untilコマンドのイメージ
gdb07
GUIではコマンドを入力するステップ実行の代わりにボタン操作でステップ実行を行う。
各ステップ実行ボタンは以下。(なお、以下の例ではuntilコマンドに該当するボタンは存在しないがEclipseのplug-inにより追加されるかもしれない)
gdb08

コールスタックの確認

プログラムが停止した時にどこから呼ばれたのか(どこを通ってきたのか)を調べるときにバックトレースを使用します。バックトレースは現在の位置(カレントフレーム)までの経路をフレーム単位で出力します。
コールスタックの出力例

 

ソースコードの表示

プログラムの停止位置がソースコードのどこで停止しているか知りたい場合、ソースコードを表示したくなることがあるかと思います。listコマンドを使用することにより停止位置の前後のソースコードを表示することが可能です。

ソースコードの続きを表示します。 既に表示された最後の行がlistコマンドによって表示されたのであれば、その最後の行の次の行以降が表示されます。

現在のソースコードの行番号linenumを中心に、その前後の行を表示します。
 

値の参照

 

データの参照

変数の値を調べる通常の方法はprintコマンドを使用します。printコマンドは省略型で「p」です。

expは(プログラムの記述言語による)式です。 デフォルトでは、expの値はexpのデータ型にとって適切な形式で表示されます。
メモリ上のデータを参照する場合はxコマンドを使用します。ユーザ・プログラム内のデータ型にかかわらず、メモリ上の値をいくつかの形式で調べることができます。

 

出力フォーマット

数値を16進数や10進数で表示するためにはprintコマンドに出力フォーマットを指定します。
/fに出力フォーマット形式を指定することで出力フォーマットに対応した表示を行うことができます。

指定できる出力フォーマットは以下

指定子 説明
x 値を整数値とみなし、16進で表示します。
d 値を符号付き10進の整数値として表示します。
u 値を符号なし10進の整数値として表示します。
o 値を8進の整数値として表示します。
t 値を2進の整数値として表示します。`t’はtwoを省略したものです。
a 値を16進の絶対アドレスおよび、そのアドレスより前にあるシンボルのうち最も近い位置にあるものからのオフセット・アドレスとして表示します。
c 値を整数値とみなし、文字定数として表示します。
f 値を浮動小数点数値とみなし、典型的な浮動小数点の構文で出力します。

 

配列表示

メモリ内の連続的に配置されている同一型のオブジェクトを表示したい場合、2項演算子の「@」を使用することによって配列として表示することが出来ます。
以下のサンプルソースは変数arrayに値を設定しています。printコマンドを使用して通常の表示を行った場合は先頭の値のみ表示され、それ以降に設定された値までは表示されません。

変数arrayを通常の表示と配列で表示した例。

 

 

基本編のまとめ

以上が基本的なGDBの使い方となります。応用的なブレイクの張り方やマルチスレッドのデバッグ方法などの応用編はまた別の機会にまとめたいと思います。
Unix系のシステムでC++やC言語で開発する機会がありましたら、GDBはフリーで高機能のデバッガですので是非試してみてください。その際にこちらの記事が参考となりましたら幸いです。

【応用編へ】
GDBを使用したデバッグ手法(応用編)

オリエンタルインフォーメイションサービス(OIS)