¿Es “argv [0] = nombre-del-ejecutable” un estándar aceptado o simplemente una convención común?

102

Al pasar un argumento a main()en una aplicación C o C ++, ¿ argv[0]siempre será el nombre del ejecutable? ¿O es solo una convención común y no se garantiza que sea cierta el 100% del tiempo?

Mike Willekes
fuente
19
En Unix, tenga en cuenta: execl("/home/hacker/.hidden/malicious", "/bin/ls", "-s", (char *)0);. El nombre del ejecutable no guarda relación con el valor en argv[0].
Jonathan Leffler

Respuestas:

118

Las conjeturas (incluso las conjeturas) son divertidas, pero para estar seguro es necesario consultar los documentos de estándares. Por ejemplo, ISO C11 establece (mi énfasis):

Si el valor de argces mayor que cero, la cadena apuntada por argv[0] representa el nombre del programa; argv[0][0]será el carácter nulo si el nombre del programa no está disponible en el entorno del host.

Entonces no, es solo el nombre del programa si ese nombre está disponible. Y "representa" el nombre del programa, no necesariamente es el nombre del programa. La sección anterior dice:

Si el valor de argces mayor que cero, los miembros de la matriz argv[0]hasta argv[argc-1]inclusive contendrán punteros a cadenas, a las que el entorno host da valores definidos por la implementación antes del inicio del programa.

Esto no ha cambiado con respecto a C99, el estándar anterior, y significa que incluso los valores no están dictados por el estándar, depende completamente de la implementación.

Esto significa que el nombre del programa puede estar vacío si el entorno del host no lo proporciona, y cualquier otra cosa si el entorno del host lo proporciona, siempre que "cualquier otra cosa" represente de alguna manera el nombre del programa. En mis momentos más sádicos, consideraría traducirlo al swahili, ejecutarlo a través de un cifrado de sustitución y luego almacenarlo en orden inverso de bytes :-).

Sin embargo, definido por la implementación hace tener un significado específico en las normas ISO - el documento de ejecución obligada cómo funciona. Así que incluso UNIX, que puede poner todo lo que quiera argv[0]con la execfamilia de llamadas, tiene que documentarlo (y lo hace).

paxdiablo
fuente
3
Ese puede ser el estándar, pero Unix simplemente no lo hace cumplir y no se puede contar con él.
dmckee --- ex-moderador gatito
4
La pregunta no mencionaba UNIX en absoluto . Era una pregunta C simple y llanamente, por lo tanto, ISO C es el documento de referencia. El nombre del programa es la implementación definida en el estándar, por lo que una implementación es libre de hacer lo que quiera, incluido permitir algo allí que no sea el nombre real; pensé que lo había dejado claro en la penúltima oración.
paxdiablo
2
Pax, no te voté en contra, y no apruebo a los que lo hicieron porque esta respuesta es lo más autoritaria posible . Pero creo que la falta de fiabilidad del valor de argv[0]es pertinente a la programación en el mundo real.
dmckee --- ex-moderador gatito
4
@caf, eso es correcto. Lo he visto con cosas tan diversas como la ruta completa del programa ('/ progpath / prog'), solo el nombre del archivo ('prog'), un nombre ligeramente modificado ('-prog'), un nombre descriptivo (' prog - un programa para progresar ') y nada (' '). La implementación tiene que definir lo que contiene, pero eso es todo lo que requiere el estándar.
paxdiablo
3
¡Gracias a todos! Gran discusión a partir de una pregunta (aparentemente) simple. Aunque la respuesta de Richard es válida para los sistemas operativos * nix, elegí la respuesta de paxdiablo porque estoy menos interesado en el comportamiento de un sistema operativo específico y principalmente interesado en la existencia (o ausencia) de un estándar aceptado. (Si tiene curiosidad: en el contexto de la pregunta original, no tengo sistema operativo. Estoy escribiendo código para construir el búfer argc / argv sin procesar para un ejecutable cargado en un dispositivo incrustado y necesitaba saber qué debo hacer con argv [0]). ¡+1 a StackOverflow por ser increíble!
Mike Willekes
48

En *nixsistemas de tipo con exec*()llamadas, argv[0]será lo que la persona que llama ponga en el argv0lugar de la exec*()llamada.

