覆盖C中的函数调用

时间:2009-03-06 02:44:35

标签: c function linker override

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

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

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

#include <apiheader.h>    

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

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

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

10 个答案:

答案 0 :(得分:76)

使用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"声明包装函数。

答案 1 :(得分:67)

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

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

并按如下方式实现该功能(在包含intercept.c的{​​{1}}中):

intercept.h

然后确保要拦截呼叫的每个源文件都包含:

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

在顶部。

然后,当你使用“#include "intercept.h" ”进行编译时,所有文件都会调用你的函数而不是真正的函数,你的函数仍可以调用真函数。

在没有“-DINTERCEPT”的情况下进行编译将防止发生拦截。

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

答案 2 :(得分:36)

您可以使用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覆盖,而无需修改/重新编译程序,因此您可以在没有源代码的程序上执行此操作。不是很好吗?

答案 3 :(得分:24)

如果您使用GCC,则可以使用weak功能。非弱函数的那些can be overridden

<强> 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),它就不能完全 。但是,一旦你做了这个改变,就可以通过在一个文件中编写全局定义并将其链接起来轻松覆盖函数。

答案 4 :(得分:10)

  

通常需要修改   现有代码库的行为   包装或更换功能。什么时候   编辑那些的源代码   功能是一个可行的选择,这可以   是一个直截了当的过程。什么时候   功能的来源不可能   编辑(例如,如果功能是   由系统C库提供),   那么替代技术是   需要。在这里,我们提出这样的   适用于UNIX,Windows和Windows的技术   Macintosh OS X平台。

这是一篇很棒的PDF文件,介绍了如何在OS X,Linux和Windows上完成这项工作。

它没有任何令人惊讶的技巧,这里没有记录(这是一套惊人的响应BTW)......但这是一个很好的阅读。

Intercepting arbitrary functions on Windows, UNIX, and Macintosh OS X platforms (2004), by Daniel S. Myers and Adam L. Bazinet

你可以download the PDF directly from an alternate location (for redundancy)

最后,如果前两个消息来源不知所措,here's a Google search result for it

答案 5 :(得分:9)

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

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

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

答案 6 :(得分:3)

在包含两个存根库的链接器中也有一种棘手的方法。

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

图书馆#2与图书馆#1相关联,拦截了电话并在图书馆#1中调用了重新定义的版本。

在这里对链接订单要非常小心,否则它将无效。

答案 7 :(得分:3)

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

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

<强> override.h

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

<强> foo.c的

function foo() { return 1234; }

<强> override.c

function foo() { return 5678; }

在Makefile中使用pattern-specific variable values添加编译器标志-include override.h

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

除此之外:也许您也可以使用-D 'foo(x) __attribute__((weak))foo(x)'来定义宏。

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

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

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

答案 8 :(得分:0)

