Apakah "argv [0] = name-of-executable" merupakan standar yang diterima atau hanya konvensi umum?

102

Saat meneruskan argumen ke main()dalam aplikasi C atau C ++, akan argv[0]selalu menjadi nama yang dapat dieksekusi? Atau apakah ini hanya konvensi umum dan tidak dijamin benar 100% setiap saat?

Mike Willekes
sumber
19
Pada Unix, pertimbangkan: execl("/home/hacker/.hidden/malicious", "/bin/ls", "-s", (char *)0);. Nama yang dapat dieksekusi tidak ada hubungannya dengan nilai di argv[0].
Jonathan Leffler

Jawaban:

118

Menebak (bahkan menebak-nebak) memang menyenangkan, tetapi Anda benar-benar harus menggunakan dokumen standar untuk memastikannya. Misalnya, ISO C11 menyatakan (penekanan saya):

Jika nilai argclebih besar dari nol, string yang ditunjukkan oleh argv[0] mewakili nama program; argv[0][0]akan menjadi karakter null jika nama program tidak tersedia dari lingkungan host.

Jadi tidak, itu hanya nama program jika nama itu tersedia. Dan "mewakili" nama program, belum tentu adalah nama program. Bagian sebelumnya menyatakan:

Jika nilai argclebih besar dari nol, anggota array argv[0]melaluiargv[argc-1] inklusif harus berisi pointer ke string, yang diberikan nilai yang ditentukan implementasi oleh lingkungan host sebelum program dimulai.

Ini tidak berubah dari C99, standar sebelumnya, dan berarti bahkan nilai tidak ditentukan oleh standar - terserah pada implementasi sepenuhnya.

Ini berarti bahwa nama program dapat kosong jika lingkungan host tidak menyediakannya, dan hal lainnya jika lingkungan host tidak menyediakannya, asalkan "apa pun" entah bagaimana mewakili nama program. Pada saat-saat saya yang lebih sadis, saya akan mempertimbangkan untuk menerjemahkannya ke dalam bahasa Swahili, menjalankannya melalui sandi substitusi kemudian menyimpannya dalam urutan byte terbalik :-).

Namun, definisi implementasi memang memiliki arti khusus dalam standar ISO - implementasi harus mendokumentasikan cara kerjanya. Jadi, bahkan UNIX, yang dapat memasukkan apa pun yang disukainya argv[0]dengan execkeluarga panggilan, harus (dan memang) mendokumentasikannya.

paxdiablo
sumber
3
Itu mungkin standarnya, tetapi unix sama sekali tidak memaksakannya, dan Anda tidak dapat mengandalkannya.
dmckee --- mantan moderator anak kucing
4
Pertanyaannya tidak menyebutkan UNIX sama sekali . Itu adalah pertanyaan C yang sederhana dan sederhana, maka ISO C adalah dokumen referensi. Nama program adalah implementasi yang ditentukan dalam standar sehingga implementasi bebas untuk melakukan apa yang diinginkannya, termasuk mengizinkan sesuatu di sana yang bukan nama sebenarnya - saya pikir saya telah menjelaskannya di kalimat kedua dari belakang.
paxdiablo
2
Pax, saya tidak menolak Anda, dan tidak menyetujui mereka yang melakukannya karena jawaban ini seotoritatif mungkin . Tapi menurut saya nilai yang tidak dapat diandalkan argv[0]adalah tepat untuk pemrograman di dunia nyata.
dmckee --- mantan moderator anak kucing
4
@caf, itu benar. Saya telah melihatnya memegang beragam hal seperti jalur lengkap program ('/ progpath / prog'), hanya nama file ('prog'), nama yang sedikit dimodifikasi ('-prog'), nama deskriptif (' prog - program untuk progging ') dan tidak ada (' '). Implementasinya harus menentukan apa yang dimilikinya tetapi hanya itu yang dibutuhkan standar.
paxdiablo
3
Terimakasih semuanya! Diskusi bagus dari pertanyaan (yang tampaknya) sederhana. Meskipun jawaban Richard berlaku untuk sistem operasi * nix, saya memilih jawaban paxdiablo karena saya kurang tertarik dengan perilaku OS tertentu, dan terutama tertarik pada ada (atau tidak adanya) standar yang diterima. (Jika Anda penasaran: Dalam konteks pertanyaan awal - Saya tidak memiliki sistem operasi. Saya sedang menulis kode untuk membangun buffer argc / argv mentah untuk dapat dieksekusi yang dimuat ke perangkat yang disematkan dan perlu mengetahui apa yang harus saya lakukan dengan argv [0]). 1 untuk StackOverflow karena menjadi luar biasa!
Mike Willekes
48

