Tuesday, February 14, 2006

The char *apple[] Argument Vector

We're all familiar with the arguments passed to the main function by the OS:

  1. int argc

  2. char *argv[]

  3. char *envp[]

But programs started on Mac OS X (i.e. Darwin) actually have access to another argument - the apple vector. The apple vector is defined as char *apple[] and it's passed as the 4th argument to the main() function (it's actually stored right after envp on the stack).

But what is it used for? Well, Apple can use the apple vector to pass whatever "hidden" parameters they want to any program. And they do actually use it, too. Currently, apple[0] contains the path where the executing binary was found on disk. What's that you say? How is apple[0] different from argv[0]? The difference is that argv[0] can be set to any arbitrary value when execve(2) is called. For example, shells often differentiate a login shell from a regular shell by starting login shells with the first character in argv[0] being a -. For example:

$ ps aux | grep -- -bash
jgm 262 0.0 0.1 27820 752 p1 S 5Feb06 0:01.58 -bash

So, we can see that the bash login shell on my Mac was started with a dash in its name. In this example, bash's argv[0] would equal -bash, but its apple[0] would contain the path to where the bash binary was actually found (likely apple[0] would be /bin/bash).

Let's write a simple program to see all this in action:

// Compile with: gcc -o apple apple.c
#include <stdio.h>
int main(int argc, char *argv[], char *envp[], char *apple[]) {
printf("argv[0] = %s\n", argv[0]);
printf("apple[0] = %s\n", apple[0]);
return 0;

And here's a few runs:

$ ./apple
argv[0] = ./apple
apple[0] = ./apple

$ PATH=. apple
argv[0] = apple
apple[0] = ./apple

$ PATH=/Users/jgm apple
argv[0] = apple
apple[0] = /Users/jgm/apple

So, we can see that apple[0] is not the same as argv[0] and that it contains the path to where the executing image was found on disk (taking into account the $PATH).

Now, if want to test the bash example above (where argv[0] doesn't match the binary name), we can write another small test program:

// Compile with: gcc -o exec_apple exec_apple.c
#include <unistd.h>
int main() {
char *theArgv[] = {"-apple", NULL};
execve("./apple", theArgv, NULL);
return 1;

And a run:

$ ./exec_apple
argv[0] = -apple
apple[0] = ./apple

So, just as we expected; argv[0] can really be set to anything by execve(2) but apple[0] should always contain the real path to the executing binary image.

Pretty neat huh?

UPDATE 10/30/2006 here

No comments: