覆盖 C 库函数,调用原始函数

Posted

技术标签:

【中文标题】覆盖 C 库函数,调用原始函数【英文标题】:Overriding C library functions, calling original 【发布时间】:2013-09-26 08:25:25 【问题描述】:

我对这段代码如何以及为什么会这样工作感到有些困惑。在我从事的任何项目中,我实际上都没有遇到过这种情况,我什至没有想过自己做。

override_getline.c:

#include <stdio.h>

#define OVERRIDE_GETLINE

#ifdef OVERRIDE_GETLINE
ssize_t getline(char **lineptr, size_t *n, FILE *stream)

    printf("getline &lineptr=%p &n=%p &stream=%p\n", lineptr, n, stream);
    return -1; // note: errno has undefined value

#endif

main.c:

#include <stdio.h>

int main()

    char *buf = NULL;
    size_t len = 0;
    printf("Hello World! %zd\n", getline(&buf, &len, stdin));
    return 0;

最后,示例编译并运行命令:

gcc main.c override_getline.c && ./a.out

使用OVERRIDE_GETLINE 定义,自定义函数被调用,如果它被注释掉,正常的库函数被调用,两者都按预期工作。

问题

    正确的说法是什么? “覆盖”、“阴影”还是其他?

    这是 gcc 特定的,还是 POSIX,或 ANSI C,甚至完全未定义?

    如果函数是 ANSI C 函数或(如这里)POSIX 函数,有什么区别吗?

    覆盖函数在哪里被调用?至少通过同一链接中的其他.o 文件,我认为.a 文件也添加到链接命令中。使用链接器的-l 命令行选项添加的静态或动态库怎么样?

    如果可能,如何从覆盖的 getline 调用库版本的 getline?

【问题讨论】:

【参考方案1】:

链接器将首先搜索您在命令行中提供的文件中的符号,然后再搜索库。这意味着一旦它看到getline 已被定义,它将不再寻找另一个getline 符号。这就是链接器在所有平台上的工作方式。

这当然对您的第五点有影响,因为从链接器的角度来看,您的函数原始的,因此不可能调用“原始”getline .

对于第五点,你可能想看看例如this old answer.

【讨论】:

没办法调用原版?使用dlopen + dlsym 怎么样? @cnicutar 好吧,没有简单的方法。最简单的方法可能是使用链接器的 --wrap 参数,我刚刚更新了我的答案,并提供了如何操作的链接。 在将函数重命名为__wrap_getline 后使用gcc -Wl,--wrap=getline main.c override_getline.c 编译,并在调试打印运行良好后从中调用__real_getline(main.c 仍然调用普通getline)! 至于标准的编译器无关方法:您不能创建调用原始符号的具有不同名称(前缀)的包装函数,将其与原始库链接到目标文件中,然后创建包装使用调用已链接的前缀函数名称的原始名称?不过没试过。【参考方案2】:

没有标准方法可以在您的程序中使用两个同名函数,但是通过一些类似 UNIX 的实现(尤其是 GNU libc),您或许可以摆脱这种情况:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>

ssize_t getline(char **lineptr, size_t *n, FILE *stream)

   ssize_t (*realfunc)(char**, size_t *, FILE*) =
       (ssize_t(*)(char**, size_t *, FILE*))(dlsym (RTLD_NEXT, "getline"));
   return realfunc(lineptr, n, stream);

您需要为此链接-ldl

【讨论】:

【参考方案3】:

这里发生的是您依赖于链接器的行为。链接器会在看到标准库中的版本之前找到您的 getline 实现,因此它会链接到您的例程。所以实际上你通过链接顺序的机制覆盖了这个函数。当然,其他链接器的行为可能会有所不同,如果您指定适当的命令行开关,我相信 gcc 链接器甚至可能会抱怨重复符号。

为了能够同时调用您的自定义例程和库例程,您通常会求助于宏,例如

#ifdef OVERRIDE_GETLINE
#define GETLINE(l, n, s) my_getline(l, n, s)
#else
#define GETLINE(l, n, s) getline(l, n, s)
#endif

#ifdef OVERRIDE_GETLINE
ssize_t my_getline(char **lineptr, size_t *n, FILE *stream)

   // ...
   return getline(lineptr, n, stream);

#endif

请注意,这需要您的代码将getline 称为GETLINE,这相当难看。

【讨论】:

如果我在应用程序代码(不同的源文件)中定义了两次getline,那么我会得到链接器error。这里甚至没有警告。我会看看我是否能找到会引发带有问题代码的警告的开关。 我认为这可能是由于 object fileslibraries 之间的差异以及链接器处理它们的方式。【参考方案4】:

如果您与共享库链接,您会看到预期的行为。链接器只会将它分配给您的功能,就像它最初一样。它也可以从任何其他外部库函数中正确调用,因为链接器会在扫描链接库时使您的函数可导出。

但是 - 如果你没有链接到你的函数的外部库(所以它没有标记为可导出,也没有插入符号表),然后 dlopen() 一些想要使用它的库在运行时 - 它不会找到所需的功能。此外,如果您首先 dlopen(RTLD_NOW|RTLD_GLOBAL) 原始库,则每个后续 dlopen() 的库都将使用 this 库代码,而不是您的。无论如何,您的代码(或您在编译阶段而不是运行时链接的任何库)仍将与您的函数保持一致。

【讨论】:

以上是关于覆盖 C 库函数,调用原始函数的主要内容,如果未能解决你的问题,请参考以下文章

是否可以防止 C 共享库中的函数覆盖?

在运行时重定向 c 函数并调用原始函数

linux内核系统调用和标准C库函数的关系分析

C库函数和系统调用的区别

标准C库函数和系统调用的关系

Android 逆向ptrace 函数 ( C 标准库 ptrace 函数简介 | ptrace 函数真实作用 )