El shell usa la convención de que este es el nombre del programa, y ​​la mayoría de los otros programas siguen la misma convención, por lo que argv[0]normalmente el nombre del programa.

Pero un programa Unix deshonesto puede llamar exec()y hacer argv[0]lo que quiera, así que no importa lo que diga el estándar C, no puede contar con esto el 100% del tiempo.

Richard Pennington
fuente
4
Esta es una mejor respuesta que la de paxdiablo anterior. El estándar simplemente lo llama el "nombre del programa", pero esto no se aplica en ninguna parte que yo sepa. Los núcleos de Unix pasan uniformemente la cadena pasada a execve () sin cambios al proceso hijo.
Andy Ross
4
El estándar C está limitado en lo que puede decir porque no sabe sobre 'execve ()', etc. El estándar POSIX ( opengroup.org/onlinepubs/9699919799/functions/execve.html ) tiene más que decir, lo que lo deja claro que lo que está en argv [0] depende del capricho del proceso que ejecuta la llamada al sistema 'execve ()' (o relacionada).
Jonathan Leffler
1
@Andy, eres libre de tener tus opiniones :-) Pero te equivocas con la aplicación. Si una implementación no sigue el estándar, entonces no es conforme. Y de hecho, dado que está definido por la implementación en cuanto a cuál es el "nombre del programa", un sistema operativo como UNIX se ajusta siempre que especifique cuál es el nombre. Eso incluye poder falsificar descaradamente el nombre de un programa cargando argv [0] con cualquier cosa que desee en la familia de llamadas ejecutivas.
paxdiablo
Esa es la belleza de la palabra "representa" en el estándar cuando se refiere a argv [0] ("representa el nombre del programa") y argv [1..N] ("representan los argumentos del programa"). "Unladen swallow" es un nombre de programa válido.
Richard Pennington
8

Según el estándar C ++, sección 3.6.1:

argv [0] será el puntero al carácter inicial de una NTMBS que representa el nombre utilizado para invocar el programa o ""

Entonces no, no está garantizado, al menos por el Estándar.


fuente
5
¿Supongo que es una cadena de varios bytes terminada en nulo?
paxdiablo
5

ISO-IEC 9899 establece:

5.1.2.2.1 Inicio del programa

Si el valor de argces mayor que cero, la cadena apuntada por argv[0]representa el nombre del programa; argv[0][0]será el carácter nulo si el nombre del programa no está disponible en el entorno del host. Si el valor de argces mayor que uno, las cadenas apuntado por argv[1]medio argv[argc-1]representar los parámetros del programa .

También he usado:

#if defined(_WIN32)
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    return GetModuleFileNameA(NULL, pathName, (DWORD)pathNameCapacity);
  }
#elif defined(__linux__) /* elif of: #if defined(_WIN32) */
  #include <unistd.h>
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    size_t pathNameSize = readlink("/proc/self/exe", pathName, pathNameCapacity - 1);
    pathName[pathNameSize] = '\0';
    return pathNameSize;
  }
#elif defined(__APPLE__) /* elif of: #elif defined(__linux__) */
  #include <mach-o/dyld.h>
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    uint32_t pathNameSize = 0;

    _NSGetExecutablePath(NULL, &pathNameSize);

    if (pathNameSize > pathNameCapacity)
      pathNameSize = pathNameCapacity;

    if (!_NSGetExecutablePath(pathName, &pathNameSize))
    {
      char real[PATH_MAX];

      if (realpath(pathName, real) != NULL)
      {
        pathNameSize = strlen(real);
        strncpy(pathName, real, pathNameSize);
      }

      return pathNameSize;
    }

    return 0;
  }
#else /* else of: #elif defined(__APPLE__) */
  #error provide your own implementation
#endif /* end of: #if defined(_WIN32) */

Y luego solo tiene que analizar la cadena para extraer el nombre del ejecutable de la ruta.

Gregory Pakosz
fuente
2
El /proc/self/path/a.outenlace simbólico se puede utilizar en Solaris 10 y posteriores.
efímero
Se votó a favor del código (sin decir que sea ideal o correcto, por ejemplo, en Windows GetModuleFileNameWdebería usarse para poder recuperar cualquier ruta, pero solo la presencia del código constituye una buena guía).
Saludos y hth. - Alf
4

