gcc+gdbによるプログラムのデバッグ 第1回 ステップ実行、変数の操作、ブレークポイント

s.arakawa

作成していたプログラムを実行した際に、OSが謎のメッセージを残してプログラムが異常終了しました。皆さんはこのような時にどうしますか?

アプローチは人それぞれでしょうが、上記の方法では効率が良くない場合があります。

ここで「デバッガ」というものを使用すると、デバッグ作業の負担をかなり軽くすることができます。今回は、デバッガのうち最も有名であろう「gdb」というデバッガを紹介します。

gccとgdbを用いたプログラムの開発

gcc は Gnu Compiler Collection の略称で、様々な言語を処理可能なコンパイラです。基本的にはC言語のコンパイルに使用されるでしょうが、C++やFORTRANなどの言語もコンパイル可能になっています。

gdb は Gnu DeBugger の略称で、gcc(もしくは互換のコンパイラ)でコンパイルしたプログラムのデバッグを支援します。gcc以外のコンパイラでコンパイルしたプログラムもデバッグできますが、かなり機能は制限されてしまいます。

今回はコンパイラにgccを使用したC言語のプログラム開発で、gdbでそのデバッグを助ける方法を紹介します。

gccとgdbを使用する利点

C言語を開発する場合は様々な開発環境がありますが、gccとgdbを使うと次のような利点があります。

プログラムのコンパイル

gdbを使用してデバッグする場合、コンパイル時には以下のオプション「-g -O0」をつけてコンパイルする必要があります。

gcc -g -O0 <ソースファイル名>

これらのオプションには、以下のような意味があります。

オプション 意味
-g ファイルにデバッグ情報を付加する。これがないとデバッグ時に変数名や行番号が表示されない
-O0 最適化を行わない。最適化を行うと、コードの入れ替えや削除が行われてしまい、デバッグしにくくなる

実際の運用上では「-g -O2」というオプションをつけるのが普通ですが、-O2は強い最適化を施すオプションであり、慣れるまではデバッグが大変になってしまいます。

今回は「-g -O0」を必ずつけてコンパイルするようにしてください。必要ならば「-Wall」などのオプションを加えても構いません。

ステップ実行

簡単なバブルソートのプログラムをトレースしてみましょう。

bubblesort.c
#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.out
4 1 3 2
1 2 3 4

配列 4 1 3 2 がソートされていることが確認できます。

基本的な操作を覚えるまで、バグの無いプログラムで話を進めます。

next

まずは next コマンドでステップアウト実行を行います。

最初に、先ほどコンパイルして作成された a.out ファイルを gdb 上で実行してみましょう。

$ gdb a.out
GNU 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 are
welcome 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 main
run

上の2行は、「main関数に入るまで(break main)実行する(run)」という意味を表しています。

実際に実行すると、次のように表示されるはずです。

(gdb) break main
Breakpoint 1 at 0x8048358: file bubblesort.c, line 9.
(gdb) run
Starting program: /***/a.out

さて、runをした直後に次のようなメッセージが表示されたでしょうか。

Breakpoint 1, main (argc=1, argv=0xbfeceb74) at bubblesort.c:9
9         int array[4] = {4, 1, 3, 2};
(gdb)

これはブレークポイントと呼ばれるものです。
先ほどの「break main」というコマンドで指定した「main関数に入るまで」という条件を満たしたため、プログラムを一時停止して、デバッグ用のコマンドを受け付けています。
ブレークポイントについては後ほど詳しく説明します。

さて、次は next コマンドを使用して、プログラムの流れを追いましょう。

そのまま next とタイプし、ENTERキーを押します。

(gdb) next
10        printarray(&array[0], 4);
(gdb)

すると、今まで

9         int array[4] = {4, 1, 3, 2};

のように表示されていたものが、

10        printarray(&array[0], 4);

という具合に、次の行になりました。

続けて next コマンドを実行しましょう。

(gdb) next
4 1 3 2
11        sort(array, 4);
(gdb)

先ほどの行に表示されていた printarray(&array[0], 4); を実行し、その結果として

4 1 3 2

と表示しています。

次々とnextコマンドでステップ実行を行います。

(gdb) next
12        printarray(&array[0], 4);
(gdb) next
1 2 3 4
13        return 0;
(gdb) next
14      }

これ以降には有用な情報が無いため、 continue コマンドで一気に実行します。

(gdb) continue
Continuing.

Program exited normally.

最後に、quit コマンドでgdbを終了させましょう。

(gdb) quit

step

nextコマンドでは、関数の呼び出し時に、呼び出し先の内部に渡ってトレースすることができませんでした。
そこで、ステップイン実行と呼ばれるタイプのstepコマンドを使用して、関数の内部をトレースします。

