Czy „argv [0] = nazwa-pliku wykonywalnego” jest akceptowanym standardem, czy tylko powszechną konwencją?

102

Czy podczas przekazywania argumentu main()w aplikacji C lub C ++ argv[0]zawsze będzie nazwa pliku wykonywalnego? A może to tylko powszechna konwencja i nie gwarantuje się, że będzie prawdziwa w 100% przypadków?

Mike Willekes
źródło
20
Na Unix, należy rozważyć: execl("/home/hacker/.hidden/malicious", "/bin/ls", "-s", (char *)0);. Nazwa pliku wykonywalnego nie ma związku z wartością w argv[0].
Jonathan Leffler

Odpowiedzi:

119

Zgadywanie (nawet zgadywanie oparte na wiedzy) jest fajne, ale naprawdę musisz przejść do dokumentów norm, aby mieć pewność. Na przykład ISO C11 stwierdza (moje wyróżnienie):

Jeśli wartość argcjest większa od zera, ciąg wskazywany przez argv[0] reprezentuje nazwę programu; argv[0][0]będzie znakiem pustym, jeśli nazwa programu nie jest dostępna w środowisku hosta.

Więc nie, to tylko nazwa programu, jeśli ta nazwa jest dostępna. I „reprezentuje” nazwę programu, niekoniecznie jest to nazwa programu. W poprzedniej sekcji podano:

Jeśli wartość argcjest większa od zera, elementy składowe tablicy argv[0]za pośrednictwem argv[argc-1]włącznie powinny zawierać wskaźniki do łańcuchów, którym środowisko hosta nadaje wartości zdefiniowane w ramach implementacji przed uruchomieniem programu.

Jest to niezmienione od C99, poprzedniego standardu, i oznacza, że ​​nawet wartości nie są podyktowane przez standard - to całkowicie zależy od implementacji.

Oznacza to, że nazwa programu może być pusta, jeśli środowisko gospodarz nie zapewniają go, i cokolwiek innego, jeśli środowisko gospodarz ma dostarczyć go, pod warunkiem, że „coś jeszcze” jakoś reprezentuje nazwę programu. W moich bardziej sadystycznych momentach rozważałbym przetłumaczenie go na suahili, przepuszczenie przez szyfr podstawieniowy, a następnie zapisanie w odwrotnej kolejności bajtów :-).

Jednak realizacja zdefiniowanej ma mieć znaczenie szczególne w normach ISO - dokument realizacja musi, jak to działa. Więc nawet UNIX, który można umieścić cokolwiek ona lubi się argv[0]z execrodziną połączeń, musi (i robi) dokument niego.

