72

呼び出しをログに記録するために、さまざまな API への特定の関数呼び出しをオーバーライドしたいのですが、実際の関数に送信される前にデータを操作したい場合もあります。

たとえばgetObjectName、ソース コードで関数を何千回も使用するとします。この関数の動作を変更して別の結果を確認したいので、一時的にこの関数をオーバーライドしたいことがあります。

次のような新しいソース ファイルを作成します。

#include <apiheader.h>    

const char *getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return "name should be here";
}

通常どおりに他のすべてのソースをコンパイルしますが、API のライブラリにリンクする前に、まずこの関数にリンクします。オーバーライド関数内で実際の関数を呼び出せないことを除いて、これは正常に機能します。

リンク/コンパイル エラー/警告を取得せずに関数を「オーバーライド」する簡単な方法はありますか? 理想的には、リンク オプションをいじったり、プログラムの実際のソース コードを変更したりするのではなく、余分なファイルを 1 つか 2 つコンパイルしてリンクするだけで関数をオーバーライドできるようにしたいと考えています。

4

10 に答える 10

87

--wrapgcc を使用すると、Linux では次のようにリンカー フラグを使用できます。

gcc program.c -Wl,-wrap,getObjectName -o program

関数を次のように定義します。

const char *__wrap_getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return __real_getObjectName( anObject ); // call the real function
}

これにより、へのすべての呼び出しがgetObjectName()(リンク時に) ラッパー関数に再ルーティングされることが保証されます。ただし、この非常に便利なフラグは、Mac OS X の gcc にはありません。

extern "C"ただし、g++ でコンパイルする場合は、ラッパー関数を で宣言することを忘れないでください。

于 2009-03-06T03:24:15.427 に答える
82

intercept.h呼び出しをキャプチャ/変更するのがソースのみの場合、最も簡単な解決策は、ヘッダー ファイル ( ) を次のようにまとめることです。

#ifdef INTERCEPT
    #define getObjectName(x) myGetObjectName(x)
#endif

次に、次のように関数を実装します (intercept.cこれには は含まれませんintercept.h)。