Di bawah *nixsistem tipe dengan exec*()panggilan, argv[0]akan menjadi apa pun yang ditempatkan pemanggil ke argv0tempat dalam exec*()panggilan.

Shell menggunakan konvensi bahwa ini adalah nama program, dan kebanyakan program lain mengikuti konvensi yang sama, jadi argv[0]biasanya nama programnya.

Tetapi program Unix yang nakal dapat memanggil exec()dan membuat argv[0]apa pun yang disukainya, jadi apa pun yang dikatakan standar C, Anda tidak dapat mengandalkan ini 100% setiap saat.

Richard Pennington
sumber
4
Ini adalah jawaban yang lebih baik daripada jawaban paxdiablo di atas. Standar hanya menyebutnya "nama program", tetapi ini tidak diberlakukan di mana pun menurut pengetahuan saya. Kernel Unix secara seragam meneruskan string yang diteruskan ke execve () tidak berubah ke proses anak.
Andy Ross
4
Standar C terbatas dalam apa yang dapat dikatakan karena ia tidak tahu tentang 'execve ()' dll. Standar POSIX ( opengroup.org/onlinepubs/9699919799/functions/execve.html ) memiliki lebih banyak hal untuk dikatakan - membuatnya jelas bahwa apa yang ada di argv [0] adalah pada saat proses menjalankan pemanggilan sistem 'execve ()' (atau terkait).
Jonathan Leffler
1
@Andy, Anda bebas menyatakan pendapat Anda :-) Tapi Anda salah tentang penegakan hukum. Jika implementasi tidak mengikuti standar maka itu tidak sesuai. Dan pada kenyataannya, karena implementasinya ditentukan seperti apa "nama program" itu, OS seperti UNIX akan menyesuaikannya selama ia menentukan apa namanya. Itu termasuk kemampuan untuk secara terang-terangan memalsukan nama program dengan memuat argv [0] dengan apa pun yang Anda inginkan dalam keluarga panggilan exec.
paxdiablo
Itulah keindahan dari kata "mewakili" dalam standar ketika merujuk ke argv [0] ("itu mewakili nama program") dan argv [1..N] ("mereka mewakili argumen program"). "unladen swallow" adalah nama program yang valid.
Richard Pennington
8

Menurut Standar C ++, bagian 3.6.1:

argv [0] harus menjadi penunjuk ke karakter awal NTMBS yang mewakili nama yang digunakan untuk menjalankan program atau ""

Jadi tidak, itu tidak dijamin, setidaknya oleh Standard.


sumber
5
Saya berasumsi itu null string multi-byte diakhiri?
paxdiablo
5

ISO-IEC 9899 menyatakan:

5.1.2.2.1 Memulai program

Jika nilai argclebih besar dari nol, string yang ditunjukkan oleh argv[0]mewakili nama program; argv[0][0]akan menjadi karakter null jika nama program tidak tersedia dari lingkungan host. Jika nilai argclebih besar dari satu, string yang ditunjuk oleh argv[1]melalui argv[argc-1]mewakili parameter Program .

Saya juga telah menggunakan:

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

Dan kemudian Anda hanya perlu mengurai string untuk mengekstrak nama yang dapat dieksekusi dari jalur.

Gregory Pakosz
sumber
2
The /proc/self/path/a.outsymlink mungkin dapat digunakan pada Solaris 10 dan ke atas.
efemient
Suara positif untuk kode (tidak mengatakan itu ideal atau benar, misalnya pada Windows GetModuleFileNameWharus digunakan untuk dapat mengambil jalur apa pun, tetapi hanya dengan adanya kode merupakan panduan yang baik).
Cheers and hth. - Alf
4

Halaman ini menyatakan:

Elemen argv [0] biasanya berisi nama program, tetapi ini tidak boleh diandalkan - bagaimanapun juga tidak biasa bagi program untuk tidak mengetahui namanya sendiri!

