/ proc / self / exe없이 현재 실행 파일 경로 찾기


190

Linux는 / proc / self / exe로 쉽게 사용할 수 있습니다. 그러나 크로스 플랫폼 인터페이스를 사용하여 C / C ++에서 현재 응용 프로그램 디렉토리를 찾는 편리한 방법이 있는지 알고 싶습니다. argv [0]와 관련하여 일부 프로젝트가 진행되는 것을 보았지만 완전히 신뢰할 수있는 것 같지는 않습니다.

예를 들어, / proc /가없는 Mac OS X을 지원해야한다면 어떻게해야합니까? #ifdefs를 사용하여 플랫폼 별 코드 (예 : NSBundle)를 분리 하시겠습니까? 아니면 argv [0], $ PATH 등에서 실행 파일의 경로를 추론하여 가장자리에서 버그를 찾을 위험이 있습니까?



나는 구글했다 : 내 ps -o comm. 여기에서 나를 데리고 것은 "/proc/pid/path/a.out"
유역

IMHO 프라이드의 답변 은 "크로스 플랫폼 인터페이스"요구 사항을 올바르게 해결하고 통합하기가 쉽기 때문에 최상위에 있어야합니다.
Stéphane Gourichon

답변:


348

일부 OS 관련 인터페이스 :

이식 가능하지만 신뢰성이 떨어지는 방법은을 사용하는 것 argv[0]입니다. 호출 프로그램에 의해 무엇이든 설정할 수 있지만, 규칙에 따라 실행 파일의 경로 이름 또는을 사용하여 찾은 이름으로 설정됩니다 $PATH.

bash 및 ksh를 포함한 일부 쉘 은 실행하기 전에 환경 변수 " _" 를 실행 파일의 전체 경로로 설정합니다. 이 경우 사용할 getenv("_")수 있습니다. 그러나 이것은 모든 쉘이이를 수행하지는 않기 때문에 신뢰할 수 없으며 프로그램으로 실행하기 전에 변경하지 않은 상위 ​​프로세스에서 설정되거나 남아있을 수 있습니다.


3
또한 _NSGetExecutablePath ()는 심볼릭 링크를 따르지 않습니다.
nause

1
NetBSD : readlink / proc / curproc / exe DragonFly BSD : readlink / proc / curproc / file
naruse 5

6
솔라리스 : char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));; 그에서 다르다 getexecname()의 당량을 수행하는 - pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.

4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)"실행 파일의 경로가 아니라 데이터를 저장해야하는 사용자 별 디렉토리의 경로 이름입니다.

2
OpenBSD는 여전히 2017 년에 할 수없는 유일한 곳입니다. PATH와 argv [0] 방식을 사용해야합니다
Lothar

27

의 사용은 /proc/self/exe비 휴대용 및 신뢰할 수 없다. Ubuntu 12.04 시스템에서 심볼릭 링크를 읽고 따르려면 루트 사용자 여야합니다. 이렇게하면 Boost 예제가 작성되고 whereami()게시 된 솔루션이 실패 할 수 있습니다.

이 게시물은 매우 길지만 실제 문제에 대해 설명하고 실제로 테스트 스위트에 대한 유효성 검사와 함께 작동하는 코드를 제공합니다.

프로그램을 찾는 가장 좋은 방법은 시스템이 사용하는 것과 동일한 단계를 다시 추적하는 것입니다. 이는 argv[0]파일 시스템 루트, pwd, 경로 환경에 대해 해결 을 사용 하고 심볼릭 링크 및 경로 이름 정규화를 고려 하여 수행됩니다 . 이것은 메모리에서 왔지만 과거에는이 작업을 성공적으로 수행했으며 다양한 상황에서 테스트했습니다. 작동한다고 보장되지는 않지만 문제가 더 크지 않을 경우 논의 된 다른 방법보다 전체적으로 더 안정적입니다. Unix 호환 시스템에는 적절한 처리 방법이 있습니다argv[0] 프로그램을 하지 않지만 인증 된 환경에서 실행 중입니다. 1970 년 이래로 모든 유닉스 파생 시스템과 상당히 유닉스 파생 시스템에도 이식성이 뛰어나다. 기본적으로 libc () 표준 기능과 표준 명령 줄 기능에 의존하기 때문이다. Linux (모든 버전), Android, Chrome OS, Minix, 원본 Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, 다음 단계 등 약간만 수정하면 VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2 등이 될 수 있습니다. GUI 환경에서 직접 프로그램을 시작한 argv[0]경우 절대 경로로 설정해야 합니다.

