最近、DLL (c/c++ で記述) を逆アセンブルしたところ、コード セグメント内に多くの「ジャンプ スタブ」があることに気付きました。これらのスタブは、DLL 内の関数にジャンプするだけです。
例えば:
jmp foo()
jmp foo2()
...
コンパイラ (Visual Studio 2012) がこれらの関数スタブをバイナリ内に含めるのはなぜですか?
ありがとう!
最近、DLL (c/c++ で記述) を逆アセンブルしたところ、コード セグメント内に多くの「ジャンプ スタブ」があることに気付きました。これらのスタブは、DLL 内の関数にジャンプするだけです。
例えば:
jmp foo()
jmp foo2()
...
コンパイラ (Visual Studio 2012) がこれらの関数スタブをバイナリ内に含めるのはなぜですか?
ありがとう!
すべてのスタブの後に大量の 0xCC バイトがありますか? その場合、インクリメンタル リンクを有効にしてコンパイルされたコードを見ています (デバッグ ビルドのデフォルト)。
インクリメンタル リンク用にコンパイルする場合、コンパイラはすべての関数のスタブを作成し、すべての呼び出しがスタブを経由するようにします。関数を更新されたコードに置き換える必要がある場合は、新しいコードを最後に追加し、ジャンプ サンクにパッチを当てるだけで済みます。既存の呼び出しはすべて新しいコードにリダイレクトされます。追加の CC は、新しい関数が追加された場合に備えて、より多くのスタブ用に予約されています。
詳細な背景情報については、 MSDN を参照してください。
これが、リンカと DLL のシンボルが「マッシュアップ」される方法です。これにより、シンボル テーブルで正しい種類のオフセットが使用され、DLL をロードする (したがって、DLL 内の関数のアドレスを更新する) ローダーによって解決できることが保証され、コンパイルされたコードは、たとえば、関数ポインタ:
void (*fptr)() = foo;
が DLL 内の場所への単なる参照である場合foo
、このアドレスがどのように解決されるかはローダーに依存します。それを解決するのは、「本当の foo にたどり着く foo() エントリーポイントがここにある」という問題を解決するよりもはるかに複雑です。
DLL は再配置可能です。つまり、メモリ内のどこにでも配置される可能性があります。これは、それらへのすべての呼び出しを書き直す必要があることを意味します。このようなすべての呼び出しを小さな jumptable にまとめると、再配置の場合に 1 ページだけを書き換える必要があります。コードの変更されていないページはプロセス間で共有できますが、各プロセスには変更されたページの独自のコピーがあるため、これは重要です。