Подпрограмма обертки для write () с включенным unistd.h приводит к ошибке - PullRequest
3 голосов
/ 03 марта 2011

Я пишу программу-оболочку для write(), чтобы переопределить исходную системную функцию, и в ней мне нужно выполнить другую программу через execve();для которого я включаю заголовочный файл unistd.h.Я получаю ошибку conflicting types for 'write' /usr/include/unistd.h:363:16: note: previous declaration of 'write'was here.Я был бы очень признателен, если бы кто-то мог помочь мне, так как мне нужно вызывать другую программу изнутри оболочки, а также посылать ей аргументы из подпрограммы оболочки.

Ответы [ 5 ]

4 голосов
/ 05 марта 2011

У компоновщика 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, передав соответствующие флаги компоновщику.(Мы можем передавать произвольные опции через gcc компоновщику, используя слегка нечетный синтаксис -Wl,option.)

$ gcc -o test2 -Wl,--wrap -Wl,write hello.o wrapper.o
$ ./test2
[wrapped] Hello, world!
$
2 голосов
/ 06 марта 2011

Альтернативой использованию опции GNU liner --wrap symbol в качестве , предложенной Мэтью Слаттери , было бы использование dlsym() для получения адреса символа execve() во время выполненияво избежание проблем во время компиляции с включением unistd.h.

Предлагаю прочитать сообщение в блоге Джея Конрода под названием Учебное пособие: Вложение функций в Linux для получения дополнительной информации о замене вызовов функций в динамических библиотеках навызовы ваших собственных функций-оболочек.

В следующем примере предоставляется функция-оболочка write(), которая вызывает исходный 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 () называется
hello world
$

Примечание. Этот код предоставлен в качестве примера того, что возможно.Я бы рекомендовал следовать совету Джонатана Леффлера по разделению кода при построении окончательной реализации.

2 голосов
/ 03 марта 2011

Совершенно плохая идея попробовать обернуть write() и использовать функции POSIX.Если вы решили работать в стандарте C, вы можете обернуть write(), потому что это имя не зарезервировано для стандарта.Однако, как только вы начинаете использовать функции POSIX - а execve() - это функция POSIX, - вы сталкиваетесь с конфликтами;POSIX оставляет за собой имя write().

Если вы хотите попробовать, вы можете избежать неприятностей, если тщательно разделите код.У вас есть write() оболочка в одном исходном файле, который не включает <unistd.h>, или вы используете любые функции, не определенные в стандарте C для заголовков, которые вы включаете.У вас есть код, который делает execve() во втором файле, который включает <unistd.h>.И вы связываете эти части вместе с соответствующими вызовами функций.

Если вам повезет, все будет работать так, как задумано.Если вам не повезет, весь ад вырвется на свободу.И обратите внимание, что ваш статус удачи может меняться на разных машинах в зависимости от факторов, которые находятся вне вашего контроля, таких как обновления o / s (исправления ошибок) или обновления.Это очень хрупкое дизайнерское решение обернуть write().

1 голос
/ 03 марта 2011

Просто сделаю иллюстрацию для привлечения внимания Маггена (следовательно, вики сообщества):

Вы хотите переопределить write и позвонить write изнутри вашего переопределения. Что-то вроде

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

Лучше подумай об этом:)

0 голосов
/ 06 марта 2011

Если вы выделите свой код надлежащим образом, как предложено Джонатаном Леффлером , вы сможете избежать проблем во время компиляции, связанных с включением 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
$

...