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
920
自作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
120
Coqで選択公理を形式化してみた
soukouki
0
170
「プログラミング」と「数学」の関係 〜カリー・ハワード同系対応と定理証明支援系Coq〜
soukouki
1
79
型クラスと依存型のカルパッチョ、代数的構造を添えて
soukouki
2
460
Coqのコントリビューターになった話
soukouki
0
140
次に流行る※プログラミング言語「Lean」
soukouki
2
1.3k
証明しながらプログラミング! - タクティックによるCoqプログラミング
soukouki
0
210
帰納型とパターンマッチングの紹介
soukouki
0
120
ステータスバーに歌詞を表示させてみた!
soukouki
0
100
Other Decks in Technology
See All in Technology
スモールスタート、不都合な真実 〜 耳当たりの良い言葉に現場が振り回されないために/20240930-ssmjp-small-start
opelab
13
1.8k
Binary Hacks Rebooted 私選ハック集
nullpo_head
1
180
OPENLOGI Company Profile
hr01
0
54k
入門 KRR
donkomura
0
110
tenntennはなんでnewmoにnew社したの? - YAPC::Hakodate 2024
tenntenn
PRO
0
220
How CERN serves 1EB of data via FUSE
ennael
PRO
0
16k
Azure Verified Moduleを触って分かった注目ポイント/azure-verified-module-begin
mhrtech
1
380
【インフラエンジニアbooks】30分でわかる「AWS継続的セキュリティ実践ガイド」
hssh2_bin
4
1.6k
Product Utilization of Large Language Models Starting Today
ymatsuwitter
3
1.3k
【shownet.conf_】持続可能な次世代Wi-Fi運用に向けて
shownet
PRO
0
350
ADRを運用して3年経った僕らの現在地
onk
PRO
12
5k
Slackbot × RAG で実現する社内情報検索の最適化
howdy39
2
330
Featured
See All Featured
Building Your Own Lightsaber
phodgson
102
6k
GraphQLの誤解/rethinking-graphql
sonatard
65
9.9k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.1k
Scaling GitHub
holman
458
140k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
126
18k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Web Components: a chance to create the future
zenorocha
310
42k
How to train your dragon (web standard)
notwaldorf
87
5.6k
Building a Scalable Design System with Sketch
lauravandoore
459
32k
KATA
mclloyd
27
13k
The Pragmatic Product Professional
lauravandoore
31
6.2k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
25
1.7k
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