Namun, halaman lain tampaknya mendukung fakta bahwa itu selalu merupakan nama yang dapat dieksekusi. Yang ini menyatakan:

Anda akan melihat bahwa argv [0] adalah jalur dan nama program itu sendiri. Ini memungkinkan program menemukan informasi tentang dirinya sendiri. Ia juga menambahkan satu lagi ke larik argumen program, jadi kesalahan umum saat mengambil argumen baris perintah adalah mengambil argv [0] saat Anda menginginkan argv [1].

ChrisF
sumber
11
Beberapa program memanfaatkan fakta bahwa mereka tidak tahu nama yang digunakan untuk memanggil mereka. Saya yakin BusyBox ( busybox.net/about.html ) berfungsi seperti ini. Hanya ada satu yang dapat dieksekusi yang mengimplementasikan banyak utilitas baris perintah yang berbeda. Ini menggunakan banyak tautan simbolis dan argv [0] untuk menentukan alat baris perintah apa yang harus dijalankan
Trent
Ya, saya ingat melihat bahwa "gunzip" adalah tautan simbolis ke "gzip", dan sejenak bertanya-tanya bagaimana cara kerjanya.
David Thornley
2
Banyak program melihat argv [0] untuk mendapatkan informasi; Misalnya, jika komponen terakhir dari nama dimulai dengan tanda hubung ('/ bin / -sh', misalnya), maka shell akan menjalankan profil dan hal-hal lain seperti shell login.
Jonathan Leffler
2
@ Jon: Saya pikir shell login dimulai dengan argv[0]="-/bin/sh"? Bagaimanapun, itulah yang terjadi pada semua mesin yang saya gunakan.
singkat
3

Aplikasi yang memiliki argv[0] !=nama yang dapat dieksekusi

  • banyak shell menentukan apakah mereka shell login dengan memeriksa argv[0][0] == '-'. Kerang login memiliki properti yang berbeda, terutama karena mereka mengambil beberapa file default seperti /etc/profile.

    Biasanya init itu sendiri atau gettyyang menambahkan awalan -, lihat juga: /unix/299408/how-to-login-automatically-without-typing-the-root-username-or-password -in-build / 300152 # 300152

  • biner multi-panggilan, mungkin yang paling terkenal adalah Busybox . Symlink beberapa nama ini misalnya /bin/shdan /bin/lske satu exebutable /bin/busybox, yang mengenali alat mana yang digunakan argv[0].

    Ini memungkinkan untuk memiliki satu executable kecil yang terhubung secara statis yang mewakili banyak alat, dan pada dasarnya akan bekerja pada lingkungan Linux apa pun.

Lihat juga: /unix/315812/why-does-argv-include-the-program-name/315817

execveContoh POSIX yang argv[0] !=dapat dijalankan di mana nama yang dapat dieksekusi

Lainnya disebutkan exec , tetapi ini adalah contoh yang dapat dijalankan.

ac

#define _XOPEN_SOURCE 700
#include <unistd.h>

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

bc

#include <stdio.h>

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

Kemudian:

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

Memberikan:

yada yada

Ya, argv[0]bisa juga:

Diuji di Ubuntu 16.10.

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

Saya tidak yakin apakah ini adalah konvensi yang hampir universal atau standar, tetapi Anda harus mematuhinya. Saya belum pernah melihatnya dieksploitasi di luar sistem Unix dan mirip Unix. Di lingkungan Unix - dan mungkin khususnya di masa lalu - program mungkin memiliki perilaku yang sangat berbeda tergantung pada nama yang digunakan untuk memanggilnya.

DIEDIT: Saya melihat dari posting lain pada saat yang sama dengan saya bahwa seseorang telah mengidentifikasinya sebagai berasal dari standar tertentu, tapi saya yakin konvensi lama mendahului standar.

Joe Mabel
sumber
1
Saya berharap jika orang akan "mengurangi" tanggapan saya, mereka akan memberikan indikasi apa yang tidak mereka sukai tentangnya.
Joe Mabel
0

Jika Anda memulai program Amiga dengan Workbench argv [0] tidak akan disetel, hanya dengan CLI.

Polluks
sumber