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
1.1k
自作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
150
Coqで選択公理を形式化してみた
soukouki
0
230
「プログラミング」と「数学」の関係 〜カリー・ハワード同系対応と定理証明支援系Coq〜
soukouki
1
120
型クラスと依存型のカルパッチョ、代数的構造を添えて
soukouki
2
490
Coqのコントリビューターになった話
soukouki
0
160
次に流行る※プログラミング言語「Lean」
soukouki
2
1.5k
証明しながらプログラミング! - タクティックによるCoqプログラミング
soukouki
0
230
帰納型とパターンマッチングの紹介
soukouki
0
130
ステータスバーに歌詞を表示させてみた!
soukouki
0
130
Other Decks in Technology
See All in Technology
Amazon VPC Lattice 最新アップデート紹介 - PrivateLink も似たようなアップデートあったけど違いとは
bigmuramura
0
200
複雑性の高いオブジェクト編集に向き合う: プラガブルなReactフォーム設計
righttouch
PRO
0
120
オプトインカメラ:UWB測位を応用したオプトイン型のカメラ計測
matthewlujp
0
180
NilAway による静的解析で「10 億ドル」を節約する #kyotogo / Kyoto Go 56th
ytaka23
3
380
LINEスキマニにおけるフロントエンド開発
lycorptech_jp
PRO
0
330
サイバー攻撃を想定したセキュリティガイドライン 策定とASM及びCNAPPの活用方法
syoshie
3
1.3k
権威ドキュメントで振り返る2024 #年忘れセキュリティ2024
hirotomotaguchi
2
750
非機能品質を作り込むための実践アーキテクチャ
knih
5
1.5k
ガバメントクラウドのセキュリティ対策事例について
fujisawaryohei
0
560
Turing × atmaCup #18 - 1st Place Solution
hakubishin3
0
490
podman_update_2024-12
orimanabu
1
280
株式会社ログラス − エンジニア向け会社説明資料 / Loglass Comapany Deck for Engineer
loglass2019
3
32k
Featured
See All Featured
Fireside Chat
paigeccino
34
3.1k
What's in a price? How to price your products and services
michaelherold
243
12k
Facilitating Awesome Meetings
lara
50
6.1k
Music & Morning Musume
bryan
46
6.2k
Six Lessons from altMBA
skipperchong
27
3.5k
Thoughts on Productivity
jonyablonski
67
4.4k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
32
2.7k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.1k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.3k
A better future with KSS
kneath
238
17k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
29
2k
Java REST API Framework Comparison - PWX 2021
mraible
28
8.3k
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