Esta página dice:

El elemento argv [0] normalmente contiene el nombre del programa, pero no se debe confiar en él; de todos modos, ¡es inusual que un programa no sepa su propio nombre!

Sin embargo, otras páginas parecen respaldar el hecho de que siempre es el nombre del ejecutable. Este dice:

Notará que argv [0] es la ruta y el nombre del programa en sí. Esto permite que el programa descubra información sobre sí mismo. También agrega uno más a la matriz de argumentos del programa, por lo que un error común al obtener argumentos de la línea de comandos es tomar argv [0] cuando desee argv [1].

ChrisF
fuente
11
Algunos programas aprovechan el hecho de que no conocen el nombre que se utilizó para invocarlos. Creo que BusyBox ( busybox.net/about.html ) funciona de esta manera. Solo hay un ejecutable que implementa muchas utilidades de línea de comandos diferentes. Utiliza un montón de enlaces simbólicos y argv [0] para determinar qué herramienta de línea de comandos debe ejecutarse
Trent
Sí, recuerdo haber notado que "gunzip" era un enlace simbólico a "gzip", y me pregunté por un momento cómo funcionaba.
David Thornley
2
Muchos programas buscan información en argv [0]; por ejemplo, si el último componente del nombre comienza con un guión ('/ bin / -sh', por ejemplo), entonces el shell ejecutará el perfil y otras cosas como un shell de inicio de sesión.
Jonathan Leffler
2
@Jon: ¿Pensé que se empezaron con los shells de inicio de sesión argv[0]="-/bin/sh"? Ese es el caso de todas las máquinas que he usado, de todos modos.
ephemient
3

Aplicaciones de tener argv[0] !=nombre ejecutable

  • muchos shells determinan si son un shell de inicio de sesión comprobando argv[0][0] == '-'. Los shells de inicio de sesión tienen propiedades diferentes, en particular, que obtienen algunos archivos predeterminados como /etc/profile.

    Por lo general, es el propio init o el gettyque agrega el encabezado -, consulte también: /unix/299408/how-to-login-automatically-without-typing-the-root-username-or-password -in-build / 300152 # 300152

  • binarios de múltiples llamadas, quizás más notablemente Busybox . Estos enlaces simbólicos de varios nombres, por ejemplo, /bin/shya /bin/lsun único ejecutable /bin/busybox, que reconoce desde qué herramienta utilizar argv[0].

    Esto hace posible tener un solo ejecutable pequeño vinculado estáticamente que representa múltiples herramientas y funcionará básicamente en cualquier entorno Linux.

Consulte también: /unix/315812/why-does-argv-include-the-program-name/315817

Ejemplo de POSIX ejecutable execvedonde argv[0] !=el nombre del ejecutable

Otros mencionaron exec, pero aquí hay un ejemplo ejecutable.

C.A

#define _XOPEN_SOURCE 700
#include <unistd.h>

int main(void) {
    char *argv[] = {"yada yada", NULL};
    char *envp[] = {NULL};
    execve("b.out", argv, envp);
}

antes de Cristo

#include <stdio.h>

int main(int argc, char **argv) {
    puts(argv[0]);
}

Luego:

gcc a.c -o a.out
gcc b.c -o b.out
./a.out

Da:

yada yada

Sí, argv[0]también podría ser:

Probado en Ubuntu 16.10.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2

No estoy seguro de si se trata de una convención casi universal o de un estándar, pero de cualquier forma debería respetarlo. Sin embargo, nunca lo he visto explotado fuera de Unix y sistemas similares a Unix. En entornos Unix, y tal vez particularmente en los viejos tiempos, los programas pueden tener comportamientos significativamente diferentes según el nombre con el que se invocan.

EDITADO: Veo en otras publicaciones al mismo tiempo que la mía que alguien lo ha identificado como proveniente de un estándar en particular, pero estoy seguro de que la convención es anterior al estándar.

Joe Mabel
fuente
1
Seguro que deseo que si la gente va a "marcar" mi respuesta, den alguna indicación de lo que no les gusta de ella.
Joe Mabel
0

Si inicia un programa Amiga con Workbench, argv [0] no se configurará, solo mediante CLI.

Contamina
fuente