Chapter 27.1 โ€” Executing a New Program: execve()

Chapter 27.1 โ€” Executing a New Program: execve()
Linux System Programming ยท EmbeddedPathashala
๐Ÿ“Œ Topic
execve()
๐Ÿง  Level
Beginner
๐Ÿ’ป Examples
3 Programs
โ“ Q&A
8 Questions

What is execve()?

Imagine your Linux process is like a person running a program. The execve() system call is like that person picking up a completely new script, throwing away the old one, and starting fresh โ€” in the same body (same process ID).

When you call execve(), the kernel loads a brand new program into the current process’s memory. The old program code, stack, heap, and data โ€” all gone. The new program starts running from its own main().

Key point: The process ID does NOT change. Only the running program changes.

How execve() Works โ€” Step by Step

Step What Happens Before execve() After execve()
1 Process identity PID = 1234 PID = 1234 (unchanged)
2 Program code Old program text New program loaded
3 Stack & Heap Old process memory Replaced completely
4 Execution starts Old main() running New main() starts
5 Return value โ€” Never returns on success

Function Signature

#include <unistd.h>

int execve(const char *pathname, char *const argv[], char *const envp[]);

/* Returns: never on success, -1 on error */
Parameter Type Purpose Example
pathname const char * Path to the new program to run "/bin/ls"
argv[] char *const [] Command-line arguments (NULL-terminated) {"ls", "-l", NULL}
envp[] char *const [] Environment variables (NULL-terminated) {"HOME=/root", NULL}

โš ๏ธ Important Rules About execve()

  • argv[0] is the program name by convention โ€” usually the basename of the executable
  • Both argv[] and envp[] must end with a NULL pointer
  • execve() never returns if it succeeds โ€” your old code is gone!
  • If it does return, it means an error occurred โ€” check errno
  • The process ID remains the same after exec

Example 1: Basic execve() โ€” Run /bin/ls

This is the simplest possible use of execve(). We launch the ls command from a C program.

/* example1_execve_basic.c
 * Run: gcc -o example1 example1_execve_basic.c
 *      ./example1
 */
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(void)
{
    /* argv[0] = program name, argv[1] = flag, NULL = end of list */
    char *argv[] = { "ls", "-l", "/tmp", NULL };

    /* Simple environment: just one variable */
    char *envp[] = { "HOME=/root", "PATH=/bin:/usr/bin", NULL };

    printf("Before execve: PID = %d\n", getpid());
    printf("About to exec /bin/ls ...\n");

    /* Replace this process with /bin/ls */
    execve("/bin/ls", argv, envp);

    /* If we reach here, execve() failed */
    printf("execve failed: %s\n", strerror(errno));
    return 1;
}
Expected Output:
Before execve: PID = 4321
About to exec /bin/ls ...
(ls output of /tmp directory)

Notice: the “After execve” message never prints โ€” because execve() replaced the process!

Example 2: execve() with fork() โ€” Classic Pattern

The most common real-world use: fork() first, then exec() in the child. This way the parent process keeps running.

/* example2_fork_execve.c
 * Run: gcc -o example2 example2_fork_execve.c
 *      ./example2
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

int main(void)
{
    pid_t child_pid;
    int status;

    printf("Parent PID: %d โ€” starting fork\n", getpid());

    child_pid = fork();

    if (child_pid == -1) {
        perror("fork failed");
        exit(1);
    }

    if (child_pid == 0) {
        /* ---- CHILD PROCESS ---- */
        printf("Child PID: %d โ€” about to exec /bin/date\n", getpid());

        char *argv[] = { "date", NULL };
        char *envp[] = { "TZ=Asia/Kolkata", NULL };  /* IST timezone */

        execve("/bin/date", argv, envp);

        /* Only reached if exec fails */
        fprintf(stderr, "exec failed: %s\n", strerror(errno));
        exit(127);
    }

    /* ---- PARENT PROCESS ---- */
    printf("Parent: waiting for child %d to finish...\n", child_pid);

    if (waitpid(child_pid, &status, 0) == -1) {
        perror("waitpid failed");
        exit(1);
    }

    if (WIFEXITED(status))
        printf("Parent: child exited with status %d\n", WEXITSTATUS(status));

    printf("Parent: continuing after child done\n");
    return 0;
}
Expected Output:
Parent PID: 1000 โ€” starting fork
Child PID: 1001 โ€” about to exec /bin/date
Thu Jun 5 10:30:00 IST 2025
Parent: waiting for child 1001 to finish...
Parent: child exited with status 0
Parent: continuing after child done

Example 3: execve() Environment Variables

Here we show how the child program receives and can read the environment variables we pass via envp.

/* example3_exec_env.c
 * TWO files: the launcher and the receiver
 * File 1: launcher.c โ€” calls execve() with custom env
 * File 2: show_env.c โ€” prints its env and argv
 */