先ほどと同様に、main関数まで実行します。

$ gcc -g -O0 bubblesort.c
$ gdb a.out
...

(gdb) break main
Breakpoint 1 at 0x8048358: file bubblesort.c, line 9.
(gdb) run
Starting program: /***/a.out

ブレークポイントにヒットした後、ステップ実行を行います。

最初は関数呼び出しではありませんので、stepでもnextでも同じです。

Breakpoint 1, main (argc=1, argv=0xbfe93554) at bubblesort.c:9
9         int array[4] = {4, 1, 3, 2};
(gdb) next

次に、printarray関数の中身をトレースしてみましょう。
stepコマンドで中に入ります。

10        printarray(&array[0], 4);
(gdb) step

stepコマンドの直後は、他の関数にジャンプしたことが表示されます。

printarray (array=0xbfe934b0, length=4) at bubblesort.c:19

実引数の情報もこの時点で表示されます。

次々とstepコマンドを入力します。

19        for (i = 0; i < length; i++) {
(gdb) step
20          printf("%d ", array[i]);
(gdb) step
19        for (i = 0; i < length; i++) {
(gdb) step
20          printf("%d ", array[i]);
(gdb) step
19        for (i = 0; i < length; i++) {
(gdb) step
20          printf("%d ", array[i]);
(gdb) step
19        for (i = 0; i < length; i++) {
(gdb) step
20          printf("%d ", array[i]);
(gdb) step
19        for (i = 0; i < length; i++) {
(gdb) step

printfは標準関数であるため、中身をトレースしません。また、この時点ではprintfの出力が行われませんが、これは出力前にバッファに格納されるからです。

最後に、改行を出力するとバッファがフラッシュされてコンソールに表示されます。

22        printf("\n");
(gdb) step
4 1 3 2
23      }
(gdb) step

main関数に戻ってきたので、continueコマンドで最後まで一気に実行しましょう。

main (argc=1, argv=0xbfe93554) at bubblesort.c:11
11        sort(array, 4);
(gdb) continue
Continuing.
1 2 3 4

Program exited normally.

quitコマンドでgdbを終了します。

(gdb) quit

ソースプログラムの表示

step実行中に、現在のソースコードの位置を知りたい場合があるかもしれません。
その場合は、list コマンドを使用します。

とりあえず、先ほどのプログラム(bubblesort.c)で、sort関数の中に入ります。

$ gcc -g -O0 bubblesort.c
$ gdb a.out
...

(gdb) break main
Breakpoint 1 at 0x8048358: file bubblesort.c, line 9.
(gdb) run
Starting program: /***/a.out

Breakpoint 1, main (argc=1, argv=0xbff26cb4) at bubblesort.c:9
9         int array[4] = {4, 1, 3, 2};
(gdb) next
10        printarray(&array[0], 4);
(gdb) next
4 1 3 2
11        sort(array, 4);
(gdb) step
sort (array=0xbff26c10, length=4) at bubblesort.c:28
28        for (i = 0; i < length - 1; i++) {
(gdb) step
29          for (j = 0; j < length - i - 1; j++) {
(gdb) step
30            if (array[j] > array[j+1]) {
(gdb) step

この状態でlistコマンドを実行すると、以下のように現在の前後の行が表示されます。

31              swap(&array[j], &array[j+1]);
(gdb) list
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      }

変数の取り扱い

プログラムをデバッグする際、重要となるのが「ある時点での変数の値」です。ここでは、変数の取り扱いについて調査します。

変数の内容を表示

変数の内容を表示する際、やってしまいがちなのが以下のようなプログラムです。

machine_gun.c
/* 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.out
4 1 3 2
i=0, j=0, array[j]=4, array[j+1]=1
i=0, j=1, array[j]=4, array[j+1]=3
i=0, j=2, array[j]=4, array[j+1]=2
i=1, j=0, array[j]=1, array[j+1]=3
i=1, j=1, array[j]=3, array[j+1]=2
i=2, j=0, array[j]=1, array[j+1]=2
1 2 3 4

小さなプログラムであればこの方法で問題ないかもしれませんが、コンパイルにとても時間が掛かるプログラムなどでこれを行う場合は非常に効率が悪いです。

そこで、gdbを利用して変数の内容を表示させてみましょう。

とりあえずgdbを起動し、好きな場所までステップ実行をします。

$ gcc -g -O0 bubblesort.c
$ gdb a.out
...

(gdb) break main
Breakpoint 1 at 0x8048358: file bubblesort.c, line 9.
(gdb) run
Starting program: /***/a.out

Breakpoint 1, main (argc=1, argv=0xbfec9bc4) at bubblesort.c:9
9         int array[4] = {4, 1, 3, 2};
(gdb) step
10        printarray(&array[0], 4);
(gdb) next
4 1 3 2
11        sort(array, 4);
(gdb) step
sort (array=0xbfec9b20, length=4) at bubblesort.c:28
28        for (i = 0; i < length - 1; i++) {
(gdb) step
29          for (j = 0; j < length - i - 1; j++) {
(gdb) step
30            if (array[j] > array[j+1]) {
(gdb) step
31              swap(&array[j], &array[j+1]);
(gdb) next
29          for (j = 0; j < length - i - 1; j++) {
(gdb) step
30            if (array[j] > array[j+1]) {
(gdb) step
31              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 = 3

whatisコマンドや、ptypeコマンドを使用することによって、その変数の型を調べることができます。

(gdb) whatis array
type = int *
(gdb) ptype array
type = 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) continue
Continuing.
1 2 3 4

Program exited normally.
(gdb) quit

変数の変更

デバッグしている最中に、「もしこの時点で、この変数の値が~だったらどうなるんだろう?」と考えることがあるかもしれません。

いちいちソースコードを書き換えて試してみても良いのですが、デバッガでこの作業を実行することができます。

$ gcc -g -O0 bubblesort.c
$ gdb a.out
...

(gdb) break main
Breakpoint 1 at 0x8048358: file bubblesort.c, line 9.
(gdb) run
Starting 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) next
10        printarray(&array[0], 4);

setコマンドを使用して、配列の最後の要素を変更してみます。
setコマンドの文法は、「set <書き込み先> = <式>」という形式になっています。

(gdb) set array[3] = -999

本当に変更されているか、printコマンドで確認してみましょう。

(gdb) print array
$1 = {4, 1, 3, -999}

変更が終了したため、残りを一気に実行します。

(gdb) continue
Continuing.
4 1 3 -999
-999 1 3 4

Program exited normally.

配列の中身が変更されたため、ソート後の結果も変更されてしまいました。

確認したので、gdbを終了します。

(gdb) quit

ブレークポイント

今までの作業は、別にデバッガでなくても行えるものでした。

デバッガを使用した場合 デバッガを使用しない場合
プログラムの流れをトレース 1行毎にprintfを入れて、トレースログを生成する
変数の内容を表示 表示したい位置にprintfを入れて、変数の内容をダンプ
変数の内容を設定 変更したい位置に代入文を入れる

しかし、ブレークポイントという機能はデバッガの手助けなしでは実現できません。
ブレークポイントとはプログラムの強制一時停止を行うポイントで、実行中のプログラムがブレークポイントに遭遇するとプログラムは一時停止され、デバッガによるプログラムへの介入を行えるようになります。

ブレークポイントは次のような場所に設定できます。

その他、C++などでは「例外が発生した瞬間」などにもブレークポイントを設定することができます。

行番号ブレーク

ブレークポイントとしてよく使用されるのは、「プログラムの特定の位置」です。

例として、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:30
Breakpoint 1 at 0x804842e: file bubblesort.c, line 30.

確認するためにlistコマンドを使用します。listコマンドは引数に行番号を取ることによって、現在位置ではなく指定した行番号の前後を表示します。

(gdb) list bubblesort.c:30
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        }

また、設定したブレークポイントは info breakpointsコマンドで確認することができます。

(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x0804842e in sort at bubblesort.c:30

さて、runコマンドを使用して、プログラムを開始しましょう。

(gdb) run
Starting program: /***/a.out
4 1 3 2

すると、すぐにブレークポイントにヒットしたという旨が伝えられ、プログラムが一時停止します。

Breakpoint 1, sort (array=0xbfea7cd0, length=4) at bubblesort.c:30
30            if (array[j] > array[j+1]) {
(gdb)

ここでは、今までのようにprintで変数を表示したり、step/nextでステップ実行を行ったりできます。

(gdb) print j
$1 = 0

ブレークポイントで停止したプログラムを再開する場合、continueコマンドを使用します。

(gdb) continue
Continuing.

再開してもすぐにブレークポイントにヒットして、プログラムが一時停止してしまいます。

Breakpoint 1, sort (array=0xbfea7cd0, length=4) at bubblesort.c:30
30            if (array[j] > array[j+1]) {
(gdb)

変数jの値を出力すると、確かにプログラムが進んでいることが確認できます。

(gdb) print j
$2 = 1

面倒になってきたので、deleteコマンドでブレークポイントを削除しましょう。

(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x0804842e in sort at bubblesort.c:30
        breakpoint already hit 2 times
(gdb) delete 1

deleteコマンドの引数には、info breakpointsコマンドで表示されたNumを指定します。

continueコマンドでプログラムを最後まで実行します。ブレークポイントは削除したため、途中で停止したりはしないはずです。

(gdb) continue
Continuing.
1 2 3 4

Program exited normally.

gdbを終了します。

(gdb) quit

ステップ実行を行うstepコマンドやnextコマンドは、プログラムの全ての行にブレークポイントを設定していたのに近い状態であることがわかります。

関数ブレーク

特定の関数のデバッグをする場合、対象とする関数以外のトレースはあまり意味がない場合があります。

そこで、対象とする関数に入った直後にプログラムを一時停止するようなブレークポイントを設定します。

$ gcc -g -O0 bubblesort.c
$ gdb a.out
...

まずは、breakコマンドを使用してswap関数にブレークポイントを設定します。
設定方法は「break 関数名」です。

(gdb) break swap
Breakpoint 1 at 0x8048489: file bubblesort.c, line 40.

プログラムを開始します。

(gdb) run
Starting program: /***/a.out
4 1 3 2

実行すると、すぐにswap関数の入り口で停止します。

Breakpoint 1, swap (a=0xbfe6ea50, b=0xbfe6ea54) at bubblesort.c:40
40        temp = *a;

swap関数が正しく変数の値を入れ替えているか調査するため、*aと*bの内容を確認します。

(gdb) print *a
$1 = 4
(gdb) print *b
$2 = 1

swap関数の出口までステップ実行します。
ここで、43行目にブレークポイントを設定した後にcontinueコマンドでも良いのですが、数行なのでnextで十分です。

(gdb) next
41        *a = *b;
(gdb) next
42        *b = temp;
(gdb) next
43      }

swap関数の出口まで来たので、*aと*bの内容を確認します。

(gdb) print *a
$3 = 1
(gdb) print *b
$4 = 4

最初と比較して、*aと*bの内容が入れ替わっていることが確認できました。

最後まで実行した後、終了します。

(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048489 in swap at bubblesort.c:40
        breakpoint already hit 1 time
(gdb) delete 1
(gdb) continue
Continuing.
1 2 3 4

Program exited normally.
(gdb) quit

最初で使用していた「break main」というコマンドは、「関数mainにブレークポイントを設定する」という意味を持っていました。

条件付ブレーク

使用するプログラムを変えて説明します。
以下のプログラムは階乗を計算するプログラムです。

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 == 1
Breakpoint 1 at 0x80483ff: file factorial.c, line 21.

今回はプログラムの起動時に引数を渡します。通常は「./a.out 10」などのように実行ファイルの起動時に引数を設定しますが、gdbを使用する場合はrunコマンドの引数として渡すことが可能です。

(gdb) run 10
Starting program: /***/a.out 10

しばらくするとブレークポイントにヒットします。この際、n=1になっていることが確認できます。

Breakpoint 1, factorial (n=1) at factorial.c:21
21        if (n < 1) {

ステップ実行で流れを追ってみましょう。

(gdb) next
24        else if (n == 1) {
(gdb) next
25          return 1;
(gdb) next
31      }

n=1でreturn 1をしていることが確認できました。

最後まで実行して終了します。

(gdb) continue
Continuing.
factorial(10) = 3628800

Program exited normally.
(gdb) quit

この条件式には、複雑な条件を書くこともできます。

ヒット時に自動でコマンド実行

ブレークポイントは様々な設定ができ、「ヒット時に自動でコマンドを実行」などの複雑な設定も可能です。

例として、ソースコードを変更せずに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:29
Breakpoint 1 at 0x8048430: file factorial.c, line 29.

ブレークポイントの情報を眺めます。ブレークポイント番号が1であることが分かります。

(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048430 in factorial at factorial.c:29

ブレークポイント1に対して、commandコマンドを実行し、「ヒット直後の動作」を記述します。endと入力することによって記述を終了します。

(gdb) command 1
Type 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

上記のコマンドは以下のような流れになっています

  1. "factorial(n-1) = m" を表示
  2. プログラムの再開

ここでprintfは初めて紹介しますが、ここまで記事を読めた方に説明するのは僭越ですので省略。

今回は factorial(5) くらいで実行してみます。

(gdb) run 5
Starting program: /***/a.out 5

すると、以下のように出力されました。

Breakpoint 1, factorial (n=2) at factorial.c:29
29          return m * n;
factorial(1) = 1

Breakpoint 1, factorial (n=3) at factorial.c:29
29          return m * n;
factorial(2) = 2

Breakpoint 1, factorial (n=4) at factorial.c:29
29          return m * n;
factorial(3) = 6

Breakpoint 1, factorial (n=5) at factorial.c:29
29          return m * n;
factorial(4) = 24
factorial(5) = 120

Program exited normally.

少々見づらいですが、factorialの結果を 1..5 の範囲で出力しています(5は通常のプログラムによる出力)。

最後にgdbを終了させましょう。

(gdb) quit