簡體   English   中英

包含unistd.h的write()的包裝程序會導致錯誤

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

我正在為write()編寫一個包裝程序來覆蓋原始系統函數,在其中我需要通過execve()執行另一個程序; 我包含頭文件unistd.h 我得到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 如果有人可以幫助我,我會非常感激,因為我需要從包裝器中調用另一個程序,並從包裝程序內部向它發送參數。

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的原型與原始匹配。我已經明確添加了匹配的原型,但另一個可能的選擇是 #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
$

現在,將我們之前創建的hello.owrapper.o ,將相應的標志傳遞給鏈接器。 (我們可以使用稍微奇怪的-Wl,option語法通過gcc將任意選項傳遞給鏈接器。)

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

嘗試包裝write()並使用POSIX函數是一個絕對糟糕的主意。 如果您選擇使用標准C,那么您可以包裝write()因為它不是標准保留的名稱。 但是,一旦你開始使用POSIX函數 - 而execve()是一個POSIX函數 - 那么你就會遇到沖突; POSIX保留名稱write()

如果你想嘗試,如果你仔細分離代碼,你可能會僥幸逃脫。 您將write()包裝器放在一個源文件中,該文件不包含<unistd.h>或者使用C標准中未定義的任何函數來包含您所包含的標頭。 您的代碼在第二個包含<unistd.h>文件中執行execve() 然后使用適當的函數調用將這些部分鏈接在一起。

如果幸運的話,它將按預期工作。 如果你不幸運,一切都會破裂。 請注意,根據您控制之外的因素,例如o / s更新(錯誤修復)或升級,您的運氣狀態可能會在不同的計算機上發生變化。 包裝write()是一個非常脆弱的設計決定。

使用像Matthew Slattery建議的GNU liner --wrap symbol選項的另一種方法是使用dlsym()在運行時獲取execve()符號的地址,以避免包含unistd.h的編譯時問題。 。

我建議閱讀Jay Conrod的博客文章“ 教程:Linux中的函數插入”,以獲取有關通過調用自己的包裝函數替換動態庫中函數調用的其他信息。

下面的示例提供了一個write()包裝函數,它在調用execve()之前調用原始的write() execve() ,並且不包含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()調用
你好,世界
$

注意:此代碼是作為可能的示例提供的。 我建議遵循Jonathan Leffler關於構造最終實現的代碼隔離的建議

只是為Muggen的注意力召喚(因此社區維基):

您希望從重新定義中重新定義write和調用write 就像是

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

更好地思考它:)

如果按照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
你好,世界
$

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM