在没有/ 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”
盆地

恕我直言,prideout的答案应该放在首位,因为它可以正确满足“跨平台接口”的要求,并且易于集成。
斯蒂芬·古里康

Answers:


348

一些特定于操作系统的接口:

使用便携式(但可靠性较差)方法argv[0]。尽管可以由调用程序将其设置为任何内容,但按照惯例,它可以设置为可执行文件的路径名或使用找到的名称$PATH

某些shell(包括bash和ksh)在执行之前将环境变量“ _” 设置为可执行文件的完整路径。在这种情况下,您可以使用getenv("_")它来获取它。但是,这是不可靠的,因为并非所有的shell都这样做,并且可以将其设置为任何值,也可以将其保留在执行程序之前未对其进行更改的父进程中。


3
还要注意_NSGetExecutablePath()不遵循符号链接。
naruse

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

6
Solaris :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系统上,您必须是root用户才能读取/关注符号链接。这将使Boost示例成为可能,并且whereami()发布的解决方案可能会失败。

这篇文章很长,但是讨论了实际问题,并提供了与针对测试套件的验证一起实际工作的代码。

查找程序的最佳方法是追溯系统使用的相同步骤。通过使用argv[0]针对文件系统根目录,pwd,路径环境并考虑符号链接和路径名规范化的解析来完成此操作。这是从内存中获取的,但是我过去已经成功完成了此操作,并在各种不同的情况下对其进行了测试。它不能保证能正常工作,但是如果不能保证,那么您可能会遇到更大的问题,并且比所有其他讨论的方法都更可靠。在Unix兼容系统上,有些情况下argv[0]不会使您进入程序,但是您正在一个可证明是损坏的环境中执行。从1970年左右开始,它也可以移植到所有Unix派生系统,甚至某些非Unix派生系统,因为它基本上依赖于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]为绝对路径。

可以理解,几乎每个Unix兼容操作系统上已发行的shell都基本上以相同的方式找到程序,并以几乎相同的方式设置操作环境(带有一些可选的附加功能)。并且任何其他启动程序的程序都可以为该程序创建相同的环境(argv,环境字符串等),就像从外壳程序运行一样,并带有一些可选的附加功能。程序或用户可以为其启动的其他下级程序设置一个偏离此约定的环境,但是如果这样做,则是一个错误,并且该程序没有合理的期望下级程序或其下属可以正常运行。

可能的值argv[0]包括:

  • /path/to/executable —绝对路径
  • ../bin/executable —相对于pwd
  • bin/executable —相对于pwd
  • ./foo —相对于pwd
  • executable —基本名称,在路径中查找
  • bin//executable —相对于pwd,非规范
  • src/../bin/executable -相对于pwd,非规范,回溯
  • bin/./echoargc —相对于pwd,非规范

您不应该看到的值:

  • ~/bin/executable —在程序运行之前重写。
  • ~user/bin/executable —在程序运行之前重写
  • alias —在程序运行之前重写
  • $shellvariable —在程序运行之前重写
  • *foo* -通配符,在程序运行之前重写,不是很有用
  • ?foo? -通配符,在程序运行之前重写,不是很有用

此外,它们可能包含非规范的路径名和多层符号链接。在某些情况下,同一个程序可能有多个硬链接。例如,/bin/ls/bin/ps/bin/chmod/bin/rm,等可以是硬连接/bin/busybox

要找到自己,请按照以下步骤操作:

  • 将pwd,PATH和argv [0]保存到程序的入口(或库的初始化)中,因为它们以后可能会更改。

  • 可选:特别是对于非Unix系统,请分开但不要丢弃路径名host / user / drive前缀部分(如果存在);通常在冒号之前或在初始“ //”之后的部分。

  • 如果argv[0]是绝对路径,则以该路径为起点。绝对路径可能以“ /”开头,但在某些非Unix系统上,绝对路径可能以“ \”或驱动器号或名称前缀后跟冒号开头。

  • 否则,如果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 /”来规范化文件名。请注意,如果您越过网络安装点,这可能会改变含义,因此规范化并不总是一件好事。在网络服务器上,符号链接中的“ ..”可用于遍历服务器上下文中而不是客户端上的另一个文件的路径。在这种情况下,您可能需要客户端上下文,以便可以进行标准化。还可以将“ /./”之类的模式转换为“ /”,将“ //”之类的模式转换为“ /”。在shell中,readlink --canonicalize将解析多个符号链接并规范化名称。Chase可能执行类似操作,但未安装。 realpath()canonicalize_file_name()(如果存在)可能会有所帮助。

如果realpath()在编译时不存在,则可以从许可的库发行版本中借用一个副本,然后自己进行编译,而不是浪费时间。如果要使用小于PATH_MAX的缓冲区,请修复潜在的缓冲区溢出(传递sizeof输出缓冲区,认为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,fuse文件系统,硬链接等)。让Shell命令设置PATH但无法导出。

您不一定需要为非Unix系统编写代码,但是了解一些特殊特性是一个好主意,因此您可以以这样的方式编写代码,即以后某人移植起来也不会那么困难。请注意,某些系统(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的输入值将不终止。
  • 需要将其重写为库函数,而不仅仅是打印结果。
    • 它无法规范化名称(使用我上面链接到的realpath代码)
    • 它无法解析符号链接(使用realpath代码)

因此,如果您同时结合了HP代码和realpath代码并修复了两者以抵抗缓冲区溢出,那么您应该拥有可以正确解释的内容argv[0]

以下内容说明argv[0]了在Ubuntu 12.04上调用同一程序的各种方式的实际值。是的,该程序被意外命名为echoargc而不是echoargv。这是使用脚本进行干净复制而完成的,但是在shell中手动执行操作可获得相同的结果(除非别名在脚本中不起作用,除非您明确启用它们)。

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))则是错误的(即使target是以空字符串开头)source。长度是可以安全地附加到目标的字符数(不包括尾随的null),因此sizeof(target)-1最大值。
乔纳森·莱夫勒

