【问题标题】:how to pass argument to constructor on library load?如何在库加载时将参数传递给构造函数?
【发布时间】:2015-01-19 08:43:17
【问题描述】:

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

【问题讨论】:

    标签: c linux gcc constructor shared-libraries


    【解决方案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
    

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

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

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

    问题?

    【讨论】:

    • 这当然假设 /proc 已挂载,但并非总是如此。
    • @JonathonReinhart:是的。但是,无论如何,您不能假设一个程序在没有安装在 Linux 中的 /proc 的情况下也能正常工作。如果没有/proc,你会失去很多功能,包括像fexecve() 这样的一些glibc 函数,以及像pidof 这样的实用程序停止工作。不过,这并不重要:正如我所说,“大多数动态库 [应该] 在环境变量中接受参数”,而这绝不依赖于 /proc
    【解决方案2】:

    你不能。

    __attribute__((constructor)) 根本不支持这个。

    您似乎没有任何理由不能在main() 的开头直接调用my_load(argc, argv)

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

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

    【讨论】:

      【解决方案3】:

      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。

      【讨论】:

        【解决方案4】:

        很抱歉在这里复活了一个老歌,但我刚刚在 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,因为否则它会积极地删除构造函数和析构函数。但除此之外,是的,这已经可以了。

        【讨论】:

          猜你喜欢
          • 2012-05-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多