Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
自作Cコンパイラ 8時間の奮闘
Search
soukouki
September 14, 2024
Technology
0
980
自作Cコンパイラ 8時間の奮闘
2024-09-07 セキュリティキャンプ アフターイベント
2024-09-14 traP & Zli 合同LT
で発表したスライドになります。
soukouki
September 14, 2024
Tweet
Share
More Decks by soukouki
See All by soukouki
定理証明支援系Coq(セキュリティキャンプLT会)
soukouki
1
140
Coqで選択公理を形式化してみた
soukouki
0
200
「プログラミング」と「数学」の関係 〜カリー・ハワード同系対応と定理証明支援系Coq〜
soukouki
1
95
型クラスと依存型のカルパッチョ、代数的構造を添えて
soukouki
2
470
Coqのコントリビューターになった話
soukouki
0
160
次に流行る※プログラミング言語「Lean」
soukouki
2
1.4k
証明しながらプログラミング! - タクティックによるCoqプログラミング
soukouki
0
220
帰納型とパターンマッチングの紹介
soukouki
0
120
ステータスバーに歌詞を表示させてみた!
soukouki
0
110
Other Decks in Technology
See All in Technology
OCI Vault 概要
oracle4engineer
PRO
0
9.7k
Terraform Stacks入門 #HashiTalks
msato
0
360
DynamoDB でスロットリングが発生したとき/when_throttling_occurs_in_dynamodb_short
emiki
0
250
The Role of Developer Relations in AI Product Success.
giftojabu1
0
130
日経電子版のStoreKit2フルリニューアル
shimastripe
1
140
RubyのWebアプリケーションを50倍速くする方法 / How to Make a Ruby Web Application 50 Times Faster
hogelog
3
950
TypeScriptの次なる大進化なるか!? 条件型を返り値とする関数の型推論
uhyo
2
1.7k
OTelCol_TailSampling_and_SpanMetrics
gumamon
1
190
AI前提のサービス運用ってなんだろう?
ryuichi1208
8
1.4k
複雑なState管理からの脱却
sansantech
PRO
1
150
生成AIが変えるデータ分析の全体像
ishikawa_satoru
0
170
Shopifyアプリ開発における Shopifyの機能活用
sonatard
4
250
Featured
See All Featured
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
What's new in Ruby 2.0
geeforr
343
31k
Reflections from 52 weeks, 52 projects
jeffersonlam
346
20k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
28
2k
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Building Adaptive Systems
keathley
38
2.3k
Optimising Largest Contentful Paint
csswizardry
33
2.9k
It's Worth the Effort
3n
183
27k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
26
2.1k
5 minutes of I Can Smell Your CMS
philhawksworth
202
19k
The Cost Of JavaScript in 2023
addyosmani
45
6.8k
Transcript
自作Cコンパイラ 8時間の奮闘 2024-09-07 セキュリティキャンプ アフターイベント 2024-09-14 traP & Zli 合同LT
1
自己紹介 sou7といいます。 東北の山奥にある、会津 大学の学部4年生です。 定理証明支援系とかを触 ってます。 ArchLinux + i3 ユーザー
です。 会津を走る只見線の風景→ 2
SNS ActivityPub/Misskey @
[email protected]
GitHub @soukouki Twitter @sou7_ _ _ 3
趣味 シミュレーションゲーム(特に物流系) Simutrans(輸送シミュレーションゲーム) Factorio(工場建設・輸送シミュレーションゲーム) ライトノベルを読み漁ること なろうをひたすら読んだりしてます Obsidian 日記や技術メモ、ブログ記事まで全てObsidianで管理しています 4
S06 Cコンパイラゼミ セキュキャンのCコンパイラゼミに参加しました! Cコンパイラゼミは、その名の通りC言語のコンパイラを自作するゼ ミです。自作Cコンパイラ 10cc を作成しました。 <ここでデモを入れる> 5
10ccのコード 第1世代 GCC 10ccのコード 第2世代 第1世代 10ccのコード 第3世代 第2世代 達成したこと
期間中に見事 セルフホスト を達成す ることが出来ました。セルフ ホストとは、自作Cコンパイ ラ自身で、自分自身をコンパ イルすることです。図でいう 第2世代以降を作るのがセル フホストになります。 1. セルフホストの要件には、第2世代と第3世代を一致させることで不動点を 作るというのもありますが、今回は省略します。 6
Cコンパイラゼミで得られたこと C言語に対する深い知識 例: switch文の構文、知ってますか? → Duff's device で検索! 例: スタック領域に変数を確保するって、何が行われているの?
アセンブリの知識 わからない命令の調べ方 基本的な演算, ジャンプ, レジスタの扱いなど 難しいバグについても根気強く向き合う経験 セルフホストの際には込み入ったバグが多く発生します。 コードは全部手元にあるので、気合を入れてデバッグすれば必ず解 決できます! 7
8時間デバッグ大会 このコンパイラを作る中で、一番苦労したデバッグの話をします。こ のデバッグには計 8時間 かかりました。 そんなバグについて解説していきます。みなさんも何がバグの原因だ ったのかを想像しながら聞いてみて下さい。 8
時は2024年7月21日 Cコンパイラゼミはセキュキャン本番前から週に1回作業会を行って いました。このデバッグはその際に発生したものです。 9
第2世代を作ることは出来たが、第2世代にどんな入力 を与えても空のアセンブリを吐く 第1世代を使って、第2世代をコンパイルすることに成功しました。し かい、動かしてみるとどうにも変です。 第2世代にどんなCのコードを読み込ませても、空のアセンブリしか 吐かないのです。関数を入れても、文字列を入れても上手く動きませ ん。 10
フランケンコンパイル セルフホストのデバッグをする際に、いきなり全部セルフホストをす ると大変なので、「フランケンコンパイル」という手法を使います。 10cc のソースコードの一部を 10cc (第1世代)でコンパイルし、残り をGCCでコンパイルして、どのファイルがバグの原因なのかを探りま す。 GCCとの相互運用性を保つために、
10cc とGCCの出力するアセンブ リが互換性を持つようにしないといけません。今回は、構造体のアラ イメントをきちんと実装するだけで、フランケンコンパイルが出来る ようになりました。(バグが出始めてから2時間経過) 1. フランケンコンパイルという名称は、Cコンパイラゼミの講師であるhsjoihs氏が名付けたものです。 セキュキャンのCコンパイルゼミでしか使われていない独自用語らしいで す。 2. 実際には、構造体のアライメントをテストするために、 offsetof というものも実装しました。 11
10cc の構造 10cc は、以下のように4つの部分と、それを呼び出す main.c に別れ ています。それぞれの部分ごとにフランケンコンパイルを試していき ます。 字句解析 構文解析
意味解析 コード生成 トークン列 構文木 構文木 ソースコード アセンブリ 1. 他にも map.c というファイルがあったりしますが、ここでは省略します。 12
main.c が怪しい フランケンコンパイルを色々試すと、次の条件でコンパイルした場合 にバグることがわかりました。 main.c を第1世代でコンパイル それ以外をGCCでコンパイル 13
10ccのコード 第1世代 GCC 10ccのコード 第2世代 第1世代 10ccのコード 第3世代 第2世代 ここで
main.c を睨みつけた くなるのですが、実はそう上 手くは行きません。 直接 main.c に問題があるの ではなく、 main.c をコンパ イルした第1世代のどこかに 問題があるということです。 main.c をいくら睨みつけて も、 main.c のC言語のコード は正しいのです。 14
むずい main.c が怪しいというところまで突き止めたものの、そこから先に 進みません。最終手段としてアセンブリを沢山読むという手もありま すが、数千行のアセンブリを読み解きたくはありません。 当時のDiscord上でのやりとり 15
違った方向からテスト 行き詰まってしまったので、今度は違う部分に注目してデバッグを進 めてみます。アセンブリが出力されないということは、本来アセンブ リを出力するはずだったコード生成に何らかの異常が起きているとい うことです。 コード生成にprintfを仕込んでみると、構文木が来ていません。 構文解析にprintfを仕込んでみると、元のトークン列が空です。 字句解析にprintfを仕込んでみると、そもそもソースコードが読 み込めていません! →
ソースコードを読み込む部分にバグがあることがわかりました。 16
ソースコードを読み込む部分を書き直し なぜかわかりませんが、ソースコードを読み込む部分が上手く動いて なかったので、その部分を全て書き換えました。これで一段落! → が、このデバッグにはまだ続きがあります。 17
この時点でのエラー状況 map.cだけ自作コンパイラ、それ以外はgcc → 成功 main.cだけ自作コンパイラ、それ以外はgcc → 謎のエラー 「関数の戻り値の型がありません」!? 字句解析だけ →
失敗(セグフォ) 構文解析だけ → 成功 意味解析だけ → 成功 コード生成だけ → 成功 次に字句解析だけ 10cc のパターンを追ってみることにしました。 18
GDBでデバッグ C言語のセグフォはスタックトレースすら見れないので、まずはGDB を使ってスタックトレースを確認します。すると、意味解析のlibc関 数の呼び出しでセグフォが起きていました。 19
第1世代 (壊れている) 10ccのコード GCC 10ccのコード 第1世代(壊) 第2世代 (壊れている) この状況から、以下の形でバ グがあると予想できます。
1. 10cc のどこかにバグが あり、壊れた第1世代が 出来る。 2. その第1世代で字句解析 をコンパイルして、壊れ た第2世代が出来る。 20
3. 第2世代を動かすと、字句解析部分でメモリなどを壊している。 4. メモリなどが壊れた結果、第2世代の意味解析でセグフォが起き る。 構文解析 意味解析 トークン列(壊) 構文木(壊) ソースコード
字句解析(壊) セグフォ! 1. 実装していない機能があることに気づいて実装したり、16バイトアライメントを疑ったりとひと悶着あったのですが、割愛します。 21
memcmpが怪しい GDBでデバッグを続けると、 memcmp という標準ライブラリの関数を 呼び出した際にセグフォが起きていました。 さらに、 memcmp に渡している引数を確認すると、ヒープメモリに確 保しているはずのアドレスが 0xf7caf015
と、異様に短いことがわか りました。 メモリアドレスを処理するどこかで、64ビット必要なのに32ビット で計算してしまったのでしょう。意味解析より前の、どこかで。 1. 実は、GDBの出力を読み誤った結果、最初は memcmp ではなく strlen が原因だと思いこんでいたりしました。 2. この環境(x86_64, ArchLinux)では、正しいヒープ領域のアドレスは 0x611b3a3ca2a0 のように、16進数で12桁になります。しかし、今回の結果は8桁しかありません。 22
意味解析は 10cc の後半に位置する処理です。今回使ったポインタは 字句解析の箇所で生成したので、字句解析→意味解析のどこかで誤っ た処理が行われているとわかりました。 字句解析 構文解析 意味解析 コード生成 トークン列
構文木 構文木 ソースコード アセンブリ こんなふうに怪しい範囲が広いときは、皆さんどうするかおわかりで すね? 23
†2分探索† こうなったときは伝家の宝刀、†2分探索† を使います!処理の真 ん中あたりにおもむろに printf を仕込んで、どこでおかしくなって いるのかを探ります。 すると、字句解析のあたりですでに上位32ビットが失われていること がわかりました。さらに†2分探索†を続けると… 24
怪しい箇所を発見! cur = new_token(TK_SYMBOL, cur, p++, 1, file, line); このプログラムは正しいC言語のプログラムです。しかし、Cコンパ
イラを自作した人は分かるはずです。 インクリメントは怪しい と。 バグの匂いがプンプンします。 1. これを解いている際には、関数呼び出しとインクリメントが組み合わさったせいなのでは?と思っていました。実際は関数呼び出しは冤罪でした。 25
アセンブリを読むしかない ここまで原因が絞れたら、次はアセンブリを読むしかありません。デ バッグ出力を加えたアセンブリは、字句解析のものだけで約4万行に なります。この中から該当のコードを探します。 問題の箇所(100行くらい)を見つけたら、次はスタックの状態を考え ながらアセンブリを読み込みます。 26
バグの原因 バグ : mov edi, [rax] 正常 : mov rdi,
[rax] アセンブリの命令が、 rdi レジスタ(64ビット)ではなく、 edi レジス タ(32ビット)を使っていました。これが原因で上位32ビットが消えて しまっていたのです。 たった1文字の違いで、8時間もデバッグをする羽目になりました。 さらに、前半の main.c のバグについても、この rdi と edi のバグが 原因になっていることがわかりました。 27
まとめ セルフホストには、めちゃくちゃ大変なバグが続出します。 それを乗り越えて動作すると、とても大きな達成感がありあま す。 みんなもCコンパイラをセルフホストさせてみよう! (学部1-3年生へ)来年はセキュキャンに挑戦してみると楽しいよ! たった1文字の間違いで8時間もデバッグすることもあるの で気合を入れて頑張ろう! 28
その他 第一只見川橋梁の写真はMaedaAkihiko氏の著作物です。Creative Commons Attribution-Share Alike 4.0 29