4
strncpy代码正确,与您暗示我应该使用的方法不同。我建议您更仔细地阅读代码。它既不会溢出缓冲区,也不会使其终止。每次使用strncpy()/ stncat()都会受到复制sizeof(buffer)的限制,该值是有效的,然后缓冲区的最后一个字符用零填充,以覆盖缓冲区的最后一个字符。但是,strncat()错误地使用了size参数作为计数,并且由于它早于缓冲区溢出攻击,因此可能会溢出。
whitis

“ sudo apt-get install libbsd0 libbsd-dev”,然后是s / strncat / strlcat /
Whitis

1
不要使用PATH_MAX。这在30年前就停止了工作,始终使用malloc。
Lothar

另外,如果您使用init调用。完全解析init上exe的路径,而不仅仅是一部分,然后在调用时进行处理。如果您在解析器中使用realpath,则不可能进行延迟评估。与其他错误一起,这是我在stackoverflow上看到的最糟糕的代码,答案很长。
Lothar

13

查看Gregory Pakosz 的whereami库(只有一个C文件);它允许您获取各种平台上当前可执行文件的完整路径。目前,它可以在github上的仓库中找到


8

在Linux上,使用/proc/self/exe或的另一种选择argv[0]是使用由ELF解释器传递的信息,该信息可通过glibc获得,例如:

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

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

请注意,这getauxval是一个glibc扩展,为了稳健起见,您应该检查它是否不返回NULL(指示ELF解释器未提供该AT_EXECFN参数),但我认为这在Linux上实际上不是问题。


我喜欢它,因为它很简单,而且glibc仍然包含在Gtk +中(我正在使用)。
Colin Keenan

4

例如,如果您不得不支持没有/ proc /的Mac OS X,您会怎么做?使用#ifdefs隔离平台特定的代码(例如,NSBundle)?

是的,用#ifdefs常规方法隔离特定于平台的代码。

另一种方法是#ifdef让它的clean- less标头包含函数声明,并将实现放在平台特定的源文件中。例如,查看Poco C ++库如何对其Environment类执行类似的操作。


4

要在各个平台上可靠地完成此工作,需要使用#ifdef语句。

以下代码可在Windows,Linux,MacOS,Solaris或FreeBSD中找到可执行文件的路径(尽管未经测试的FreeBSD)。它使用boost > = 1.55.0简化代码,但是如果需要,可以很容易地删除它。只需根据操作系统和编译器的要求使用_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();

@弗兰克,不确定为什么这么说。为我工作。我看到另一个回应声称您需要root才能访问/ 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_info结构dli_fname

我希望这有帮助。


1

AFAIK,没有这种方式。而且还有一个含糊之处:如果同一个可执行文件具有指向其的多个硬链接,您希望得到什么答案?(硬链接实际上不是“指向”的,它们同一个文件,位于FS层次结构中的另一个位置。)execve()成功执行新的二进制文件后,有关其参数的所有信息都会丢失。


1
“一旦execve()成功执行新的二进制文件,有关其参数的所有信息就会丢失。” 实际上,argp和envp参数不会丢失,它们作为argv []和环境传递,并且在某些UN * Xes中,pathname参数或由其构造的东西与argp和envp一起传递(OS X / iOS,Solaris)或通过mark4o答案中列出的机制之一提供。但是,是的,如果有多个硬链接,那只会给您硬链接之一。

1

您可以使用argv [0]并分析PATH环境变量。看一下:可以找到自己的程序的示例


7
这实际上是不可靠的(尽管它通常可以与由通常的shell启动的程序一起使用),因为execvand kin分别执行argv
dmckee ---前主持小猫小猫

9
这是不正确的答案。它可能会告诉您在哪里可以找到具有相同名称程序。但是它并没有告诉您当前正在运行的可执行文件实际存在的位置。
拉里·格里茨

0

获取可执行映像路径名的更可移植的方法:

如果您具有进程ID,则ps可以为您提供可执行文件的路径。ps也是POSIX实用程序,因此它应该是可移植的

因此,如果进程ID为249297,则此命令仅为您提供路径名。

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

参数说明

-p-选择给定的进程

-o comm-显示命令名称(-o cmd选择整个命令行)

--no-heading-不显示标题行,仅显示输出。

AC程序可以通过popen运行它。


它提供带有参数的完整启动字符串。
ETech 2014年

--no-heading不可移植
好人2014年

1
如果execv的第一个参数不是绝对路径,则不起作用。
hroptatyr

-4

如果使用C,则可以使用getwd函数:

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

您将在标准输出上打印可执行文件的当前目录。


3
至少在Windows上,当前工作目录与正在运行的可执行文件没有特定关系。例如,CreateProcess可以启动.exe并完全独立地设置其工作目录。
Spike0xff '16

在其他所有OS上,情况也相同:偶然情况下,当前目录有时与可执行目录相同,但可以完全不同。
拉西

-10

程序的绝对值路径在主函数的envp的PWD中,C中还有一个名为getenv的函数,因此就可以了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.