Search code examples
cargumentsforkargc

Starting program using execv and passing arguments with out raising argc


Ive been given this code in class:

    int main(int argc, char **argv)
    {
        if(argc)
        {
            return 1;
        }

        puts(argv[3]);
        return 0;
    }

Now, Im supposed to write a second program which executes this and makes it print "Hello World!". So I have to find a way to pass in arguments (I assume with execv) while having argc stay at 0.

This is what I have so far:

int main()
{
    pid_t pid = fork();

    if(pid == 0)
    {
        char *argv[] = { "filepath", "placeholder",
                            "placeholder", "Hello World!" };
        execv("filepath", argv);
        exit(0);
    }
    else
    {
        waitpid(pid, 0, 0);
    }
    
    return 0;
}

This is going to run on Linux.

I tried mbj's original suggestion to pass a 0 in the arguments array, which sadly didnt work. However if I put 0 as the first argument, argc becomes 0, but then I get the output "LC_MEASUREMENT=de_DE.UTF-8" when trying to print the arguments. Googling this didnt really help me either.

Im at a total loss here, so any help is appreciated.


Solution

  • Okay, I figured this out now, after some experimenting of my own.

    Since passing an array that starts with NULL (0) to execv results in the program printing out LC_MEASUREMENT=de_DE.UTF-8, I realized that it must means that argv[3] is referring to an element in the process environment (LC_MEASUREMENT is one of the environment variables used to configure locale settings in Linux).

    Solution with execv

    Since execv will copy the current environment to the new program, we just have to modify the environment and put the string "Hello World!" in the correct spot before calling execv. It turns out that the string which gets printed is the one pointed to by index 2 of the environment.

    To access the current environment, we need to declare the environ variable outside of main:

    external char **environ;
    
    int main() 
    {
        ...
    

    And then do this before calling execv:

            char *argv[] = { NULL };
            environ[2] = "Hello world!";
            execv("filepath", argv);
    

    Simpler solution with execve

    Instead of declaring external char **environ and modifying the current environment before calling execv, we can use the execve function, which lets us pass in a new array of strings to use as the environment for the program:

            char *argv[] = { NULL };
            char *envp[] = { "foo", "bar", "Hello World!", NULL };
            execve("filepath", argv, envp);
    

    As the above code snippet shows, "Hello World!" needs to be at index 2 of the environment, regardless of which method we use.

    How it works

    The reason this works is that there's a standard for how command-line arguments and the environment are laid out in process memory when a program is executed: The environment array of char pointers is placed right after the command-line arguments array.

    Since these arrays are terminated with a NULL entry, it will look like this:

    +---------+---------------------------------------+ 
    | Args    | Environment                           |
    +---------+---------+---------+---------+---------+
    |  NULL   | envp[0] | envp[1] | envp[2] |  NULL   | 
    +---------+---------+---------+---------+---------+
        ^         ^                   ^                     
        |         |                   |
     argv[0]    argv[1]     ...     argv[3]
    

    I hope the ASCII art helps you understand why argv[3] means the same thing as environ[2] when we execute the program like this.