const char *myGetObjectName (object *anObject) {
    if (anObject == NULL) return "(null)";
    return getObjectName(anObject);

次に、呼び出しをインターセプトする各ソース ファイルの上部に次の行があることを確認します。

#include "intercept.h"

" " でコンパイルすると-DINTERCEPT、すべてのファイルが実際の関数ではなく関数を呼び出しますが、関数は依然として実際の関数を呼び出します。

" " なしでコンパイルする-DINTERCEPTと、インターセプトが発生しなくなります。

すべての呼び出し (ソースからの呼び出しだけでなく) を傍受したい場合は、少しトリッキーです。これは通常、実際の関数の動的な読み込みと解決 (dlload-およびdlsym-型呼び出しを使用) で行うことができますが、あなたの環境では必要ないと思います。場合。

于 2009-03-06T02:49:22.577 に答える
39

LD_PRELOADトリックシーを使用して関数をオーバーライドできますman ld.so。共有ライブラリを関数でコンパイルし、バイナリを開始します (バイナリを変更する必要さえありません!) のようにLD_PRELOAD=mylib.so myprog

関数の本体 (共有ライブラリ内) では、次のように記述します。

const char *getObjectName (object *anObject) {
  static char * (*func)();

  if(!func)
    func = (char *(*)()) dlsym(RTLD_NEXT, "getObjectName");
  printf("Overridden!\n");     
  return(func(anObject));    // call original function
}

プログラムを変更/再コンパイルせずに、stdlib からであっても、共有ライブラリから任意の関数をオーバーライドできるため、ソースがないプログラムでトリックを実行できます。いいじゃないですか。

于 2009-03-06T08:29:47.580 に答える
27

GCC を使用している場合は、関数をweak. これらは、弱い関数でオーバーライドできます。

test.c :

#include <stdio.h>

__attribute__((weak)) void test(void) { 
    printf("not overridden!\n"); 
}

int main() {
    test();
}

それは何をするためのものか?

$ gcc test.c
$ ./a.out
not overridden!

test1.c :

#include <stdio.h>

void test(void) {
    printf("overridden!\n");
}

それは何をするためのものか?

$ gcc test1.c test.c
$ ./a.out
overridden!

残念ながら、それは他のコンパイラでは機能しません。ただし、GCC を使用してコンパイルしている場合は、API 実装ファイルにインクルードするだけで、独自のファイルにオーバーライド可能な関数を含む弱い宣言を含めることができます。

弱いdecls.h :

__attribute__((weak)) void test(void);
... other weak function declarations ...

functions.c :

/* for GCC, these will become weak definitions */
#ifdef __GNUC__
#include "weakdecls.h"
#endif

void test(void) { 
    ...
}

... other functions ...

これの欠点は、API ファイルに何かを行わないと完全に機能しないことです (これらの 3 行と weakdecl が必要です)。しかし、その変更を行った後は、グローバル定義を 1 つのファイルに書き込んでリンクすることで、関数を簡単にオーバーライドできます。

于 2009-03-06T03:13:15.780 に答える
12

多くの場合、関数をラップまたは置換することにより、既存のコードベースの動作を変更することが望ましいです。これらの関数のソースコードを編集することが実行可能なオプションである場合、これは簡単なプロセスになる可能性があります。関数のソースを編集できない場合(たとえば、関数がシステムCライブラリによって提供されている場合)、別の手法が必要になります。ここでは、UNIX、Windows、およびMacintoshOSXプラットフォーム向けのこのような手法を紹介します。

これは、OS X、Linux、およびWindowsでこれがどのように行われたかをカバーする優れたPDFです。

ここに記載されていない驚くべきトリックはありません(これは驚くべき一連の応答です)...しかし、それは素晴らしい読み物です。

DanielS.MyersおよびAdamL.Bazinetによる、Windows、UNIX、およびMacintosh OS Xプラットフォームでの任意の関数のインターセプト(2004)

別の場所からPDFを直接ダウンロードできます(冗長性のため) 。

そして最後に、前の2つのソースがどういうわけか炎上した場合、これがGoogleの検索結果です

于 2009-03-23T13:23:53.950 に答える
9

関数ポインタをグローバル変数として定義できます。呼び出し元の構文は変更されません。プログラムが起動すると、ログを有効にするようにコマンドライン フラグまたは環境変数が設定されているかどうかを確認し、関数ポインターの元の値を保存して、ログ関数に置き換えることができます。特別な「ロギング対応」ビルドは必要ありません。ユーザーは「現場で」ログを有効にすることができました。

呼び出し元のソース コードを変更できる必要がありますが、呼び出し先は変更できません (これは、サード パーティのライブラリを呼び出すときに機能します)。

foo.h:

typedef const char* (*GetObjectNameFuncPtr)(object *anObject);
extern GetObjectNameFuncPtr GetObjectName;

foo.cpp:

const char* GetObjectName_real(object *anObject)
{
    return "object name";
}

const char* GetObjectName_logging(object *anObject)
{
    if (anObject == null)
        return "(null)";
    else
        return GetObjectName_real(anObject);
}

GetObjectNameFuncPtr GetObjectName = GetObjectName_real;

void main()
{
    GetObjectName(NULL); // calls GetObjectName_real();

    if (isLoggingEnabled)
        GetObjectName = GetObjectName_logging;

    GetObjectName(NULL); // calls GetObjectName_logging();
}
于 2009-03-06T03:03:48.063 に答える
3

2 つのスタブ ライブラリを含むリンカーでこれを行うには、トリッキーな方法もあります。

ライブラリ #1 はホスト ライブラリに対してリンクされ、別の名前で再定義されているシンボルを公開します。

ライブラリ #2 はライブラリ #1 に対してリンクされ、呼び出しをインターセプトし、ライブラリ #1 で再定義されたバージョンを呼び出します。

ここでのリンクの順序には十分注意してください。そうしないと機能しません。

于 2009-03-06T02:57:33.493 に答える
0

共有ライブラリ (Unix) または DLL (Windows) を使用してこれを行うこともできます (パフォーマンスが少し低下します)。その後、読み込まれる DLL を変更できます (デバッグ用の 1 つのバージョン、非デバッグ用の 1 つのバージョン)。

私は過去に同様のことをしました (あなたが達成しようとしていることを達成するためではありませんが、基本的な前提は同じです) がうまくいきました。

【OPコメントを元に編集】

実際、私が関数をオーバーライドしたい理由の 1 つは、異なるオペレーティング システムでは動作が異なると思われるためです。

これに対処するには、(私が知っている)2 つの一般的な方法があります。共有 lib/dll を使用する方法と、リンク先の異なる実装を作成する方法です。

両方のソリューション (共有ライブラリまたは異なるリンク) では、foo_linux.c、foo_osx.c、foo_win32.c (またはより良い方法は linux/foo.c、osx/foo.c、および win32/foo.c) があり、次に適切なものをコンパイルしてリンクします。

異なるプラットフォーム用の異なるコードと debug -vs- release の両方を探している場合は、最も柔軟な共有 lib/DLL ソリューションを使用する傾向があります。

于 2009-03-06T05:04:11.537 に答える
0

以下は私の実験です。本体と最後に4つの結論があります。

短縮版

一般的に言えば、関数を正常にオーバーライドするには、次のことを考慮する必要があります。

  • 弱い属性
  • 翻訳単位の配置

ロングバージョン

これらのソース ファイルがあります。

.
├── decl.h
├── func3.c
├── main.c
├── Makefile1
├── Makefile2
├── override.c
├── test_target.c
└── weak_decl.h

main.c

#include <stdio.h>

void main (void)
{
    func1();    
}

test_target.c

#include <stdio.h>

void func3(void);

void func2 (void)
{
    printf("in original func2()\n");
}

void func1 (void)
{
    printf("in original func1()\n");
    func2();
    func3();
}

func3.c

#include <stdio.h>

void func3 (void)
{
    printf("in original func3()\n");
}

decl.h

void func1 (void);
void func2 (void);
void func3 (void);

weak_decl.h

void func1 (void);

__attribute__((weak))
void func2 (void);

__attribute__((weak))
void func3 (void);

override.c

#include <stdio.h>

void func2 (void)
{
    printf("in mock func2()\n");
}

void func3 (void)
{
    printf("in mock func3()\n");
}

Makefile1:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_weak.o -include weak_decl.h
    ar cr all_weak.a test_target_weak.o func3.o
    gcc main.c all_weak.a override.o -o main -include decl.h 

Makefile2:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_strong.o -include decl.h # HERE -include differs!!
    ar cr all_strong.a test_target_strong.o func3.o
    gcc main.c all_strong.a override.o -o main -include decl.h 

Makefile1 の結果の出力:

in original func1()
in mock func2()
in mock func3()

Makefile2 の出力:

rm *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h 
override.o: In function `func2':
override.c:(.text+0x0): multiple definition of `func2'  <===== HERE!!!
all_strong.a(test_target_strong.o):test_target.c:(.text+0x0): first defined here
override.o: In function `func3':
override.c:(.text+0x13): multiple definition of `func3' <===== HERE!!!
all_strong.a(func3.o):func3.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile4:2: recipe for target 'ALL' failed
make: *** [ALL] Error 1

記号表:

all_weak.a:

test_target_weak.o:
0000000000000013 T func1  <=== 13 is the offset of func1 in test_target_weak.o, see below disassembly
0000000000000000 W func2  <=== func2 is [W]eak symbol with default value assigned
                 w func3  <=== func3 is [w]eak symbol without default value
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3 <==== func3 is a strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

all_strong.a:

test_target_strong.o:
0000000000000013 T func1
0000000000000000 T func2 <=== func2 is strong symbol
                 U func3 <=== func3 is undefined symbol, there's no address value on the left-most column because func3 is not defined in test_target_strong.c
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

どちらの場合も、override.o記号は次のとおりです。

0000000000000000 T func2  <=== func2 is strong symbol
0000000000000013 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

分解:

test_target_weak.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <func2>: <===== HERE func2 offset is 0
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # b <func2+0xb>
   b:   e8 00 00 00 00          callq  10 <func2+0x10>
  10:   90                      nop
  11:   5d                      pop    %rbp
  12:   c3                      retq   

0000000000000013 <func1>: <====== HERE func1 offset is 13
  13:   55                      push   %rbp
  14:   48 89 e5                mov    %rsp,%rbp
  17:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 1e <func1+0xb>
  1e:   e8 00 00 00 00          callq  23 <func1+0x10>
  23:   e8 00 00 00 00          callq  28 <func1+0x15>
  28:   e8 00 00 00 00          callq  2d <func1+0x1a>
  2d:   90                      nop
  2e:   5d                      pop    %rbp
  2f:   c3                      retq   

したがって、結論は次のとおりです。

  1. ファイルで定義された関数は、.oファイルで定義された同じ関数をオーバーライドできます.a。上記のMakefile1では、func2()func3()inoverride.oが対応する in をオーバーライドしますall_weak.a。両方のファイルで試しまし.oたが、うまくいきません。

  2. GCCの場合、ここVisual Studio toolchain.oについて述べたように、関数を個別のファイルに分割する必要はありません。上記の例でわかるように、(と同じファイル内) と(別のファイル内) の両方をオーバーライドできます。func2()func1()func3()

  3. 関数をオーバーライドするには、そのコンシューマー翻訳単位をコンパイルするときに、その関数を weak として指定する必要があります。これにより、その機能が弱いものとして記録されますconsumer.o。上記の例では、 andtest_target.cを消費する をコンパイルするときに、andを weak として宣言するを追加する必要があります。も定義されていますが、問題ありません。func2()func3()-include weak_decl.hfunc2()func3()func2()test_target.c

さらなる実験

上記のソースファイルのままです。ただしoverride.c、少し変更します。

override.c

#include <stdio.h>

void func2 (void)
{
    printf("in mock func2()\n");
}

// void func3 (void)
// {
//     printf("in mock func3()\n");
// }

ここで、 のオーバーライド バージョンを削除しましたfunc3()の元の実装にフォールバックしたいので、これを行いました。func3()func3.c

私はまだMakefile1構築するために使用します。ビルドはOKです。しかし、以下のように実行時エラーが発生します。

xxx@xxx-host:~/source/override$ ./main
in original func1()
in mock func2()
Segmentation fault (core dumped)

だから私は final のシンボルをチェックしましたmain:

0000000000000696 T func1
00000000000006b3 T func2
                 w func3

func3したがって、に有効なアドレスがないことがわかります。そのため、セグメントフォルトが発生します。

なぜ?func3.oall_weak.aアーカイブ ファイルに追加しませんでしたか?

ar cr all_weak.a func3.o test_target_weak.o

から実装func2を削除した で同じことを試しました。しかし、今回はセグメントフォルトはありません。func2ovrride.c

override.c

#include <stdio.h>

// void func2 (void)
// {
//     printf("in mock func2()\n");
// }

void func3 (void)
{
    printf("in mock func3()\n");
}

出力:

xxx@xxx-host:~/source/override$ ./main
in original func1()
in original func2()  <====== the original func2() is invoked as a fall back
in mock func3()

私の推測では、は と同じファイル/翻訳単位func2定義されているためです。Soは常に と一緒に持ち込まれます。そのため、リンカは常に解決できます。func1func2func1func2test_target.coverride.c

ただし、 の場合、別のファイル/翻訳単位(func3.c)func3で定義されます。弱いと宣言された場合でも、コンシューマーは弱いと記録します。残念ながら、GCC リンカは、同じファイルの他のファイルをチェックして の実装を探しませんそれは確かにありますが。test_target.ofunc3().o.afunc3()

all_weak.a:

func3.o:
0000000000000000 T func3 <========= func3 is indeed here!
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

test_target_weak.o:
0000000000000013 T func1
0000000000000000 W func2
                 w func3
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

したがって、オーバーライド バージョンを提供する必要があります。override.cそうしfunc3()ないと、解決できません。

しかし、なぜ GCC がこのように動作するのかはまだわかりません。説明できる人いたらお願いします。

(2021 年 8 月 8 日 9:01 AM 更新: このスレッドでこの動作が説明される可能性があります。うまくいけば.)

したがって、さらなる結論は次のとおりです。

  1. 一部のシンボルをウィークとして宣言する場合は、すべてのウィーク関数のオーバーライド バージョンを提供することをお勧めします。そうしないと、発信者/消費者の同じファイル/翻訳単位内に存在しない限り、元のバージョンを解決できません。
于 2021-08-04T14:43:15.977 に答える