我想为了记录调用而覆盖对各种API的某些函数调用,但我也可能希望在将数据发送到实际函数之前对其进行操作.
例如,假设我getObjectName
在源代码中使用了一个名为数千次的函数.我想暂时覆盖此函数,因为我想更改此函数的行为以查看不同的结果.
我创建了一个像这样的新源文件:
#includeconst char *getObjectName (object *anObject) { if (anObject == NULL) return "(null)"; else return "name should be here"; }
我像往常一样编译所有其他源代码,但是在链接API的库之前我首先将它链接到此函数.这工作正常,但我显然不能在我的重写函数中调用真正的函数.
是否有一种更简单的方法来"覆盖"一个函数而不会链接/编译错误/警告?理想情况下,我希望能够通过编译和链接一个或多个额外的文件来覆盖该函数,而不是通过链接选项或改变我的程序的实际源代码.
使用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 ++编译,请记住声明包装函数.
如果您只想为您的源捕获/修改调用,最简单的解决方案是将头文件(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-
类型调用)来完成,但我不认为这是你的必要案件.
您可以使用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__attribute__((weak)) void test(void) { printf("not overridden!\n"); } int main() { test(); }
它有什么作用?
$ gcc test.c $ ./a.out not overridden!
test1.c:
#includevoid test(void) { printf("overridden!\n"); }
它有什么作用?
$ gcc test1.c test.c $ ./a.out overridden!
遗憾的是,这对其他编译器不起作用.但是您可以在自己的文件中包含包含可覆盖函数的弱声明,如果您使用GCC进行编译,则只需在API实现文件中添加一个include:
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和Macintosh OS X平台的这些技术.
这是一篇很棒的PDF文件,介绍了如何在OS X,Linux和Windows上完成这项工作.
它没有任何令人惊讶的技巧,这里没有记录(这是一组惊人的响应BTW)...但这是一个很好的阅读.
在Daniel,Myers和Adam L. Bazinet的Windows,UNIX和Macintosh OS X平台(2004)上拦截任意函数.
您可以直接从备用位置下载PDF(以实现冗余).
最后,如果以前的两个消息来源不知所措,那么这是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(); }