简体   繁体   English

包含unistd.h的write()的包装程序会导致错误

[英]Wrapper routine for write() with unistd.h included results in error

I am writing a wrapper routine for write() to override the original system function and within it i need to execute another program through execve() ; 我正在为write()编写一个包装程序来覆盖原始系统函数,在其中我需要通过execve()执行另一个程序; for which I include the header file unistd.h . 我包含头文件unistd.h I get the error conflicting types for 'write' /usr/include/unistd.h:363:16: note: previous declaration of 'write'was here . 我得到conflicting types for 'write' /usr/include/unistd.h:363:16: note: previous declaration of 'write'was here的错误conflicting types for 'write' /usr/include/unistd.h:363:16: note: previous declaration of 'write'was here I would be very gratefull if someone could help me out as I need to call another program from inside the wrapper and also send arguments to it from inside the wrapper routine. 如果有人可以帮助我,我会非常感激,因为我需要从包装器中调用另一个程序,并从包装程序内部向它发送参数。

The GNU linker has a --wrap <symbol> option which allows you to do this sort of thing. GNU链接器有一个--wrap <symbol>选项,允许您执行此类操作。

If you link with --wrap write , references to write will redirect to __wrap_write (which you implement), and references to __real_write will redirect to the original write (so you can call it from within your wrapper implementation). 如果您链接--wrap write ,引用write重定向到__wrap_write (你实现),并引用__real_write会重定向到原来write (所以你可以从你的包装器实现中调用它)。

Here's a sophisticated test application using write() - I'm doing the compilation and linking steps separately because I'll want to use hello.o again in a minute: 这是一个使用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!
$

Here's an implementation of __wrap_write() , which calls __real_write() . 这是__wrap_write()的实现,它调用__real_write() (Note that we want a prototype for __real_write to match the original. I've added a matching prototype explicitly, but another possible option is to #define write __real_write before #include <unistd.h> .) (注意,我们希望__real_write的原型与原始匹配。我已经明确添加了匹配的原型,但另一个可能的选择是 #include <unistd.h> 之前 #define write __real_write 。)

$ 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
$

Now, link the hello.o we made earlier with wrapper.o , passing the appropriate flags to the linker. 现在,将我们之前创建的hello.owrapper.o ,将相应的标志传递给链接器。 (We can pass arbitrary options through gcc to the linker using the slightly odd -Wl,option syntax.) (我们可以使用稍微奇怪的-Wl,option语法通过gcc将任意选项传递给链接器。)

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

It is an utterly bad idea to try wrapping write() and use POSIX functions. 尝试包装write()并使用POSIX函数是一个绝对糟糕的主意。 If you chose to work in standard C, then you could wrap write() because it is not a name reserved to the standard. 如果您选择使用标准C,那么您可以包装write()因为它不是标准保留的名称。 However, once you start using POSIX functions - and execve() is a POSIX function - then you are running into conflicts; 但是,一旦你开始使用POSIX函数 - 而execve()是一个POSIX函数 - 那么你就会遇到冲突; POSIX reserves the name write() . POSIX保留名称write()

If you want to try, you may get away with it if you segregate the code carefully. 如果你想尝试,如果你仔细分离代码,你可能会侥幸逃脱。 You have your write() wrapper in one source file which does not include <unistd.h> or use any functions not defined in the C standard for the headers you do include. 您将write()包装器放在一个源文件中,该文件不包含<unistd.h>或者使用C标准中未定义的任何函数来包含您所包含的标头。 You have your code that does the execve() in a second file that does include <unistd.h> . 您的代码在第二个包含<unistd.h>文件中执行execve() And you link those parts together with appropriate function calls. 然后使用适当的函数调用将这些部分链接在一起。

If you are lucky, it will work as intended. 如果幸运的话,它将按预期工作。 If you aren't lucky, all hell will break loose. 如果你不幸运,一切都会破裂。 And note that your luck status might change on different machines depending on factors outside your control such as o/s updates (bug fixes) or upgrades. 请注意,根据您控制之外的因素,例如o / s更新(错误修复)或升级,您的运气状态可能会在不同的计算机上发生变化。 It is a very fragile design decision to wrap write() . 包装write()是一个非常脆弱的设计决定。

An alternative to using the GNU liner --wrap symbol option as suggested by Matthew Slattery would be to use dlsym() to obtain the address of the execve() symbol at runtime in order to avoid the compile-time issues with including unistd.h . 使用像Matthew Slattery建议的GNU liner --wrap symbol选项的另一种方法是使用dlsym()在运行时获取execve()符号的地址,以避免包含unistd.h的编译时问题。 。

I suggest reading Jay Conrod's blog post entitled Tutorial: Function Interposition in Linux for additional information on replacing calls to functions in dynamic libraries with calls to your own wrapper functions. 我建议阅读Jay Conrod的博客文章“ 教程:Linux中的函数插入”,以获取有关通过调用自己的包装函数替换动态库中函数调用的其他信息。

The following example provides a write() wrapper function that calls the original write() before calling execve() and does not include unistd.h . 下面的示例提供了一个write()包装函数,它在调用execve()之前调用原始的write() execve() ,并且不包含unistd.h It is important to note that you cannot directly call the original write() from the wrapper because it will be interpreted as a recursive call to the wrapper itself. 重要的是要注意,您不能直接从包装器调用原始write() ,因为它将被解释为对包装器本身的递归调用。

Code: 码:

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

Output: 输出:

$ gcc -Wall -Werror -ldl test.c -o test $ gcc -Wall -Werror -ldl test.c -o test
$ ./test $ ./test
write() called write()调用
hello world 你好,世界
$ $

Note: This code is provided as an example of what is possible. 注意:此代码是作为可能的示例提供的。 I would recommend following Jonathan Leffler's advice on code segregation in constructing the final implementation. 我建议遵循Jonathan Leffler关于构造最终实现的代码隔离的建议

Just making an illustration for Muggen's attention call (therefore community wiki): 只是为Muggen的注意力召唤(因此社区维基):

You want to redefine write and call write from inside your redefinition. 您希望从重新定义中重新定义write和调用write Something like 就像是

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

Better think better about it :) 更好地思考它:)

If you segregate your code appropriately as suggested by Jonathan Leffler , you should be able to avoid compile-time issues related to including unistd.h . 如果按照Jonathan Leffler的建议适当地隔离代码,则应该能够避免与包含unistd.h相关的编译时问题。 The following code is provided as an example of such segregation. 提供以下代码作为这种隔离的示例。

Note that you cannot interpose internal library function calls, since these are resolved before runtime. 请注意,您不能插入内部库函数调用,因为这些调用在运行时之前已解析。 For instance, if some function in libc calls write() , it will never call your wrapper function. 例如,如果libc中的某些函数调用write() ,它将永远不会调用您的包装函数。

Code: 码:
exec.c 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 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;
}

Output: 输出:

$ gcc -Wall -Werror test.c exec.c -o test $ gcc -Wall -Werror test.c exec.c -o test
$ ./test $ ./test
hello world 你好,世界
$ $

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM