题
为了记录调用,我想覆盖对各种 API 的某些函数调用,但我也可能想在将数据发送到实际函数之前对其进行操作。
例如,假设我使用一个名为 getObjectName
在我的源代码中出现了数千次。有时我想暂时覆盖此函数,因为我想更改此函数的行为以查看不同的结果。
我创建一个新的源文件,如下所示:
#include <apiheader.h>
const char *getObjectName (object *anObject)
{
if (anObject == NULL)
return "(null)";
else
return "name should be here";
}
我像平常一样编译所有其他源代码,但在链接到 API 库之前,我首先将其链接到此函数。这工作得很好,只是我显然无法在我的重写函数中调用真正的函数。
有没有更简单的方法来“覆盖”函数而不会出现链接/编译错误/警告?理想情况下,我希望能够通过编译和链接一个或两个额外的文件来覆盖该函数,而不是摆弄链接选项或更改程序的实际源代码。
解决方案
如果您只想为您的源捕获/修改调用,最简单的解决方案是将头文件( 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
”的情况下进行编译将防止发生拦截。
如果要拦截所有调用(不仅仅是来自源代码的调用),这有点棘手 - 这通常可以通过动态加载和解析实际函数来完成(使用 dlload -
和<代码> dlsym - 类型调用)但我不认为在你的情况下这是必要的。
其他提示
使用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&quot; C&quot;
声明包装函数。
您可以使用 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文件做任何事情(需要这三行和weakdecls),它就不能完全 。但是,一旦你做了这个改变,就可以通过在一个文件中编写全局定义并将其链接起来轻松覆盖函数。
通常需要修改 现有代码库的行为 包装或更换功能。什么时候 编辑那些的源代码 功能是一个可行的选择,这可以 是一个直截了当的过程。什么时候 功能的来源不可能 编辑(例如,如果功能是 由系统C库提供), 那么替代技术是 需要。在这里,我们提出这样的 适用于UNIX,Windows和Windows的技术 Macintosh OS X平台。
这是一篇很棒的PDF文件,介绍了如何在OS X,Linux和Windows上完成这项工作。
它没有任何令人惊讶的技巧,这里没有记录(这是一套惊人的响应BTW)...但这是一个很好的阅读。
拦截Windows,UNIX和Macintosh OS上的任意功能X平台(2004年),作者:Daniel S. Myers和Adam L. Bazinet 。
最后,如果前两个消息来源不知所措,这是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();
}
在包含两个存根库的链接器中也有一种棘手的方法。
库#1与主机库链接,并以另一个名称公开重新定义的符号。
图书馆#2与图书馆#1相关联,拦截了电话并在图书馆#1中调用了重新定义的版本。
在这里对链接订单要非常小心,否则它将无效。
基于 @Johannes Schaub 的答案,提供适合您不拥有的代码的解决方案。
将要重写的函数别名为弱定义函数,然后自己重新实现它。
覆盖.h
#define foo(x) __attribute__((weak))foo(x)
foo.c
function foo() { return 1234; }
覆盖.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)来执行此操作(会有一点性能损失)。然后,您可以更改加载的DLL /(一个版本用于调试,一个版本用于非调试)。
我过去做过类似的事情(不是要达到你想要达到的目的,但基本前提是一样的)并且效果很好。
[根据OP评论编辑]
实际上我想要的原因之一 覆盖功能是因为我 怀疑他们的行为不同 不同的操作系统。
有两种常见的方式(我知道)处理它,共享的lib / dll方式或编写你链接的不同实现。
对于这两种解决方案(共享库或不同的链接),您将拥有foo_linux.c,foo_osx.c,foo_win32.c(或者更好的方法是linux / foo.c,osx / foo.c和win32 / foo.c )然后编译并链接相应的。
如果您正在寻找不同平台和调试-vs- release的不同代码,我可能倾向于使用共享库/ DLL解决方案,因为它是最灵活的。