的使用/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()代替。但是,有可能它比实际用户更早地在路径上找到了无法访问的程序。