用C覆盖函数调用

为了logging调用,我想覆盖对各种API的某些函数调用,但是在将数据发送到实际函数之前,我也可能想要处理它们。

例如,假设我在源代码中使用了一个名为getObjectName的函数数千次。 我想暂时重写这个函数,因为我想改变这个函数的行为来查看不同的结果。

我创build一个像这样的新的源文件:

 #include <apiheader.h> const char *getObjectName (object *anObject) { if (anObject == NULL) return "(null)"; else return "name should be here"; } 

我通常会编译所有其他的源代码,但是在与API库连接之前,首先将它与此函数链接起来。 这工作正常,除了我可以显然不调用我的重写函数内的真正的function。

有没有更容易的方法来“重写”一个函数,而不会得到链接/编译错误/警告? 理想情况下,我希望能够通过编译和链接一个或多个额外的文件来重写该函数,而不是绕过链接选项或更改我的程序的实际源代码。

如果只是想要捕获/修改调用的源代码,最简单的解决scheme是将一个头文件( intercept.h )放在一起:

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

并执行如下的函数(在intercept.c 中不包含intercept.h ):

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

然后确保每个你想截取电话的源文件都有:

 #include "intercept.h" 

在顶部。

然后,当你用“ -DINTERCEPT ”编译时,所有的文件都会调用你的函数而不是真正的函数,你的函数仍然可以调用真正的函数。

编译没有“ -DINTERCEPT ”将防止发生拦截。

如果你想拦截所有的调用(不只是来自你的源代码),这有点麻烦 – 这通常可以通过dynamic加载和分解真实函数来完成(使用dlload-dlsym-types的调用),但是我不认为你的情况是必要的。

有了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中没有的。

记住要用extern "C"声明包装函数,但是如果你用g ++编译的话。

您可以使用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,你可以使你的function变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进行编译,则可以在其自己的文件中包含可覆盖函数的弱声明,只将include包含到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文件做些什么(不需要那三行和weakdecls),就完全不能工作。 但是一旦你做了这个改变,通过在一个文件中写入一个全局定义并且把它连接起来,函数就可以很容易地被覆盖。

通常需要通过包装或replace函数来修改现有代码库的行为。 编辑这些函数的源代码是一个可行的选项,这可以是一个直截了当的过程。 当function的来源不能被编辑时(例如,如果function是由系统C库提供的话),则需要替代技术。 在这里,我们介绍了UNIX,Windows和Macintosh OS X平台的这些技术。

这是一个很好的PDF,介绍了如何在OS X,Linux和Windows上完成这个工作。

它没有任何惊人的技巧,这里没有logging(这是一个惊人的响应BTW)…但这是一个很好的阅读。

http://wwwold.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf

您可以将函数指针定义为全局variables。 调用者的语法不会改变。 当你的程序启动时,它可以检查是否有一些命令行标志或环境variables被设置为启用日志logging,然后保存函数指针的原始值并将其replace为日志loggingfunction。 您不需要特殊的“启用日志logging”构build。 用户可以在“现场”启用日志logging。

您将需要能够修改调用者的源代码,但不能调用被调用者(所以调用第三方库时这会起作用)。

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(); } 

在涉及两个存根库的链接器中也有一个棘手的方法。

库#1与主库链接,并以另一个名称公开重新定义的符号。

库#2与库#1链接,与该库调用进行对接并调用库#1中的重新定义的版本。

在这里要非常小心的链接命令,否则将无法正常工作。

你可以使用一个共享库(Unix)或一个DLL(Windows)来做到这一点(会有一些性能损失)。 然后,您可以更改DLL /以便加载(一个版本用于debugging,一个版本用于非debugging)。

我过去做过类似的事情(不是实现你所要达到的目标,但基本前提是一样的),结果很好。

[基于OP评论编辑]

实际上,我想覆盖函数的原因之一是因为我怀疑他们在不同的操作系统上performance不同。

有两种常见的方法(我知道的)处理这个问题,共享的lib / dll方法或编写不同的实现链接。

对于这两个解决scheme(共享库或不同的链接),你将有foo_linux.c,foo_osx.c,foo_win32.c(或更好的方法是linux / foo.c,osx / foo.c和win32 / foo.c),然后编译和链接适当的一个。

如果你正在为不同的平台寻找两个不同的代码,并且debug -vs-release,我可能会倾向于使用共享的lib / DLL解决scheme,因为它是最灵活的。

build立在@Johannes Schaub的解答适合你不拥有的代码的解决scheme。

将您想要覆盖的函数别名为一个弱定义的函数,然后自己重​​新实现。

override.h

 #define foo(x) __attribute__((weak))foo(x) 

foo.c的

 function foo() { return 1234; } 

override.c

 function foo() { return 5678; } 

在Makefile中使用模式特定的variables值来添加编译器标志-include override.h

 %foo.o: ALL_CFLAGS += -include override.h 

另外:也许你也可以使用-D 'foo(x) __attribute__((weak))foo(x)'来定义你的macros。

编译并链接文件与重新实现( override.c )。

  • 这使您可以覆盖任何源文件中的单个函数,而无需修改代码。

  • 缺点是您必须为每个要覆盖的文件使用单独的头文件。