기본적으로 출시 된 모든 유닉스 호환 운영 체제의 거의 모든 셸은 기본적으로 동일한 방식으로 프로그램을 찾고 운영 환경을 거의 동일한 방식으로 설정합니다 (일부 옵션 추가 옵션 포함). 그리고 프로그램을 시작하는 다른 프로그램은 쉘에서 실행되는 것과 같은 환경 (argv, 환경 문자열 등)을 추가 옵션과 함께 생성 할 것으로 예상됩니다. 프로그램이나 사용자는 시작되는 다른 하위 프로그램에 대해이 규칙에서 벗어난 환경을 설정할 수 있지만, 그렇지 않은 경우 이는 버그이며 하위 프로그램이나 해당 하위 프로그램이 올바르게 작동 할 것으로 기대할 수 없습니다.

가능한 값은 argv[0]다음 과 같습니다.

  • /path/to/executable — 절대 경로
  • ../bin/executable — pwd와 관련
  • bin/executable — pwd와 관련
  • ./foo — pwd와 관련
  • executable — 기본 이름, 경로에서 찾기
  • bin//executable — 비정규적인 암호
  • src/../bin/executable — 비밀번호, 비정규, 역 추적
  • bin/./echoargc — 비정규적인 암호

볼 수없는 값 :

  • ~/bin/executable — 프로그램이 실행되기 전에 다시 작성되었습니다.
  • ~user/bin/executable — 프로그램이 실행되기 전에 다시 작성
  • alias — 프로그램이 실행되기 전에 다시 작성
  • $shellvariable — 프로그램이 실행되기 전에 다시 작성
  • *foo* — 와일드 카드, 프로그램이 실행되기 전에 다시 작성되었지만 그다지 유용하지 않음
  • ?foo? — 와일드 카드, 프로그램이 실행되기 전에 다시 작성되었지만 그다지 유용하지 않음

또한 비정규 경로 이름과 여러 계층의 심볼릭 링크가 포함될 수 있습니다. 경우에 따라 동일한 프로그램에 대한 여러 개의 하드 링크가있을 수 있습니다. 예를 들어, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, 등으로 하드 링크를 할 수있다 /bin/busybox.

