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?
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?
Jawaban:
Menebak (bahkan menebak-nebak) memang menyenangkan, tetapi Anda benar-benar harus menggunakan dokumen standar untuk memastikannya. Misalnya, ISO C11 menyatakan (penekanan saya):
Jika nilai
argc
lebih besar dari nol, string yang ditunjukkan olehargv[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
argc
lebih besar dari nol, anggota arrayargv[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 exec
keluarga panggilan, harus (dan memang) mendokumentasikannya.
argv[0]
adalah tepat untuk pemrograman di dunia nyata.
Di bawah *nix
sistem tipe dengan exec*()
panggilan, argv[0]
akan menjadi apa pun yang ditempatkan pemanggil ke argv0
tempat 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.
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.
ISO-IEC 9899 menyatakan:
5.1.2.2.1 Memulai program
Jika nilai
argc
lebih besar dari nol, string yang ditunjukkan olehargv[0]
mewakili nama program;argv[0][0]
akan menjadi karakter null jika nama program tidak tersedia dari lingkungan host. Jika nilaiargc
lebih besar dari satu, string yang ditunjuk olehargv[1]
melaluiargv[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.
/proc/self/path/a.out
symlink mungkin dapat digunakan pada Solaris 10 dan ke atas.
GetModuleFileNameW
harus digunakan untuk dapat mengambil jalur apa pun, tetapi hanya dengan adanya kode merupakan panduan yang baik).
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].
argv[0]="-/bin/sh"
? Bagaimanapun, itulah yang terjadi pada semua mesin yang saya gunakan.
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 getty
yang 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/sh
dan /bin/ls
ke 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
execve
Contoh 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.
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.
execl("/home/hacker/.hidden/malicious", "/bin/ls", "-s", (char *)0);
. Nama yang dapat dieksekusi tidak ada hubungannya dengan nilai diargv[0]
.