【问题标题】:Wrapper routine for write() with unistd.h included results in error包含 unistd.h 的 write() 包装例程导致错误
【发布时间】:2011-03-03 14:31:10
【问题描述】:

我正在为write() 编写一个包装例程以覆盖原始系统函数,并且在其中我需要通过execve() 执行另一个程序;为此我包含了头文件unistd.h。我收到错误 conflicting types for 'write' /usr/include/unistd.h:363:16: note: previous declaration of 'write'was here。如果有人可以帮助我,我将非常感激,因为我需要从包装器内部调用另一个程序并从包装器例程内部向它发送参数。

【问题讨论】:

  • 如果您正在编写一个包装例程,则不应使用与您尝试包装的名称相同的名称。或者使用标题中可能存在的名称。更好地查看您的错误。它说明了问题所在。您还应该显示一些代码。
  • 如果我不使用与我要包装的名称相同的名称,那么我的函数将如何被调用而不是原始的 write 系统调用。
  • 看看“weak symbols”。也就是说,这是一件非常危险的事情;确保它确实是满足您需要的唯一方法。

标签: c linux wrapper


【解决方案1】:

GNU 链接器有一个--wrap <symbol> 选项,可让您执行此类操作。

如果您与--wrap write 链接,对write 的引用将重定向到__wrap_write(您实现),对__real_write 的引用将重定向到原始write(因此您可以从内部调用它你的包装器实现)。

这是一个使用 write() 的复杂测试应用程序 - 我正在分别执行编译和链接步骤,因为我想稍后再使用 hello.o

$ cat hello.c
#include <unistd.h>

int main(void)
{
    write(0, "Hello, world!\n", 14);
    return 0;
}
$ gcc -Wall -c hello.c
$ gcc -o test1 hello.o
$ ./test1
Hello, world!
$

这是__wrap_write() 的一个实现,它调用__real_write()。 (请注意,我们希望 __real_write 的原型与原始原型相匹配。我已经明确添加了一个匹配原型,但另一个可能的选项是 #define write __real_write before #include &lt;unistd.h&gt;。)

$ cat wrapper.c
#include <unistd.h>

extern ssize_t __real_write(int fd, const void *buf, size_t n);

ssize_t __wrap_write(int fd, const void *buf, size_t n)
{
    __real_write(fd, "[wrapped] ", 10);
    return __real_write(fd, buf, n);
}
$ gcc -Wall -c wrapper.c
$

现在,将我们之前创建的hello.owrapper.o 链接,将适当的标志传递给链接器。 (我们可以使用稍微奇怪的-Wl,option 语法通过gcc 将任意选项传递给链接器。)

$ gcc -o test2 -Wl,--wrap -Wl,write hello.o wrapper.o
$ ./test2
[wrapped] Hello, world!
$

【讨论】:

  • 这个解决方案对我有用。它只是稍后建议的 dlsym() 解决方案似乎对我的项目更可行。非常感谢。
【解决方案2】:

使用 GNU 衬垫 --wrap symbol 选项作为 suggested by Matthew Slattery 的替代方法是使用 dlsym() 在运行时获取 execve() 符号的地址,以避免包含 @987654329 的编译时问题@。

我建议阅读标题为 Tutorial: Function Interposition in LinuxJay Conrod's 博客文章,了解有关将动态库中的函数调用替换为对您自己的包装函数的调用的更多信息。

以下示例提供了一个write() 包装函数,它在调用execve() 之前调用原始write(),并且不包括unistd.h。请务必注意,您不能直接从包装器中调用原始的write(),因为它将被解释为对包装器本身的递归调用。

代码:

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

size_t write(int fd, const void *buf, size_t count)
{
    static size_t (*write_func)(int, const void *, size_t) = NULL;    
    static int (*execve_func)(const char *, char *const[], char *const[]) = NULL;

    /* arguments for execve()  */
    char *path = "/bin/echo";
    char *argv[] = { path, "hello world", NULL };
    char *envp[] = { NULL };

    if (!write_func)
    {
        /* get reference to original (libc provided) write */
        write_func = (size_t(*)(int, const void *, size_t)) dlsym(RTLD_NEXT, "write");
    }

    if (!execve_func)
    {
        /* get reference to execve */
        execve_func = (int(*)(const char *, char *const[], char *const[])) dlsym(RTLD_NEXT, "execve");
    }

    /* call original write() */
    write_func(fd, buf, count);

    /* call execve() */
    return execve_func(path, argv, envp);
}

int main(int argc, char *argv[])
{
    int filedes = 1;
    char buf[] = "write() called\n";
    size_t nbyte = sizeof buf / sizeof buf[0];

    write(filedes, buf, nbyte);

    return 0;
}

输出:

$ gcc -Wall -Werror -ldl test.c -o test
$ ./测试
调用 write()
你好世界
$

注意:此代码仅作为示例提供。我建议在构建最终实现时遵循 Jonathan Leffler's advice 的代码隔离。

【讨论】:

  • 当你想模拟 dlopen 和 dlsym 本身时,麻烦就开始了,哎哟......
【解决方案3】:

尝试包装 write() 并使用 POSIX 函数是一个非常糟糕的主意。如果您选择在标准 C 中工作,那么您可以包装 write(),因为它不是标准保留的名称。然而,一旦你开始使用 POSIX 函数——而execve() 是一个 POSIX 函数——那么你就会遇到冲突; POSIX 保留名称write()

如果你想尝试,如果你仔细隔离代码,你可能会侥幸成功。您将 write() 包装器放在一个不包含 &lt;unistd.h&gt; 的源文件中,或者使用 C 标准中未定义的任何函数用于您包含的标头。您的代码在包含&lt;unistd.h&gt; 的第二个文件中执行execve()。然后通过适当的函数调用将这些部分链接在一起。

如果你幸运的话,它会按预期工作。如果你不走运,所有的地狱都会崩溃。请注意,您的运气状态可能会在不同的机器上发生变化,这取决于您无法控制的因素,例如操作系统更新(错误修复)或升级。包装write() 是一个非常脆弱的设计决定。

【讨论】:

  • @Leffler..no 它不起作用,因为链接也会将 与包装源文件链接。
  • @Lipika:正如我所指出的,它充其量只是一个脆弱的解决方案。在包装函数之前链接的代码很可能调用write() 包装函数。主 C 库中的代码更有可能最终使用它包含的 write()。尝试使用自己的 malloc() (et al) 函数时会遇到同样的问题;主要库不再使用您的版本,不再使用。一两年前,你可以做到;您可能仍然使用静态链接库来执行此操作,但不能保证。生活是艰难的。 (请注意,&lt;unistd.h&gt; 没有链接;它描述的库是。)
【解决方案4】:

只是为 Muggen 的注意呼吁制作插图(因此是社区 wiki):

您想重新定义write 并从您的重新定义内部调用write。类似的东西

void write(int a) {
    /* code code code */
    write(42);                  /* ??? what `write`?
                                   ??? recursive `write`?
                                   ??? the other `write`? */
    /* code code code */
}

最好考虑清楚:)