자신을 찾으려면 아래 단계를 따르십시오.

  • 나중에 변경 될 수 있으므로 pwd, PATH 및 argv [0]을 프로그램 시작 (또는 라이브러리 초기화)에 저장하십시오.

  • 선택 사항 : 특히 비 유닉스 시스템의 경우 경로 이름 호스트 / 사용자 / 드라이브 접두어 부분이 있으면 분리하지만 버리지 마십시오. 콜론 앞에 오는 부분이나 초기 "//"뒤에 오는 부분.

  • 경우 argv[0]절대 경로이며, 출발 지점 것을 사용한다. 절대 경로는 아마도 "/"로 시작하지만 유닉스가 아닌 일부 시스템에서는 "\"로 시작하거나 드라이브 문자 나 이름 접두사 뒤에 콜론이 올 수 있습니다.

  • 그렇지 않으면 argv[0]상대 경로 인 경우 ( "/"또는 "\"를 포함하지만 "../../bin/foo"와 같이 시작하지 않는 경우 pwd + "/"+ argv [0]을 사용하십시오 (사용 현재가 아닌 프로그램 시작 시점의 현재 작업 디렉토리).

  • 그렇지 않으면 argv [0]이 일반 기본 이름 (슬래시 없음) 인 경우 PATH 환경 변수의 각 항목과 차례로 조합하여 시도하고 성공한 첫 번째 항목을 사용하십시오.

  • 옵션 : 그렇지 않으면 아주 플랫폼 특정 시도 /proc/self/exe, /proc/curproc/file(BSD)를하고 (char *)getauxval(AT_EXECFN),하고 dlgetname(...)있는 경우. argv[0]사용 가능하고 권한 문제가 발생하지 않는 경우 이전 기반 방법을 사용해 볼 수도 있습니다. 거의 존재하지 않는 경우 (모든 시스템의 모든 버전을 고려할 때) 시스템이 존재하고 실패하지 않는 경우에는 더 신뢰할 수 있습니다.

  • 선택 사항 : 명령 행 매개 변수를 사용하여 전달 된 경로 이름을 확인하십시오.

  • 선택 사항 : 랩퍼 스크립트가 명시 적으로 전달한 환경의 경로 이름이 있는지 확인하십시오 (있는 경우).

  • 선택 사항 : 최후의 수단으로 환경 변수 "_"를 시도하십시오. 사용자 쉘과 같은 다른 프로그램을 가리킬 수도 있습니다.

  • 심볼릭 링크를 해결하면 여러 계층이있을 수 있습니다. 무한 루프의 가능성이 있지만, 존재한다면 프로그램이 호출되지 않을 것입니다.

  • "/foo/../bar/"와 같은 하위 문자열을 "/ bar /"로 해석하여 파일 이름을 정규화하십시오. 네트워크 탑재 지점을 통과하면 의미가 변경 될 수 있으므로 정식화가 항상 좋은 것은 아닙니다. 네트워크 서버에서 symlink의 ".."를 사용하여 클라이언트 대신 서버 컨텍스트에서 다른 파일의 경로를 탐색 할 수 있습니다. 이 경우 클라이언트 컨텍스트를 원할 수 있으므로 정규화가 가능합니다. 또한 "/./"와 같은 패턴을 "/"로, "//"를 "/"로 변환하십시오. 쉘에서 readlink --canonicalize여러 심볼릭 링크를 해결하고 이름을 정식화합니다. 체이스는 비슷하지만 설치되지는 않습니다. realpath()또는있는 canonicalize_file_name()경우 도움이 될 수 있습니다.

realpath()컴파일 타임에 존재하지 않는 경우 , 라이센스가 허가 된 라이브러리 배포본에서 사본을 빌려서 바퀴를 다시 만들지 않고 직접 컴파일 할 수 있습니다. PATH_MAX보다 작은 버퍼를 사용하려면 잠재적 버퍼 오버 플로우 (출력 버퍼의 크기를 전달하고 strncpy ()와 strcpy ()를 생각하십시오)를 수정하십시오. 이름이 바뀐 개인 사본이 있으면 테스트하기보다는 이름을 바꾸는 것이 더 쉬울 수 있습니다. android / darwin / bsd의 허용 라이센스 사본 : https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

여러 번의 시도가 성공하거나 부분적으로 성공할 수 있으며 모두 같은 실행 파일을 가리 키지는 않을 수도 있으므로 실행 파일을 확인하십시오. 그러나 읽기 권한이 없을 수도 있습니다. 읽을 수없는 경우이를 실패로 취급하지 마십시오. 또는 찾으려고하는 "../lib/"디렉토리와 같이 실행 파일과 근접한 것을 확인하십시오. 여러 버전, 패키지 및 로컬로 컴파일 된 버전, 로컬 및 네트워크 버전, 로컬 및 USB 드라이브 휴대용 버전 등이있을 수 있으며 다른 위치 지정 방법으로 인해 두 가지 호환되지 않는 결과가 발생할 수 있습니다. "_"는 단순히 잘못된 프로그램을 가리킬 수 있습니다.

를 사용하는 프로그램 은 프로그램을로드하는 데 사용 된 실제 경로와 호환되지 execve않도록 설정 argv[0]하고 PATH, "_", pwd 등을 손상시킬 수는 있지만 일반적으로 그렇게 할 이유는 많지 않습니다. 그러나 실행 환경이이를 포함하여 다양한 방식으로 변경 될 수 있다는 사실을 무시하는 취약한 코드가있는 경우 보안에 영향을 줄 수 있습니다 (chroot, 퓨즈 파일 시스템, 하드 링크 등). 쉘 명령이 PATH를 설정하지만 내 보내지 못합니다.

유닉스 이외의 시스템에서는 반드시 코딩 할 필요는 없지만 일부 특성을 알고 있으면 나중에 다른 사람이 이식하기 어려운 방식으로 코드를 작성할 수 있습니다. . 일부 시스템 (DEC VMS, DOS, URL 등)에는 "C : \", "sys $ drive : [foo] bar"및 "file과 같이 콜론으로 끝나는 드라이브 이름 또는 기타 접두사가있을 수 있습니다. : /// foo / bar / baz " 이전 DEC VMS 시스템은 "["및 "]"를 사용하여 프로그램이 POSIX 환경에서 컴파일 된 경우 경로의 디렉토리 부분을 묶습니다. VMS와 같은 일부 시스템에는 파일 버전이있을 수 있습니다 (끝 부분에 세미콜론으로 구분됨). 일부 시스템은 "// drive / path / to / file"또는 "user @ host : / path / to / file"(scp 명령) 또는 "file과 같이 두 개의 연속 슬래시를 사용합니다. (공백으로 구분) 및 "PATH"는 콜론으로 구분되지만 프로그램은 PATH를 수신해야하므로 경로에 대해 걱정할 필요가 없습니다. DOS 및 일부 다른 시스템에는 드라이브 접두어로 시작하는 상대 경로가있을 수 있습니다. C : foo.exe는 C 드라이브의 현재 디렉토리에서 foo.exe를 참조하므로 C :에서 현재 디렉토리를 찾아 pwd에 사용해야합니다. (공백으로 구분) 및 "PATH"는 콜론으로 구분되지만 프로그램은 PATH를 수신해야하므로 경로에 대해 걱정할 필요가 없습니다. DOS 및 일부 다른 시스템에는 드라이브 접두어로 시작하는 상대 경로가있을 수 있습니다. C : foo.exe는 C 드라이브의 현재 디렉토리에서 foo.exe를 참조하므로 C :에서 현재 디렉토리를 찾아 pwd에 사용해야합니다.

내 시스템의 심볼릭 링크 및 래퍼 예 :

/usr/bin/google-chrome is symlink to
/etc/alternatives/google-chrome  which is symlink to
/usr/bin/google-chrome-stable which is symlink to
/opt/google/chrome/google-chrome which is a bash script which runs
/opt/google/chome/chrome

사용자 청구서 게시 위의 세 가지 기본 사례를 처리하는 HP 프로그램에 대한 링크를 했습니다 argv[0]. 그래도 약간의 변경이 필요합니다.

  • 그것은 다시 작성해야 할 것입니다 모든 strcat()strcpy() 사용에 strncat()strncpy(). 변수가 길이 PATHMAX로 선언되었지만 길이 PATHMAX-1의 입력 값과 연결된 문자열의 길이는> PATHMAX이고 길이 PATHMAX의 입력 값은 종료되지 않습니다.
  • 결과를 인쇄하기보다는 라이브러리 함수로 다시 작성해야합니다.
    • 이름을 정식화하지 못합니다 (위의 링크 코드를 사용하십시오)
    • 심볼릭 링크를 확인하지 못합니다 (실시간 코드 사용).

따라서 HP 코드와 realpath 코드를 모두 결합하고 버퍼 오버플로에 견딜 수 있도록 수정하면 올바르게 해석 할 수있는 것이 있어야합니다 argv[0].

다음 argv[0]은 Ubuntu 12.04에서 동일한 프로그램을 호출하는 다양한 방법 에 대한 실제 값을 보여줍니다 . 그리고 네, 프로그램 이름은 실수로 echoargv 대신 echoargc로 명명되었습니다. 이것은 깨끗한 복사를 위해 스크립트를 사용하여 수행되었지만 쉘에서 수동으로 수행하면 동일한 결과를 얻습니다 (명명하게 사용하도록 설정하지 않으면 스크립트에서 별칭이 작동하지 않음).

cat ~/src/echoargc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
main(int argc, char **argv)
{
  printf("  argv[0]=\"%s\"\n", argv[0]);
  sleep(1);  /* in case run from desktop */
}
tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
e?hoargc
  argv[0]="echoargc"
./echoargc
  argv[0]="./echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"


gnome-desktop-item-edit --create-new ~/Desktop
# interactive, create desktop link, then click on it
  argv[0]="/home/whitis/bin/echoargc"
# interactive, right click on gnome application menu, pick edit menus
# add menu item for echoargc, then run it from gnome menu
 argv[0]="/home/whitis/bin/echoargc"

 cat ./testargcscript 2>&1 | sed -e 's/^/    /g'
#!/bin/bash
# echoargc is in ~/bin/echoargc
# bin is in path
shopt -s expand_aliases
set -v
cat ~/src/echoargc.c
tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
echoargc
bin/echoargc
bin//echoargc
bin/./echoargc
src/../bin/echoargc
cd ~/bin
*echo*
e?hoargc
./echoargc
cd ~/src
../bin/echoargc
cd ~/junk
~/bin/echoargc
~whitis/bin/echoargc
alias echoit=~/bin/echoargc
echoit
echoarg=~/bin/echoargc
$echoarg
ln -s ~/bin/echoargc junk1
./junk1
ln -s /home/whitis/bin/echoargc junk2
./junk2
ln -s junk1 junk3
./junk3

이 예는이 게시물에서 설명하는 기술이 다양한 상황에서 작동해야하며 일부 단계가 필요한 이유를 보여줍니다.

편집 : 이제 argv [0]을 인쇄하는 프로그램이 실제로 스스로 찾기 위해 업데이트되었습니다.

// Copyright 2015 by Mark Whitis.  License=MIT style
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

// "look deep into yourself, Clarice"  -- Hanibal Lector
char findyourself_save_pwd[PATH_MAX];
char findyourself_save_argv0[PATH_MAX];
char findyourself_save_path[PATH_MAX];
char findyourself_path_separator='/';
char findyourself_path_separator_as_string[2]="/";
char findyourself_path_list_separator[8]=":";  // could be ":; "
char findyourself_debug=0;

int findyourself_initialized=0;

void findyourself_init(char *argv0)
{

  getcwd(findyourself_save_pwd, sizeof(findyourself_save_pwd));

  strncpy(findyourself_save_argv0, argv0, sizeof(findyourself_save_argv0));
  findyourself_save_argv0[sizeof(findyourself_save_argv0)-1]=0;

  strncpy(findyourself_save_path, getenv("PATH"), sizeof(findyourself_save_path));
  findyourself_save_path[sizeof(findyourself_save_path)-1]=0;
  findyourself_initialized=1;
}


int find_yourself(char *result, size_t size_of_result)
{
  char newpath[PATH_MAX+256];
  char newpath2[PATH_MAX+256];

  assert(findyourself_initialized);
  result[0]=0;

  if(findyourself_save_argv0[0]==findyourself_path_separator) {
    if(findyourself_debug) printf("  absolute path\n");
     realpath(findyourself_save_argv0, newpath);
     if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
     if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 1");
      }
  } else if( strchr(findyourself_save_argv0, findyourself_path_separator )) {
    if(findyourself_debug) printf("  relative path to pwd\n");
    strncpy(newpath2, findyourself_save_pwd, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    realpath(newpath2, newpath);
    if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
    if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 2");
      }
  } else {
    if(findyourself_debug) printf("  searching $PATH\n");
    char *saveptr;
    char *pathitem;
    for(pathitem=strtok_r(findyourself_save_path, findyourself_path_list_separator,  &saveptr); pathitem; pathitem=strtok_r(NULL, findyourself_path_list_separator, &saveptr) ) {
       if(findyourself_debug>=2) printf("pathitem=\"%s\"\n", pathitem);
       strncpy(newpath2, pathitem, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       realpath(newpath2, newpath);
       if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
      if(!access(newpath, F_OK)) {
          strncpy(result, newpath, size_of_result);
          result[size_of_result-1]=0;
          return(0);
      } 
    } // end for
    perror("access failed 3");

  } // end else
  // if we get here, we have tried all three methods on argv[0] and still haven't succeeded.   Include fallback methods here.
  return(1);
}

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

  char newpath[PATH_MAX];
  printf("  argv[0]=\"%s\"\n", argv[0]);
  realpath(argv[0], newpath);
  if(strcmp(argv[0],newpath)) { printf("  realpath=\"%s\"\n", newpath); }
  find_yourself(newpath, sizeof(newpath));
  if(1 || strcmp(argv[0],newpath)) { printf("  findyourself=\"%s\"\n", newpath); }
  sleep(1);  /* in case run from desktop */
}

