如何在库加载时将参数传递给构造函数?

时间:2015-01-19 08:43:18

标签: c linux gcc constructor shared-libraries

我正在尝试在Linux中创建一个共享库。如何在加载库时将参数传递给my_load()函数?在我的C应用程序中,我调用了test_func()然后它在被调用函数之前自动执行my_load(),最后它执行my_unload()

#include <stdio.h>

void __attribute__ ((constructor)) my_load(int argc, char *argv[]);
void __attribute__ ((destructor)) my_unload(void);
void test_func(void);

void my_load(int argc, char *argv[]) {
printf("my_load: %d\n", argc);
}

void my_unload(void) {
printf("my_unload\n");
}

void test_func(void) {
printf("test_func()\n");
}

3 个答案:

答案 0 :(得分:1)

你不能。

__attribute__((constructor))根本不支持此功能。

似乎没有任何理由可以在my_load(argc, argv)的最开始时拨打main()

当程序正常退出或从main返回时,可以使用atexit注册要调用的函数。

int main(int argc, char **argv)
{
    my_load(argc, argv);
    atexit(my_unload);

    // ...
}

答案 1 :(得分:1)

AFAIK,无法将参数传递给gcc构造函数和析构函数。你能做的最好就是使用全局变量。

在您的示例中,您可以尝试:

主要:

int Argc;
char *Argv[];

int main(int argc, char *argv[]) {
    Argc = argc;
    Argv = argv;
    ...
}

在共享库中:

extern int Argc;
...
void __attribute__ ((constructor)) my_load();
...
void my_load() {
printf("my_load: %d\n", Argc);
}

但无论如何,只有通过dlopen明确加载共享库时,它才有效。它在链接时直接引用,构造函数将在main中的第一条指令之前调用,并且您将始终在Argc中找到原始值或0。

答案 2 :(得分:1)

您的动态库总是可以读取/proc/self/cmdline以查看用于执行当前可执行文件的命令行参数。 example.c

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

static char **get_argv(int *const argcptr)
{
    char  **argv;
    char   *data = NULL;
    size_t  size = 0;    /* Allocated to data */
    size_t  used = 0;
    size_t  argc, i;
    ssize_t bytes;
    int     fd;

    if (argcptr)
        *argcptr = 0;

    do {
        fd = open("/proc/self/cmdline", O_RDONLY | O_NOCTTY);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1)
        return NULL;

    while (1) {

        if (used >= size) {
            char *old_data = data;
            size = (used | 4095) + 4096;
            data = realloc(data, size + 1);
            if (data == NULL) {
                free(old_data);
                close(fd);
                errno = ENOMEM;
                return NULL;
            }
        }

        do {
            bytes = read(fd, data + used, size - used);
        } while (bytes == (ssize_t)-1 && errno == EINTR);
        if (bytes < (ssize_t)0) {
            free(data);
            close(fd);
            errno = EIO;
            return NULL;

        } else
        if (bytes == (ssize_t)0)
            break;

        else
            used += bytes;
    }

    if (close(fd)) {
        free(data);
        errno = EIO;
        return NULL;
    }

    /* Let's be safe and overallocate one pointer here. */
    argc = 1;
    for (i = 0; i < used; i++)
        if (data[i] == '\0')
            argc++;

    /* Reallocate to accommodate both pointers and data. */
    argv = realloc(data, (argc + 1) * sizeof (char *) + used + 1);
    if (argv == NULL) {
        free(data);
        errno = ENOMEM;
        return NULL;
    }
    data = (char *)(argv + argc + 1);
    memmove(data, argv, used);

    /* In case the input lacked a trailing NUL byte. */
    data[used] = '\0';

    /* Assign the pointers. */
    argv[0] = data;
    argc = 0;
    for (i = 0; i < used; i++)
        if (data[i] == '\0')
            argv[++argc] = data + i + 1;
    /* Final pointer points to past data. Make it end the array. */
    argv[argc] = NULL;

    if (argcptr)
        *argcptr = (int)argc;

    return argv;
}

/* Example standard error functions, that avoid the use of stdio.h.
*/
static void wrerr(const char *p)
{
    if (p != NULL) {
        const char *const q = p + strlen(p);
        ssize_t           n;

        while (p < q) {
            n = write(STDERR_FILENO, p, (size_t)(q - p));
            if (n > (ssize_t)0)
                p += n;
            else
            if (n != (ssize_t)-1)
                return;
            else
            if (errno != EINTR)
                return;
        }
    }
}
static void wrerrint(const int i)
{
    char          buffer[32];
    char         *p = buffer + sizeof buffer;
    unsigned int  u;

    if (i < 0)
        u = (unsigned int)(-i);
    else
        u = (unsigned int)i;

    *(--p) = '\0';
    do {
        *(--p) = '0' + (u % 10U);
        u /= 10U;
    } while (u > 0U);
    if (i < 0)
        *(--p) = '-';

    wrerr(p);
}



static void init(void) __attribute__((constructor));
static void init(void)
{
    int    argc, i, saved_errno;
    char **argv;

    saved_errno = errno;

    argv = get_argv(&argc);
    if (argv == NULL) {
        const char *const errmsg = strerror(errno);
        wrerr("libexample.so: get_argv() failed: ");
        wrerr(errmsg);
        wrerr(".\n");
        errno = saved_errno;
        return;
    }

    for (i = 0; i < argc; i++) {
        wrerr("libexample.so: argv[");
        wrerrint((int)i);
        wrerr("] = '");
        wrerr(argv[i]);
        wrerr("'\n");
    }

    free(argv);

    errno = saved_errno;
    return;
}

使用例如

进行编译
gcc -Wall -fPIC -shared example.c -ldl -Wl,-soname,libexample.so -o libexample.so

并使用例如

进行测试
LD_PRELOAD=./libexample.so /bin/echo foo bar baz baaz

(请注意,普通echo是内置的shell,您需要执行另一个二进制文件,如/bin/echo来加载预加载库。)

但是,大多数动态库都会在环境变量中使用参数;例如,YOURLIB_MEM用于某些内存大小提示,或YOURLIB_DEBUG用于在运行时启用详细调试输出。

(我的示例代码不使用stdio.h输出,因为并非所有二进制文件都使用它,特别是如果用其他语言编写的话。而wrerr()wrerrint()是小的愚蠢辅助函数,使用低级unistd.h I / O直接写入标准错误;这总是有效,并且在运行时产生最小的副作用。)

有问题吗?