GDB を使ってみるテスト

適当に小さなコードを作って、 -g 付きでコンパイルしてみました:

$ make test CFLAGS=-g
cc -g    test.c   -o test

C の挙動を知るには、 GDB を使うのがいろいろいいと思います。
手っ取り早くいろいろ試すのは ideone とかもいいんですが、こっちのほうがインタプリタっぽくてイイです。

$ gdb test
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /tmp/test...done.

コードの中身はこんなんです。
list の代わりに list main とかすると main だけ見れます。
list でなくて l でもいいです。

(gdb) list
1    #include <stdio.h>
2	#include <stdlib.h>
3	
4	int printfloat(float f) {
5		return printf("%f\n", f);
6	}
7	
8	int main(int args, const char **argv) {
9		return printfloat(atof(argv[1]));
10	}

run (r) で実行できます。 ./test ってやってるようなもんです。

(gdb) run
Starting program: /tmp/test 

Program received signal SIGSEGV, Segmentation fault.
0x001691f2 in ?? () from /lib/i386-linux-gnu/libc.so.6

セグメンテーションフォールトだ! バックトレースを見たい。

(gdb) backtrace
#0  0x001691f2 in ?? () from /lib/i386-linux-gnu/libc.so.6
#1  0x00166953 in strtod () from /lib/i386-linux-gnu/libc.so.6
#2  0x001620ac in atof () from /lib/i386-linux-gnu/libc.so.6
#3  0x08048449 in main (args=1, argv=0xbffff3f4) at test.c:9

strtod() の中の実装が謎のメモリ領域を見に行こうとしてピシャリと 叱られたんですかね。それにしても atof(3) って内部的にはただ strtod(3) を呼んでるだけなんですかね。ためになる。up, up, up, print argv[1] とかしてみると楽しいのかも。

gdb は、何か事情があって止まってしまっているプログラムにアタッチできたり、 このように不正終了して core ファイルを出力したときにそのバックトレースを 見ることができたりするので、いろいろと便利です。 core は ulimit しておかないと 普通は出ません。 -g ともども、運用中もできればオンにしておきたいですねー。 レアな状況の解析に役立つと思います。

怒られてるのは at test.c:9 が元凶っぽいので、 test.c:9 を 再度確認することができます。

(gdb) list test.c:9
4	int printfloat(float f) {
5		return printf("%f\n", f);
6	}
7	
8	int main(int args, const char **argv) {
9		return printfloat(atof(argv[1]));
10	}
11	

あー便利だった。continue (c) で再開します。再開といってもこの場合はそのままプロセスを終了するだけですが。

(gdb) continue
Continuing.

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.

気を取り直して引数をつけて再度実行します。

(gdb) run 2.71
Starting program: /tmp/test 2.71
2.710000
[Inferior 1 (process 2784) exited with code 011]

表示もでき、無事にプロセスも終了しました。ステータス9だけど。

(gdb) break 5
Breakpoint 1 at 0x804841a: file test.c, line 5.

ブレークポイント (braek (b)) をつけて再度実行しました。

(gdb) run 2.71
Starting program: /tmp/test 2.71

Breakpoint 1, printfloat (f=2.71000004) at test.c:5
5		return printf("%f\n", f);

printf() を実行する前に止まりました。 さっきも見たように、 backtrace (bt) で現在位置への 道順を知ることができます。Java の例外スタックトレースみたいな。

(gdb) backtrace
#0  printfloat (f=2.71000004) at test.c:5
#1  0x08048459 in main (args=2, argv=0xbffff3e4) at test.c:9

f がなにかを知るためには、 print (p) や whatis (wha) が役に立ちます。いや値はバックトレースにも書いてありますけど。

(gdb) print f
$1 = 2.71000004
(gdb) whatis f
type = float

print の後にはいろんな式が書けます。これは楽しいのでいろいろ楽しむとよいです。

(gdb) print printf("%f\n", f)
2.710000
$2 = 9
(gdb) print printf("%f\n", 3.14)
3.140000
$3 = 9
(gdb) print printf("%f\n", 3)
0.000000
$4 = 9

いろいろやった結果 0.00.. が出てきましたが、これって何ですかね…。 print で代入もしてもよいです。

(gdb) print f = 100
$5 = 100

ところで、ステップ実行もできます。

(gdb) step
100.000000
6	}

先ほど代入した 100 が無事表示されましたね。

(gdb) step
main (args=2, argv=0xbffff3e4) at test.c:10
10	}

main に戻ってきたところで、そういえば argv って何が入ってるっけと思ったときは、 @配列の長さ という書き方を使って PHP でいう print_r() 風のことができます。うひょ。

(gdb) print *argv@2
$6 = {0xbffff55b "/tmp/test", 0xbffff565 "2.71"}

あとは終了処理だけなので、ステップしていくのをやめてプログラムを再開します。

(gdb) continue
Continuing.
[Inferior 1 (process 2785) exited with code 013]

あー GDB 便利だった。

(gdb) quit