그리고 여기 이전의 모든 테스트에서 실제로 발견되었다는 것을 보여주는 결과가 있습니다.

tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
  realpath="/home/whitis/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
e?hoargc
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
./echoargc
  argv[0]="./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
rm junk1 junk2 junk3
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"

위에서 설명한 두 가지 GUI 실행도 프로그램을 올바르게 찾습니다.

하나의 잠재적 함정이 있습니다. 이 access()기능은 테스트 전에 프로그램이 setuid 인 경우 권한을 삭제합니다. 일반 사용자가 아닌 관리자 권한으로 프로그램을 찾을 수있는 상황이있는 경우에는 이러한 테스트가 실패 할 수 있지만 이러한 상황에서 프로그램을 실제로 실행할 수는 없습니다. 대신 euidaccess ()를 사용할 수 있습니다. 그러나 실제 사용자보다 경로에서 더 일찍 액세스 할 수없는 프로그램을 찾을 수 있습니다.


1
당신은 그것에 많은 노력을 기울였습니다 – 잘 했어요. 불행히도 코드에서 strncpy()(특히) strncat()안전 하지도 않습니다 . strncpy()null 종료를 보증하지 않습니다. 소스 문자열이 대상 공간보다 길면 문자열이 널로 종료되지 않습니다. strncat()사용하기가 매우 어렵습니다. 대상보다 길면 ( 빈 문자열이 시작 strncat(target, source, sizeof(target))하더라도) 잘못되었습니다 . 길이는 후행 널을 제외하고 대상에 안전하게 추가 할 수있는 문자 수이므로 최대 값입니다. targetsourcesizeof(target)-1
Jonathan Leffler

