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

tzxcd3kk  于 2023-01-12  发布在  其他
关注(0)|答案(5)|浏览(151)

我尝试在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");
}
dgtucam1

dgtucam11#

您的动态库始终可以读取/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

(Note普通的echo是一个shell内置函数,您需要执行另一个类似/bin/echo的二进制文件来加载预加载库。)
但是,大多数动态库都在环境变量中接受参数;例如,YOURLIB_MEM用于某个内存大小提示,或者YOURLIB_DEBUG用于在运行时期间启用详细调试输出。
(My示例代码不使用stdio. h输出,因为不是所有的二进制文件都使用它,特别是用其他语言编写的时候,wrerr()wrerrint()是小而愚蠢的辅助函数,使用低级unistd. h I/O直接写入标准错误;这总是有效的,并且在运行时引起最小的副作用。
有问题吗?

2w3kk1z5

2w3kk1z52#

你不能。
__attribute__((constructor))根本不支持这种情况。
似乎没有任何理由不能在main()的开头调用my_load(argc, argv)
您可以使用atexit来注册一个函数,以便在程序正常退出或从main返回时调用。

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

    // ...
}
tnkciper

tnkciper3#

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。

798qvoo8

798qvoo84#

很抱歉在这里重复一个老问题,但我刚刚在Linux和Mac OS上测试了这个问题:

$ gcc -x c -o test_prog -
#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");
}

int main() { return 0; }

它会在两个系统上打印结果:

$ ./test_prog foo bar baz
my_load: 4
my_unload

为了让它作为一个共享库工作,我不得不添加链接器选项-Wl,--no-gc-sections,因为否则它会积极地删除构造函数和析构函数。

esbemjvw

esbemjvw5#

这并不使用__attribute__ ((constructor))语法,但如果指定一个自定义_init函数,则可以这样做:

// foo.c
#include <stdio.h>
void my_constructor(int argc, char**argv) {
  printf("my_constructor init: %s\n", argv[1]);
}

为此,您需要传递ld -init my_constructorgcc -Wl,-init,my_constructor,例如

gcc foo.c -shared -o libfoo.so -Wl,-init,my_constructor

相关问题