Uso de execve en C

Abdul Mateen 12 octubre 2023
  1. la llamada al sistema exec en C
  2. la llamada al sistema execve en C
Uso de execve en C

Este tutorial discutirá el uso de execve para ejecutar los comandos estándar de Linux y nuestros ejecutables en C.

Primero, discutiremos la llamada al sistema exec y la familia exec. A continuación, llamaremos a nuestros ejecutables en C y, finalmente, analizaremos los comandos estándar de Linux y nuestros ejecutables.

la llamada al sistema exec en C

La llamada al sistema exec reemplaza el proceso en ejecución con algún otro proceso ejecutable. El espacio de direcciones del proceso en ejecución se reemplaza con el espacio de direcciones del nuevo proceso.

Es importante tener en cuenta que el nuevo programa se carga en el mismo espacio de direcciones. El ID del proceso sigue siendo el mismo.

El nuevo programa se ejecutará de forma independiente; es decir, el punto de partida será el punto de entrada del nuevo programa. La llamada al sistema exec tiene muchas variantes.

  1. execl
  2. execle
  3. execlp
  4. execv
  5. execve
  6. execvp

Estas funciones utilizan la misma base exec seguida de una o varias letras. El detalle de las letras extra está abajo.

  1. e - Aquí, e es para variables de entorno; esta función tiene una matriz de punteros que apuntan a variables de entorno. La lista de variables de entorno se pasa explícitamente al programa recién cargado.
  2. l - Aquí, l es para argumentos de línea de comando. Podemos dar la lista de argumentos de línea de comando a la función.
  3. p - Aquí, p es para la ruta de la variable de entorno. En esta función, la variable de ruta ayuda a encontrar el archivo, pasado como argumento al proceso recién cargado.
  4. v - Aquí, v también es para los argumentos de la línea de comandos. Sin embargo, en esta función, los argumentos de la línea de comando se pasan al proceso recién cargado como una matriz de punteros.

En la llamada al sistema básica exec, el espacio de direcciones del proceso actual se reemplaza con el espacio de direcciones del programa recién cargado; como resultado, el proceso que se está ejecutando actualmente finaliza. El proceso recién cargado se pasa como argumento en esta llamada al sistema.

El proceso recién cargado tiene la misma identificación de proceso, las mismas variables de entorno y el mismo conjunto de descriptores de archivo. Sin embargo, las estadísticas de la CPU y la memoria virtual se ven afectadas.

Sintaxis de las llamadas exec

Aquí, tenemos seis sintaxis de llamadas al sistema, variantes de llamadas al sistema ejecutivas básicas.

int execl(const char* path, const char* arg, ...) int execle(
    const char* path, const char* arg, ...,
    char* const envp
        []) int execlp(const char* file, const char* arg,
                       ...) int execv(const char* path,
                                      const char* argv
                                          []) int execve(const char* path,
                                                         const char* argv[],
                                                         char* const envp
                                                             []) int execvp(const char*
                                                                                file,
                                                                            const char* argv
                                                                                []) int execvpe(const char*
                                                                                                    file,
                                                                                                const char* argv
                                                                                                    [],
                                                                                                char* const envp
                                                                                                    [])

Primero, el tipo de retorno de todas las funciones es int. Sin embargo, en caso de una operación exitosa (es decir, el nuevo programa se carga y reemplaza), no se devuelve nada porque el programa actual ya no está allí para recibir el valor de retorno.

En caso de fallo por algún error, no se carga el nuevo programa, y se devuelve -1 al programa existente.

En el primer argumento, hay una diferencia entre ruta y archivo. La p, execlp, execvp y execvpe tienen un archivo en lugar de la ruta.

La path especifica la ruta completa del archivo que se ejecutará/cargará. El file especifica el nombre de la ruta, lo que ayuda a localizar el archivo del nuevo programa.

En el segundo argumento, la diferencia es que las funciones con v tienen una matriz bidimensional de tipo char con varias cadenas (incluido el nombre del archivo).

En cambio, otros procedimientos tienen uno o más arreglos unidimensionales de tipo char, donde el primer elemento de esta lista contiene el nombre del file, el segundo elemento puede incluir algunos parámetros, etc.

Por último, en el caso de funciones que tienen e, un tercer/último parámetro tiene una variable de entorno como una matriz de punteros.

Codificación Ejemplo de llamada al sistema exec

Es mejor ver un ejemplo antes de continuar la discusión. En este ejemplo, estamos usando el código fuente del programa como entrada del programa.

Aquí, hemos guardado este programa (con el nombre execl0.c) en el directorio de código ejecutable. Esto significa que tanto el código fuente como el ejecutable existen en el mismo directorio.

#include <stdio.h>
#include <unistd.h>
int main(void) {
  char binaryPath[] = "/bin/wc";
  char arg1[] = "wc";
  char arg2[] = "-w";
  char arg3[] = "execl0.c";
  printf("First line of current program\n");
  execl(binaryPath, arg1, arg2, arg3, NULL);
  printf("Last line of current program\n");
  return 1;
}

El código anterior utiliza una llamada al sistema execl, que tiene solo unas pocas variables simples de tipo char*. La primera variable contiene la ruta y el nombre del nuevo programa (a ejecutar), y la segunda variable tiene un parámetro wc (nuevamente, el nombre del programa).

La tercera variable tiene un parámetro -w para ejecutar el comando como wc -w para contar palabras en el archivo fuente.