4
내가 사용해야하는 방법과 달리 strncpy 코드는 정확합니다. 코드를 더주의 깊게 읽으십시오. 버퍼가 넘치거나 종료되지 않습니다. strncpy () / stncat ()의 각 사용은 유효한 sizeof (buffer)를 복사하여 제한되며, 버퍼의 마지막 문자는 버퍼의 마지막 문자를 덮어 쓰는 0으로 채워집니다. 그러나 strncat ()은 size 매개 변수를 카운트로 잘못 사용하며 버퍼 오버 플로우 공격보다 오래 걸리기 때문에 오버 플로우 될 수 있습니다.
whitis

"sudo apt-get install libbsd0 libbsd-dev", s / strncat / strlcat /
whi

1
PATH_MAX를 사용하지 마십시오. 이것은 30 년 전에 작동을 멈췄습니다. 항상 malloc을 사용하십시오.
Lothar

또한 init 호출을 사용하는 경우. init에서 exe 로의 경로를 완전히 파악하고 부분이 아니라 나중에 호출하십시오. 리졸버에서 realpath를 사용하면 지연 평가가 불가능합니다. 다른 오류와 함께 단순히 긴 대답으로 스택 오버 플로우에서 본 최악의 코드입니다.
Lothar

13

Gregory Pakosz (단일 C 파일이 있음) 의 whereami 라이브러리를 확인하십시오 . 다양한 플랫폼에서 현재 실행 파일의 전체 경로를 얻을 수 있습니다. 현재 github에서 리포지토리로 사용할 수 있습니다 .


