2011-03-03 101 views
3

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

+2

如果你正在编写一个包装程序,你不应该使用与你要包装的名称相同的名称。或者使用标题中可能存在的名称。更好地看看你的错误。它说什么是问题。你还应该显示一些代码。 – Muggen 2011-03-03 14:33:33

+0

如果我不使用与我想要包装的名称相同的名称,那么我的函数将如何调用原始写系统调用的instaed。 – 2011-03-03 15:22:54

+0

看看[弱符号](http://en.wikipedia。org/wiki/Weak_symbol)“。这就是说,这是一个非常危险的事情;确保它是真正实现你所需要的唯一方法 – geekosaur 2011-03-05 21:09:30

回答

2

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

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

以下示例提供了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
$ ./test
write() called
hello world
$

注:此代码是作为什么是可能的一个例子。在构建最终实现时,我会建议遵循代码隔离Jonathan Leffler's advice

1

只是让为Muggen的注意调用(因此社会维基)的说明:

要重新定义write并从重新定义里面调用write。像

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

更好的东西更好地思考一下吧:)

+0

为了理解递归,你必须首先理解递归, – jschmier 2011-03-06 05:00:21

2

它是一个完全坏主意,试图包装write()和使用POSIX功能。如果您选择使用标准C,那么您可以打包write(),因为它不是标准保留的名称。但是,一旦你开始使用POSIX函数 - 并且execve()是一个POSIX函数 - 那么你会遇到冲突; POSIX保留名称write()

如果你想尝试,你可能会摆脱它,如果你仔细隔离代码。您的write()包装在一个源文件中,该文件不包含<unistd.h>,或者对于包含的标题使用C标准中未定义的任何函数。你有你的代码,execve()在第二个文件中包含<unistd.h>。你可以用适当的函数调用把这些部分连接起来。

如果你很幸运,它将按预期工作。如果你不幸运,所有地狱都会破裂。并且请注意,根据控制之外的因素(例如o/s更新(错误修复)或升级),您的运气状态可能会在不同的计算机上发生变化。这是一个非常脆弱的设计决定,以包装write()

+0

@ Leffler。 .no不起作用,因为链接也会将与封装源文件联系起来 – 2011-03-06 06:35:04

+1

@Lipika:正如我所指出的那样,它至多是一个脆弱的解决方案,在封装函数之前链接的代码可能会调用write()包装函数,主C库中的代码更有可能最终使用它所包含的'write()',而你尝试使用你自己的'malloc()'(等)函数会遇到同样的问题;主库不要使用你的版本,不要再使用你的版本了,十年前或之后,你可以做到这一点,你仍然可以使用静态链接库,但不能保证它的使用寿命很长(注意' '不链接;它描述的库s是)。 – 2011-03-06 06:46:14

4

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#include <unistd.h>之前。)

$ 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.o先用wrapper.o,将适当的标志传递给链接器。(我们可以用奇略-Wl,option语法通过gcc传递任意选项链接。)

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

这个解决方案适用于我。它只是后面提出的dlsym()解决方案似乎对我的项目更加可行。非常感谢。 – 2011-03-08 02:34:55

0

如果适当隔离代码由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
$ ./test
hello world
$

+0

@jschmier我很抱歉,但我可能会错过这里的东西。我得到一个错误:错误:嵌套函数'execve_func'声明但未定义。如果我在测试中包含文件exec.c。 c作为#include“exec.c”,那么我得到和以前一样的错误。 – 2011-03-07 12:40:59

+0

@Lipika - 你不需要在* test.c *中包含* exec.c *作为'#include“exec.c”'。在我的示例中,这两个源文件保持分离,只包含显示的内容,并通过**中显示的** gcc -Wall -Werror test.c exec.c -o test **命令组合形成最终的可执行文件。 *** ***输出。 – jschmier 2011-03-07 15:26:19

+0

@jschmier它通过gcc -Wall -Werror test.c exec.c -o测试我得到的错误为错误:嵌套函数'execve_func'声明但未定义。 – 2011-03-07 15:46:27