10

Is it possible to access the arguments to main outside of main (namely in a shared library constructor) on Linux other than by parsing /proc/self/cmdline?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 1
    C does not define any way to access the command-line arguments other than via the parameters of the `main()` function. That function can pass those parameters to other functions, but no other function can obtain them autonomously (via any facility defined by the language). – John Bollinger May 21 '16 at 00:00
  • 2
    The question this was marked as a duplicate of is how to pass the arguments of `main()` to another function. This question asks how to access the command-line arguments of a process *before `main()` is even called*. That's completely different. There's probably a dupe out there, but the one selected isn't it. – Andrew Henle May 21 '16 at 00:01
  • Previous duplicate was [Process argc and argv outside of main()](https://stackoverflow.com/questions/12862798/process-argc-and-argv-outside-of-main). – Jonathan Leffler May 21 '16 at 02:02

1 Answers1

24

You can do this by putting the constructor in the .init_array section. Functions in the .init_array (unlike .init) are called with the same arguments main will be called with: argc, argv and env.

Here's a simple example. I used LD_PRELOAD simply to avoid complicating the example with code which actually links and uses a shared library, but it would work in a more normal scenario as well.

file: printargs.c

#include <stdio.h>

static int printargs(int argc, char** argv, char** env) {
  puts("In printargs:");
  for (int i = 0; i < argc; ++i)
  printf("  Arg %d (%p) '%s'\n", i, (void*)argv[i], argv[i]);
  return 0;
}

/* Put the function into the init_array */
__attribute__((section(".init_array"))) static void *ctr = &printargs;

Build and use the shared library

(If you use -Wall, you will see a warning, because ctr is unused.)

$ gcc -o printargs.so -std=c11 -shared -fpic printargs.c
$ LD_PRELOAD=./printargs.so /bin/echo Hello, world.
In printargs:
  Arg 0 (0x7ffc7617102f) '/bin/echo'
  Arg 1 (0x7ffc76171039) 'Hello,'
  Arg 2 (0x7ffc76171040) 'world.'
Hello, world.

This solution comes from a suggestion by Mike Frysinger in the libc-help mailing list and there is an even more laconic version of this answer here on SO.

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • I misread the question. Thanks for the feedback with my answer. :-) – sjsam May 21 '16 at 05:00
  • Note that if the application modifies `argv[n]` before the library is loaded, you will get modified command line this way, while you would get original command line from `/proc/self/cmdline`. – Employed Russian May 23 '16 at 01:33
  • @EmployedRussian: That depends on what you mean by "modifies argv[n]". If the application changes the value of argv[n], then that will not be reflected in /proc/self/cmdline. If it changes the string that argv[n] points at (say, changing its case), then the change will be visible in /proc/self/cmdline. In the vast majority of cases, though, no changes are made and if you really wanted to ensure that you saw the original values, you should either use a wrapper around main or LD_PRELOAD. – rici May 23 '16 at 02:34
  • @emloyed: also, as I was just reminded, `/proc/self/cmdline` is truncated at 4096 bytes, so it might only give you part of the arguments. – rici May 23 '16 at 16:51
  • When using compiler/linker optimizations, the compiler/linker may remove the `ctr` variable and the `.init_array` section. You can add `__attribute__((used))` to the definition of `ctr` to avoid this. – rveerd Jun 27 '18 at 09:36