feature image

2022年8月28日 | ブログ記事

Cコンパイラ作成で出会ったgdbあれこれ

この記事はtraP夏のブログリレー18日目の記事です。

こんにちは。19BのImperiです。普段はCTFとかやってます。

この記事ではCコンパイラ作成の中で出会ったgdbデバッグのあれやこれやをまとめていきます。まさかの今夏ブログリレーで自作コンパイラ記事2個目です

Cコンパイラ作成の経緯

製作中のCコンパイラがこちら  

GitHub - Imperi13/my-cc
Contribute to Imperi13/my-cc development by creating an account on GitHub.

Rui Ueyamaさんのcompiler bookを読み、今年度の5月くらいから趣味でCコンパイラの制作を始めました。行き詰ったりバグったりしながらゆるゆる開発していたんですが、今年の夏に参加したセキュリティキャンプの中で様々な人と交流しモチベアップ → 8月上旬にセルフホストまで出来ました。

このコンパイラ作るにあたってセキュリティキャンプの方々(特にCコンパイラゼミの方々)には本来申し込んだゼミでもないのに本当にお世話になりました。この場を借りて感謝申し上げます。

今は標準ライブラリのincludeも含めて動くようにプリプロセッサや更なる機能を実装しています。

Q. なんでそもそも作ろうと思ったの?

A. 本能です。

