memologue RSSフィード

書いている人

日記というよりは備忘録、ソフトウェア技術者の不定期メモ。あるいはバッドノウハウ集。プライベートで調査した細々した諸々のスナップショット。嘘が散りばめられています。ISO/IEC 14882(C++)とPOSIX, GCC, glibc, ELFの話ばかりで、WindowsやMacの話はありません。特に記載がなければLinux/x86とILP32が前提です。時間の経過と共に古い記事は埋もれてしまいます。検索エンジンから飛んできた場合は、ページ内検索をご利用いただくかgoogleキャッシュを閲覧してみてください。技術的な記事を書きためる場所として使っています。言及してもらえると喜びます。主要な記事の一覧を書いておきます:

にんきコンテンツ: [GCC] mainを一度も呼ばないばかりか蹂躙する / Binary2.0 Conference 2006 発表資料 / C++ で SICP / ついカッとなって実行バイナリにパッチ / hogetrace - 関数コールトレーサ

昔のPOSIX関係の記事: シグナルの送受に依存しない設計にしよう / シグナルハンドラで行ってよい処理を知ろう / マルチスレッドのプログラムでのforkはやめよう / スレッドの「非同期キャンセル」を行わない設計にしよう / スレッドの「遅延キャンセル」も出来る限り避けて通ろう / マルチスレッドプログラミングの「常識」を守ろう / C++でsynchronized methodを書くのは難しい / シグナルハンドラからのforkするのは安全か? / g++ の -fthreadsafe-statics ってオプション知ってます? / 非同期シグナルとanti-pattern / localtimeやstrtokは本当にスレッドセーフにできないのか / UNIXの規格について / マルチスレッドと共有変数 - volatile?なにそれ。 / type punning と strict aliasing / アセンブラで遊ぶ時に便利なgdb設定 / 最近の記事は一覧から

2004-06-02

[] gccの生成するトランポリンコードについて

gccでは、次のように、関数の中で関数を定義することができます(内部に書かれた関数をnested functionなどと呼びます)。

void func(void) {
  int x;
  void inner_func(void) {
    printf("%d\n", x);  // a.
  }
  inner_func();         // b.
}

ここで、nested function では、外側の関数で宣言されているローカル変数を参照することができます(a.の部分)。どのように外側の関数が呼び出されたかによって、外側の関数のローカル変数のアドレスは変化しますから、b. でinner_func()を呼ぶとき、何らかの方法でそのアドレス(以下Xとします)を渡してやる必要があります。さもないとa.で正しい値をprintfできません。


Linux/x86 + gcc-3.3.3 では、ECXレジスタを用いてアドレスXを渡すことになっているようです。

func:
        (略)
        leal    -8(%ebp), %ecx 
        call    inner_func.0 

さて、C言語には関数ポインタというものがあります。関数ポインタを用いて、次のようなことをしたらどうなるでしょう?

void caller(void (*fp)(void)) {
  fp();
}

void func(void) {
  int x;
  void inner_func(void) {
    printf("%d\n", x);  // a.
  }
  caller(inner_func);   // b.
}

caller関数にアドレスXを渡し、そしてcaller関数がinner_func関数にアドレスXを渡せばよいと思われるかもしれませんが、caller関数はlibcなど外部の、コンパイル済みの関数かもしれませんので*1、caller関数を通常と異なる方法でコンパイルすることはできません。


ここで必要になるのが「トランポリン」です。func関数は、次のようにコンパイルされます。

  • func関数のスタック上に、動的に実行コードを書く
  • caller関数には、inner_funcのアドレスを渡すのではなく、いま書いた実行コードの先頭アドレスを渡すようにする

動的に生成される実行コードは、次の2命令です。

  • アドレスXをECXレジスタに書き込む命令 (mov $0xXXXXXXXX,%ecx)
  • inner_func関数に無条件にジャンプする命令 (jmp $0xYYYYYYYY)

関数ポインタを経由した関数fの呼び出しが、一旦自分が動的に生成したコードを経由することから、「トランポリン」と呼ばれるわけですね。

func:
        (略)
        leal    -40(%ebp), %eax 
        movl    $inner_func.0, %ecx 
        (略)
        movl    %ecx, %edx 
        movb    $-71, (%eax)    # -71 (=0xB9) は "ECXへのMOV"
        leal    -8(%ebp), %ecx 
        movl    %ecx, 1(%eax)   # アドレスX
        movb    $-23, 5(%eax)   # -23 (=0xE9) は "JMP"
        movl    %edx, 6(%eax)   # inner_funcのアドレス
        (略)
        leal    -40(%ebp), %eax 
        pushl   %eax            # callerに渡すのは動的に生成
                                # したコードの先頭アドレス
        call    caller 

        # あ、-O2 でコンパイルしたほうがむしろ読みやすい鴨... orz

以上。なお、g++ではnested functionは許可されていません。


参考:

http://www.uwsg.iu.edu/hypermail/linux/kernel/9912.3/0437.html

*1:qsort(3)など

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/yupo5656/20040602/p1
Connection: close