2

Cバインディングと任意のシグネチャを持つ関数が与えられた場合、関数へのポインタを作成し、それを渡し、ラップして、呼び出すのは簡単です。

int fun(int x, int y)
{
   return x + y;
}
void* funptr()
{
   return (void*)&fun;
}
int wrapfun(int x, int y)
{
   // inject additional wrapper logic
   return ((int (*)(int, int))funptr())(x, y);
}

発信者と着信者が同じ呼び出し規約に従い、署名に同意する限り、すべてが機能します。

ここで、ライブラリを数千の関数でラップしたいとします。ラップするすべての関数の名前を使用nmまたはreadelf取得できますが、署名を気にする必要はなく、ライブラリに関連付けられたヘッダーファイルをインクルードする必要もありません。

場合によっては、バージョンとプラットフォーム間で外観上の変更が発生することを考えると、ヘッダーをきれいに含めることができない場合があります。例えば:

// from openssl/ssl.h v0.9.8
SSL_CTX* SSL_CTX_new(SSL_METHOD* meth);
// from openssl/ssl.h v1.0.0
SSL_CTX* SSL_CTX_new(const SSL_METHOD* meth);

それが私の背景の論理的根拠であり、あなたはそれを離れるか、または取るかもしれません。とにかく、私の質問はこれです:

書く方法はありますか

// pseudocode
void wrapfun()
{
    return ((void (*)())funptr())();
}

の呼び出し元はwrapfunの署名を知っていますがfun、それwrapfun自体は知っている必要はありませんか?

4

2 に答える 2

5

コンパイルされたC関数から生成されたアセンブリを見ると、すべての関数本体がラップされていることがわかります。

pushq %rbp
movq  %rsp, %rbp
; body
leave
ret

http://en.wikipedia.org/wiki/X86_instruction_listingsleaveは、命令を(AT&T構文で)80186に相当するものとしてリストしています。

movq  %rbp, %rsp
popq  %rpb

つまりleave、最初の2行の逆です。呼び出し元のスタックフレームを保存し、独自のスタックフレームを作成して、最後に巻き戻します。

クロージングは​​、ここで得retたものの逆であり、 http://www.unixwiz.net/techtips/win32-callconv-asm.htmlは、これらのペアの命令中に発生する命令ポインターレジスタの非表示のプッシュとポップを示しています。call

このアセンブリwrapfunはコンパイラによって関数用に作成されているため、void関数ポインタ呼び出しが単独で機能しない理由。funラッパーを作成して、呼び出し元が設定したスタックフレームを、独自のスタックフレームを邪魔することなく、の呼び出しに直接渡すことができるようにする必要があります。つまり、Cの呼び出し規約を遵守し、同時に違反します。

Cプロトタイプを考えてみましょう

int wrapfun(int x, int y);

アセンブリ実装とペアになっています(AT&T x86_64)

  .file "wrapfun.s"
  .globl wrapfun
  .type   wrapfun, @function
wrapfun:
  call    funptr
  jmp     *%rax
  .size   wrapfun, .-wrapfun

fun基本的に、スタックを私のスタックとまったく同じように見せたいので、通常のスタックポインタとベースポインタの操作をスキップします。を呼び出すとfunptr、独自のスタックスペースが作成され、結果がレジスタに保存されRAXます。独自のスタックスペースがなく、呼び出し元IPがスタックの一番上にうまく座っているため、ラップされた関数に無条件でジャンプして、最後までジャンプさせることができますret。このようにして、関数ポインターが呼び出されると、呼び出し元によってセットアップされたスタックが表示されます。

ローカル変数を使用したり、パラメーターをに渡したりする必要がある場合はfunptr、いつでもスタックを設定し、呼び出しの前にスタックを破棄できます。

wrapfun:
  pushq   %rbp
  movl    %rsp, %rbp ; set up my stack
  call    funptr
  leave              ; tear down my stack
  jmp     *%rax

または、コンパイラが前後に何をするかについての知識を利用して、このロジックをインラインアセンブリに埋め込むこともできます。

void wrapfun()
{
    void* p = funptr();
    __asm__(
        "movq -8(%rbp), %rax\n\t"
        "leave\n\t"
        "popq %rbx\n\t"
        "call *%rax\n\t"
        "pushq %rbx\n\t"
        "pushq %ebp\n\t"    // repeat initial function setup
        "movq %rsp, %rbp"   // so it can be torn down correctly
    );
}

このアプローチには、魔法の前にCローカル変数を簡単に宣言できるという利点があります。宣言された最後のローカル変数はRBP-sizeof(var)にあり、スタックを破棄する前にRAXに保存します。もう1つの考えられる利点は、C前処理装置を使用して、たとえば、個別のソースファイルを必要とせずにインライン32ビットまたは64ビットアセンブリを実行できることです。

編集:欠点は、IPをレジスタに保存する必要がRBXあるため、呼び出し元が使用しないようにすることで、アプリケーションの移植性が制限されることです。

要するに、答えはイエスです。手を少し汚したいのであれば、署名を知らなくても関数をラップすることは間違いなく可能です。移植性についての約束はありません;)。

于 2012-11-09T18:04:28.713 に答える
2

Ryan の回答に加えて、libffi ( GCC コンパイラ内にある可能性のある外部関数インターフェイス ライブラリ) の使用も検討する必要があります。それはあなたの目標に適しており、詳細を「移植可能」に抽象化します (それによってサポートされる多くのアーキテクチャ、システム、および ABI のために)。

于 2012-11-09T18:21:06.033 に答える