Reputation: 574
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
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
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
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
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
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