/* ===== FILE 1: launcher.c ===== */
/* gcc -o launcher launcher.c && gcc -o show_env show_env.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <path-to-show_env>\n", argv[0]);
        exit(1);
    }

    /* Build argument vector */
    char *new_argv[] = {
        "show_env",        /* argv[0]: program name */
        "hello",           /* argv[1] */
        "world",           /* argv[2] */
        NULL               /* Must end with NULL */
    };

    /* Build environment vector โ€” "name=value" pairs */
    char *new_envp[] = {
        "GREETING=Namaste",
        "PLACE=Hyderabad",
        "LANG=en_IN",
        NULL               /* Must end with NULL */
    };

    printf("Launcher PID: %d\n", getpid());
    printf("Launching: %s\n", argv[1]);

    execve(argv[1], new_argv, new_envp);

    /* Should not reach here */
    fprintf(stderr, "execve failed: %s\n", strerror(errno));
    exit(1);
}

/* ===== FILE 2: show_env.c ===== */
#include <stdio.h>
#include <stdlib.h>

extern char **environ;  /* Global environment pointer */

int main(int argc, char *argv[])
{
    int i;
    char **ep;

    printf("=== show_env: PID = %d ===\n", getpid());

    printf("\n--- Command-line arguments ---\n");
    for (i = 0; i < argc; i++)
        printf("  argv[%d] = %s\n", i, argv[i]);

    printf("\n--- Environment variables ---\n");
    for (ep = environ; *ep != NULL; ep++)
        printf("  %s\n", *ep);

    return 0;
}
Expected Output:
Launcher PID: 2000
Launching: ./show_env
=== show_env: PID = 2000 === โ† Same PID!
--- Command-line arguments ---
argv[0] = show_env
argv[1] = hello
argv[2] = world
--- Environment variables ---
GREETING=Namaste
PLACE=Hyderabad
LANG=en_IN

Common Errors from execve()

errno Meaning Common Cause
EACCES Permission denied File has no execute permission, or filesystem mounted noexec
ENOENT File not found Wrong path, typo in pathname
ENOEXEC Not a valid executable Script without #! line, corrupt binary
ETXTBSY File busy Another process has the file open for writing
E2BIG Argument list too large argv + envp total exceeds system limit

What Changes vs What Stays After execve()

Attribute After execve() Notes
Process ID (PID) โœ… Unchanged Same process, just different program
Parent PID (PPID) โœ… Unchanged Still the same parent
Open file descriptors โœ… Unchanged (unless FD_CLOEXEC set) See Chapter 27.4
Program code (text) โŒ Replaced New program’s code loaded
Stack & Heap โŒ Replaced Fresh memory for new program
Signal handlers โŒ Reset to default Handler functions are gone with old code
Effective UID/GID โš ๏ธ May change If new file has setUID/setGID bit

๐Ÿ” setUID and execve()

If the file being exec’d has the set-user-ID (SUID) bit set, the process’s effective user ID changes to the file owner’s UID during exec. This is how programs like passwd run with root privileges even when called by a normal user.

/* Check setUID bit before exec */
#include <sys/stat.h>

struct stat sb;
stat("/usr/bin/passwd", &sb);

if (sb.st_mode & S_ISUID)
    printf("passwd has SUID bit set โ€” runs as root!\n");

โ“ Interview Questions โ€” execve()

Q1. What does execve() do to the calling process?
Answer: It replaces the current process image (code, stack, heap, data) with a new program. The process ID stays the same. execve() does not return on success โ€” the new program starts running from its own main().
Q2. Why must argv[] and envp[] end with NULL?
Answer: Both are arrays of string pointers. The kernel needs to know where the list ends. A NULL pointer at the end acts as a sentinel/terminator โ€” without it, the kernel would read past the end of the array into garbage memory.
Q3. When would you use fork() before execve()?
Answer: When you want the parent to continue running after the child execs a new program. fork() creates a copy of the process; the child then calls execve() to replace itself. The parent waits or continues independently.
Q4. What does ENOEXEC error mean in execve()?
Answer: The file exists and has execute permission, but the kernel cannot recognize its format as a valid executable. This often means it’s a shell script without a #!/bin/sh line, or a binary for a different architecture.
Q5. After execve(), does the new program inherit open file descriptors?
Answer: Yes, by default all open file descriptors (stdin, stdout, stderr, and any others) remain open across execve(). You can prevent this with the FD_CLOEXEC flag on individual descriptors.
Q6. What happens to signal handlers after execve()?
Answer: Signal handlers are reset to SIG_DFL (default action). This is because the handler function lives in the old program’s code, which is gone after exec. Signals set to SIG_IGN remain ignored.
Q7. What is /proc/PID/exe?
Answer: It is a symbolic link in the Linux /proc filesystem that points to the absolute path of the executable file currently being run by that process. For example, /proc/1234/exe โ†’ /usr/bin/python3.
Q8. What is argv[0] by convention in execve()?
Answer: argv[0] should be the program name โ€” typically the basename of the pathname being executed. This is just a convention (the kernel doesn’t enforce it), but programs use argv[0] to display their own name in error messages and usage output.

Next: exec() Library Functions

Learn about execl(), execlp(), execvp() and how they simplify execve()

โ†’ Part 2: exec() Library Functions ๐Ÿ  All Tutorials

Leave a Reply

Your email address will not be published. Required fields are marked *