1

関数を呼び出すプラグインを持つ複雑なプログラムに取り組んでいますが、これらの関数のメソッドは起動時に選択され、関数ポインターを使用して割り当てられます。

関数ポインターを渡すのではなく、適切な関数を呼び出すために、メインの実行可能ファイルに効率的なラッパー関数をいくつか用意したいと思います。

これはプラグイン インターフェイス用であるため、呼び出し規約は、__cdeclまたは__stdcallビルド ターゲットに応じて (マクロを使用して) 定義され、関数は として宣言されextern "C"ます。

基本的に、プラグインが必要に応じてロードできるように、実行可能ファイルで SYMBOL を宣言できるようにしたいと考えています。複雑な科学的問題を解決するために必要なさまざまなタスクについて、これらのタスクの結果を取得するソリューションまたはメソッドの範囲はさまざまですが、これらはプラグイン自体に保存されるため、新しいメソッドを簡単に追加できます (再コンパイルする必要はありません)。アプリケーション全体) これにより、新しいメソッドを簡単に共有できます。基本コードを持っている人なら誰でも、経験を必要としないプラグインを自分で追加できるからです。

私が考え出したどの方法でも、この概念を使用するか、プラグインをロードするときに関数マップをプラグインに渡す必要がありますが、その関数マップの詳細は、ロードされる構成とプラグインに依存するため、実際にはしません問題になるプラグインのロードが完了するまで、それが何であるかを知ってください。したがって、私の解決策は、マップをグローバル変数のセットとしてメインの実行可能ファイルに保存し、ラッパー関数からアクセスできるようにすることです。

ただし、関数には呼び出し後と戻る前にスタックを操作する呼び出し規則があるため、これは単純ではありません。これはラッパーでは無視する必要があります。また、 intel x386 の関数呼び出しでjmpはなく、intel x386 ASM の無条件ジャンプを実行する必要があります。 callASM と制御は、ジャンプ先の関数から、ラッパーではなく呼び出し元のコードに戻る必要があります。ただし、コンパイラ/プラットフォーム/プロセッサとは無関係にこれを行うには、C/C++ コードが必要です。

以下は、私のアイデアをテストし、私がやりたいことを示すために収集するためにスローする基本的な概念の例です。

C++ コード (Microsoft Visual C++ 2010 (固有))

#include <iostream>
void * pFunc;
int doit(int,int);
int wrapper(int, int);
int main() {
    pFunc = (void*)doit;
    std::cout << "Wrapper(2,3): " << wrapper(2,3) << std::endl;
    std::cout << "doit(2,3):    " << doit(2,3) << std::endl;
    return 0; }
int doit(int a,int b) { return a*b; }
__declspec(naked) int wrapper(int, int) { __asm jmp pFunc }

コードは正常に動作することがテストされており、両方の呼び出しで 6 が出力されます

ラッパーと doit の ASM 出力

PUBLIC  ?wrapper@@YAHHH@Z               ; wrapper
; Function compile flags: /Odtp
;   COMDAT ?wrapper@@YAHHH@Z
_TEXT   SEGMENT
___formal$ = 8                      ; size = 4
___formal$ = 12                     ; size = 4
?wrapper@@YAHHH@Z PROC                  ; wrapper, COMDAT
; File c:\users\glen fletcher\documents\visual studio 2010\projects\test_wrapper\test_wrapper.cpp
; Line 15
    jmp DWORD PTR ?pFunc@@3PAXA         ; pFunc
?wrapper@@YAHHH@Z ENDP                  ; wrapper
_TEXT   ENDS
PUBLIC  ?doit@@YAHHH@Z                  ; doit
; Function compile flags: /Ogtp
;   COMDAT ?doit@@YAHHH@Z
_TEXT   SEGMENT
_a$ = 8                         ; size = 4
_b$ = 12                        ; size = 4
?doit@@YAHHH@Z PROC                 ; doit, COMDAT
; Line 14
    push    ebp
    mov ebp, esp
    mov eax, DWORD PTR _a$[ebp]
    imul    eax, DWORD PTR _b$[ebp]
    pop ebp
    ret 0
?doit@@YAHHH@Z ENDP                 ; doit
; Function compile flags: /Ogtp
_TEXT   ENDS

ラッパー用非ラッパー ASM

PUBLIC wrapper
_1$ = 8
_2$ = 12
_TEXT SEGMENT
wrapper PROC
   push ebp
   mov ebp, esp
   mov eax, DWORD PTR _2$[ebp]
   push eax
   mov ecx, DWORD PTR _1$[ebp]
   push ecx
   call DWORD PTR pFunc
   add esp, 8
   pop ebp
   ret 0
wrapper ENDP
_TEXT ENDS

