コンパイルされた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
あるため、呼び出し元が使用しないようにすることで、アプリケーションの移植性が制限されることです。
要するに、答えはイエスです。手を少し汚したいのであれば、署名を知らなくても関数をラップすることは間違いなく可能です。移植性についての約束はありません;)。