8

중 하나를 사용하는 리눅스에 대체 /proc/self/exe또는 argv[0]엘프 인터프리터에 의해 전달 된 정보를 사용하고는, 같은 glibc에 의해 제공 :

#include <stdio.h>
#include <sys/auxv.h>

int main(int argc, char **argv)
{
    printf("%s\n", (char *)getauxval(AT_EXECFN));
    return(0);
}

참고 getauxvalglibc가 확장, 그리고 당신이 그것을 반환하지 않도록 확인해야 강력한 것으로 NULL합니다 (ELF 인터프리터가 제공되지 않았 음을 나타내는 AT_EXECFN매개 변수),하지만이 이제까지 실제로 리눅스에 문제가 있다고 생각하지 않습니다.


간단하고 glibc는 어쨌든 Gtk +에 포함되어 있기 때문에 이것을 좋아합니다 (사용중인).
Colin Keenan

4

예를 들어, / proc /가없는 Mac OS X을 지원해야한다면 어떻게해야합니까? #ifdefs를 사용하여 플랫폼 별 코드 (예 : NSBundle)를 분리 하시겠습니까?

예, 플랫폼 별 코드를 격리하는 #ifdefs것이 일반적인 방법입니다.

또 다른 접근법은 깔끔한 #ifdef헤더 를 가지고 함수 선언을 포함하고 구현을 플랫폼 별 소스 파일에 넣는 것입니다. 예를 들어, Poco C ++ 라이브러리가 환경 클래스 와 유사한 기능을 수행하는 방법을 확인하십시오 .