(ref: https://trap.jp/post/1638/)

Q. なんでgdbデバッグするの?

A. なんかgdb使いこなすのカッコよくないですか

バグった!gdbでデバッグするぞ

ここではソースコードをgccでコンパイルしてできたバイナリを第一世代(mycc1)、mycc1でコンパイルしてできたバイナリを第二世代(mycc2)とします。

#0 前提: エラー、警告、ログをちゃんと出力する

とりあえずコンパイラの警告やlinterで単純なミスは防ぎましょう(結論)

実装したり規格について調べると、厄介な条件分岐や意外な文法に気付くことがあります。

とりあえずerror(fmt,arg)not_implemented(),assert()的な関数を用意して、気付いた段階でその箇所に置いておくのが気持ち的にも楽です。僕の場合正味かなりのバグはこういったassertで防ぐor簡単に解決できました。

また仮にそのエラーメッセージから直接的な原因が分からなかったとしても、gdbデバッグする上で結構嬉しい事があります。デバッグ情報が付いていないバイナリにおいても関数の呼び出しは追いかけやすい(関数名のラベル、スタックフレーム)ので、errorの呼び出し元を見れば原因が分かりやすくなります。

#1 mycc1でのデバッグ

まずオプションで-gを指定して(gcc -g ...)デバッグ情報付きでコンパイルします。

そうすればmycc1にデバッグ情報が付くため、ソースコードも型情報も...

ソースコードも
型情報も

綺麗に表示されて嬉しい! (なおgdbのプラグインとして pwndbg を利用しています)

特に型情報は変数を表示するときにgdbが見やすくしてくれるので、圧倒的にデバッグがしやすくなります。

これでmycc1がsegmentation faultで落ちるときも、gdb --args ./mycc1 test.cで起動
runで実行すればセグフォを吐く場所で止まり、対応するソースコードも表示されるため原因が掴みやすくなります。変数を確認したければprint (変数名)でさきほどの型情報のように変数の型情報を利用した見やすい表示がされます。

また #0 で挙げたようなerror()で落とすような状況でも上手くデバッグすることができます。

ではデバッグの例として以下のコード(test.c)をmycc1でコンパイルし、#0で用意していたerror()が呼ばれて落ちた場合を見ていきましょう。(いい例思いつかなかった)

int main(){
  int a,b;
  a = 10;
  b = 20;
  return a+b;
}

gdb --args ./mycc1 test.cでgdbを起動
error()が呼ばれて落ちるのでbreak errorでerror関数にブレークポイントを貼ります。
runコマンドを入力すれば実行を開始して、無事error関数で処理が止まります。
この状態でbacktraceを入力すると...

parse_external_declが怪しい...?

error()が呼び出されるまでの関数呼び出しの流れを見ることが出来ます。expectもただのassertionなので、その前のparse_external_declが怪しそうです。

frame 2で対応する関数のスタックフレームに移動します。この状態で関数内でのローカル変数を表示してみましょう。

tokは現在パース中のトークン列を管理するローカル変数です。tok->strを見る限り
int a,b;のコロンで落ちていそうです。また上の行に表示されていますが、これは
parse.cの134行目 expect(&tok,tok,";");でexpectを呼び出しているようです。
ここまで情報が得られれば、実装の中でどのケースを考慮していなかったか分かりやすくなります。
ちなみに今回はint a,b;のように複数の変数を宣言する場合(複数のdeclarator)を考慮していなかったのが原因でした。

今回はソースコードの構文解析を例にしましたが、文字列でデバッグしづらいコンパイラ内での型情報の処理とかも同様に変数を表示していけば、十分な情報が得られることでしょう。

#2 mycc2でのデバッグ

#1まではデバッグ情報付きのmycc1が舞台だったので基本的なコマンドさえ覚えれば直観的に表示できて手間がかかりませんでしたが、mycc2ではそうは行きません。mycc1にはgccの-gのようなデバッグ情報付きコンパイルは実装していないためソースコードとアセンブリの対応はもちろん型情報も持っていません。

こうなると関数名とメモリ上の値、四則演算と転送命令たくさんのアセンブリしか見れないのでちょっと辛い気持ちになります。

悲しい図

それでも何とかgdbで少しでも効率よくデバッグしたい...

ということでちょっとだけ楽になる方法を考えてみました。もっといい方法あるかもしれないので情報お持ちの方は教えてほしいです。

gdbではadd-symbol-fileで追加のシンボルテーブルをロードすることができます。
そこでgcc -g -c (欲しい型を利用しているソースコード)でデバッグ情報付きのオブジェクトファイルを作成し、これをgdbの中でadd-symbol-fileでロードします。

異なるコンパイラ(gcc)で作ったdebug_info読み込んで何になるんだという気持ちになりますが、これでなんか型情報は持ってこれます。但しこれはgccでコンパイルした際のメモリレイアウトが記述された型情報なのでもしmyccが異なるレイアウトを採用していたら(たぶん)使い物になりません。

まぁでも大体同じレイアウトになるでしょ(投げやり)

嬉しい

これで型情報は持ってこれましたが、変数名やソースコードとの対応はこういう裏技では厳しいと思います。そもそもgccとmyccでコンパイル結果のアセンブリが違うし...

ただその型情報を利用していると明らかな場所では、これだけでもかなりデバッグしやすくなるのではないのかなと思います。例えばparse_hoge(Token *tok)に対して
print *(Token *)$rdiのようにprintすればTokenの型情報を利用してmycc1のときのように見やすく表示してくれます。



(まぁ実はセルフホストのときにはあまりバグらなかったから #2 で挙げた裏技ほとんど利用してないんですけどね)

まとめ

できる限りログとかassertとかテスト使って、未然にバグを防ごう。

それでも訳分からんバグが発生したら、この記事のようにgdbを利用してみてはいかがでしょうか。

後書き(言い訳)

実は #2 の後に 「#3 myccでもデバッグ情報を付けてみよう」ということでDWARF調べて、.debug_infoセクションを吐くようにしてみる計画を立てていたんですが、ブログリレーに間に合わないのでこの記事では断念しました。というか #3 書いてなくとも間に合ってません。計画性...

「#3 myccでもデバッグ情報を付けてみよう」はまたどこかで書きます。

Imperi icon
この記事を書いた人
Imperi

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2023年4月27日
Vulkanのデバイスドライバを自作してみた
kegra icon kegra
2022年9月26日
競プロしかシラン人間が web アプリ QK Judge を作った話
tqk icon tqk
2022年9月16日
5日でゲームを作った #tararira
Komichi icon Komichi
2023年9月27日
夏のブログリレーは終わらない【駄文】
Komichi icon Komichi
2023年9月13日
ブログリレーを支えるリマインダー
H1rono_K icon H1rono_K
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記