您可以使用共享库(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解决方案,因为它是最灵活的。

答案 9 :(得分:0)

以下是我的实验。正文和结尾有4个结论。

短版

一般来说,要成功覆盖一个函数,你必须考虑:

  • 弱属性
  • 翻译单元排列

长版

我有这些源文件。

.
├── decl.h
├── func3.c
├── main.c
├── Makefile1
├── Makefile2
├── override.c
├── test_target.c
└── weak_decl.h
<块引用>

main.c

#include <stdio.h>

void main (void)
{
    func1();    
}
<块引用>

test_target.c

#include <stdio.h>

void func3(void);

void func2 (void)
{
    printf("in original func2()\n");
}

void func1 (void)
{
    printf("in original func1()\n");
    func2();
    func3();
}
<块引用>

func3.c

#include <stdio.h>

void func3 (void)
{
    printf("in original func3()\n");
}
<块引用>

decl.h

void func1 (void);
void func2 (void);
void func3 (void);
<块引用>

weak_decl.h

void func1 (void);

__attribute__((weak))
void func2 (void);

__attribute__((weak))
void func3 (void);
<块引用>

覆盖.c

#include <stdio.h>

void func2 (void)
{
    printf("in mock func2()\n");
}

void func3 (void)
{
    printf("in mock func3()\n");
}
<块引用>

Makefile1:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_weak.o -include weak_decl.h
    ar cr all_weak.a test_target_weak.o func3.o
    gcc main.c all_weak.a override.o -o main -include decl.h 
<块引用>

Makefile2:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_strong.o -include decl.h # HERE -include differs!!
    ar cr all_strong.a test_target_strong.o func3.o
    gcc main.c all_strong.a override.o -o main -include decl.h 
<块引用>

Makefile1 结果的输出:

in original func1()
in mock func2()
in mock func3()
<块引用>

Makefile2 的输出:

rm *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h 
override.o: In function `func2':
override.c:(.text+0x0): multiple definition of `func2'  <===== HERE!!!
all_strong.a(test_target_strong.o):test_target.c:(.text+0x0): first defined here
override.o: In function `func3':
override.c:(.text+0x13): multiple definition of `func3' <===== HERE!!!
all_strong.a(func3.o):func3.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile4:2: recipe for target 'ALL' failed
make: *** [ALL] Error 1

符号表:

<块引用>

all_weak.a:

test_target_weak.o:
0000000000000013 T func1  <=== 13 is the offset of func1 in test_target_weak.o, see below disassembly
0000000000000000 W func2  <=== func2 is [W]eak symbol with default value assigned
                 w func3  <=== func3 is [w]eak symbol without default value
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3 <==== func3 is a strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts
<块引用>

all_strong.a:

test_target_strong.o:
0000000000000013 T func1
0000000000000000 T func2 <=== func2 is strong symbol
                 U func3 <=== func3 is undefined symbol, there's no address value on the left-most column because func3 is not defined in test_target_strong.c
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

在这两种情况下,override.o 符号:

0000000000000000 T func2  <=== func2 is strong symbol
0000000000000013 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts
<块引用>

拆解:

test_target_weak.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <func2>: <===== HERE func2 offset is 0
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # b <func2+0xb>
   b:   e8 00 00 00 00          callq  10 <func2+0x10>
  10:   90                      nop
  11:   5d                      pop    %rbp
  12:   c3                      retq   

0000000000000013 <func1>: <====== HERE func1 offset is 13
  13:   55                      push   %rbp
  14:   48 89 e5                mov    %rsp,%rbp
  17:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 1e <func1+0xb>
  1e:   e8 00 00 00 00          callq  23 <func1+0x10>
  23:   e8 00 00 00 00          callq  28 <func1+0x15>
  28:   e8 00 00 00 00          callq  2d <func1+0x1a>
  2d:   90                      nop
  2e:   5d                      pop    %rbp
  2f:   c3                      retq   

所以结论是:

  1. .o 文件中定义的函数可以覆盖 .a 文件中定义的相同函数。在上面的 Makefile1 中,func2() 中的 func3()override.o 覆盖了 all_weak.a 中的对应项。我尝试使用这两个 .o 文件,但它不起作用。

  2. 对于 GCC,您不需要将函数拆分为单独的 .o 文件,如用于 Visual Studio 工具链的 here 所述强>。我们可以在上面的示例中看到,func2()(与 func1() 在同一个文件中)和 func3()(在单独的文件中)都可以被覆盖。

  3. 要覆盖一个函数,在编译其消费者翻译单元时,您需要将该函数指定为弱函数。这会将该函数记录为 consumer.o 中的弱函数。在上面的例子中,在编译消耗test_target.cfunc2()的{​​{1}}时,需要添加func3(),它声明-include weak_decl.hfunc2()一样弱。 func3() 也在 func2() 中定义,但没问题。

一些进一步的实验

还是上面的源文件。但是稍微改变一下 test_target.c

<块引用>

覆盖.c

override.c

这里我删除了 #include <stdio.h> void func2 (void) { printf("in mock func2()\n"); } // void func3 (void) // { // printf("in mock func3()\n"); // } 的覆盖版本。 我这样做是因为我想回到 func3() 中最初的 func3() 实现。

我仍然使用 func3.c 来构建。构建没问题。但是会发生如下运行时错误:

Makefile1

所以我检查了最后一个 xxx@xxx-host:~/source/override$ ./main in original func1() in mock func2() Segmentation fault (core dumped) 的符号:

main

所以我们可以看到 0000000000000696 T func1 00000000000006b3 T func2 w func3 没有有效地址。这就是段错误发生的原因。

为什么?我没有将 func3 添加到 func3.o 存档文件中吗?

<块引用>

ar cr all_weak.a func3.o test_target_weak.o

我对 all_weak.a 进行了同样的尝试,从 func2 中删除了 func2 实现。但这次没有段错误。

<块引用>

覆盖.c

ovrride.c
<块引用>

输出:

#include <stdio.h>

// void func2 (void)
// {
//     printf("in mock func2()\n");
// }

void func3 (void)
{
    printf("in mock func3()\n");
}

我的猜测是,因为 xxx@xxx-host:~/source/override$ ./main in original func1() in original func2() <====== the original func2() is invoked as a fall back in mock func3() 定义在与 func2 相同的文件/翻译单元中。因此,func1 总是与 func2 一起引入。因此链接器始终可以解析 func1,无论是从 func2 还是 test_target.c

但是对于 override.c,它是在单独的文件/翻译单元 (func3.c) 中定义的。如果它被声明为弱,消费者 func3 仍会将 test_target.o 记录为弱。但不幸的是,GCC 链接器不会检查同一个 func3() 文件中的其他 .o 文件来寻找 .a 的实现。尽管它确实存在。

<块引用>

all_weak.a:

func3()

因此我必须在 func3.o: 0000000000000000 T func3 <========= func3 is indeed here! U _GLOBAL_OFFSET_TABLE_ U puts test_target_weak.o: 0000000000000013 T func1 0000000000000000 W func2 w func3 U _GLOBAL_OFFSET_TABLE_ U puts 中提供覆盖版本,否则无法解析 override.c

但我还是不明白为什么 GCC 会这样。如果有人可以解释,请。

(2021 年 8 月 8 日上午 9:01 更新: this 线程可以解释这种行为,希望。)

所以进一步的结论是:

  1. 如果您将某个符号声明为弱函数,则最好提供所有弱函数的覆盖版本。否则,除非原始版本位于调用方/消费者的同一文件/翻译单元中,否则无法解析。