4

여러 플랫폼에서이 작업을 안정적으로 수행하려면 #ifdef 문을 사용해야합니다.

아래 코드는 Windows, Linux, MacOS, Solaris 또는 FreeBSD에서 실행 파일의 경로를 찾습니다 (FreeBSD는 테스트되지 않았 음). 그것은 사용 부스트 코드를 단순화하기> = 1.55.0을하지만 원하는 경우 제거하는 쉬운 충분하다. OS 및 컴파일러에서 요구하는대로 _MSC_VER 및 __linux와 같은 정의를 사용하십시오.

#include <string>
#include <boost/predef/os.h>

#if (BOOST_OS_WINDOWS)
#  include <stdlib.h>
#elif (BOOST_OS_SOLARIS)
#  include <stdlib.h>
#  include <limits.h>
#elif (BOOST_OS_LINUX)
#  include <unistd.h>
#  include <limits.h>
#elif (BOOST_OS_MACOS)
#  include <mach-o/dyld.h>
#elif (BOOST_OS_BSD_FREE)
#  include <sys/types.h>
#  include <sys/sysctl.h>
#endif

/*
 * Returns the full path to the currently running executable,
 * or an empty string in case of failure.
 */
std::string getExecutablePath() {
#if (BOOST_OS_WINDOWS)
    char *exePath;
    if (_get_pgmptr(&exePath) != 0)
        exePath = "";
#elif (BOOST_OS_SOLARIS)
    char exePath[PATH_MAX];
    if (realpath(getexecname(), exePath) == NULL)
        exePath[0] = '\0';
#elif (BOOST_OS_LINUX)
    char exePath[PATH_MAX];
    ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath));
    if (len == -1 || len == sizeof(exePath))
        len = 0;
    exePath[len] = '\0';
#elif (BOOST_OS_MACOS)
    char exePath[PATH_MAX];
    uint32_t len = sizeof(exePath);
    if (_NSGetExecutablePath(exePath, &len) != 0) {
        exePath[0] = '\0'; // buffer too small (!)
    } else {
        // resolve symlinks, ., .. if possible
        char *canonicalPath = realpath(exePath, NULL);
        if (canonicalPath != NULL) {
            strncpy(exePath,canonicalPath,len);
            free(canonicalPath);
        }
    }
#elif (BOOST_OS_BSD_FREE)
    char exePath[2048];
    int mib[4];  mib[0] = CTL_KERN;  mib[1] = KERN_PROC;  mib[2] = KERN_PROC_PATHNAME;  mib[3] = -1;
    size_t len = sizeof(exePath);
    if (sysctl(mib, 4, exePath, &len, NULL, 0) != 0)
        exePath[0] = '\0';
#endif
    return std::string(exePath);
}

위의 버전은 실행 파일 이름을 포함한 전체 경로를 반환합니다. 대신 실행 파일 이름이없는 경로를 원하면 #include boost/filesystem.hpp>return 문을 다음과 같이 변경하십시오.

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();

@ 프랭크, 왜 그렇게 말했는지 모르겠습니다. 나를 위해 작동합니다. 나는 / proc / self / exe에 액세스하기 위해 루트가 필요하다는 또 다른 응답을 보았지만 시도한 Linux 시스템 (CentOS 또는 Mint)에서는 찾지 못했습니다.
jtbr

2

