yutopp's blog

サンドバッグになりたい

LLVMと俺俺ランタイムで例外を実装する(その1)

環境: Arch Linux(x86_64), LLVM 3.4, GCC 4.9.0

文鳥言語の言語機能に例外を追加したかったので,先にLLVM側の調査とランタイムを実装しました(進捗).自分で思い出すために後々読み返しそうなので,例外ハンドリングについてメモっておきます.

何かの参考になれば幸い.間違いなどは指摘していただけると嬉しいです.

今回実装したのはLinuxx86_64用の実装でtable-basedのものです.

コードはこちら yutopp/llvm-exception-handling-test

例外ハンドリング

LLVMの例外ハンドリングのドキュメントはこちら Exception Handling in LLVM

今回は,このドキュメントで示されている"Itanium ABI Zero-cost Exception Handling"を元に自前の例外ランタイムを実装したことになりますが,このドキュメントは,CやC++で使われている例外ハンドリングについて深く説明しているため,実際に言語のランタイムに実装するとなると少し物足りない感じがありました.でも読みやすくてスバラシイ〜

そして,次に参考にしたのが,LLVM PROJECT BLOG.New libunwind implementation in libc++abi

大体

例外のunwindには,libcxxabiに含まれるcxa*例外関数群が要求するUnwind*関数群(高レベルAPI)と,old HP libunwind projectで定義されたインターフェースであるunw*関数群(低レベル操作)の二通りがある.

多くのアーキテクチャでは”ゼロコスト”例外をC++に使っている.ゼロコストとは,例外が発生しない通常の実行では余計な命令が生じないものである.しかし,例外が投げられた場合には,ランタイムはどのようにレジスタをリストアし,現在のスタックフレームからcatch項まで"unwind"するかを,side tableを参照し把握しなければならない. スタックフレームを修正,全てのレジスタをリストアし,適切にスレッドをcatch節にresumeさせることが,libunwindの主な用途である.

みたいな事が書いてありました.

Itanium C++ ABI: Exception Handling でいうと,”libcxxabiに含まれるcxa*例外関数群” が”Level II: C++ ABI”以降,”Unwind*関数群”が,”Level I. Base ABI”になるようです.

従って,今回はLLVMと共に自前の例外ランタイムを実装したいので,このC++ ABIを使わずに,この部分を自前で実装すればいいことになります.また,Level 1 ABIの実装は共通なので,既にある実装を使うことができます. というわけで,Level 1 ABIについては,今回は既存の実装を利用しています(申し訳 of the world).

フォーマット

LLVMが生成してくれるASM テーブルフォーマット(ドキュメントてきとう訳)

例外が投げられた際に,どんな処理を取るべきか決定するために例外ハンドリングランタイムに使われるテーブルが2つあります.

Exception Handling Frame

例外ハンドリングフレームであるeh_frameは,DWARF debug infoに使われているunwind frameにとても良く似ています. このフレームは,現在のフレームをtear downし,以前のフレームの情報をリストアするために必要な全ての情報を含んでいます. コンパイル単位における,それぞれの関数の例外ハンドリングフレームに加え,全ての関数の共通する情報を定義した一般的な例外ハンドリングフレームも存在します.

Exception Tables

例外テーブルは関数のコードのある部分で例外が投げられた際に,どのアクションを起こすべきか,という情報を含んでいます. 例外テーブルを必要としないリーフ関数(注:関数テーブルエントリ(スタックフレームなど)を使用しない関数のこと)やnon-throwな関数のみを呼び出す関数を除いて,各関数には一つの例外テーブルが存在します.

これらのフォーマットの読み込みをコードに落とすのに,以下のスライドがとても参考になりました.

Compiler Internals: Exceptions and RTTI

また,例外処理で出てくる用語は以下の感じ

単語 意味
LSDA Language Specific Data Areaのこと.
landingpad 例外が投げられたあと,catch,またはclean upするための,ユーザーコードのセクションのこと.personality routineによってexception runtimeからコントロールを得た後,通常のユーザーコードに合流させるか,resumeによってランタイムに戻る,新しい例外をraiseするかといった目的に用いる.
personality routine C++(や他の言語)のランタイムライブラリにある,system unwind libraryとlanguage-specific exception handling semanticsとの間のインターフェースを提供する関数のこと.

LLVMでの流れ

  • landingpad 命令で例外の着地点をつくる
  • @llvm.eh.typeid.for を用いて例外を選択する

おわり

今回実装したもの

  • 言語固有の例外ハンドリングの関数(allocate, throw, resume, free)
  • personality routine

言語固有の例外ハンドリングの関数

C++ ABIを真似て適当に作った.

personality routine

今回の山場でした.unwindを行うライブラリと言語固有の機能のインターフェースを取り持つ関数です.

2段階のフェーズに分かれて呼ばれるようで,最初の探索フェーズで

  • LSDAの読み込み
  • landingpad の選択
  • call siteやaction_tableの読み込み
  • 適切なcatch節やclean upの選択
  • その情報の保存

を行い.

次の段階で,

  • 第一段階の情報の引き出し
  • 目的の節へのジャンプの登録

を行います.

C++D言語のランタイムの実装を参考にしました!

eh_personality.cc Source File

druntime/src/ldc/eh.d at ldc · ldc-developers/druntime

おわりに

くぅ〜疲れました!w

Binary Hacks ください.

その2に続く→

参考資料

dzeta.jp技術資料 - NPTLとC++例外処理 force_unwindの存在をこれで知りました.

g++の例外を捕まえよう!!(素手で)

exception handling | 0xdeadp0rk

Mono:Runtime:Documentation:LLVM

unwind自体の実装

gcc/libgcc/unwind-dw2.c at master · mirrors/gcc https://llvm.org/svn/llvm-project/libcxxabi/trunk/src/Unwind/UnwindLevel1.c