GAIO CLUB

2023年11月06日

【第15回】デバッグ情報

静的解析/コンパイラ技術
いまさら聞けない静的解析/コンパイラ技術
デバッガを使用してC言語プログラムをデバッグする時には、コンパイルする時にデバッグオプションを使用する必要があります。GCCであれば、-g オプションです。デバッグオプションを使用することをデバッグモードコンパイルと言うこともあります。今回のお話は、デバッグについてです。

デバッグ情報

デバッグオプションを指定するとコンパイラはC言語デバッグのための情報を作ります。

一般的に、デバッグ情報はアセンブリソースの中に埋め込まれ、アセンブル&リンクされてバイナリファイルに入ります。リンカは複数のファイルを1つのファイルにまとめるので、リンクされたバイナリファイルには複数のファイルのデバッグ情報が含まれます。
C言語のデバッグ情報には、C言語の基本情報(型の大きさ・レジスタ情報など)、ソースファイル名、変数・関数の情報、行の情報など、C言語プログラムをデバッグする際に必要な情報が含まれており、この情報をデバッガが使用します。
デバッグ情報の出し方はコンパイラによりまちまちです。

以下にお見せするのは、GCC(ARM)のデバッグ情報例です。
左のCソースをデバッグモードでコンパイルしてできたのが右のアセンブリソースで、アセンブリソース中の赤字部分がデバッグ情報行です。

アセンブリソース中に命令が挿入されているように見えますが、デバッグ用の特別なアセンブリ命令が埋め込まれているわけではありません。アセンブリソース中のデバッグ情報行は、その情報がこの位置を示すという意味でそこにあります。たとえば、「Cソースファイルの3行目はここから始まるよ」というように示しています。

デバッグオプションを使用しなければ、赤字部分は無くなります。デバッグモードのアセンブリソースから赤字部分を削除したものと、デバッグオプション無しのアセンブリソースが同じかどうかは、コンパイラやCソースプログラムに依存すると考えたほうが良いです。同じと言い切れないのは、デバッグモードコンパイルの時に、コンパイラがデバッグのしやすさを考慮し、アセンブリソースを工夫する可能性があるからです。

デバッグ時の弊害

コンパイル時にデバッグオプションと最適化オプションを併用すると、コンパイラの最適化処理の影響でデバッグに弊害が起きます。良くあるのは、行や変数が削除されたことが原因で、止めたい行にブレークポイントを設定できない、変数を見れない等の問題です。最適化オプションを指定しない時にもこのような問題が起きることはありますが、最適化によって発生しやすくなります。

C言語文法のスコープ内にありながら、変数を参照できないという問題も良く起こります。C言語の文法では、変数の宣言方法や宣言箇所によってその変数の使用可能な範囲が決まります。スコープと呼ばれるものです。スコープ内であればその変数を使用することができますが、スコープ外ではその変数を使用することはできません。
では、デバッグ時にも同じように、このスコープ内で変数の参照が可能かというと、そうはいかないことがあります。

コンパイラは、引数や使用頻度の高いオート変数をレジスタに置いて使用するようなアセンブリ言語に変換します。しかし、レジスタは演算処理を行う時にも必要なので、文や式を処理するために自由に使えるレジスタを残しておかなければなりません。レジスタが不足するとアセンブリ言語化に苦労し、アセンブリ命令が増え、コードサイズが大きくなってしまいます。ですから、できる限りレジスタを有効に使うために、使い終わった変数用のレジスタは他の用途に使います。

そうすると、プログラムの停止位置によっては、スコープ内であっても変数の値はどこにも無くなり、デバッガから変数が見れなくなります。もし、デバッガが、変数を割り当てていたレジスタの値を見せたら、すでに他の変数が使用していて、デタラメな値が見えるかもしれません。スタック変数も同じように、使い終わったスタックエリアを別の変数が使用することがあります。

使い終わった変数を別のスタックに保存し参照可能にしておくことで、いつでも変数の値を見れるようにする方法はありますが、プログラム動作に不要なスタックの使用や代入命令を生成するようなコンパイラは無いと思います。
最後に、述語付き命令(条件付き実行命令)を持つCPUをご紹介します。

命令を先読みしながら動作するRISCでは、先読みした命令が条件分岐などで無駄になるのを防ぐ工夫を持つものがあります。たとえば、以下の例では、コンパイルされた左下のアセンブリ言語に分岐命令がありません。
述語付き命令と呼ばれる命令は、ある条件の時に加算するというように、条件によって演算したり何もしなかったりする命令で、この命令を使うと条件分岐する必要が無くなります。逆に言えば、真でも偽でもC言語の全行の機械語が動作してしまいます。カバレッジ計測的には、1つのデータで真偽を網羅してしまうか、真偽別のパスを確認できない状況になるかもしれません。

デバッガでステップ実行した場合にも、1度の処理で真偽の両パスを通ることになり、なにかのバグのように見えるかもしれませんが、変数aには正しい値が入っているはずです。
CPUの命令など気にしなくて良いC言語ですが、このように機械語の影響を受けることがあるのです。
数度にわたってコンパイラの話を聞いていただきましたが、コンパイラに関するお話は今回で終わりです。
面倒な話ばかりでしたが、ご参考になりましたら嬉しいです。
ありがとうございました。

筆者紹介

浅野 昌尚(あさの まさなお)

ガイオ・テクノロジー株式会社

開発1部 QTXグループ

1980年代から30年以上にわたり汎用構造のCコンパイラ開発に従事し、その間に8ビットマイコンからRISC・VLIW・画像処理プロセッサまで、さまざまなCPU向けのクロスCコンパイラを開発。

人気のコラム

最新のコラム