クロスプラットフォームおよびクロスコンパイラの方法で生成された元のコードを取得するにはどうすればよいですか?? コンパイラによって生成されたエピローグおよびプロローグ コードを使用する C/C++ 関数の標準とは対照的に、注: プロセッサに関する仮定を行いたくないため、別の ASM ファイルを実行できず、コンパイラにコードを生成させたい無条件ジャンプ文だけで。

gotopFunc はラベルではなく変数であるため機能しませgotoん。とにかく関数間で機能するかどうかさえわかりません。

4

2 に答える 2

2

ご質問の限りでは、

クロスプラットフォームおよびクロスコンパイラの方法で生成された元のコードを取得するにはどうすればよいですか?

行く、答えは「まったくない」です。

関数呼び出し規則は、プラットフォームおよびコンパイラ/言語の仕様に深く関わっていますABI (Application Binary Interface)と呼ばれるものに触れています。次のような問題:

  • すべての番号/タイプ/パラメーターの順序について、パラメーターは呼び出し元から呼び出された関数にどのように/どこで渡されますか?
  • 言語の「隠された」機能 (C++ などthis) はどのように実装されていますか?
  • レジスタの使用に関するルールは何ですか (「ターゲット コンテキスト」への関数呼び出しを行うことによって、どのレジスタが上書きされますか) ?
  • 「値を返す」のすべてのタイプについて、戻り値はどのように/どこにありますか?
  • ソース (呼び出し元) とターゲット (呼び出し先) のコンテキストは、同じデータ構造のレイアウト規則を使用していますか?
  • プロセッサの動作状態の変化にどのように対処できますか (64 ビット モードで実行中に 32 ビット コードを呼び出そうとした場合や、その逆の場合など)。

私はこの SO スレッドで同様の回答をしましたが、特に「ダウンコール」 64bit Windows --> 32bit Windows の実行に関する質問を対象としていますstdcall。残念ながら、「複雑で、一般的には不可能であり、常にコード/コンパイラとOSに非常に強く依存している」ことを除いて、そこに追加することはあまりありません.

これは、特定のケースで実行できます(専門用語は「サンク」です。すべての「サンク」は非常に具体的です。たとえば、呼び出された関数が 32 ビット Windows/x86 スタイルfastcallを使用し、単一のパラメーターを持つことがわかっている場合は、" thunk" は、たとえば 64 ビット Linux コードから呼び出すことができるインターフェイス (およびおそらくプロセッサ状態スイッチ) を実行します。そのサンクは、最初のパラメーターが渡された浮動小数点値であるサンクとは異なりますが、XMM0. .. 等々。

一般的なケースについては... SOであるプログラミング知識の無限のヒープを参照してください。申し訳ありませんが、一般的な関数ポインターはありません:(

編集: 問題がコード生成である場合は、次のことを試してください。

/* sourcefile 1 */
extern void (*p)(char *, ...);

static __inline__ void wrapper(char *arg, char *s) {
    return p(arg, s);
}

int main(int argc, char **argv)
{
    wrapper("Hello, I am %s\n", argv[0]);
    return 0;
}

/* sourcefile 2 */
extern void printf(char*, ...);
void (*p)(char *, ...) = printf;

これら 2 つをコンパイルしgcc 、最適化を使用すると、コンパイラは次のコードを作成しますmain

0000000000400500 <メイン>:
  400500: 48 83 ec 08 サブ $0x8、%rsp
  400504: 48 8b 36 移動 (%rsi)、%rsi
  400507: bf 0c 06 40 00 mov $0x40060c,%edi
  40050c: ff 15 d6 03 10 00 callq *1049558(%rip) # 5008e8 <p>
  400512: 31 c0 xor %eax,%eax
  400514: 48 83 c4 08 追加 $0x8、%rsp
  400518: c3 retq

これはほとんどあなたが望むものです - を排除する wrapper()ことを除いて、関数ポインタを介して呼び出しを直接インライン化します。

于 2013-11-14T11:47:07.073 に答える
0

ネイキッド関数を使用したり、関数ポインターのリストを渡したりするのではなく、問題の解決策を見つけました。

関数ポインターの構造体へのポインターを渡すことができます。

struct Functions {
   bool (AppAPI *logInfo(std::string,...)),
   bool (AppAPI *logWarn(std::string,...)),
   bool (AppAPI *logError(std::string,...)),
   bool (AppAPI *registerFunction(std::string,void *))
   ...
} PluginFunctions;

for (int i = 0;i<plugins;i++) {
   plugin[i].initialize(&PluginFunctions)
}

PluginFunctions.logInfo = LogInfo;
...

プラグインの init 関数には構造体へのポインターが渡されるため、これを格納して関数ポインターの現在の値をメモリからロードできます。構造体はメモリ内のポインターの単なるテーブルであり、関数ポインターは構造体の後に設定できます。プラグインに渡され、プラグインを更新します。

于 2013-11-15T03:03:21.417 に答える