Sepertinya saya bahwa Linux mudah dengan / proc / self / exe. Tapi saya ingin tahu apakah ada cara mudah untuk menemukan direktori aplikasi saat ini di C / C ++ dengan antarmuka lintas platform. Saya telah melihat beberapa proyek bergurau dengan argv [0], tetapi tampaknya tidak sepenuhnya dapat diandalkan.
Jika Anda harus mendukung, katakanlah, Mac OS X, yang tidak memiliki / proc /, apa yang akan Anda lakukan? Gunakan #ifdefs untuk mengisolasi kode khusus platform (NSBundle, misalnya)? Atau coba simpulkan jalur yang dapat dieksekusi dari argv [0], $ PATH dan yang lainnya, berisiko menemukan bug dalam kasus tepi?
ps -o comm
. Apa yang membawaku ke sini adalah: "/proc/pid/path/a.out"Jawaban:
Beberapa antarmuka khusus OS:
_NSGetExecutablePath()
( man 3 dyld )readlink /proc/self/exe
getexecname()
sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
readlink /proc/curproc/file
(FreeBSD tidak memiliki procfs secara default)readlink /proc/curproc/exe
readlink /proc/curproc/file
GetModuleFileName()
denganhModule
=NULL
Metode portabel (tetapi kurang dapat diandalkan) adalah untuk digunakan
argv[0]
. Meskipun dapat diatur untuk apa saja oleh program panggilan, dengan konvensi itu diatur ke salah satu nama jalur yang dapat dieksekusi atau nama yang ditemukan menggunakan$PATH
.Beberapa shell, termasuk bash dan ksh, mengatur variabel lingkungan "
_
" ke path penuh dari executable sebelum dieksekusi. Dalam hal ini Anda dapat menggunakannyagetenv("_")
untuk mendapatkannya. Namun ini tidak dapat diandalkan karena tidak semua shell melakukan ini, dan itu dapat diatur ke apa pun atau ditinggalkan dari proses induk yang tidak mengubahnya sebelum menjalankan program Anda.sumber
char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));
; itu berbeda darigetexecname()
- yang tidak berartipargs -x <PID> | grep AT_SUN_EXECNAME
...Penggunaan
/proc/self/exe
non-portable dan tidak dapat diandalkan. Di sistem Ubuntu 12.04 saya, Anda harus root untuk membaca / mengikuti symlink. Ini akan membuat Boost contoh dan mungkinwhereami()
solusi yang diposting gagal.Posting ini sangat panjang tetapi membahas masalah aktual dan menyajikan kode yang benar-benar berfungsi bersamaan dengan validasi terhadap test suite.
Cara terbaik untuk menemukan program Anda adalah menelusuri kembali langkah-langkah yang sama dengan yang digunakan sistem. Ini dilakukan dengan menggunakan
argv[0]
diselesaikan terhadap root sistem file, pwd, lingkungan path dan mempertimbangkan symlink, dan kanonik pathname. Ini dari ingatan tetapi saya telah berhasil melakukannya di masa lalu dan mengujinya dalam berbagai situasi yang berbeda. Ini tidak dijamin untuk bekerja, tetapi jika tidak, Anda mungkin memiliki masalah yang jauh lebih besar dan lebih dapat diandalkan secara keseluruhan daripada metode lain yang dibahas. Ada situasi pada sistem yang kompatibel Unix di mana penanganan yang tepatargv[0]
tidak akan membawa Anda ke program Anda, tetapi kemudian Anda mengeksekusi di lingkungan yang rusak secara sertifikasi. Ia juga cukup portabel untuk semua sistem turunan Unix sejak sekitar tahun 1970 dan bahkan beberapa sistem turunan non-Unix karena pada dasarnya bergantung pada fungsionalitas standar libc () dan fungsionalitas baris perintah standar. Ini harus bekerja di Linux (semua versi), Android, Chrome OS, Minix, Bell Labs Unix asli, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Nextstep, dll. Dan dengan sedikit modifikasi mungkin VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2, dll. Jika suatu program diluncurkan langsung dari lingkungan GUI, itu harusnya diaturargv[0]
ke jalur absolut.Memahami bahwa hampir setiap shell pada setiap sistem operasi Unix yang kompatibel yang pernah dirilis pada dasarnya menemukan program dengan cara yang sama dan mengatur lingkungan operasi dengan cara yang hampir sama (dengan beberapa tambahan opsional). Dan setiap program lain yang meluncurkan program diharapkan untuk menciptakan lingkungan yang sama (argv, string lingkungan, dll.) Untuk program itu seolah-olah dijalankan dari shell, dengan beberapa tambahan opsional. Suatu program atau pengguna dapat mengatur lingkungan yang menyimpang dari konvensi ini untuk program bawahan lainnya yang diluncurkan tetapi jika itu terjadi, ini adalah bug dan program tidak memiliki harapan yang masuk akal bahwa program bawahan atau bawahannya akan berfungsi dengan benar.
Nilai yang mungkin
argv[0]
termasuk:/path/to/executable
- jalur absolut../bin/executable
- relatif terhadap pwdbin/executable
- relatif terhadap pwd./foo
- relatif terhadap pwdexecutable
- basename, temukan di jalurbin//executable
- relatif terhadap pwd, non-kanoniksrc/../bin/executable
- relatif terhadap pwd, non-kanonik, mundurbin/./echoargc
- relatif terhadap pwd, non-kanonikNilai yang seharusnya tidak Anda lihat:
~/bin/executable
- ditulis ulang sebelum program Anda berjalan.~user/bin/executable
- ditulis ulang sebelum program Anda berjalanalias
- ditulis ulang sebelum program Anda berjalan$shellvariable
- ditulis ulang sebelum program Anda berjalan*foo*
- wildcard, ditulis ulang sebelum program Anda berjalan, tidak terlalu berguna?foo?
- wildcard, ditulis ulang sebelum program Anda berjalan, tidak terlalu bergunaSelain itu, ini mungkin berisi nama jalur non-kanonik dan beberapa lapisan tautan simbolik. Dalam beberapa kasus, mungkin ada beberapa tautan keras ke program yang sama. Sebagai contoh,
/bin/ls
,/bin/ps
,/bin/chmod
,/bin/rm
, dll mungkin hard link ke/bin/busybox
.Untuk menemukan diri Anda, ikuti langkah-langkah di bawah ini:
Simpan pwd, PATH, dan argv [0] pada entri ke program Anda (atau inisialisasi perpustakaan Anda) karena mereka dapat berubah nanti.
Opsional: khususnya untuk sistem non-Unix, pisahkan tetapi jangan membuang bagian pathfiks host / pengguna / drive pathname, jika ada; bagian yang sering mendahului titik dua atau mengikuti inisial "//".
Jika
argv[0]
adalah jalur absolut, gunakan itu sebagai titik awal. Path absolut mungkin dimulai dengan "/" tetapi pada beberapa sistem non-Unix mungkin dimulai dengan "\" atau awalan huruf atau nama drive diikuti oleh titik dua.Lain jika
argv[0]
path relatif (berisi "/" atau "\" tetapi tidak dimulai dengan itu, seperti "../../bin/foo", lalu gabungkan pwd + "/" + argv [0] (gunakan hadir direktori kerja dari saat program dimulai, bukan saat ini).Lain jika argv [0] adalah nama dasar polos (tanpa garis miring), kemudian gabungkan dengan setiap entri dalam variabel lingkungan PATH secara bergantian dan cobalah dan gunakan yang pertama yang berhasil.
Opsional: Lain, coba platform yang sangat spesifik
/proc/self/exe
,/proc/curproc/file
(BSD), dan(char *)getauxval(AT_EXECFN)
, dandlgetname(...)
jika ada. Anda bahkan dapat mencobaargv[0]
metode-metode berbasis sebelum ini , jika tersedia dan Anda tidak menemui masalah izin. Dalam kejadian yang agak tidak mungkin (ketika Anda mempertimbangkan semua versi dari semua sistem) bahwa mereka hadir dan tidak gagal, mereka mungkin lebih berwibawa.Opsional: periksa nama jalur yang dilewatkan menggunakan parameter baris perintah.
Opsional: periksa nama path di lingkungan yang diteruskan secara eksplisit oleh skrip wrapper Anda, jika ada.
Opsional: Sebagai upaya terakhir cobalah variabel lingkungan "_". Mungkin menunjuk ke program yang sama sekali berbeda, seperti shell pengguna.
Selesaikan symlink, mungkin ada beberapa lapisan. Ada kemungkinan loop tak terbatas, meskipun jika ada program Anda mungkin tidak akan dipanggil.
Kanonisasi nama file dengan menyelesaikan substring seperti "/foo/../bar/" ke "/ bar /". Catatan ini berpotensi mengubah artinya jika Anda melewati titik pemasangan jaringan, jadi kanonisasi tidak selalu merupakan hal yang baik. Pada server jaringan, ".." di symlink dapat digunakan untuk melintasi jalur ke file lain dalam konteks server alih-alih pada klien. Dalam hal ini, Anda mungkin menginginkan konteks klien sehingga kanonikisasi tidak apa-apa. Juga konversi pola seperti "/./" ke "/" dan "//" ke "/". Dalam shell,
readlink --canonicalize
akan menyelesaikan banyak symlink dan mengkanoniskan nama. Chase mungkin melakukan hal serupa tetapi tidak diinstal.realpath()
ataucanonicalize_file_name()
, jika ada, dapat membantu.Jika
realpath()
tidak ada pada waktu kompilasi, Anda dapat meminjam salinan dari distribusi pustaka berlisensi yang diizinkan, dan kompilasi dalam diri Anda sendiri daripada menciptakan kembali roda. Perbaiki potensi buffer overflow (masukkan sizeof output buffer, pikirkan strncpy () vs strcpy ()) jika Anda akan menggunakan buffer kurang dari PATH_MAX. Mungkin lebih mudah hanya menggunakan salinan pribadi yang diganti namanya daripada menguji apakah ada. Salinan lisensi permisif dari android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.cKetahuilah bahwa beberapa upaya mungkin berhasil atau sebagian berhasil dan mereka mungkin tidak semua menunjuk ke executable yang sama, jadi pertimbangkan untuk memverifikasi executable Anda; namun, Anda mungkin tidak memiliki izin baca - jika Anda tidak bisa membacanya, jangan menganggapnya sebagai kegagalan. Atau verifikasi sesuatu yang dekat dengan executable Anda seperti direktori "../lib/" yang Anda coba temukan. Anda mungkin memiliki beberapa versi, paket dan versi yang dikompilasi secara lokal, versi lokal dan jaringan, dan versi portabel lokal dan USB-drive, dll. Dan ada kemungkinan kecil bahwa Anda mungkin mendapatkan dua hasil yang tidak kompatibel dari metode penempatan yang berbeda. Dan "_" mungkin hanya menunjuk ke program yang salah.
Suatu program yang menggunakan
execve
sengaja dapat diaturargv[0]
agar tidak sesuai dengan jalur aktual yang digunakan untuk memuat program dan merusak PATH, "_", pwd, dll. Meskipun secara umum tidak ada banyak alasan untuk melakukannya; tetapi ini dapat memiliki implikasi keamanan jika Anda memiliki kode rentan yang mengabaikan fakta bahwa lingkungan eksekusi Anda dapat diubah dalam berbagai cara termasuk, tetapi tidak terbatas, untuk yang satu ini (chroot, sistem file sekering, tautan keras, dll.) untuk perintah shell untuk mengatur PATH tetapi gagal untuk mengekspornya.Anda tidak perlu kode untuk sistem non-Unix tetapi akan lebih baik jika Anda mengetahui beberapa keanehan sehingga Anda dapat menulis kode sedemikian rupa sehingga tidak sulit bagi seseorang untuk porting nanti . Perlu diketahui bahwa beberapa sistem (DEC VMS, DOS, URL, dll.) Mungkin memiliki nama drive atau awalan lain yang diakhiri dengan tanda titik dua seperti "C: \", "sys $ drive: [foo] bar", dan "file : /// foo / bar / baz ". Sistem DEC VMS lama menggunakan "[" dan "]" untuk melampirkan bagian direktori dari path meskipun ini mungkin telah berubah jika program Anda dikompilasi dalam lingkungan POSIX. Beberapa sistem, seperti VMS, mungkin memiliki versi file (dipisahkan oleh tanda titik koma di bagian akhir). Beberapa sistem menggunakan dua garis miring berurutan seperti pada "// drive / path / ke / file" atau "user @ host: / path / ke / file" (perintah scp) atau "file: (dibatasi dengan spasi) dan "PATH" dibatasi dengan titik dua tetapi program Anda harus menerima PATH sehingga Anda tidak perlu khawatir tentang jalur. DOS dan beberapa sistem lain dapat memiliki jalur relatif yang dimulai dengan drive prefix. C: foo.exe merujuk ke foo.exe di direktori saat ini di drive C, jadi Anda perlu mencari direktori saat ini di C: dan menggunakannya untuk pwd. (dibatasi dengan spasi) dan "PATH" dibatasi dengan titik dua tetapi program Anda harus menerima PATH sehingga Anda tidak perlu khawatir tentang jalur. DOS dan beberapa sistem lain dapat memiliki jalur relatif yang dimulai dengan drive prefix. C: foo.exe merujuk ke foo.exe di direktori saat ini di drive C, jadi Anda perlu mencari direktori saat ini di C: dan menggunakannya untuk pwd.
Contoh symlink dan pembungkus di sistem saya:
Perhatikan bahwa tagihan pengguna memposting tautan di atas ke program di HP yang menangani tiga kasus dasar
argv[0]
. Perlu beberapa perubahan, namun:strcat()
danstrcpy()
untuk menggunakanstrncat()
danstrncpy()
. Meskipun variabel-variabel tersebut dideklarasikan dari panjang PATHMAX, nilai input panjang PATHMAX-1 plus panjang string yang digabungkan adalah> PATHMAX dan nilai input dari panjang PATHMAX akan tidak ditentukan.Jadi, jika Anda menggabungkan kedua kode HP dan kode realpath dan memperbaiki keduanya agar tahan terhadap buffer overflow, maka Anda harus memiliki sesuatu yang dapat diinterpretasikan dengan benar.
argv[0]
.Berikut ini menggambarkan nilai aktual
argv[0]
untuk berbagai cara menjalankan program yang sama di Ubuntu 12.04. Dan ya, program itu sengaja dinamai echoargc bukan echoargv. Ini dilakukan dengan menggunakan skrip untuk membersihkan penyalinan tetapi melakukannya secara manual di shell mendapatkan hasil yang sama (kecuali alias tidak berfungsi dalam skrip kecuali Anda mengaktifkannya secara eksplisit).Contoh-contoh ini menggambarkan bahwa teknik yang dijelaskan dalam posting ini harus bekerja dalam berbagai keadaan dan mengapa beberapa langkah diperlukan.
EDIT: Sekarang, program yang mencetak argv [0] telah diperbarui untuk benar-benar menemukan dirinya sendiri.
Dan ini adalah output yang menunjukkan bahwa dalam setiap tes sebelumnya ternyata menemukan dirinya sendiri.
Kedua peluncuran GUI yang dijelaskan di atas juga menemukan program dengan benar.
Ada satu perangkap potensial. The
access()
Fungsi tetes izin jika program ini setuid sebelum pengujian. Jika ada situasi di mana program dapat ditemukan sebagai pengguna yang ditinggikan tetapi bukan sebagai pengguna biasa, maka mungkin ada situasi di mana tes ini akan gagal, meskipun tidak mungkin program tersebut benar-benar dapat dieksekusi dalam keadaan tersebut. Orang bisa menggunakan euidaccess () sebagai gantinya. Namun, ada kemungkinan bahwa ia mungkin menemukan program yang tidak dapat diakses lebih awal di jalur daripada yang bisa dilakukan pengguna sebenarnya.sumber
strncpy()
pun (terutama)strncat()
digunakan dengan aman dalam kode.strncpy()
tidak menjamin pengakhiran nol; jika string sumber lebih panjang dari ruang target, string tersebut tidak dibatalkan nol.strncat()
sangat sulit digunakan;strncat(target, source, sizeof(target))
salah (meskipuntarget
string kosong untuk memulai) jikasource
lebih panjang dari target. Panjangnya adalah jumlah karakter yang dapat dengan aman ditambahkan ke target tanpa menyertakan trailing null, jadisizeof(target)-1
maksimum.Check out whereami perpustakaan dari Gregory Pakosz (yang memiliki hanya C file tunggal); itu memungkinkan Anda untuk mendapatkan path lengkap ke executable saat ini di berbagai platform. Saat ini, tersedia sebagai repo di github di sini .
sumber
Alternatif di Linux untuk menggunakan salah satu
/proc/self/exe
atauargv[0]
menggunakan informasi yang diberikan oleh penerjemah ELF, yang disediakan oleh glibc sebagai berikut:Perhatikan bahwa itu
getauxval
adalah ekstensi glibc, dan untuk menjadi kuat Anda harus memeriksa sehingga tidak kembaliNULL
(menunjukkan bahwa juru bahasa ELF belum memberikanAT_EXECFN
parameter), tetapi saya tidak berpikir ini benar-benar masalah di Linux.sumber
Ya, mengisolasi kode spesifik platform dengan
#ifdefs
cara konvensional ini dilakukan.Pendekatan lain adalah memiliki
#ifdef
header tanpa- bersih yang berisi deklarasi fungsi dan meletakkan implementasinya dalam file sumber platform spesifik. Misalnya, lihat bagaimana pustaka Poco C ++ melakukan sesuatu yang serupa untuk kelas Lingkungan mereka .sumber
Membuat ini berfungsi dengan baik di seluruh platform membutuhkan penggunaan pernyataan #ifdef.
Kode di bawah ini menemukan jalur executable di Windows, Linux, MacOS, Solaris atau FreeBSD (walaupun FreeBSD belum teruji). Ini menggunakan boost > = 1.55.0 untuk menyederhanakan kode tetapi cukup mudah untuk menghapusnya jika Anda mau. Cukup gunakan definisi seperti _MSC_VER dan __linux seperti yang dibutuhkan oleh OS dan kompiler.
Versi di atas mengembalikan path lengkap termasuk nama yang dapat dieksekusi. Jika sebaliknya Anda menginginkan jalur tanpa nama yang dapat dieksekusi,
#include boost/filesystem.hpp>
dan ubah pernyataan kembali ke:sumber
Bergantung pada versi QNX Neutrino , ada berbagai cara untuk menemukan path lengkap dan nama file yang dapat dieksekusi yang digunakan untuk memulai proses yang berjalan. Saya menunjukkan pengidentifikasi proses sebagai
<PID>
. Coba yang berikut ini:/proc/self/exefile
ada, maka isinya adalah informasi yang diminta./proc/<PID>/exefile
ada, maka isinya adalah informasi yang diminta./proc/self/as
ada, maka:open()
berkas.sizeof(procfs_debuginfo) + _POSIX_PATH_MAX
,.devctl(fd, DCMD_PROC_MAPDEBUG_BASE,...
.procfs_debuginfo*
.path
bidangprocfs_debuginfo
struktur. Peringatan : Untuk beberapa alasan, kadang-kadang, QNX menghilangkan garis miring pertama/
dari path file. Sebutkan itu/
ketika dibutuhkan.3.
dengan file/proc/<PID>/as
.dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)
manadlinfo
adalahDl_info
struktur yangdli_fname
mungkin berisi informasi yang diminta.Saya harap ini membantu.
sumber
AFAIK, tidak ada cara seperti itu. Dan ada juga ambiguitas: apa yang ingin Anda dapatkan sebagai jawaban jika executable yang sama memiliki banyak hard-link "menunjuk" ke sana? (Hard-link tidak benar-benar "menunjuk", mereka adalah file yang sama, hanya di tempat lain dalam hirarki FS.) Setelah mengeksekusi () berhasil mengeksekusi biner baru, semua informasi tentang argumennya hilang.
sumber
Anda dapat menggunakan argv [0] dan menganalisis variabel lingkungan PATH. Lihatlah: Contoh program yang dapat menemukan dirinya sendiri
sumber
execv
dan kerabat mengambil jalan ke executable terpisah dariargv
Cara yang lebih portabel untuk mendapatkan nama jalur dari gambar yang dapat dieksekusi:
ps dapat memberi Anda jalan executable, mengingat Anda memiliki id proses. Juga ps adalah utilitas POSIX sehingga harus portabel
jadi jika id proses adalah 249297 maka perintah ini memberi Anda nama path saja.
Penjelasan argumen
-p - memilih proses yang diberikan
-o comm - menampilkan nama perintah (-o cmd memilih seluruh baris perintah)
--no-heading - jangan tampilkan baris heading, hanya output.
Program AC dapat menjalankan ini melalui popen.
sumber
Jika Anda menggunakan C, Anda dapat menggunakan fungsi getwd:
Ini akan Anda cetak pada output standar, direktori saat ini yang dapat dieksekusi.
sumber
Path nilai absolut dari sebuah program adalah di PWD dari envp fungsi utama Anda, juga ada fungsi di C yang disebut getenv, jadi begitulah.
sumber