覆盖C中的函数调用

dre*_*lax 68 c linker overriding function

我想为了记录调用而覆盖对各种API的某些函数调用,但我也可能希望在将数据发送到实际函数之前对其进行操作.

例如,假设我getObjectName在源代码中使用了一个名为数千次的函数.我想暂时覆盖此函数,因为我想更改此函数的行为以查看不同的结果.

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

#include <apiheader.h>    

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

我像往常一样编译所有其他源代码,但是在链接API的库之前我首先将它链接到此函数.这工作正常,但我显然不能在我的重写函数中调用真正的函数.

是否有一种更简单的方法来"覆盖"一个函数而不会链接/编译错误/警告?理想情况下,我希望能够通过编译和链接一个或多个额外的文件来覆盖该函数,而不是通过链接选项或改变我的程序的实际源代码.

cod*_*gic 77

使用gcc,在Linux下你可以--wrap像这样使用链接器标志:

gcc program.c -Wl,-wrap,getObjectName -o program
Run Code Online (Sandbox Code Playgroud)

并将您的功能定义为:

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

这将确保将所有调用getObjectName()重新路由到您的包装函数(在链接时).然而,在Mac OS X下的gcc中没有这个非常有用的标志.

extern "C"如果您正在使用g ++编译,请记住声明包装函数.

  • 这是一个很好的方式.不知道那一个.但如果我正在阅读该联机帮助页,那么它应该是"__real_getObjectName(anObject);" 它由链接器路由到getObjectName.否则你将再次递归调用__wrap_getObjectName.还是我错过了什么? (3认同)
  • 顺便说一下,编译器不提供 __real* 符号的声明。你必须自己用 `extern char *__real_getObjectName(object *anObject)` 声明它。 (3认同)

pax*_*blo 69

如果您只想为您的源捕获/修改调用,最简单的解决方案是将头文件(intercept.h)放在一起:

#ifdef INTERCEPT
    #define getObjectName(x) myGetObectName(x)
#endif
Run Code Online (Sandbox Code Playgroud)

并实现如下功能(intercept.c其中包括intercept.h):

const char *myGetObjectName (object *anObject) {
    if (anObject == NULL)
        return "(null)";
    else
        return getObjectName(anObject);
}
Run Code Online (Sandbox Code Playgroud)

然后确保要拦截呼叫的每个源文件具有:

#include "intercept.h"
Run Code Online (Sandbox Code Playgroud)

在顶部.

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

没有" -DINTERCEPT"的编译将防止发生拦截.

如果你想拦截所有的电话(不仅仅是来自你的电话),这有点棘手 - 这通常可以通过动态加载和解析真实函数(带dlload-dlsym-类型调用)来完成,但我不认为这是你的必要案件.

  • 您可以使用选项"-include file"使GCC自动包含该文件.你甚至不需要触摸任何文件然后:) (12认同)
  • 这适用于单元测试套件(如使用CGreen),您希望在函数中存根某些依赖项.对我来说一直是一个很好的答案. (2认同)

qrd*_*rdl 37

您可以使用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
}
Run Code Online (Sandbox Code Playgroud)

您可以覆盖共享库中的任何函数,甚至可以从stdlib覆盖,而无需修改/重新编译程序,因此您可以在没有源代码的程序上执行此操作.不是很好吗?

  • 不,您只能以这种方式覆盖**共享库提供的功能. (2认同)
  • @ChrisStratton你是对的,系统调用不能用这种方式覆盖,我编辑了我的答案. (2认同)

Joh*_*itb 26

如果您使用GCC,您可以使用您的功能weak.那些可以被非弱函数覆盖:

test.c:

#include <stdio.h>

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

int main() {
    test();
}
Run Code Online (Sandbox Code Playgroud)

它有什么作用?

$ gcc test.c
$ ./a.out
not overridden!
Run Code Online (Sandbox Code Playgroud)

test1.c:

#include <stdio.h>

void test(void) {
    printf("overridden!\n");
}
Run Code Online (Sandbox Code Playgroud)

它有什么作用?

$ gcc test1.c test.c
$ ./a.out
overridden!
Run Code Online (Sandbox Code Playgroud)

遗憾的是,这对其他编译器不起作用.但是您可以在自己的文件中包含包含可覆盖函数的弱声明,如果您使用GCC进行编译,则只需在API实现文件中添加一个include:

weakdecls.h:

__attribute__((weak)) void test(void);
... other weak function declarations ...
Run Code Online (Sandbox Code Playgroud)

functions.c:

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

void test(void) { 
    ...
}

... other functions ...
Run Code Online (Sandbox Code Playgroud)

这样做的缺点是,如果不对api文件执行某些操作(需要这三行和weakdecls),它就不能完全正常工作.但是一旦你做了这个改变,就可以通过在一个文件中编写一个全局定义并将其链接起来来轻松覆盖函数.

  • 那将需要修改 API,不是吗? (2认同)

小智 10

通常希望通过包装或替换函数来修改现有代码库的行为.编辑这些函数的源代码是一个可行的选择,这可能是一个直接的过程.当无法编辑函数的源时(例如,如果函数由系统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的搜索结果.


Chr*_*son 9

您可以将函数指针定义为全局变量.调用者语法不会改变.程序启动时,可以检查某个命令行标志或环境变量是否设置为启用日志记录,然后保存函数指针的原始值并将其替换为日志记录功能.您不需要特殊的"启用日志记录"构建.用户可以"在现场"启用日志记录.

您需要能够修改调用者的源代码,而不是被调用者(因此在调用第三方库时这将起作用).

foo.h中:

typedef const char* (*GetObjectNameFuncPtr)(object *anObject);
extern GetObjectNameFuncPtr GetObjectName;
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

  • 我已经考虑过这种方法,但它确实需要修改源代码,除非必须这样做,否则我真的不想这样做。尽管这具有在运行时切换的额外好处。 (2认同)

vau*_*han 5

以@Johannes Schaub 的回答为基础,提供适合您不拥有的代码的解决方案。

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

覆盖.h

#define foo(x) __attribute__((weak))foo(x)
Run Code Online (Sandbox Code Playgroud)

foo.c

function foo() { return 1234; }
Run Code Online (Sandbox Code Playgroud)

覆盖.c

function foo() { return 5678; }
Run Code Online (Sandbox Code Playgroud)

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

%foo.o: ALL_CFLAGS += -include override.h
Run Code Online (Sandbox Code Playgroud)

旁白:也许你也可以-D 'foo(x) __attribute__((weak))foo(x)'用来定义你的宏。

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

  • 这允许您从任何源文件覆盖单个函数,而无需修改代码。

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