paxdiablo
źródło
3
To może być standard, ale unix po prostu go nie wymusza i nie można na to liczyć.
dmckee --- kociak ex-moderator
4
Pytanie nie wspominając UNIX w ogóle . Było to proste i proste pytanie w C, stąd ISO C jest dokumentem odniesienia. Nazwa programu to implementacja zdefiniowana w standardzie, więc implementacja może robić, co chce, w tym dopuszczać coś, co nie jest rzeczywistą nazwą - pomyślałem, że wyjaśniłem to w przedostatnim zdaniu.
paxdiablo
2
Pax, nie głosowałem na ciebie i nie pochwalam tych, którzy to zrobili, ponieważ ta odpowiedź jest tak autorytatywna, jak to tylko możliwe . Ale myślę, że zawodność wartości argv[0]jest odpowiednia dla programowania w prawdziwym świecie.
dmckee --- kociak byłego moderatora
4
@caf, zgadza się. Widziałem, że zawiera tak różnorodne rzeczy, jak pełna ścieżka programu („/ progpath / prog”), tylko nazwa pliku („prog”), nieco zmodyfikowana nazwa („-prog”), nazwa opisowa („ prog - program do proggowania ') i nic (' '). Implementacja musi definiować, co zawiera, ale to wszystko, czego wymaga standard.
paxdiablo
3
Dziękuję wszystkim! Świetna dyskusja z (pozornie) prostego pytania. Chociaż odpowiedź Richarda jest ważna dla systemów operacyjnych * nix, wybrałem odpowiedź paxdiablo, ponieważ jestem mniej zainteresowany zachowaniem określonego systemu operacyjnego, a przede wszystkim interesuje mnie istnienie (lub brak) akceptowanego standardu. (Jeśli jesteś ciekawy: w kontekście pierwotnego pytania - nie mam systemu operacyjnego. Piszę kod, aby zbudować surowy bufor argc / argv dla pliku wykonywalnego załadowanego na urządzenie osadzone i chciałem wiedzieć, co powinienem zrobić z argv [0]). +1 do StackOverflow za bycie niesamowitym!
Mike Willekes
49

W *nixsystemach typu z exec*()połączeniami argv[0]będzie to, co dzwoniący umieści w argv0miejscu w exec*()wywołaniu.

Powłoka używa konwencji, że jest to nazwa programu, a większość innych programów stosuje tę samą konwencję, więc argv[0]zwykle jest to nazwa programu.

Ale fałszywy program Unix może wywołać exec()i zrobić argv[0]wszystko, co mu się podoba, więc bez względu na to, co mówi standard C, nie możesz liczyć na 100% czasu.

Richarda Penningtona
źródło
4
To lepsza odpowiedź niż powyższa odpowiedź paxdiablo. Standard nazywa to po prostu „nazwą programu”, ale według mojej wiedzy nie jest to nigdzie narzucane. Jądra systemu Unix jednolicie przekazują ciąg przekazany do execve () niezmieniony do procesu potomnego.
Andy Ross
4
Standard C jest ograniczony w tym, co może powiedzieć, ponieważ nie wie o 'execve ()' itp. Standard POSIX ( opengroup.org/onlinepubs/9699919799/functions/execve.html ) ma więcej do powiedzenia - wyjaśniając to że to, co znajduje się w argv [0], jest kaprysem procesu, wykonuje wywołanie systemowe „execve ()” (lub powiązane).
Jonathan Leffler
1
@Andy, możesz mieć swoje opinie :-) Ale mylisz się co do egzekwowania. Jeśli implementacja nie jest zgodna ze standardem, oznacza to, że jest niezgodna. I faktycznie, ponieważ jest zdefiniowana w ramach implementacji jako „nazwa programu”, system operacyjny taki jak UNIX jest zgodny, o ile określa, jaka jest nazwa. Obejmuje to możliwość rażącego fałszowania nazwy programu przez załadowanie do argv [0] wszystkiego, co chcesz z rodziny wywołań exec.
paxdiablo
Na tym polega piękno słowa „reprezentuje” w standardzie, kiedy odnosi się do argv [0] („reprezentuje nazwę programu”) i argv [1..N] („reprezentują argumenty programu”). „jaskółka nieobciążona” to poprawna nazwa programu.
Richard Pennington,
9

Zgodnie ze standardem C ++, sekcja 3.6.1:

argv [0] będzie wskaźnikiem do początkowego znaku NTMBS, który reprezentuje nazwę używaną do wywołania programu lub ""

Więc nie, nie jest to gwarantowane, przynajmniej przez Standard.


źródło
5
Zakładam, że to ciąg wielobajtowy zakończony wartością zerową?
paxdiablo
6

ISO-IEC 9899 stwierdza:

5.1.2.2.1 Uruchomienie programu

Jeśli wartość argcjest większa od zera, łańcuch wskazywany przez argv[0]reprezentuje nazwę programu; argv[0][0]będzie znakiem pustym, jeśli nazwa programu nie jest dostępna w środowisku hosta. Jeśli wartość argcjest większa niż jeden, ciągi wskazywane przez argv[1]przez argv[argc-1]reprezentują parametry programu .

Użyłem też:

#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) */

Następnie wystarczy przeanalizować ciąg, aby wyodrębnić nazwę pliku wykonywalnego ze ścieżki.

Gregory Pakosz
źródło
2
/proc/self/path/a.outDowiązanie może być użyteczny w systemie Solaris 10 i wyżej.
ephemient
Głosowano za kodem (nie mówiąc, że jest idealny lub poprawny, np. W systemie Windows GetModuleFileNameWnależy użyć, aby móc pobrać dowolną ścieżkę, ale sama obecność kodu stanowi dobrą wskazówkę).
Pozdrawiam i hth. - Alf
4

