と、いうわけで、前回の話をふまえて、unwind-dwarf2環境下でC言語で例外を投げてみる

#include <stddef.h>
struct type_info;
struct pointer_type_info {
  /* type_info */
  void *vtbl;
  const char *name;

  /* pbase_type_info */
  unsigned int flags;
  void *pointee;
};

extern char _ZTVN10__cxxabiv119__pointer_type_infoE[]; /* pointer_type_infoのvtbl */
extern char _ZTIc[];

static const char pointer_to_constchar_name[] = "PKc";

struct pointer_type_info pointer_to_constchar = {
  (void*)&(_ZTVN10__cxxabiv119__pointer_type_infoE[8]),
  pointer_to_constchar_name,
  0,
  &_ZTIc,
};

void *__cxa_allocate_exception( size_t size );
void __cxa_throw( void *obj,
		  struct type_info *tinfo,
		  void (*dest) (void*) ) __attribute__ ((__noreturn__));

void
throw_string( void )
{
  const char **ptr = (const char**)__cxa_allocate_exception( sizeof(char*) );
  *ptr = "nanika";
  __cxa_throw( ptr, (struct type_info*)&pointer_to_constchar, NULL );
}

void throw_string_end(void){}

__asm__ (
	 ".section .eh_frame, \"a\", @progbits\n"
	 ".cie:\n"
	 ".long .cie_end-.cie_begin\n"

	 ".cie_begin:\n"
	 ".long 0\n"
	 ".byte 1\n"
	 ".ascii \"\\0\"\n"
	 ".uleb128 1\n"
	 ".sleb128 -4\n"
	 ".byte 8\n" /* 8番目がretaddr */
	 ".uleb128 0\n"
	 ".byte	0xc	# DW_CFA_def_cfa\n"
	 ".uleb128 0x4\n"
	 ".uleb128 0x4\n"
	 ".byte	0x88	# DW_CFA_offset, column 0x8\n" / 8番目のレジスタにオフセット設定 */
	 ".uleb128 0x1\n" /* 8番目のオフセットが 1 (フレームから戻り値までのオフセット) */

	 ".align 4\n"
	 ".cie_end:\n"
);


__asm__ (
	 ".fde:\n"
	 ".long .fde_end-.fde_begin\n"

	 ".fde_begin:\n"
	 ".long .fde_begin-.cie\n"
	 ".long throw_string\n"
	 ".long throw_string_end - throw_string\n"
	 ".uleb128 0x0	# Augmentation size\n"
	 ".byte	0xe	# DW_CFA_def_cfa_offset\n" /* フレームアドレスはcfaレジスタから見て8byte目に埋まってる */
	 ".uleb128 0x8\n"
	 ".byte	0x85	# DW_CFA_offset, column 0x5\n" /* 5番目は ebp */
	 ".uleb128 0x2\n"
	 ".byte	0xd	# DW_CFA_def_cfa_register\n" /* cfaレジスタは5番目(ebp) */
	 ".uleb128 0x5\n"
	 ".align 4\n"
	 ".fde_end:\n");

これで動くはず。(コメントは勘なので信じないで)


ただし、次のような極めて些細な欠点が。

  • __asm__使ったらC言語じゃないような。
  • CPU依存
  • -fomit-frame-pointerする時は、書き換えないと使えない
  • -fomit-frame-pointerすると、ローカル変数が増えるたびに書き換えないと

これらの欠点を乗り越えるだけの勇気があればC言語で例外を投げるのだって問題無くできるはず。ソフトウェア開発に必要なのは勇気(Courage)です!!


あー、あと、catchするほうはLSDAの解析がめんどうなので、皆さんの宿題にしておきます(適当)。


と、いうわけで、謎は大体明かになった。あとは、適当に必要になった時に調べればなんとかなりそうだ。
最後に_Unwind_RaiseExceptionについて簡単に説明して、終わりにしよう。


_Unwind_RaiseExceptionは、そんなに難しいことをしているわけではない。personality呼び出しと、スタック巻き戻しをループしてるだけだ。
ただ、少し見ればわかるように、このループは、二回回される。_Unwind_RaiseException_Phase2が二回目のソレ。
一週目がハンドラ探し。personalityに引数_UA_SEARCH_PHASEを渡して、_URC_HANDLER_FOUNDが返ってきたら、ハンドラを見つけたとする。んで、二週目が_UA_CLEANUP_PHASEを渡して、クリーンアップの実行。
んで、二週目の時に、一週目で見つけたハンドラには、_UA_HANDLER_FRAMEを渡してごにょごにょ。_URC_INSTALL_CONTEXTが返ってきたら、クリーンアップを終了して、uw_install_contextで、そこまでジャンプ。と、いう感じ。

uw_install_contextは、SjLjのほうの実装を見れば、非常にわかりやすくて良い。

static void __attribute__((noreturn))
uw_install_context (struct _Unwind_Context *current __attribute__((unused)),
                    struct _Unwind_Context *target)
{
  _Unwind_SjLj_SetContext (target->fc);
  longjmp (target->fc->jbuf, 1);
}

longjmpしてる。


だがしかし!!unwind-dwarf2のほうの実装がいまいちよくわからないのだった。builtin_eh_returnがよくわからない…


と、いうわけで、最後の最後がよくわからないという非常に中途半端な感じを残しつつ、もう疲れたので終わりにしようと思います。いや、ほんと疲れたよ。色々勉強にはなったけど。