Marss
Marss

Reputation: 574

how to pass argument to constructor on library load?

I am trying to create a shared library in Linux. How can I pass an argument to function my_load() when library is loaded? In my C application, I make a call to test_func() then it automatically executes my_load() first before the called function then lastly it executes 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");
}

Upvotes: 1

Views: 2517

Answers (5)

rumpel
rumpel

Reputation: 8300

This doesn’t use the __attribute__ ((constructor)) syntax, but if you specify a custom _init function, you can do so:

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

To do so you need to pass ld -init my_constructor or gcc -Wl,-init,my_constructor, e.g.

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

Upvotes: 0

David M. Lloyd
David M. Lloyd

Reputation: 2825

Sorry to resurrect an oldie here but I just tested this on both Linux and 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; }

And it prints this result on both systems:

$ ./test_prog foo bar baz
my_load: 4
my_unload

In order for it to work as a shared library I did have to add the linker option -Wl,--no-gc-sections because it otherwise aggressively removed the constructor & destructor. But otherwise yeah this works already.

Upvotes: 0

Nominal Animal
Nominal Animal

Reputation: 39356

Your dynamic library can always read /proc/self/cmdline to see what the command-line parameters used to execute the current executable are. 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;
}

Compile using e.g.

gcc -Wall -fPIC -shared example.c -ldl -Wl,-soname,libexample.so -o libexample.so

and test using e.g.

LD_PRELOAD=./libexample.so /bin/echo foo bar baz baaz

(Note that plain echo is a shell built-in, and you need to execute another binary like /bin/echo to load the preload library.)

However, most dynamic libraries take arguments in environment variables instead; for example, YOURLIB_MEM for some memory size hint, or YOURLIB_DEBUG for enabling verbose debugging output during runtime.

(My example code does not use stdio.h output, because not all binaries use it, especially if written in some other language. Instead, the wrerr() and wrerrint() are small stupid helper functions that use low-level unistd.h I/O to write directly to standard error; this always works, and causes minimal side effects at run time.)

Questions?

Upvotes: 2

Serge Ballesta
Serge Ballesta

Reputation: 148965

AFAIK, there is no way to pass arguments to gcc constructor and destructor functions. The best you can do is to use global variables.

In you example, you could try :

In main :

int Argc;
char *Argv[];

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

In shared library :

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

But anyway, it can only work if you explicitely load the shared library through dlopen. It it is directly referenced at link time, the constructor function will be called before first instruction in main and you will always find the original value or 0 in Argc.

Upvotes: 1

Jonathon Reinhart
Jonathon Reinhart

Reputation: 137438

You can't.

__attribute__((constructor)) simply doesn't support this.

There doesn't seem to be any reason you can't just call my_load(argc, argv) at the very beginning of main().

You can use atexit to register a function to be called when your program exits normally, or returns from main.

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

    // ...
}

Upvotes: 1

Related Questions