También es importante tener en cuenta dos declaraciones de print adicionales, la primera antes de la llamada al sistema y la segunda al final del programa.

Producción :

First line of current program
32 execl0.c

El resultado muestra que nuestro nuevo programa se cargó y ejecutó con éxito. Sin embargo, tenga en cuenta que se ejecuta la primera instrucción print (consulte la primera línea de salida ('Primera línea del programa actual').

La última declaración de print no se ejecuta porque el programa actual finaliza automáticamente cuando se carga correctamente un nuevo programa.

La segunda línea de salida muestra el recuento de palabras en el archivo execl0.c.

la llamada al sistema execve en C

Ahora, discutiremos la llamada execve en detalle.

Sintaxis:

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

Aquí, el primer argumento es el camino; como ya se ha comentado, la variable de entorno path ayuda a encontrar el programa a ejecutar como un nuevo programa.

El segundo argumento es una matriz bidimensional de caracteres o una matriz unidimensional de cadenas que tienen una lista de argumentos de línea de comando.

El tercer argumento es nuevamente una matriz bidimensional de caracteres o una matriz unidimensional de cadenas que tienen una lista de variables de entorno.

En la familia exec, execve es un comando convincente con una ruta de tres argumentos, una lista de argumentos de línea de comando y una lista de variables de entorno. Veamos un código para ejecutar el comando echo desde el programa.

#include <stdio.h>
#include <unistd.h>
int main(void) {
  char *binaryPath = "/bin/bash";
  char *args[] = {binaryPath, "-c",
                  "echo visit $HOSTNAME:Fun with your browser", "", NULL};
  char *const env[] = {"HOSTNAME=www.delftstack.com", "port=8080", NULL};
  execve(binaryPath, args, env);
  return 1;
}

En la primera línea de la función principal, /bin/bash es la ruta donde existe el comando. En la segunda línea, la lista de argumentos de la línea de comandos contiene tres parámetros antes de NULL que finaliza el argumento.

Nuevamente, el primer argumento es la ruta y el segundo parámetro, -c, significa cmd, que permite pasar el código como una cadena.

El tercer parámetro es el comando; como en el código, echo es el comando.

La tercera línea del código tiene dos cadenas con dos variables de entorno, HOSTNAME y port. Finalmente, la salida del código es:

visit www.delftstack.com : Fun with your browser

En este código, hemos ejecutado un comando de Linux desde nuestro programa. A continuación, ejecutaremos un programa ejecutable externo dentro del programa actual.

Antes que nada, mira este programa:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *args[]) {
  int i;
  int count = atoi(args[1]);
  for (i = 1; i <= count; i++) printf("[%d]", i);
  printf("\n");
  return 0;
}

Este programa está tomando argumentos de línea de comandos. El argumento de la línea de comando (pasado como una cadena) se convierte en un número entero en la segunda línea de la función main.

A continuación, ejecutamos un bucle de uno a contar e imprimimos el conteo entre corchetes. Vea la salida de este código.

$./ test 4 [1][2][3][4]

Hemos creado el ejecutable con el nombre test. Hemos ejecutado el archivo test con el parámetro 4 desde un símbolo del sistema.

Podemos ver contando de uno a cuatro entre corchetes en la salida.

A continuación, tenemos que ejecutar este programa test como un comando externo de otro programa. Para ello, tenemos que especificar la ruta del programa ejecutable test.

Aquí, la ruta completa es /home/mateen/Documents/test. Por lo tanto, especificaremos esta ruta en nuestro próximo programa para ubicar el archivo ejecutable.

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *ar[]) {
  printf("This is the first line\n");
  char *binaryPath = "/bin/bash";
  char name[80] = "/home/mateen/Documents/test ";
  strcat(name, ar[1]);
  char *args[] = {binaryPath, "-c", name, NULL};
  char *env_args[] = {"/bin/bash", (char *)0};
  execve(binaryPath, args, env_args);
  printf("This is the last line\n");
  return 1;
}

Hemos incluido otra biblioteca para usar la función para concatenar cadenas. En la tercera línea de la función main, tenemos una ruta completa y un nombre de archivo porque este no es un comando de Linux; en cambio, este es un programa ejecutable definido por el usuario (ya discutido en detalle).

En la siguiente línea, estamos concatenando el argumento de la línea de comando pasado a nuestro programa actual con el nombre del nuevo programa. Nuevamente, en la quinta línea, tenemos argumentos de línea de comando que tienen una ruta, -c.

El tercer parámetro es el nombre de la variable que tiene ruta + nombre del ejecutable + argumento pasado al programa actual.

Producción :

$ ./a.out 5
This is the first line
[1][2][3][4][5]

Estamos ejecutando nuestro programa actual con el parámetro de línea de comando 5. La primera línea de salida tiene la primera instrucción print.

A continuación, puede ver que se ejecuta nuestro programa de prueba. La cuenta de 1 a 5 se escribe entre corchetes.

Finalmente, la conclusión es que podemos ejecutar tanto los comandos de Linux como nuestros programas ejecutables usando execve. En el caso del comando Linux, podemos pasar la ruta para ubicar el programa Linux.

En el caso de algún otro ejecutable/externo, podemos dar una ruta completa con el nombre del archivo; en este caso, el programa se ubicará automáticamente en la ruta dada. En este caso, el comando ignorará la variable de ruta en la tercera línea de la función main.