呼び出しを記録するために、さまざまなAPIへの特定の関数呼び出しをオーバーライドしたいのですが、実際の関数に送信される前にデータを操作することもできます。
たとえば、ソースコードでgetObjectName
という関数を何千回も使用するとします。この関数の動作を変更して異なる結果を確認したいので、一時的にこの関数をオーバーライドしたい場合があります。
次のような新しいソースファイルを作成します。
#include <apiheader.h>
const char *getObjectName (object *anObject)
{
if (anObject == NULL)
return "(null)";
else
return "name should be here";
}
他のすべてのソースは通常どおりコンパイルしますが、APIのライブラリとリンクする前に、まずこの関数に対してリンクします。これは、オーバーライド関数内で明らかに実際の関数を呼び出せないことを除いて、正常に機能します。
エラー/警告をリンク/コンパイルせずに関数を「オーバーライド」する簡単な方法はありますか?理想的には、リンクオプションをいじったり、プログラムの実際のソースコードを変更したりせずに、余分なファイルを1つまたは2つコンパイルしてリンクするだけで、関数をオーバーライドできるようにしたいと考えています。
呼び出しをキャプチャ/変更するのがソースのみの場合、最も簡単な解決策は、ヘッダーファイル(intercept.h
)を次のようにまとめることです。
#ifdef INTERCEPT
#define getObjectName(x) myGetObectName(x)
#endif
そして、次のように関数を実装します(intercept.c
which does n't include intercept.h
):
const char *myGetObjectName (object *anObject) {
if (anObject == NULL)
return "(null)";
else
return getObjectName(anObject);
}
次に、呼び出しを傍受する各ソースファイルに次のものがあることを確認します。
#include "intercept.h"
頂点で。
次に、「-DINTERCEPT
」でコンパイルすると、すべてのファイルが実際の関数ではなく関数を呼び出しますが、関数は実際の関数を呼び出すことができます。
「-DINTERCEPT
」なしでコンパイルすると、インターセプトが発生しなくなります。
すべての呼び出し(ソースからの呼び出しだけでなく)をインターセプトする場合は少し複雑です-これは通常、実際の関数の動的な読み込みと解決(dlload-
およびdlsym-
type呼び出し)で実行できますが、あなたの場合は必要ないと思います。
Gccを使用すると、Linuxでは、次のように_--wrap
_リンカーフラグを使用できます。
_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にはありません。
ただし、g ++でコンパイルする場合は、_extern "C"
_を使用してラッパー関数を宣言することを忘れないでください。
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からでも共有ライブラリから任意の関数をオーバーライドできるため、ソースを持たないプログラムでトリックを実行できます。いいじゃない?
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実装ファイルにインクルードを配置することができます。
weakdecls.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行とweakdeclsが必要です)。ただし、一度その変更を行うと、1つのファイルにグローバル定義を記述し、それをリンクすることにより、関数を簡単にオーバーライドできます。
関数をラップまたは置換することにより、既存のコードベースの動作を変更することが望ましい場合がよくあります。これらの関数のソースコードの編集が実行可能なオプションである場合、これは簡単なプロセスです。関数のソースを編集できない場合(たとえば、関数がシステムCライブラリによって提供される場合)、代替の手法が必要です。ここでは、UNIX、Windows、およびMacintosh OS Xプラットフォーム向けのこのようなテクニックを紹介します。
これは素晴らしいPDFこれがOS X、Linux、Windowsでどのように行われたかをカバーしています。
ここに記載されていない素晴らしいトリックはありません(これは驚くべき回答のセットです)...しかし、それは素晴らしい読み物です。
Windows、UNIX、およびMacintosh OS Xプラットフォームでの任意の関数のインターセプト(2004年)、ダニエルS.マイヤーズおよびアダムL.バジネットによる 。
PDFを別の場所から直接(冗長性のために)ダウンロード できます。
そして最後に、前の2つのソースが何らかの形で炎上した場合、 これに対するGoogleの検索結果 です。
関数ポインタをグローバル変数として定義できます。呼び出し元の構文は変更されません。プログラムの起動時に、コマンドラインフラグまたは環境変数がロギングを有効にするように設定されているかどうかを確認し、関数ポインターの元の値を保存して、ロギング関数に置き換えます。特別な「ロギングが有効」なビルドは必要ありません。ユーザーは「フィールドで」ロギングを有効にできます。
呼び出し元のソースコードを変更できる必要がありますが、呼び出し先は変更できません(サードパーティのライブラリを呼び出すときにこれが機能します)。
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();
}
2つのスタブライブラリを含むリンカでそれを行うトリッキーな方法もあります。
ライブラリ#1は、ホストライブラリに対してリンクされ、別の名前で再定義されているシンボルを公開します。
ライブラリ#2は、ライブラリ#1に対してリンクされ、呼び出しをインターセプトし、ライブラリ#1で再定義されたバージョンを呼び出します。
ここでリンクの順序に注意してください。そうしないと機能しません。
所有していないコードに適したソリューションを使用して、@ Johannes Schaubの回答を基に作成します。
オーバーライドする関数を弱く定義された関数にエイリアスし、自分で再実装します。
override.h
_#define foo(x) __attribute__((weak))foo(x)
_
foo.c
_function foo() { return 1234; }
_
override.c
_function foo() { return 5678; }
_
Makefileで パターン固有の変数値 を使用して、コンパイラフラグ_-include override.h
_を追加します。
_%foo.o: ALL_CFLAGS += -include override.h
_
余談:おそらく、-D 'foo(x) __attribute__((weak))foo(x)'
を使用してマクロを定義することもできます。
ファイルをコンパイルして、再実装(_override.c
_)にリンクします。
これにより、コードを変更することなく、任意のソースファイルから単一の関数をオーバーライドできます。
欠点は、オーバーライドするファイルごとに個別のヘッダーファイルを使用する必要があることです。
共有ライブラリ(Unix)またはDLL(Windows)を使用してこれを行うこともできます(パフォーマンスが多少低下します)。ロード済み(デバッグ用に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ソリューションを使用する傾向があります。