72

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

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

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

#include <apiheader.h>    

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

我像往常一样编译我的所有其他源代码,但在与 API 的库链接之前,我先将它与这个函数链接起来。这很好用,除非我显然不能在我的覆盖函数中调用真正的函数。

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

4

10 回答 10

87

使用 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++ 编译,请记住声明包装函数。

于 2009-03-06T03:24:15.427 回答
82

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

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

然后按如下方式实现函数(intercept.c其中包括intercept.h):

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

然后确保要拦截调用的每个源文件在顶部都有以下内容:

#include "intercept.h"

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

不带“”的编译-DINTERCEPT将防止发生拦截。

如果您想拦截所有调用(不仅仅是来自您的源的调用),这会有点棘手 - 这通常可以通过动态加载和解析真实函数(使用dlload-dlsym-类型调用)来完成,但我认为在您的案子。

于 2009-03-06T02:49:22.577 回答
39

LD_PRELOAD您可以使用trick-see覆盖函数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 中的函数,而无需修改/重新编译程序,因此您可以对没有源代码的程序进行操作。这不是很好吗?

于 2009-03-06T08:29:47.580 回答
27

如果你使用 GCC,你可以让你的函数weak. 这些可以被非弱函数覆盖:

测试.c

#include <stdio.h>

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

int main() {
    test();
}

它有什么作用?

$ gcc test.c
$ ./a.out
not overridden!

测试1.c

#include <stdio.h>

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

它有什么作用?

$ gcc test1.c test.c
$ ./a.out
overridden!

可悲的是,这不适用于其他编译器。但是您可以在自己的文件中包含包含可覆盖函数的弱声明,如果您使用 GCC 进行编译,则只需将 include 放入 API 实现文件中:

弱decls.h

__attribute__((weak)) void test(void);
... other weak function declarations ...

函数.c

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

void test(void) { 
    ...
}

... other functions ...

这样做的缺点是,如果不对api 文件做一些事情(需要这三行和weakdecls),它就不能完全工作。但是,一旦您进行了更改,就可以通过在一个文件中编写全局定义并将其链接到其中来轻松覆盖函数。

于 2009-03-06T03:13:15.780 回答
12

通常希望通过包装或替换函数来修改现有代码库的行为。当编辑这些函数的源代码是一个可行的选择时,这可能是一个直接的过程。当无法编辑函数的源代码时(例如,如果函数由系统 C 库提供),则需要替代技术。在这里,我们介绍了适用于 UNIX、Windows 和 Macintosh OS X 平台的此类技术。

这是一个很棒的 PDF,涵盖了如何在 OS X、Linux 和 Windows 上完成此操作。

它没有任何此处未记录的惊人技巧(顺便说一句,这是一组惊人的响应)......但它是一本不错的读物。

在 Windows、UNIX 和 Macintosh OS X 平台上拦截任意函数 (2004),作者 Daniel S. Myers 和 Adam L. Bazinet

您可以直接从其他位置下载 PDF(为了冗余)

最后,如果前两个来源不知何故火了,这里有一个谷歌搜索结果

于 2009-03-23T13:23:53.950 回答
9

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

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

富.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();
}
于 2009-03-06T03:03:48.063 回答
5

以@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)。

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

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

于 2017-07-11T19:47:16.057 回答
3

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

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

库#2 链接到库#1,拦截调用并调用库#1 中重新定义的版本。

此处的链接订单要非常小心,否则它将不起作用。

于 2009-03-06T02:57:33.493 回答
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- 版本,我可能会倾向于使用共享的 lib/DLL 解决方案,因为它是最灵活的。

于 2009-03-06T05:04:11.537 回答
0

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

简洁版本

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

  • 弱属性
  • 翻译单元安排

长版

我有这些源文件。

.
├── decl.h
├── func3.c
├── main.c
├── Makefile1
├── Makefile2
├── override.c
├── test_target.c
└── weak_decl.h

主程序

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

函数3.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");
}

生成文件1:

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 

生成文件2:

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()and func3()inoverride.o覆盖了all_weak.a. 我尝试了这两个.o文件,但它不起作用。

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

  3. 要覆盖一个函数,在编译其消费者翻译单元时,您需要将该函数指定为弱。这将在consumer.o. 在上面的示例中,当编译test_target.c消耗func2()and的 , 时func3(),您需要添加-include weak_decl.h, 将func2()and声明func3()为弱。func2()也定义了,但test_target.c没关系。

一些进一步的实验

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

覆盖.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.oall_weak.a存档文件中吗?

ar cr all_weak.a func3.o test_target_weak.o

我尝试了同样的事情func2,我从中删除了func2实现ovrride.c。但这一次没有段错误。

覆盖.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与. 所以总是带进来。所以链接器总是可以解析,无论是从or 。func1func2func1func2test_target.coverride.c

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

all_weak.a:

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否则func3()无法解决。

但我仍然不知道为什么 GCC 会这样。如果有人可以解释,请。

(2021 年 8 月 8 日上午 9:01 更新: 这个线程可能会解释这种行为,希望如此。)

所以进一步的结论是:

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