gcc+gdbによるプログラムのデバッグ 第1回 ステップ実行、変数の操作、ブレークポイント
作成していたプログラムを実行した際に、OSが謎のメッセージを残してプログラムが異常終了しました。皆さんはこのような時にどうしますか?
- あきらめる
- 最初から作り直す
- ソースコードを読んで、怪しい部分を探す
- デバッグ用のコードを大量に埋め込んで、そこから探す
アプローチは人それぞれでしょうが、上記の方法では効率が良くない場合があります。
ここで「デバッガ」というものを使用すると、デバッグ作業の負担をかなり軽くすることができます。今回は、デバッガのうち最も有名であろう「gdb」というデバッガを紹介します。
gccとgdbを用いたプログラムの開発
Section titled “gccとgdbを用いたプログラムの開発”gcc は Gnu Compiler Collection の略称で、様々な言語を処理可能なコンパイラです。基本的にはC言語のコンパイルに使用されるでしょうが、C++やFORTRANなどの言語もコンパイル可能になっています。
gdb は Gnu DeBugger の略称で、gcc(もしくは互換のコンパイラ)でコンパイルしたプログラムのデバッグを支援します。gcc以外のコンパイラでコンパイルしたプログラムもデバッグできますが、かなり機能は制限されてしまいます。
今回はコンパイラにgccを使用したC言語のプログラム開発で、gdbでそのデバッグを助ける方法を紹介します。
gccとgdbを使用する利点
Section titled “gccとgdbを使用する利点”C言語を開発する場合は様々な開発環境がありますが、gccとgdbを使うと次のような利点があります。
- 様々なOSでプログラムを開発できる
- オープンソースなので、お金を掛けずにプログラムの開発ができる
- 様々な環境で動作可能なプログラムを開発できる
- 簡単な方法でデバッグ可能
- 使いこなすと、OSやデーモンなどのデバッグといった複雑なこともできる
プログラムのコンパイル
Section titled “プログラムのコンパイル”gdbを使用してデバッグする場合、コンパイル時には以下のオプション「-g -O0」をつけてコンパイルする必要があります。
gcc -g -O0 <ソースファイル名>これらのオプションには、以下のような意味があります。
| オプション | 意味 |
|---|---|
| -g | ファイルにデバッグ情報を付加する。これがないとデバッグ時に変数名や行番号が表示されない |
| -O0 | 最適化を行わない。最適化を行うと、コードの入れ替えや削除が行われてしまい、デバッグしにくくなる |
実際の運用上では「-g -O2」というオプションをつけるのが普通ですが、-O2は強い最適化を施すオプションであり、慣れるまではデバッグが大変になってしまいます。
今回は「-g -O0」を必ずつけてコンパイルするようにしてください。必要ならば「-Wall」などのオプションを加えても構いません。
ステップ実行
Section titled “ステップ実行”簡単なバブルソートのプログラムをトレースしてみましょう。
#include <stdio.h>
void printarray(int *,int);void sort(int *,int);void swap(int *,int *);
/* program entry */int main(int argc, char **argv) { int array[4] = {4, 1, 3, 2}; printarray(&array[0], 4); sort(array, 4); printarray(&array[0], 4); return 0;}
/* printout */void printarray(int *array, int length) { int i; for (i = 0; i < length; i++) { printf("%d ", array[i]); } printf("\n");}
/* bubble sort */void sort(int *array, int length) { int i, j; for (i = 0; i < length - 1; i++) { for (j = 0; j < length - i - 1; j++) { if (array[j] > array[j+1]) { swap(&array[j], &array[j+1]); } } }}
/* swap a <=> b */void swap(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp;}このファイルをコンパイルします。
gcc -g -O0 bubblesort.c実際に実行してみましょう。
$ ./a.out4 1 3 21 2 3 4配列 4 1 3 2 がソートされていることが確認できます。
基本的な操作を覚えるまで、バグの無いプログラムで話を進めます。
まずは next コマンドでステップアウト実行を行います。
最初に、先ほどコンパイルして作成された a.out ファイルを gdb 上で実行してみましょう。
$ gdb a.outGNU gdb Red Hat Linux (5.3.90-0.20030710.41rh)Copyright 2003 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".上のライセンス情報が表示された後、**(gdb)**というプロンプトが出現します。
そこで、次のコマンドを実行してください。
break mainrun上の2行は、「main関数に入るまで(break main)実行する(run)」という意味を表しています。
実際に実行すると、次のように表示されるはずです。
(gdb) break mainBreakpoint 1 at 0x8048358: file bubblesort.c, line 9.(gdb) runStarting program: /***/a.outさて、runをした直後に次のようなメッセージが表示されたでしょうか。
Breakpoint 1, main (argc=1, argv=0xbfeceb74) at bubblesort.c:99 int array[4] = {4, 1, 3, 2};(gdb)これはブレークポイントと呼ばれるものです。
先ほどの「break main」というコマンドで指定した「main関数に入るまで」という条件を満たしたため、プログラムを一時停止して、デバッグ用のコマンドを受け付けています。
ブレークポイントについては後ほど詳しく説明します。
さて、次は next コマンドを使用して、プログラムの流れを追いましょう。
そのまま next とタイプし、ENTERキーを押します。
(gdb) next10 printarray(&array[0], 4);(gdb)すると、今まで
9 int array[4] = {4, 1, 3, 2};のように表示されていたものが、
10 printarray(&array[0], 4);という具合に、次の行になりました。
続けて next コマンドを実行しましょう。
(gdb) next4 1 3 211 sort(array, 4);(gdb)先ほどの行に表示されていた printarray(&array[0], 4); を実行し、その結果として
4 1 3 2と表示しています。
次々とnextコマンドでステップ実行を行います。
(gdb) next12 printarray(&array[0], 4);(gdb) next1 2 3 413 return 0;(gdb) next14 }これ以降には有用な情報が無いため、 continue コマンドで一気に実行します。
(gdb) continueContinuing.
Program exited normally.最後に、quit コマンドでgdbを終了させましょう。
(gdb) quitnextコマンドでは、関数の呼び出し時に、呼び出し先の内部に渡ってトレースすることができませんでした。
そこで、ステップイン実行と呼ばれるタイプのstepコマンドを使用して、関数の内部をトレースします。
先ほどと同様に、main関数まで実行します。
$ gcc -g -O0 bubblesort.c$ gdb a.out...
(gdb) break mainBreakpoint 1 at 0x8048358: file bubblesort.c, line 9.(gdb) runStarting program: /***/a.outブレークポイントにヒットした後、ステップ実行を行います。
最初は関数呼び出しではありませんので、stepでもnextでも同じです。
Breakpoint 1, main (argc=1, argv=0xbfe93554) at bubblesort.c:99 int array[4] = {4, 1, 3, 2};(gdb) next次に、printarray関数の中身をトレースしてみましょう。
stepコマンドで中に入ります。
10 printarray(&array[0], 4);(gdb) stepstepコマンドの直後は、他の関数にジャンプしたことが表示されます。
printarray (array=0xbfe934b0, length=4) at bubblesort.c:19実引数の情報もこの時点で表示されます。
次々とstepコマンドを入力します。
19 for (i = 0; i < length; i++) {(gdb) step20 printf("%d ", array[i]);(gdb) step19 for (i = 0; i < length; i++) {(gdb) step20 printf("%d ", array[i]);(gdb) step19 for (i = 0; i < length; i++) {(gdb) step20 printf("%d ", array[i]);(gdb) step19 for (i = 0; i < length; i++) {(gdb) step20 printf("%d ", array[i]);(gdb) step19 for (i = 0; i < length; i++) {(gdb) stepprintfは標準関数であるため、中身をトレースしません。また、この時点ではprintfの出力が行われませんが、これは出力前にバッファに格納されるからです。
最後に、改行を出力するとバッファがフラッシュされてコンソールに表示されます。
22 printf("\n");(gdb) step4 1 3 223 }(gdb) stepmain関数に戻ってきたので、continueコマンドで最後まで一気に実行しましょう。
main (argc=1, argv=0xbfe93554) at bubblesort.c:1111 sort(array, 4);(gdb) continueContinuing.1 2 3 4
Program exited normally.quitコマンドでgdbを終了します。
(gdb) quitソースプログラムの表示
Section titled “ソースプログラムの表示”step実行中に、現在のソースコードの位置を知りたい場合があるかもしれません。
その場合は、list コマンドを使用します。
とりあえず、先ほどのプログラム(bubblesort.c)で、sort関数の中に入ります。
$ gcc -g -O0 bubblesort.c$ gdb a.out...
(gdb) break mainBreakpoint 1 at 0x8048358: file bubblesort.c, line 9.(gdb) runStarting program: /***/a.out
Breakpoint 1, main (argc=1, argv=0xbff26cb4) at bubblesort.c:99 int array[4] = {4, 1, 3, 2};(gdb) next10 printarray(&array[0], 4);(gdb) next4 1 3 211 sort(array, 4);(gdb) stepsort (array=0xbff26c10, length=4) at bubblesort.c:2828 for (i = 0; i < length - 1; i++) {(gdb) step29 for (j = 0; j < length - i - 1; j++) {(gdb) step30 if (array[j] > array[j+1]) {(gdb) stepこの状態でlistコマンドを実行すると、以下のように現在の前後の行が表示されます。
31 swap(&array[j], &array[j+1]);(gdb) list26 void sort(int *array, int length) {27 int i, j;28 for (i = 0; i < length - 1; i++) {29 for (j = 0; j < length - i - 1; j++) {30 if (array[j] > array[j+1]) {31 swap(&array[j], &array[j+1]);32 }33 }34 }35 }変数の取り扱い
Section titled “変数の取り扱い”プログラムをデバッグする際、重要となるのが「ある時点での変数の値」です。ここでは、変数の取り扱いについて調査します。
変数の内容を表示
Section titled “変数の内容を表示”変数の内容を表示する際、やってしまいがちなのが以下のようなプログラムです。
/* bubble sort */void sort(int *array, int length) { int i, j; for (i = 0; i < length - 1; i++) { for (j = 0; j < length - i - 1; j++) { /* この時点のarrayの内容を表示 */ printf("i=%d, j=%d, array[j]=%d, array[j+1]=%d\n", i, j, array[j], array[j+1]); if (array[j] > array[j+1]) { swap(&array[j], &array[j+1]); } } }}実行してみると、次のように表示されます。
$ gcc -g -O0 machine_gun.c$ ./a.out4 1 3 2i=0, j=0, array[j]=4, array[j+1]=1i=0, j=1, array[j]=4, array[j+1]=3i=0, j=2, array[j]=4, array[j+1]=2i=1, j=0, array[j]=1, array[j+1]=3i=1, j=1, array[j]=3, array[j+1]=2i=2, j=0, array[j]=1, array[j+1]=21 2 3 4小さなプログラムであればこの方法で問題ないかもしれませんが、コンパイルにとても時間が掛かるプログラムなどでこれを行う場合は非常に効率が悪いです。
そこで、gdbを利用して変数の内容を表示させてみましょう。
とりあえずgdbを起動し、好きな場所までステップ実行をします。
$ gcc -g -O0 bubblesort.c$ gdb a.out...
(gdb) break mainBreakpoint 1 at 0x8048358: file bubblesort.c, line 9.(gdb) runStarting program: /***/a.out
Breakpoint 1, main (argc=1, argv=0xbfec9bc4) at bubblesort.c:99 int array[4] = {4, 1, 3, 2};(gdb) step10 printarray(&array[0], 4);(gdb) next4 1 3 211 sort(array, 4);(gdb) stepsort (array=0xbfec9b20, length=4) at bubblesort.c:2828 for (i = 0; i < length - 1; i++) {(gdb) step29 for (j = 0; j < length - i - 1; j++) {(gdb) step30 if (array[j] > array[j+1]) {(gdb) step31 swap(&array[j], &array[j+1]);(gdb) next29 for (j = 0; j < length - i - 1; j++) {(gdb) step30 if (array[j] > array[j+1]) {(gdb) step31 swap(&array[j], &array[j+1]);printコマンドによって、その時点での変数の中身を調査することができます。
(gdb) print i$1 = 0(gdb) print j$2 = 1(gdb) print array[j]$3 = 4(gdb) print array[j+1]$4 = 3whatisコマンドや、ptypeコマンドを使用することによって、その変数の型を調べることができます。
(gdb) whatis arraytype = int *(gdb) ptype arraytype = int *array変数はもともと長さ4の配列なので、次のようなコマンドで一気に中身を表示させることもできます。
(gdb) print *(int [4]*)array$5 = {1, 4, 3, 2}printコマンドは、引数としてC言語で使用される式を取ります。そのため、以下のような計算式などを実行することもできます。
(gdb) print 5 * 4 * 3 * 2 * 1$2 = 120最後に、gdbを終了させましょう。
(gdb) continueContinuing.1 2 3 4
Program exited normally.(gdb) quitデバッグしている最中に、「もしこの時点で、この変数の値が~だったらどうなるんだろう?」と考えることがあるかもしれません。
いちいちソースコードを書き換えて試してみても良いのですが、デバッガでこの作業を実行することができます。
$ gcc -g -O0 bubblesort.c$ gdb a.out...
(gdb) break mainBreakpoint 1 at 0x8048358: file bubblesort.c, line 9.(gdb) runStarting program: /***/a.out
Breakpoint 1, main (argc=1, argv=0xbff15104) at bubblesort.c:9今回の変更ターゲットは、int array[4]にします。とりあえず、元の値 {4, 1, 3, 2} を正しく代入させるため、nextします。
9 int array[4] = {4, 1, 3, 2};(gdb) next10 printarray(&array[0], 4);setコマンドを使用して、配列の最後の要素を変更してみます。
setコマンドの文法は、「set <書き込み先> = <式>」という形式になっています。
(gdb) set array[3] = -999本当に変更されているか、printコマンドで確認してみましょう。
(gdb) print array$1 = {4, 1, 3, -999}変更が終了したため、残りを一気に実行します。
(gdb) continueContinuing.4 1 3 -999-999 1 3 4
Program exited normally.配列の中身が変更されたため、ソート後の結果も変更されてしまいました。
確認したので、gdbを終了します。
(gdb) quitブレークポイント
Section titled “ブレークポイント”今までの作業は、別にデバッガでなくても行えるものでした。
| デバッガを使用した場合 | デバッガを使用しない場合 |
|---|---|
| プログラムの流れをトレース | 1行毎にprintfを入れて、トレースログを生成する |
| 変数の内容を表示 | 表示したい位置にprintfを入れて、変数の内容をダンプ |
| 変数の内容を設定 | 変更したい位置に代入文を入れる |
しかし、ブレークポイントという機能はデバッガの手助けなしでは実現できません。
ブレークポイントとはプログラムの強制一時停止を行うポイントで、実行中のプログラムがブレークポイントに遭遇するとプログラムは一時停止され、デバッガによるプログラムへの介入を行えるようになります。
ブレークポイントは次のような場所に設定できます。
- 指定した行番号のプログラムを実行しようとする瞬間
- 関数を呼び出した瞬間
その他、C++などでは「例外が発生した瞬間」などにもブレークポイントを設定することができます。
行番号ブレーク
Section titled “行番号ブレーク”ブレークポイントとしてよく使用されるのは、「プログラムの特定の位置」です。
例として、bubblesort.cプログラムのsort関数内で、隣り合う二つの要素を比較している箇所にブレークポイントを設定してみます。
25|/* bubble sort */26|void sort(int *array, int length) {27| int i, j;28| for (i = 0; i < length - 1; i++) {29| for (j = 0; j < length - i - 1; j++) {30| if (array[j] > array[j+1]) { /* <- この位置にブレークポイントを設定 */31| swap(&array[j], &array[j+1]);32| }33| }34| }35|}$ gcc -g -O0 bubblesort.c$ gdb a.out...ここで、breakコマンドを使用して、ブレークポイントの設定を行います。
設定方法は「break ファイル名:行番号」です。今回ブレークポイントを設定する位置は bubblesort.c の 30 行目ですので、以下のようになります。
(gdb) break bubblesort.c:30Breakpoint 1 at 0x804842e: file bubblesort.c, line 30.確認するためにlistコマンドを使用します。listコマンドは引数に行番号を取ることによって、現在位置ではなく指定した行番号の前後を表示します。
(gdb) list bubblesort.c:3025 /* bubble sort */26 void sort(int *array, int length) {27 int i, j;28 for (i = 0; i < length - 1; i++) {29 for (j = 0; j < length - i - 1; j++) {30 if (array[j] > array[j+1]) {31 swap(&array[j], &array[j+1]);32 }33 }34 }また、設定したブレークポイントは info breakpointsコマンドで確認することができます。
(gdb) info breakpointsNum Type Disp Enb Address What1 breakpoint keep y 0x0804842e in sort at bubblesort.c:30さて、runコマンドを使用して、プログラムを開始しましょう。
(gdb) runStarting program: /***/a.out4 1 3 2すると、すぐにブレークポイントにヒットしたという旨が伝えられ、プログラムが一時停止します。
Breakpoint 1, sort (array=0xbfea7cd0, length=4) at bubblesort.c:3030 if (array[j] > array[j+1]) {(gdb)ここでは、今までのようにprintで変数を表示したり、step/nextでステップ実行を行ったりできます。
(gdb) print j$1 = 0ブレークポイントで停止したプログラムを再開する場合、continueコマンドを使用します。
(gdb) continueContinuing.再開してもすぐにブレークポイントにヒットして、プログラムが一時停止してしまいます。
Breakpoint 1, sort (array=0xbfea7cd0, length=4) at bubblesort.c:3030 if (array[j] > array[j+1]) {(gdb)変数jの値を出力すると、確かにプログラムが進んでいることが確認できます。
(gdb) print j$2 = 1面倒になってきたので、deleteコマンドでブレークポイントを削除しましょう。
(gdb) info breakpointsNum Type Disp Enb Address What1 breakpoint keep y 0x0804842e in sort at bubblesort.c:30 breakpoint already hit 2 times(gdb) delete 1deleteコマンドの引数には、info breakpointsコマンドで表示されたNumを指定します。
continueコマンドでプログラムを最後まで実行します。ブレークポイントは削除したため、途中で停止したりはしないはずです。
(gdb) continueContinuing.1 2 3 4
Program exited normally.gdbを終了します。
(gdb) quitステップ実行を行うstepコマンドやnextコマンドは、プログラムの全ての行にブレークポイントを設定していたのに近い状態であることがわかります。
関数ブレーク
Section titled “関数ブレーク”特定の関数のデバッグをする場合、対象とする関数以外のトレースはあまり意味がない場合があります。
そこで、対象とする関数に入った直後にプログラムを一時停止するようなブレークポイントを設定します。
$ gcc -g -O0 bubblesort.c$ gdb a.out...まずは、breakコマンドを使用してswap関数にブレークポイントを設定します。
設定方法は「break 関数名」です。
(gdb) break swapBreakpoint 1 at 0x8048489: file bubblesort.c, line 40.プログラムを開始します。
(gdb) runStarting program: /***/a.out4 1 3 2実行すると、すぐにswap関数の入り口で停止します。
Breakpoint 1, swap (a=0xbfe6ea50, b=0xbfe6ea54) at bubblesort.c:4040 temp = *a;swap関数が正しく変数の値を入れ替えているか調査するため、aとbの内容を確認します。
(gdb) print *a$1 = 4(gdb) print *b$2 = 1swap関数の出口までステップ実行します。
ここで、43行目にブレークポイントを設定した後にcontinueコマンドでも良いのですが、数行なのでnextで十分です。
(gdb) next41 *a = *b;(gdb) next42 *b = temp;(gdb) next43 }swap関数の出口まで来たので、aとbの内容を確認します。
(gdb) print *a$3 = 1(gdb) print *b$4 = 4最初と比較して、aとbの内容が入れ替わっていることが確認できました。
最後まで実行した後、終了します。
(gdb) info breakpointsNum Type Disp Enb Address What1 breakpoint keep y 0x08048489 in swap at bubblesort.c:40 breakpoint already hit 1 time(gdb) delete 1(gdb) continueContinuing.1 2 3 4
Program exited normally.(gdb) quit最初で使用していた「break main」というコマンドは、「関数mainにブレークポイントを設定する」という意味を持っていました。
条件付ブレーク
Section titled “条件付ブレーク”使用するプログラムを変えて説明します。
以下のプログラムは階乗を計算するプログラムです。
factorial.c
#include <stdio.h>
int factorial(int);
/* program entry */int main(int argc, char **argv) { int m, n; if (argc != 2) { printf("usage: %s n\n", argv[0]); return 1; } m = atoi(argv[1]); n = factorial(m); printf("factorial(%d) = %d\n", m, n);
return 0;}
/* factorial */int factorial(int n) { if (n < 1) { return 0; } else if (n == 1) { return 1; } else { int m = factorial(n - 1); return m * n; }}ソースコードを見れば自明ですが、factorial関数を実行する際に、引数のnが1であった場合にどのような挙動をするか調査します。
単純にfactorial関数にブレークポイントを設定し、continueコマンドを何回も実行すればfactorial(1)を実行する瞬間を捉えられますが、少々面倒です。
そこで、今回は「条件付ブレークポイント」を設定してみます。
条件付ブレークポイントは、ブレークポイントに遭遇した瞬間に条件をチェックし、条件を満たさなかった場合はプログラムを停止せずに実行を継続させます。
つまり、「factorial関数に入った直後で、かつ n == 1」であるようなブレークポイントを設定することができます。
コンパイルしてgdbで実行します。
$ gcc -g -O0 factorial.c$ gdb a.out...この際に注意するのは、gdbを実行する際に、factorial.cで使用する引数を取らないことです。
「factorial関数に入った直後で、かつ n == 1」であるようなブレークポイントを設定します。
設定方法は「break 停止位置 if 条件」で、条件を満たしたときのみ停止位置で停止します。
(gdb) break factorial if n == 1Breakpoint 1 at 0x80483ff: file factorial.c, line 21.今回はプログラムの起動時に引数を渡します。通常は「./a.out 10」などのように実行ファイルの起動時に引数を設定しますが、gdbを使用する場合はrunコマンドの引数として渡すことが可能です。
(gdb) run 10Starting program: /***/a.out 10しばらくするとブレークポイントにヒットします。この際、n=1になっていることが確認できます。
Breakpoint 1, factorial (n=1) at factorial.c:2121 if (n < 1) {ステップ実行で流れを追ってみましょう。
(gdb) next24 else if (n == 1) {(gdb) next25 return 1;(gdb) next31 }n=1でreturn 1をしていることが確認できました。
最後まで実行して終了します。
(gdb) continueContinuing.factorial(10) = 3628800
Program exited normally.(gdb) quitこの条件式には、複雑な条件を書くこともできます。
ヒット時に自動でコマンド実行
Section titled “ヒット時に自動でコマンド実行”ブレークポイントは様々な設定ができ、「ヒット時に自動でコマンドを実行」などの複雑な設定も可能です。
例として、ソースコードを変更せずにfactorialの途中計算結果を表示してみましょう。
ソースコードを眺めてみると、途中結果が出ているのはfactorial関数内の28行目付近です。
28行目で途中結果が出るため、29行目にブレークポイントを設定して 変数 m の値を factorial(n-1) の結果として表示します。
19|/* factorial */20|int factorial(int n) {21| if (n < 1) {22| return 0;23| }24| else if (n == 1) {25| return 1;26| }27| else {28| int m = factorial(n - 1);29| return m * n; /* <- ここにブレークポイントを設定 */30| }31|}まずはgdbを起動します。
$ gcc -g -O0 factorial.c$ gdb a.out...通常通りにブレークポイントを設定します。
(gdb) break factorial.c:29Breakpoint 1 at 0x8048430: file factorial.c, line 29.ブレークポイントの情報を眺めます。ブレークポイント番号が1であることが分かります。
(gdb) info breakpointsNum Type Disp Enb Address What1 breakpoint keep y 0x08048430 in factorial at factorial.c:29ブレークポイント1に対して、commandコマンドを実行し、「ヒット直後の動作」を記述します。endと入力することによって記述を終了します。
(gdb) command 1Type commands for when breakpoint 1 is hit, one per line.End with a line saying just "end".>printf "factorial(%d) = %d\n", n - 1, m>continue>end上記のコマンドは以下のような流れになっています
- “factorial(n-1) = m” を表示
- プログラムの再開
ここでprintfは初めて紹介しますが、ここまで記事を読めた方に説明するのは僭越ですので省略。
今回は factorial(5) くらいで実行してみます。
(gdb) run 5Starting program: /***/a.out 5すると、以下のように出力されました。
Breakpoint 1, factorial (n=2) at factorial.c:2929 return m * n;factorial(1) = 1
Breakpoint 1, factorial (n=3) at factorial.c:2929 return m * n;factorial(2) = 2
Breakpoint 1, factorial (n=4) at factorial.c:2929 return m * n;factorial(3) = 6
Breakpoint 1, factorial (n=5) at factorial.c:2929 return m * n;factorial(4) = 24factorial(5) = 120
Program exited normally.少々見づらいですが、factorialの結果を 1..5 の範囲で出力しています(5は通常のプログラムによる出力)。
最後にgdbを終了させましょう。
(gdb) quit