未来のいつか/hyoshiokの日記

hyoshiokの日々思うことをあれやこれや

gdb豆知識(続き)

gdb豆知識(d:id:hyoshiok:20080926#p1)の続き。

Windowsの開発環境は知らないのだけど、噂によると素敵なIDE(統合開発環境)が整備されていて、マウスでぐりぐり、ヘルプも充実、簡単簡単らしい。それから比べると、GNU Emacs+gcc+gdbというのは微妙にレトロ感あふれる。とは言うもののデバッグのプロセスはIDE的マルチウィンドウ、GUIでグリグリであろうとなかろうと基本は変らないので、よしとしよう。

コンパイル時のオプション

gccの-gオプションを付ける。Makefileコンパイルオプションは通常CFLAGSなのでCFLAGS=-gとしておく。最適化オプション(-O)と同時に付けることができるので(素晴しい)、最適化オプションも適当に付けておく。

時々、デバッグオプション(-g)を付けるときは最適化オプションをつけない、あるいは-O0(アルファベットのオーにゼロ)をするというような都市伝説のようなものがあるが、わたしは意味がないと思う。最適化したコードは、ソースコードの字面とは異なる実行フローとか、関数のインライン化とかいろいろ施されているが、それも含めてのデバッグプロセスになれるようにしたい。

gccのmanには下記のようなことが書いてある。

       -g  Produce debugging information in the operating system's
           native format (stabs, COFF, XCOFF, or DWARF 2).  GDB can
           work with this debugging information.

           On most systems that use stabs format, -g enables use of
           extra debugging information that only GDB can use; this
           extra information makes debugging work better in GDB but
           will probably make other debuggers crash or refuse to read
           the program.  If you want to control for certain whether to
           generate the extra information, use -gstabs+, -gstabs,
           -gxcoff+, -gxcoff, or -gvms (see below).

           GCC allows you to use -g with -O.  The shortcuts taken by
           optimized code may occasionally produce surprising results:
           some variables you declared may not exist at all; flow of
           control may briefly move where you did not expect it; some
           statements may not be executed because they compute
           constant results or their values were already at hand; some
           statements may execute in different places because they
           were moved out of loops.

           Nevertheless it proves possible to debug optimized output.
           This makes it reasonable to use the optimizer for programs
           that might have bugs.

gdbの起動方法

emacsの中から M-x gdb で起動する。プログラム名を入力する。

Current directory is ~/work/coreutils-6.10/build-tree/coreutils-6.10/src/
GNU gdb 6.8-debian
Copyright (C) 2008 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 "i486-linux-gnu"...
(gdb) 

というような感じで起動できると思う。

ブレークポイントの設定

ブレークポイントは break (bと省略する) で設定する。

(gdb) b main
Breakpoint 1 at 0x8049724: file nl.c, line 455.

実行の開始

run 引数 で実行を開始する。下記はnlというプログラムにnl.cという引数を与えた例である。ブレークポイントが設定してあれば、そこに到達すると実行を一時停止する。

(gdb) run nl.c
Starting program: /home/hyoshiok/work/coreutils-6.10/build-tree/coreutils-6.10/src/nl nl.c
Breakpoint 1, main (argc=2, argv=0xbffa5c94) at nl.c:455
(gdb) 

(おまけ)使ったことのないコマンド

  • dump/append/restore

メモリの内容をファイルにダンプしたりリストアしたりするコマンドかあ。使ったことないや。

  • generate-core-file/gcore

coreファイルを生成するコマンド。使ったことないや。

  • init-if-undefined 変数=式

変数が未定義だったら式を代入。利用者定義のコマンドなんかで便利に使えそうだ。使ったことないや。

(gdb) p $a
$30 = void
(gdb) init-if-undefined $a=1
(gdb) p $a
$31 = 1
(gdb) init-if-undefined $a=2
(gdb) p $a
$32 = 1
(gdb) set $b=10
(gdb) p $b
$33 = 10
(gdb) init-if-undefined $b=1
(gdb) p $b
$34 = 10

続く。

最適化オプション(-O2など)とデバッグオプション(-g)を一緒につける101の理由

gdb豆知識(続き)(d:id:hyoshiok:20080927#p1)で

時々、デバッグオプション(-g)を付けるときは最適化オプションをつけない、あるいは-O0(アルファベットのオーにゼロ)をするというような都市伝説のようなものがあるが、わたしは意味がないと思う。最適化したコードは、ソースコードの字面とは異なる実行フローとか、関数のインライン化とかいろいろ施されているが、それも含めてのデバッグプロセスになれるようにしたい。

と記したのであるが、id:jj1bdx よりコメントをいただいたので、補足してみたい。

最適化オプションを付けると、コンパイラの種々の最適化技術により、関数をインライン化したり、ループをアンローリングしたり、ソースコードの字面とは異なるオブジェクトコードが生成される。

例えばインライン化された関数の場合、その関数名でのブレークポイントの設定ができなくなる。オブジェクトコードのエントリーポイントがなくなるのでその関数名がシンボルテーブルに載っていないため。あれれ?という感じである。

最適化によってローカルな変数がレジスタに載っていたりすると p 変数、とかやっても表示できない。p $eax とかしてレジスタを直接表示させないといけない。またまた、あれれ?という感じである。

初心者は、ここで戸惑う。

戸惑うので、それを考慮して、最適化オプションなしでコンパイル、ビルドしたくなる。それが人情というものである。それはわかるが、そこで留まってはいけない。なぜか。

CとかC++とか手続き型プログラミング言語でしこしこプログラムを書くということは、コンパイラという道具だてを利用してコンピュータに対して期待すべき動作を伝えている。コンパイラが無色透明ということはありえない。

コンパイラの最適化オプションの詳細について逐一知る必要はないが、最適化オプションによってコードの実行の順番がソースコードの順ではない場合があるということ位は理解しておく必要があると思う。

理解した上で、実行速度を向上させるために最適化オプションをつけるわけである。

最適化オプションをつけないでコンパイル、ビルドするおもちゃのコードであれば、別にどーでもいいのであるが、通常のソフトウェアであればデフォルトで-O2はつけていると思うので、わざわざ外す必要はない。

デバッグのためだけに最適化オプションを付けないという立場も考えられる。これも悪手である。

その立場の場合、最適化オプション付きのバイナリと最適化オプションなしのバイナリの2種類のバイナリを維持管理する必要がでてくる。

管理すべき実体が増えることは管理のコストが増加して、よろしくない。最適化オプションなしのバイナリで延々デバッグしていたのだが、実は最適化オプションありのバイナリでは当該バグに遭遇しないとか、そもそも、同じソースからコンパイル、ビルドしたのかをどう管理するのかとか、様々なコストが発生する。

マーフィーの法則のとおり、状況は最悪の方向にころがるのである。最適化オプションなしのバイナリはちゃんとデバッグしたんだけど、最適化オプション付きのバイナリは一世代前のバイナリで、バグ付きのまま出荷しちゃったよ、とか悪夢は列挙のいとまがない。

2つバイナリを用意すれば間違いなくテストの工数は2倍になるし、管理のコストも増大する。

プログラマは楽をしたがる人種である。なぜ好きこのんで問題を複雑化するのか。テスト、デバッグするバイナリは一つであるべきだ。そして出荷するコードが最適化オプション付きのものであれば、当然、最適化オプション付きでテスト、デバッグする。

わたしにはそれがベストプラクティスだと思う。すくなくとも、それが「わたしの流儀」である。

異論、反論、多事争論。コメント、ブックマーク、トラックバック等いただければ望外の喜びである。