星にゃーんのブログ

ほとんど無害。

セキュリティ・キャンプ全国大会2019でCコンパイラ書きました

はじめに

2019年8月13日から5日間開催されたセキュリティ・キャンプ全国大会2019のCコンパイラ自作ゼミに参加しました。 この記事は、応募から修了までのレポートです。 ほとんど下書き状態ですが、ここから文章が改善される気がしないのでこのまま公開します。

セキュキャンでの進捗

3ヶ月ぐらいゴリゴリコードを書いて、セルフホストCコンパイラを実装した。switch-case文や#ifdef、可変長引数とかも実装している。 リポジトリは↓

github.com

これまでの星にゃーん

一昨年は言語自作ゼミ、去年はCコンパイラゼミに応募するも通らなかった。3度目の応募で合格。参加者の中では応募回数トップタイらしい。

今年の経緯

しばらく前から精神がオワってしまいひきこもり生活を送っている。ひきこもりながらもGHCのコードリーディングや自作コンパイラ開発をやっていたがスランプに陥る。 リハビリのために何かをやろうと考え、周りを見渡すと全人類Cコンパイラ書いとる。あ、書けるんだ、と思ったので書き始めた。 低レイヤを知りたい人のためのCコンパイラ作成入門https://github.com/rui314/9ccを読んで勘が掴めたので手を動かすことに。

hikaliumさんに1分でリツイートされ後がなくなる星にゃーん.jpg

うかつな発言によりCコンパイラセルフホストRTAが始まってしまった。それっぽいものは簡単に書け、1週間でif文とかが動いた。3日でセルフホストはさすがに無理。再走者求む。

せっかくなので今年もCコンパイラ自作ゼミに応募してみることにした。体力面の不安が大きかったが、どうせ家に居ても死んでるので変わらないと判断。 通った。びっくり。通ったので元気が出た。単純ですね。

事前学習ではCコンパイラを書いた。週1でruiさんとhikaliumさんとのオンラインミーティングが設定された。参考になる資料や歴史の話が盛りだくさんで面白い話を毎回できた。コーディング自体はそんなに詰まることなくスムーズに進んだ。『低レベルプログラミング』と『BINARY HACKS』の知識がかなり役立った。コンパイラの開発自体はやったことがあるのも功を奏した。それでもバグりまくって常にウンウン唸ってましたが…

実装の話

3日でセルフホストと言ったので出来るだけ早くセルフホストしたい。しかし24時間ぶっ続けで書き続ける元気はないので、出来るだけ実装をサボることにした。よくあるコードを正しく解釈できればよいとして、コーナーケースへの対応は必要ない限り行わなかった。unsignedな値すらサポートしていない。意外とunsignedとsignedの区別を付けるのが面倒。

コード量が大きくなることは目に見えていたので、あえてあまり抽象化せず愚直に書くことにした。Cで短く簡潔なコードを書くのは不可能ではないが難しい。 愚直に書くといっても冗長に書くことは避けた。何事もバランスが大事。それなりに綺麗なコードが書けたんじゃないかと思う。

スタックのアライメントがバグりにバグりまくった。バグを直したと思っても数日後にまたバグが見つかる、みたいなのを繰り返した。アラインするコードがバグってるのでxmmレジスタが絡むとセグフォするが、浮動小数点数コンパイラではほとんど使わないのでなかなかバグが発覚しない。根本の原因は剰余演算を算数レベルで勘違いしていたことだった。わり算むずかしすぎる。 スタックのサイズをコード生成時にグローバル変数で管理するようにしていたがこれもバグの温床だったので、最終的に実行時にrspを-16でandしてアラインすることで解決した。

トークンに位置情報を持たせてエラー出力をいい感じにした。該当コードはこのあたり。 エラー出力は下記のような見た目になる。モダンっぽい雰囲気がする良いエラー出力。

$ ./hoc examples/parse_error.c
error at (1, 11)

int main( {
          ^

) expected

これはruiさんに教えてもらった中でも特に印象的なコード。しかしincludeを実装したあたりでエラー出力がセグフォするように。単一ファイルしか扱わないからとファイル自体の情報をトークンに入れておらず、includeしたファイルでソースコード文字列を指すグローバルなポインタが書き換わってるのが原因だった。 #includeを使うとprint_lineが正しく動作しなくなるバグを修正 · takoeight0821/hoc_nyan@c1390ae · GitHub

バグ、特にセグフォ周りのバグはセグフォとは関係ない(スタックトレース上に現れない)コードが直接の原因であることが多かった。GDBで落ちてる箇所の検討をつけた後、関連するコードでprintfデバッグをしまくった。watchpointとかをうまく使えばもっと楽になると思う。 GDBgdb-pedaが役立った。Day2に他の人が使ってるのを見て存在を思い出しインストールした。自作コンパイラのバグはスタックの異常として現れることが多かったので、スタックを自動でダンプしてくれるのが便利。もっとはやく存在を思い出したかった…

アセンブリにコメントで情報(コード生成時のノードとか)を埋め込んでおくとエスパーの助けになる。stderrへのデバッグ出力がコメントになるようにしておいて、stdoutにアセンブリを吐いて合わせて眺めると埋め込むより楽そう。早い段階で .loc を出力しておくようにするのも良いかもしれない。

セルフホストを達成した時のコミットは for(;;) への対応( 継続条件が空のforが実行されないことがあるバグを修正 · takoeight0821/hoc_nyan@83f1db0 · GitHub)。 条件が空の時に不正な値を読んでた。

GASのintel syntaxだと、 ltand のような名前をラベル名に使えないらしい。 invalid use of operatror lt のようなエラーメッセージが出る。 とりあえずソースコード内の ltand とかを別の名前にリネームして問題を回避した。時間ができたらこのあたりを再度調べてまとめておきたい。

異常系テストは大事だと再認識した。「このファイルはパースに失敗する」みたいなのがちゃんと失敗することを確かめておくと、いろんなバグを早期発見できる。上に書いた #include のバグもパースエラー出力のテストで発覚した。

まとめ

同じような興味と高いスキルを持つ人たちに囲まれて、一つのタスクにひたすら取り組み続ける機会は学生のうちはなかなか得難い。 なかなかタフな5日間だったが、気分はかなり爽快で良いリフレッシュになったと感じている。 実装力に自信がついたし、普段の日常会話で相当なストレスを溜めていることを自覚することもできた。 人生が変わったとまでは言えないが、間違いなく良い影響を受けたと思う。

セキュキャン期間中は6時起床24時就寝、3食毎日食べる規則正しい生活を送っていた。 しかし家に帰った翌日は16時まで寝ていた。無事生活習慣が元に戻りました。この話はこれでオワリです。