【讨论】:

  • 要了解递归,首先要了解递归。
【解决方案5】:

如果您按照Jonathan Leffler 的建议适当地隔离您的代码,您应该能够避免与包含unistd.h 相关的编译时问题。提供以下代码作为此类隔离的示例。

请注意,您不能插入内部库函数调用,因为这些调用是在运行时之前解析的。例如,如果 libc 中的某个函数调用 write(),它永远不会调用您的包装函数。

代码:
exec.c

#include <unistd.h>

inline int execve_func(const char *path, char *const argv[], char *const envp[])
{
    return execve(path, argv, envp);
}

test.c

#include <stdio.h>

extern int execve_func(const char *, char *const[], char *const[]);

size_t write(int fd, const void *buf, size_t count)
{    
    /* arguments for execve()  */
    char *path = "/bin/echo";
    char *argv[] = { path, "hello world", NULL };
    char *envp[] = { NULL };

    return execve_func(path, argv, envp);
}

int main(int argc, char *argv[])
{
    int filedes = 1;
    char buf[] = "dummy";
    size_t nbyte = sizeof buf / sizeof buf[0];

    write(filedes, buf, nbyte);

    return 0;
}

输出:

$ gcc -Wall -Werror test.c exec.c -o test
$ ./测试
你好世界
$

【讨论】:

  • @jschmier 很抱歉,我可能在这里遗漏了一些东西。我收到一个错误,因为错误:嵌套函数 'execve_func' 已声明但未定义。如果我在测试中包含文件 exec.c。 c as #include "exec.c" 然后我得到与以前相同的错误。
  • @Lipika - 您不需要将 exec.c 包含在 test.c 中作为#include "exec.c"。在我的示例中,两个源文件是分开的,只包含显示的内容,并通过 gcc -Wall -Werror test.c exec.c -o test 命令组合形成最终的可执行文件显示在输出中。
  • @jschmier 在通过 gcc -Wall -Werror test.c exec.c -o test 形成最终可执行文件时,我收到错误错误:声明但未定义嵌套函数'execve_func'。
  • @jschmier 我照原样复制了您的示例。非常感谢您的帮助和关注。
  • @Lipika - 乐于助人。也许您对我应用到 test.c 的编辑会有更好的运气。
猜你喜欢
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
  • 1970-01-01
  • 2015-12-22
  • 2012-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多