Saturday, July 29, 2006

Access argc and argv from Anywhere

Say you're in some random function that's stuck deep down in the middle of some big program you're writing on the Mac. And now assume that you'd like to have access to the arguments that were passed to main() when the program started. How can you do this?

Well, as it turns out, this work has already been done for us. Let's have a look inside /usr/lib/libSystem.B.dylib shall we.

$ nm /usr/lib/libSystem.B.dylib | grep _NSGetArg
...
9003a382 T __NSGetArgc
90020f60 T __NSGetArgv
...

Ahh, so it looks like we found some symbols (functions) whose names look very revealing (man nm to see more details on what the output means above). The C compiler automatically prepends a leading underscore to symbol names, so the actual function names should be: _NSGetArgc(void) and _NSGetArgv(void). Let's pick one and try to use it.

Since the function NSGetArgc() is not declared in any header files (that I know of), we'll need to declare it ourselves. But we want to link against the version of the function that's in libSystem, so we'll declare it extern. Let's take a first stab:
$ cat nsargc.c
#include <stdio.h>
extern int _NSGetArgc(void);
int main(void) {
printf("argc=%d\n", _NSGetArgc());
return 0;
}
$ gcc -o nsargc nsargc.c -Wall -std=c99
$ ./nsargc foo bar
argc=8192

Well, that's certainly not correct. Maybe we're using the function wrong. The real "argc" should be an int, but maybe this function returns a pointer to it instead of returning the actual value. Let's try that:
$ cat nsargc.c
#include <stdio.h>
extern int *_NSGetArgc(void);
int main(void) {
printf("argc=%d\n", *_NSGetArgc());
return 0;
}
$ gcc -o nsargc nsargc.c -Wall -std=c99
$ ./nsargc foo bar
argc=3

Hey! Now that's more like it. So it looks like these functions may return pointers to the values we want rather than the actual values we want. Now let's try this with argv as well.
$ cat NSGetArgs.c
#include <stdio.h>

extern int *_NSGetArgc(void);
extern char ***_NSGetArgv(void);

void DoStuff(void) {
printf("%20s = %d\n", "_NSGetArgc()", *_NSGetArgc());

char **argv = *_NSGetArgv();
for (int i = 0; argv[i] != NULL; ++i)
printf("%15s [%02d] = '%s'\n", "_NSGetArgv()", i, argv[i]);
}

int main(void) {
DoStuff();
return 0;
}
$ gcc -o NSGetArgs NSGetArgs.c -Wall -std=c99
$ ./NSGetArgs foo bar
_NSGetArgc() = 3
_NSGetArgv() [00] = './NSGetArgs'
_NSGetArgv() [01] = 'foo'
_NSGetArgv() [02] = 'bar'


Sweet, it looks like that works. So now we can get access to argc and argv from anywhere within a program. And notice that we didn't even need to declare the arguments in main's signature.

Here's a few similar functions that may be interesting.
$ nm /usr/lib/libSystem.B.dylib | grep _NSGet | grep ' T '
9002a55f T _NSGetNextSearchPathEnumeration
9003a382 T __NSGetArgc
90020f60 T __NSGetArgv
90003074 T __NSGetEnviron
90029e2d T __NSGetMachExecuteHeader
90027506 T __NSGetProgname
9014aa84 T _NSGetSectionDataInObjectFileImage
90036106 T __NSGetExecutablePath

2 comments:

Ahruman said...

Just reading through your archive. I thought NSGetArgc()/NSGetArgv() where declared somewhere in Foundation, but it seems not. Weird; I’ve seen them somewhere.

They are, however, declared in /usr/include/crt_externs.h.

M said...

I'm just curious ... if it's returning a pointer why isn't it printing out the address?