QNX Neutrino 버전에 따라 실행중인 프로세스를 시작하는 데 사용 된 실행 파일의 전체 경로와 이름을 찾는 방법은 여러 가지가 있습니다. 프로세스 식별자를로 표시합니다 <PID>. 다음을 시도하십시오 :

  1. 파일이 /proc/self/exefile 이 존재하면 해당 내용이 요청 된 정보입니다.
  2. 파일이 /proc/<PID>/exefile 이 존재하면 해당 내용이 요청 된 정보입니다.
  3. 파일 /proc/self/as이 존재하면 다음을 수행하십시오.
    1. open() 파일.
    2. 최소한 버퍼를 할당하십시오. sizeof(procfs_debuginfo) + _POSIX_PATH_MAX .
    3. 해당 버퍼를 입력으로 devctl(fd, DCMD_PROC_MAPDEBUG_BASE,... .
    4. 버퍼를로 캐스팅하십시오 procfs_debuginfo*.
    5. 요청 된 정보는 구조 의 path필드에 procfs_debuginfo있습니다. 경고 : 어떤 이유로 QNX /가 파일 경로 의 첫 번째 슬래시 를 생략하는 경우가 있습니다. /필요할 때 추가하십시오 .
    6. 정리하십시오 (파일을 닫고 버퍼를 비우는 등).
  4. 3.파일과 함께 절차를 시도 하십시오 /proc/<PID>/as.
  5. 요청 된 정보를 포함 할 수 있는 구조가 dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)어디에 있는지 보십시오 .dlinfoDl_infodli_fname

이게 도움이 되길 바란다.


1

AFAIK, 그런 식으로하지 않습니다. 또한 애매 모호함이 있습니다. 동일한 실행 파일에 "하드웨어"를 가리키는 여러 개의 하드 링크가있는 경우 어떤 답변을 얻고 싶습니까? (하드 링크가 실제로 "지점"안 된다 단지 FS 계층 구조의 다른 장소에서, 같은 파일.)에서 execve되면 () 성공적으로 새로운 바이너리를 실행 인수에 대한 모든 정보가 손실됩니다.


1
"execve ()가 새 바이너리를 성공적으로 실행하면 인수에 대한 모든 정보가 손실됩니다." 실제로 argp 및 envp 인수는 손실되지 않으며 argv [] 및 환경으로 전달되며 일부 UN * Xes에서 경로 이름 인수 또는 해당 경로로 구성된 항목은 argp 및 envp와 함께 전달됩니다 (OS X / iOS, Solaris) 또는 mark4o의 답변에 나열된 메커니즘 중 하나를 통해 사용 가능합니다. 그러나 그렇습니다. 하나 이상의 하드 링크가있는 경우 하드 링크 중 하나를 제공합니다.


0

실행 가능한 이미지의 경로 이름을 얻는 더 쉬운 방법 :

ps는 프로세스 ID가있는 경우 실행 파일의 경로를 제공 할 수 있습니다. 또한 ps는 POSIX 유틸리티이므로 휴대용이어야합니다.

따라서 프로세스 ID가 249297이면이 명령은 경로 이름 만 제공합니다.

    ps -p 24297 -o comm --no-heading

인수 설명

-p-주어진 프로세스를 선택

-o comm-명령 이름을 표시합니다 (-o cmd는 전체 명령 줄을 선택합니다)

-제목 없음-표제 란을 표시하지 않고 출력 만 표시합니다.

AC 프로그램은 popen을 통해이를 실행할 수 있습니다.


매개 변수가있는 전체 실행 문자열을 제공합니다.
ETech

-제목 없음 휴대용
Good Person

1
execv에 대한 첫 번째 인수가 절대 경로가 아닌 경우 작동하지 않습니다.
hroptatyr

-4

C를 사용하는 경우 getwd 함수를 사용할 수 있습니다.

int main()
{       
 char buf[4096];
 getwd(buf);
 printf(buf);
}

실행 파일의 현재 디렉토리 인 표준 출력에 인쇄합니다.


3
적어도 Windows에서 현재 작업 디렉토리는 실행중인 실행 파일과 특별한 관계가 없습니다. 예를 들어 CreateProcess 는 .exe를 시작하고 작업 디렉토리를 완전히 독립적으로 설정할 수 있습니다.
Spike0xff

상황은 다른 모든 OS에서 동일합니다. 현재 디렉토리는 경우에 따라 실행 디렉토리와 동일하지만 완전히 다를 수 있습니다.
Lassi

-10

프로그램의 절대 값 경로는 주 함수의 envp의 PWD에 있으며 C에는 getenv라는 함수가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.