Wydaje mi się, że Linux ma to łatwe dzięki / proc / self / exe. Chciałbym jednak wiedzieć, czy istnieje wygodny sposób na znalezienie katalogu bieżącej aplikacji w C / C ++ z interfejsami między platformami. Widziałem kilka projektów, które kręciły się z argv [0], ale nie wydaje się to całkowicie wiarygodne.
Gdybyś kiedykolwiek musiał obsługiwać, powiedzmy, Mac OS X, który nie ma / proc /, co byś zrobił? Czy użyć #ifdefs, aby wyodrębnić kod specyficzny dla platformy (na przykład NSBundle)? Lub spróbuj wydedukować ścieżkę do pliku wykonywalnego z argv [0], $ PATH i tak dalej, ryzykując znalezienie błędów w skrajnych przypadkach?
ps -o comm
. To, co mnie tu sprowadziło, to: „/proc/pid/path/a.out”Odpowiedzi:
Niektóre interfejsy specyficzne dla systemu operacyjnego:
_NSGetExecutablePath()
( man 3 dyld )readlink /proc/self/exe
getexecname()
sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
readlink /proc/curproc/file
(FreeBSD domyślnie nie ma procfs)readlink /proc/curproc/exe
readlink /proc/curproc/file
GetModuleFileName()
zhModule
=NULL
Należy użyć przenośnej (ale mniej niezawodnej) metody
argv[0]
. Chociaż program wywołujący może ustawić dowolną wartość, zgodnie z konwencją jest to albo ścieżka do pliku wykonywalnego, albo nazwa znaleziona przy użyciu$PATH
.Niektóre powłoki, w tym bash i ksh, ustawiają zmienną środowiskową „
_
” na pełną ścieżkę do pliku wykonywalnego przed jego wykonaniem. W takim przypadku możesz go użyćgetenv("_")
. Jest to jednak niewiarygodne, ponieważ nie wszystkie powłoki to robią i można ustawić na cokolwiek lub pozostać z procesu nadrzędnego, który nie zmienił tego przed uruchomieniem programu.źródło
char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));
; to różni się odgetexecname()
- co odpowiadapargs -x <PID> | grep AT_SUN_EXECNAME
...Zastosowanie
/proc/self/exe
jest nieprzenośne i zawodne. W moim systemie Ubuntu 12.04 musisz być rootem, aby czytać / podążać za dowiązaniem symbolicznym. To sprawi, że Boost będzie przykładem i prawdopodobniewhereami()
zamieszczone rozwiązania zawiodą.Ten post jest bardzo długi, ale omawia aktualne problemy i przedstawia kod, który faktycznie działa wraz z sprawdzaniem poprawności względem zestawu testów.
Najlepszym sposobem na znalezienie programu jest odtworzenie tych samych kroków, które wykonuje system. Odbywa się to za pomocą
argv[0]
rozwiązania rozwiązanego względem systemu plików root, pwd, środowiska ścieżki i rozważenia dowiązań symbolicznych oraz kanonizacji nazw ścieżek. To pochodzi z pamięci, ale robiłem to z powodzeniem w przeszłości i testowałem w różnych sytuacjach. Nie gwarantuje się, że zadziała, ale jeśli nie, prawdopodobnie masz znacznie większe problemy i jest ogólnie bardziej niezawodny niż jakakolwiek inna omawiana metoda. Istnieją sytuacje w systemie kompatybilnym z Uniksem, w których właściwe obchodzenie się zargv[0]
nie spowoduje przejścia do twojego programu, ale wtedy wykonujesz w certyfikowanym środowisku. Jest także dość przenośny dla wszystkich systemów pochodnych Uniksa od około 1970 roku, a nawet niektórych systemów nie pochodzących z Uniksa, ponieważ zasadniczo opiera się na standardowej funkcjonalności libc () i standardowej linii poleceń. Powinien działać na systemie Linux (wszystkie wersje), Android, Chrome OS, Minix, oryginalny Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Następny krok itp. I przy niewielkiej modyfikacji prawdopodobnie VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2 itd. Jeśli program został uruchomiony bezpośrednio ze środowiska GUI, powinien był ustawićargv[0]
ścieżkę bezwzględną.Zrozum, że prawie każda powłoka w każdym systemie operacyjnym kompatybilnym z Uniksem, który kiedykolwiek został wydany, zasadniczo znajduje programy w ten sam sposób i konfiguruje środowisko operacyjne prawie w ten sam sposób (z pewnymi opcjonalnymi dodatkami). Oczekuje się, że każdy inny program, który uruchamia program, utworzy dla tego programu takie samo środowisko (argv, ciągi środowiska itp.), Jakby było uruchamiane z powłoki, z pewnymi opcjonalnymi dodatkami. Program lub użytkownik może skonfigurować środowisko, które odbiega od tej konwencji, dla innych programów podrzędnych, które uruchamia, ale jeśli tak, jest to błąd i program nie ma uzasadnionych oczekiwań, że program podrzędny lub jego podwładni będą działać poprawnie.
Możliwe wartości
argv[0]
obejmują:/path/to/executable
- ścieżka bezwzględna../bin/executable
- w stosunku do pwdbin/executable
- w stosunku do pwd./foo
- w stosunku do pwdexecutable
- basename, znajdź na ścieżcebin//executable
- w stosunku do pwd, niekanonicznesrc/../bin/executable
- w stosunku do pwd, niekanonicznego, cofaniabin/./echoargc
- w stosunku do pwd, niekanoniczneWartości, których nie powinieneś widzieć:
~/bin/executable
- przepisane przed uruchomieniem programu.~user/bin/executable
- przepisane przed uruchomieniem programualias
- przepisane przed uruchomieniem programu$shellvariable
- przepisane przed uruchomieniem programu*foo*
- symbol wieloznaczny, przepisany przed uruchomieniem programu, niezbyt przydatny?foo?
- symbol wieloznaczny, przepisany przed uruchomieniem programu, niezbyt przydatnyPonadto mogą one zawierać niekanoniczne nazwy ścieżek i wiele warstw dowiązań symbolicznych. W niektórych przypadkach może istnieć wiele twardych linków do tego samego programu. Na przykład
/bin/ls
,/bin/ps
,/bin/chmod
,/bin/rm
, itd. Mogą być trudne do powiązania/bin/busybox
.Aby się znaleźć, wykonaj następujące czynności:
Zapisz pwd, PATH i argv [0] przy wejściu do programu (lub inicjalizacji biblioteki), ponieważ mogą się później zmienić.
Opcjonalnie: szczególnie w przypadku systemów innych niż Unix, należy oddzielić, ale nie odrzucać części prefiksu nazwa hosta / użytkownika / dysku, jeśli jest obecny; część, która często poprzedza dwukropek lub występuje po początkowym „//”.
Jeśli
argv[0]
jest to ścieżka bezwzględna, użyj jej jako punktu początkowego. Ścieżka bezwzględna prawdopodobnie zaczyna się od „/”, ale w niektórych systemach innych niż Unix może zaczynać się od „\” lub litery dysku lub prefiksu nazwy, po którym następuje dwukropek.W przeciwnym razie, jeśli
argv[0]
jest ścieżką względną (zawiera „/” lub „\”, ale nie zaczyna się od niej, na przykład „../../bin/foo”, a następnie połącz pwd + „/” + argv [0] (użyj obecny katalog roboczy od momentu uruchomienia programu, nie aktualny).Jeśli argv [0] jest zwykłym basenemame (bez ukośników), połącz go z każdą pozycją w zmiennej środowiskowej PATH i wypróbuj je i użyj pierwszej, która się powiedzie.
Opcjonalnie: Else spróbować samemu od platformy
/proc/self/exe
,/proc/curproc/file
(BSD), a(char *)getauxval(AT_EXECFN)
, adlgetname(...)
jeśli obecny. Możesz nawet wypróbować te wcześniejszeargv[0]
metody, jeśli są one dostępne i nie występują problemy z uprawnieniami. W dość mało prawdopodobnym przypadku (gdy weźmie się pod uwagę wszystkie wersje wszystkich systemów), że są one obecne i nie zawodzą, mogą być bardziej wiarygodne.Opcjonalnie: sprawdź nazwę ścieżki przekazaną za pomocą parametru wiersza polecenia.
Opcjonalnie: sprawdź, czy ścieżka nie jest w środowisku jawnie przekazana przez skrypt opakowania, jeśli istnieje.
Opcjonalnie: W ostateczności wypróbuj zmienną środowiskową „_”. Może to wskazywać zupełnie inny program, na przykład powłokę użytkownika.
Rozpoznawanie dowiązań symbolicznych, może istnieć wiele warstw. Istnieje możliwość nieskończonych pętli, ale jeśli one istnieją, prawdopodobnie Twój program nie zostanie wywołany.
Kanonizuj nazwę pliku, rozwiązując podciągi, takie jak „/foo/../bar/” do „/ bar /”. Zauważ, że może to potencjalnie zmienić znaczenie, jeśli przekroczysz punkt montowania sieci, więc kanonizacja nie zawsze jest dobrą rzeczą. Na serwerze sieciowym można użyć „..” w dowiązaniu symbolicznym do przejścia do innego pliku w kontekście serwera zamiast na kliencie. W takim przypadku prawdopodobnie potrzebujesz kontekstu klienta, więc kanonizacja jest w porządku. Konwertuj również wzorce, takie jak „/./” na „/” i „//” na „/”. W powłoce
readlink --canonicalize
rozwiąże wiele dowiązań symbolicznych i kanonizuje nazwę. Chase może działać podobnie, ale nie jest zainstalowany.realpath()
lubcanonicalize_file_name()
, jeśli jest obecny, może pomóc.Jeśli
realpath()
nie istnieje w czasie kompilacji, możesz pożyczyć kopię z licencjonowanej dystrybucji bibliotek i skompilować ją w sobie, zamiast wymyślać koło. Napraw potencjalne przepełnienie bufora (przekaż rozmiar bufora wyjściowego, pomyśl strncpy () vs strcpy ()), jeśli będziesz używał bufora mniejszego niż PATH_MAX. Łatwiej może być po prostu użyć prywatnej kopii o zmienionej nazwie niż testować, czy istnieje. Permissive copy copy from Android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.cNależy pamiętać, że wiele prób może zakończyć się powodzeniem lub częściowo i nie wszystkie mogą wskazywać na ten sam plik wykonywalny, dlatego należy rozważyć weryfikację pliku wykonywalnego; jednak możesz nie mieć uprawnienia do odczytu - jeśli nie możesz go odczytać, nie traktuj tego jako niepowodzenia. Lub zweryfikuj coś w pobliżu pliku wykonywalnego, na przykład katalog „../lib/”, który próbujesz znaleźć. Możesz mieć wiele wersji, wersje spakowane i lokalnie skompilowane, wersje lokalne i sieciowe, wersje lokalne i przenośne z napędem USB itp. Istnieje niewielka możliwość, że uzyskasz dwa niezgodne wyniki z różnych metod lokalizacji. „_” Może po prostu wskazywać niewłaściwy program.
Program używający
execve
może celowo ustawićargv[0]
niezgodność z rzeczywistą ścieżką użytą do załadowania programu i uszkodzenia PATH, „_”, pwd itp., Chociaż generalnie nie ma zbyt wielu powodów, aby to robić; ale może to mieć wpływ na bezpieczeństwo, jeśli masz wrażliwy kod, który ignoruje fakt, że środowisko wykonawcze można zmienić na różne sposoby, w tym między innymi (chroot, system plików bezpieczników, twarde łącza itp.) Jest to możliwe dla poleceń powłoki, aby ustawić PATH, ale nie można go wyeksportować.Niekoniecznie musisz kodować w systemach innych niż Unix, ale dobrym pomysłem byłoby zapoznanie się z niektórymi osobliwościami, abyś mógł napisać kod w taki sposób, aby nie było tak trudne dla kogoś późniejszego przeniesienia . Należy pamiętać, że niektóre systemy (DEC VMS, DOS, adresy URL itp.) Mogą mieć nazwy dysków lub inne prefiksy, które kończą się dwukropkiem, np. „C: \”, „sys $ drive: [foo] bar” i „file : /// foo / bar / baz ". Stare systemy DEC VMS używają „[” i „]” do umieszczenia części katalogu w ścieżce, chociaż mogło to ulec zmianie, jeśli Twój program jest skompilowany w środowisku POSIX. Niektóre systemy, takie jak VMS, mogą mieć wersję pliku (oddzieloną średnikiem na końcu). Niektóre systemy używają dwóch następujących po sobie ukośników, takich jak „// dysk / ścieżka / do / pliku” lub „użytkownik @ host: / ścieżka / do / pliku” (polecenie scp) lub „plik: (rozdzielany spacjami) i „PATH” rozdzielany dwukropkami, ale twój program powinien otrzymywać PATH, więc nie musisz się martwić o ścieżkę. DOS i niektóre inne systemy mogą mieć ścieżki względne rozpoczynające się od prefiksu dysku. C: foo.exe odnosi się do foo.exe w bieżącym katalogu na dysku C, więc musisz wyszukać bieżący katalog na C: i użyć go dla pwd. (rozdzielany spacjami) i „PATH” rozdzielany dwukropkami, ale twój program powinien otrzymywać PATH, więc nie musisz się martwić o ścieżkę. DOS i niektóre inne systemy mogą mieć ścieżki względne rozpoczynające się od prefiksu dysku. C: foo.exe odnosi się do foo.exe w bieżącym katalogu na dysku C, więc musisz wyszukać bieżący katalog na C: i użyć go dla pwd.
Przykład dowiązań symbolicznych i opakowań w moim systemie:
Pamiętaj, że rachunek użytkownika opublikował powyższy link do programu w HP, który obsługuje trzy podstawowe przypadki
argv[0]
. Potrzebuje jednak pewnych zmian:strcat()
orazstrcpy()
użyciestrncat()
istrncpy()
. Mimo że zmienne są deklarowane o długości PATHMAX, wartość wejściowa o długości PATHMAX-1 plus długość połączonych łańcuchów wynosi> PATHMAX, a wartość wejściowa o długości PATHMAX nie byłaby ustalona.Tak więc, jeśli połączysz zarówno kod HP, jak i kod realpath, i naprawisz oba, aby były odporne na przepełnienia bufora, powinieneś mieć coś, co może poprawnie zinterpretować
argv[0]
.Poniżej przedstawiono rzeczywiste wartości
argv[0]
różnych sposobów wywoływania tego samego programu w systemie Ubuntu 12.04. I tak, program został przypadkowo nazwany echoargc zamiast echoargv. Dokonano tego przy użyciu skryptu do czystego kopiowania, ale wykonanie go ręcznie w powłoce daje te same wyniki (z wyjątkiem tego, że aliasy nie działają w skrypcie, chyba że wyraźnie je włączysz).Te przykłady ilustrują, że techniki opisane w tym poście powinny działać w szerokim zakresie okoliczności i dlaczego niektóre kroki są konieczne.
EDYCJA: Teraz program, który wypisuje argv [0] został zaktualizowany, aby się znalazł.
A oto wynik, który pokazuje, że w każdym z poprzednich testów faktycznie się znalazł.
Dwa wyżej opisane uruchomienia GUI również poprawnie znajdują program.
Istnieje jedna potencjalna pułapka.
access()
Funkcja spada uprawnienia, jeżeli program jest setuid przed badaniem. Jeśli istnieje sytuacja, w której program może zostać znaleziony jako użytkownik z podwyższonym poziomem uprawnień, ale nie jako zwykły użytkownik, wówczas może wystąpić sytuacja, w której testy te zakończą się niepowodzeniem, chociaż jest mało prawdopodobne, aby program mógł zostać uruchomiony w takich okolicznościach. Zamiast tego można użyć euidaccess (). Możliwe jest jednak, że program może znaleźć niedostępny program na ścieżce wcześniej niż rzeczywisty użytkownik.źródło
strncpy()
ani (szczególnie) niestrncat()
jest bezpiecznie używany w kodzie.strncpy()
nie gwarantuje zerowego wypowiedzenia; jeśli łańcuch źródłowy jest dłuższy niż przestrzeń docelowa, łańcuch nie jest zakończony zerem.strncat()
jest bardzo trudny w użyciu;strncat(target, source, sizeof(target))
jest niepoprawny (nawet jeśli na początkutarget
jest pusty ciąg), jeślisource
jest dłuższy niż cel. Długość to liczba znaków, które można bezpiecznie dołączyć do celu, wyłączając końcowy null, a więcsizeof(target)-1
maksymalna.Sprawdź bibliotekę whereami Gregory'ego Pakosza (która ma tylko jeden plik C); pozwala uzyskać pełną ścieżkę do bieżącego pliku wykonywalnego na różnych platformach. Obecnie jest dostępny jako repo na github tutaj .
źródło
Alternatywą na Linuksie do używania albo
/proc/self/exe
czyargv[0]
korzysta z informacji przekazywanych przez interpreter ELF, udostępniane przez glibc jako takie:Zauważ, że
getauxval
jest to rozszerzenie glibc i aby być solidnym, powinieneś sprawdzić, aby nie zwróciłNULL
(wskazując, że interpreter ELF nie podałAT_EXECFN
parametru), ale nie sądzę, że w rzeczywistości jest to kiedykolwiek problem w Linuksie.źródło
Tak, izolując kod specyficzny dla platformy za pomocą
#ifdefs
to konwencjonalny sposób.Innym podejściem byłoby posiadanie czystego
#ifdef
nagłówka zawierającego deklaracje funkcji i umieszczanie implementacji w plikach źródłowych specyficznych dla platformy. Na przykład sprawdź, jak biblioteka Poco C ++ robi coś podobnego dla swojej klasy Environment .źródło
Niezawodne działanie tej platformy na różnych platformach wymaga użycia instrukcji #ifdef.
Poniższy kod znajduje ścieżkę do pliku wykonywalnego w systemach Windows, Linux, MacOS, Solaris lub FreeBSD (chociaż FreeBSD nie jest testowany). Używa boost > = 1.55.0, aby uprościć kod, ale można go łatwo usunąć, jeśli chcesz. Wystarczy użyć definicji takich jak _MSC_VER i __linux zgodnie z wymaganiami systemu operacyjnego i kompilatora.
Powyższa wersja zwraca pełne ścieżki, w tym nazwę pliku wykonywalnego. Jeśli zamiast tego chcesz ścieżkę bez nazwy pliku wykonywalnego,
#include boost/filesystem.hpp>
zmień instrukcję return na:źródło
W zależności od wersji QNX Neutrino istnieją różne sposoby znalezienia pełnej ścieżki i nazwy pliku wykonywalnego, który został użyty do uruchomienia uruchomionego procesu. Oznaczam identyfikator procesu jako
<PID>
. Spróbuj wykonać następujące czynności:/proc/self/exefile
istnieje, wówczas jego zawartością są wymagane informacje./proc/<PID>/exefile
istnieje, wówczas jego zawartością są wymagane informacje./proc/self/as
istnieje, to:open()
plik.sizeof(procfs_debuginfo) + _POSIX_PATH_MAX
.devctl(fd, DCMD_PROC_MAPDEBUG_BASE,...
.procfs_debuginfo*
.path
poluprocfs_debuginfo
struktury. Ostrzeżenie : Z jakiegoś powodu czasami QNX pomija pierwszy ukośnik/
ścieżki pliku. Przygotuj to w/
razie potrzeby.3.
z plikiem/proc/<PID>/as
.dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)
gdziedlinfo
jestDl_info
struktura, któradli_fname
może zawierać wymagane informacje.Mam nadzieję, że to pomoże.
źródło
AFAIK, nie ma takiej możliwości. I jest też wieloznaczność: co chciałbyś uzyskać jako odpowiedź, jeśli ten sam plik wykonywalny ma wiele „linków” do niego wskazujących? (Hard-links nie „wskazują”, są tym samym plikiem, tylko w innym miejscu w hierarchii FS.) Gdy execve () pomyślnie uruchomi nowy plik binarny, wszystkie informacje o jego argumentach zostaną utracone.
źródło
Możesz użyć argv [0] i przeanalizować zmienną środowiskową PATH. Spójrz na: Przykład programu, który może się znaleźć
źródło
execv
i kinargv
Bardziej przenośny sposób na uzyskanie nazwy ścieżki obrazu wykonywalnego:
ps może podać ścieżkę do pliku wykonywalnego, pod warunkiem, że masz identyfikator procesu. Również ps jest narzędziem POSIX, więc powinno być przenośne
więc jeśli identyfikator procesu to 249297, to polecenie podaje tylko nazwę ścieżki.
Wyjaśnienie argumentów
-p - wybiera podany proces
-o comm - wyświetla nazwę polecenia (-o cmd wybiera całą linię poleceń)
--no-header - nie wyświetlaj linii nagłówka, tylko wynik.
Program AC może uruchomić to za pomocą popen.
źródło
Jeśli używasz C, możesz użyć funkcji getwd:
Spowoduje to wydrukowanie na standardowym wyjściu, bieżącym katalogu pliku wykonywalnego.
źródło
Ścieżka wartości bezwzględnej programu znajduje się w PWD envp twojej głównej funkcji, jest też funkcja w C o nazwie getenv, więc jest to.
źródło