6

C で関数呼び出しをオーバーライドしようとしていますが、関数が同じコンパイル単位で使用されているときに問題に直面しています。以下のコードでは、関数 get_resolution() を置き換えようとしていますが、display.c ではなく test.c で実行した場合にのみ達成できます。

// display.c -------------------------------------------------------------

#include <stdio.h>

void get_resolution()
{
    printf("Original get_resolution\n");
}

void display()
{
    get_resolution();
}

// test.c ----------------------------------------------------------------

#include <stdio.h>

void __wrap_get_resolution()
{
    printf("Mock get_resolution\n");
    // __real_get_resolution(); // Should be possible to call original
}

int main()
{
    display();         // **ISSUE** Original get_resolution() is called
    get_resolution();  // __wrap_get_resolution() is called
    return 0;
}

// gcc -Wl,--wrap,get_resolution display.c test.c

私の要件は、main() から display() を呼び出すときに __wrap_get_resolution() を実行したいということですが、元の get_resolution() が呼び出されていることが常にわかります。逆アセンブリを少し分析すると、関数 get_resolution の呼び出し方が異なることがわかります。

display() 内 -> get_resolution() のアドレスは既に解決されています

00000000 <_get_resolution>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 18                sub    $0x18,%esp
   6:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
   d:   e8 00 00 00 00          call   12 <_get_resolution+0x12>
  12:   c9                      leave  
  13:   c3                      ret    

00000014 <_display>:
  14:   55                      push   %ebp
  15:   89 e5                   mov    %esp,%ebp
  17:   83 ec 08                sub    $0x8,%esp
  1a:   e8 e1 ff ff ff          call   0 <_get_resolution>
  1f:   c9                      leave  
  20:   c3                      ret    
  21:   90                      nop
  22:   90                      nop
  23:   90                      nop

main() 内 -> get_resolution のアドレスがまだ解決されていない

00000000 <___wrap_get_resolution>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 18                sub    $0x18,%esp
   6:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
   d:   e8 00 00 00 00          call   12 <___wrap_get_resolution+0x12>
  12:   c9                      leave  
  13:   c3                      ret    

00000014 <_main>:
  14:   55                      push   %ebp
  15:   89 e5                   mov    %esp,%ebp
  17:   83 e4 f0                and    $0xfffffff0,%esp
  1a:   e8 00 00 00 00          call   1f <_main+0xb>
  1f:   e8 00 00 00 00          call   24 <_main+0x10>
  24:   e8 00 00 00 00          call   29 <_main+0x15>
  29:   b8 00 00 00 00          mov    $0x0,%eax
  2e:   c9                      leave  
  2f:   c3                      ret    

問題は、関数 display() で使用される get_resolution() のアドレスをコンパイラーが解決するのを防ぎ、代わりに再配置テーブルを使用して、リンク段階で get_resolution() 関数をオーバーライドできるようにする方法です。

編集

  1. hroptatyr の応答に基づいて、追加void get_resolution() __attribute__((weak));すると、mingw-gcc を使用する場合の問題は解決しますが、ターゲット プラットフォーム QNX/ARM/gcc(4.4.2) では解決しません。
  2. 関数フックのような実行時メソッドでさえ、誰かが ARM ターゲットをサポートする優れたライブラリを指し示すことができれば受け入れられます。
4

6 に答える 6

5

そのためにプリプロセッサを使用するだけです:

void __wrap_get_resolution()
{
    /* calling the real one here */
    get_resolution();
}

#define get_resolution   __wrap_get_resolution

int main()
{
    /* the __wrap... gets called */
    get_resolution();
    ...
}

アイデアは、元の関数を表示する必要があるすべてのコードを配置した、ラッパー関数への関数呼び出しを「名前変更」することです。

より汚いバージョンは、次のように関数アドレスをローカルでシャドウすることです。

int main()
{
    void(*get_resolution)() = __wrap_get_resolution;
    get_resolution();
    ...
}

これは機能しますが、厄介な警告が表示される可能性があります。

編集
コメントで変更が望ましくないことを指摘しましたがdisplay.c、ここではhttp://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.htmlweakからの属性ソリューション

ウィーク シンボルは、ELF ターゲットでサポートされています。また、GNU アセンブラーとリンカーを使用する場合は a.out ターゲットでもサポートされています。

/* in display.c */
void __real_get_resolution()
{
    ...
}
void get_resolution() __attribute__((weak, alias("__real_get_resolution")));

/* in test.c */
void get_resolution()
{
    /* this version will take precedence over get_resolution() in display.c */
    ...
    /* lastly call the real thing */
    __real_get_resolution();
}

そしてこれからは、どこで呼び出してもget_resolution()そして「強力な」バージョンがコンパイルされていると)、ラップされたバージョンが呼び出されます。

于 2012-08-01T12:03:07.387 に答える
0

このようにするとどうなりますか:

gcc -Wl,--wrap,get_resolution test.c display.c

于 2012-08-01T12:01:40.453 に答える
0

gcc によって実行される「ラップ」機能は、コンパイラではなくリンカー段階で実行されます。get_resolution が同じコンパイル単位にあるため、コンパイラーは get_resolution がどこにあるかを認識しているため、リンカがそれを解決する必要はありません (ここで魔法が起こります)。

display.c (または含まれているヘッダー ファイルの 1 つ) を何らかの方法で変更せずにこれを解決できるとは思えません。最も簡単なのは、display.c とは別の独自の C ソース ファイルに get_resolution を配置することです。

于 2012-08-01T14:04:13.997 に答える
0

答えはおそらくここで提供されます: Cで関数呼び出しをオーバーライドする

ライブラリの関数をオーバーライドする場合は、 user (unser linux) を使用できますLD_PRELOAD

この助けを願っています。

よろしく。

于 2012-08-01T11:54:51.427 に答える
0

シェルコード以外ではなかなか実装できないと思います。理由はこれでわかります。

著者はC++コードの例を提供してくれました.同様の方法であなたの質問にそれを試してみましたが、あなたの望みを達成できませんでした.それを解決する唯一の方法はシェルコードを使用することかもしれませんが、LinuxのASLRはそれを行います.それが私の知っているすべてです。それがあなたの助けになることを願っています

于 2012-08-01T13:26:38.983 に答える
0

シンボルがアセンブリ ファイルで定義されている場合、アセンブラは常にシンボルを直接呼び出します。これを GNU で変更する方法はありませんas(私の知る限り)。これは、コンパイラが関数呼び出しをインライン化することをまだ決定していないことを前提としています!

解決策は、get_resolution内で未定義にすることdisplay.cです。これを行うには、2 つのファイルに分割することをお勧めします。1 つget_resolutionはソース ファイルで、もう 1 つは残りのソース ファイルです。これは、実際にファイルを分割せずに、#ifdefブロックを入れて異なる定義で 2 回コンパイルすることで実行できます。

于 2012-08-01T12:20:12.937 に答える