Aplikacje o argv[0] !=nazwie wykonywalnej

  • wiele powłok określa, czy są one powłoką logowania, sprawdzając argv[0][0] == '-'. Powłoki logowania mają różne właściwości, w szczególności to, że pobierają niektóre domyślne pliki, takie jak /etc/profile.

    Zwykle jest to sam init lub gettydodaje wiodący -, zobacz też: /unix/299408/how-to-login-automatically-without-typing-the-root-username-or-password -in-build / 300152 # 300152

  • pliki binarne wielu wywołań, być może przede wszystkim Busybox . Te dowiązania symboliczne obejmują wiele nazw, np. /bin/shI /bin/lsdo jednego pliku wykonywalnego /bin/busybox, który rozpoznaje narzędzie, z którego należy korzystać argv[0].

    Dzięki temu możliwe jest posiadanie jednego małego, statycznie połączonego pliku wykonywalnego, który reprezentuje wiele narzędzi i będzie działał w zasadzie w każdym środowisku Linux.

Zobacz też: /unix/315812/why-does-argv-include-the-program-name/315817

execvePrzykład Runnable POSIX, gdzie argv[0] !=nazwa pliku wykonywalnego

Wspomnieli o innych exec, ale oto przykład do uruchomienia.

ac

#define _XOPEN_SOURCE 700
#include <unistd.h>

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

pne

#include <stdio.h>

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

Następnie:

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

Daje:

yada yada

Tak, argv[0]może być również:

Testowane na Ubuntu 16.10.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
3

Ta strona zawiera :

Element argv [0] zwykle zawiera nazwę programu, ale nie należy na tym polegać - w każdym razie jest to niezwykłe, że program nie zna swojej nazwy!

Jednak inne strony wydają się potwierdzać fakt, że zawsze jest to nazwa pliku wykonywalnego. Ten stwierdza:

Zauważysz, że argv [0] to ścieżka i nazwa samego programu. Pozwala to programowi na znalezienie informacji o sobie. Dodaje również jeden więcej do tablicy argumentów programu, więc częstym błędem podczas pobierania argumentów wiersza poleceń jest pobranie argv [0], gdy chcesz argv [1].

ChrisF
źródło
11
Niektóre programy wykorzystują fakt, że nie znają nazwy użytej do ich wywołania. Uważam, że BusyBox ( busybox.net/about.html ) działa w ten sposób. Jest tylko jeden plik wykonywalny, który implementuje wiele różnych narzędzi wiersza poleceń. Używa zestawu dowiązań symbolicznych i argumentu argv [0], aby określić, jakie narzędzie wiersza poleceń powinno zostać uruchomione
Trent
Tak, pamiętam, że zauważyłem, że „gunzip” był symbolicznym dowiązaniem do „gzip” i przez chwilę zastanawiałem się, jak to działa.
David Thornley,
2
Wiele programów szuka informacji w argv [0]; na przykład, jeśli ostatni składnik nazwy zaczyna się od myślnika (na przykład „/ bin / -sh”), to powłoka uruchomi profil i inne rzeczy, jak dla powłoki logowania.
Jonathan Leffler
2
@Jon: Myślałem, że powłoki logowania zostały uruchomione argv[0]="-/bin/sh"? Tak jest w każdym razie na wszystkich maszynach, których używałem.
ephemient
3

Nie jestem pewien, czy jest to prawie uniwersalna konwencja, czy norma, ale tak czy inaczej należy jej przestrzegać. Jednak nigdy nie widziałem jego wykorzystania poza systemami uniksowymi i uniksopodobnymi. W środowiskach uniksowych - a może szczególnie w dawnych czasach - programy mogą zachowywać się znacznie inaczej w zależności od nazwy, pod jaką są wywoływane.

EDYTOWANO: Z innych postów w tym samym czasie co mój widzę, że ktoś zidentyfikował to jako pochodzące z określonego standardu, ale jestem pewien, że konwencja ta jest na długo przed standardem.

Joe Mabel
źródło
1
Z pewnością chciałbym, aby ludzie, którzy zamierzali „zanotować” moją odpowiedź, dali jakąś wskazówkę, co im się w niej nie podoba.
Joe Mabel
0

Jeśli uruchomisz program Amigi przez Workbench, argv [0] nie zostanie ustawiony, tylko przez CLI.

Polluks
źródło