在执行过程中替换可执行文件时如何处理“/proc/self/exe”的readlink()?
How to handle readlink() of "/proc/self/exe" when executable is replaced during execution?
在我的 C++ 应用程序中,我的应用程序在 fork()
ed 子进程中执行 execv()
以使用相同的可执行文件来处理新子进程中的某些工作,这些子进程具有与管道通信的不同参数到父进程。为了获得自己的路径名,我在 Linux 端口上执行以下代码(我在 Macintosh 上有不同的代码):
const size_t bufSize = PATH_MAX + 1;
char dirNameBuffer[bufSize];
// Read the symbolic link '/proc/self/exe'.
const char *linkName = "/proc/self/exe";
const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));
但是,如果可执行文件是 运行,我用磁盘上二进制文件的更新版本替换可执行文件,readlink()
字符串结果是:"/usr/local/bin/myExecutable (deleted)"
我知道我的可执行文件已被更新的更新版本替换,/proc/self/exe
的原始版本现在已被替换,但是,当我转到 execv()
时,它现在失败并显示 errno
2 - No such file or directory.
由于结果中额外的尾随 " (deleted)"
。
我希望 execv()
自己使用旧的可执行文件,或者更新后的可执行文件。我可以只检测以 " (deleted)"
结尾的字符串并修改它以忽略它并解析为更新的可执行文件,但这对我来说似乎很笨拙。
当原始可执行文件在执行过程中已被更新的可执行文件替换时,我如何 execv()
使用一组新参数 execv()
当前可执行文件(或它的替换,如果更容易的话)?
将 (deleted)
部分放入符号 link 的原因是您已将文件替换为具有不同文件的正确程序二进制文本,而符号 link到 executable 永远不再有效。假设您使用此符号 link 来获取此程序的符号 table 或加载其中嵌入的一些数据,然后更改程序... table 将不正确,您甚至会使你的程序崩溃。您正在执行的程序的 executable 文件不再可用(您已将其删除)并且您放置在其位置的程序与您正在执行的二进制文件不对应。
当你unlink(2)一个正在执行的程序时,内核会在/proc
中标记那个symlink,所以程序可以
- 检测到二进制文件已被删除,无法再访问。
- 允许您仍然收集它的姓氏的一些信息(而不是从
/proc
树中删除 symlink)
您不能写入内核正在执行的文件,但没有人会阻止您删除该文件。只要您执行该文件,该文件就会继续存在于文件系统中,但没有名称指向它(它的 space 将在进程 exit(2) 后被释放) 在内核内存中的 inode 计数变为零之前,内核不会擦除其内容,这发生在对该文件的所有使用(引用)到期时。
一个解决方案是在可执行文件启动时(例如在 main()
的开头附近)读取 link /proc/self/exe
的值一次并将其静态存储以备将来使用:
static string savedBinary;
static bool initialized = false;
// To deal with issue of long running executable having its binary replaced
// with a newer one on disk, we compute the resolved binary once at startup.
if (!initialized) {
const size_t bufSize = PATH_MAX + 1;
char dirNameBuffer[bufSize];
// Read the symbolic link '/proc/self/exe'.
const char *linkName = "/proc/self/exe";
const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));
savedBinary = dirNameBuffer;
// On at least Linux, if the executable is replaced, readlink() of
// "/proc/self/exe" gives "/usr/local/bin/flume (deleted)".
// Therefore, we just compute the binary location statically once at
// startup, before it can possibly be replaced, but we leave this code
// here as an extra precaution.
const string deleted(" (deleted)");
const size_t deletedSize = deleted.size();
const size_t pathSize = savedBinary.size();
if (pathSize > deletedSize) {
const size_t matchPos = pathSize - deletedSize;
if (0 == savedBinary.compare(matchPos, deletedSize, deleted)) {
// Deleted original binary, Issue warning, throw an exception, or exit.
// Or cludge the original path with: savedBinary.erase(matchPos);
}
}
initialized = true;
}
// Use savedBinary value.
这样一来,原始可执行文件就不太可能在 main() 缓存其二进制文件路径的微秒内被替换。因此,一个长 运行 的应用程序(例如数小时或数天)可能会在磁盘上被替换,但根据最初的问题,它可能 fork()
和 execv()
到可能有错误的更新二进制文件使固定。这具有跨平台工作的额外好处,因此 differing Macintosh code 读取二进制路径同样可以在启动后防止二进制替换。
WARNING 编者注:readlink
不会以 null 终止字符串,因此如果缓冲区之前未填充零,则上述程序可能会或可能不会意外运行致电 readlink
您可以直接在 /proc/self/exe
上调用 open
,而不是使用 readlink
来发现您自己的可执行文件的路径。由于内核已经为当前正在执行的进程打开了一个 fd,因此无论该路径是否已被新的可执行文件替换,这都会为您提供一个 fd。
接下来,您可以使用 fexecve
而不是 execv
,它接受 fd
参数而不是可执行文件的 filename
参数。
int fd = open("/proc/self/exe", O_RDONLY);
fexecve(fd, argv, envp);
为简洁起见,以上代码省略了错误处理。
在我的 C++ 应用程序中,我的应用程序在 fork()
ed 子进程中执行 execv()
以使用相同的可执行文件来处理新子进程中的某些工作,这些子进程具有与管道通信的不同参数到父进程。为了获得自己的路径名,我在 Linux 端口上执行以下代码(我在 Macintosh 上有不同的代码):
const size_t bufSize = PATH_MAX + 1;
char dirNameBuffer[bufSize];
// Read the symbolic link '/proc/self/exe'.
const char *linkName = "/proc/self/exe";
const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));
但是,如果可执行文件是 运行,我用磁盘上二进制文件的更新版本替换可执行文件,readlink()
字符串结果是:"/usr/local/bin/myExecutable (deleted)"
我知道我的可执行文件已被更新的更新版本替换,/proc/self/exe
的原始版本现在已被替换,但是,当我转到 execv()
时,它现在失败并显示 errno
2 - No such file or directory.
由于结果中额外的尾随 " (deleted)"
。
我希望 execv()
自己使用旧的可执行文件,或者更新后的可执行文件。我可以只检测以 " (deleted)"
结尾的字符串并修改它以忽略它并解析为更新的可执行文件,但这对我来说似乎很笨拙。
当原始可执行文件在执行过程中已被更新的可执行文件替换时,我如何 execv()
使用一组新参数 execv()
当前可执行文件(或它的替换,如果更容易的话)?
将 (deleted)
部分放入符号 link 的原因是您已将文件替换为具有不同文件的正确程序二进制文本,而符号 link到 executable 永远不再有效。假设您使用此符号 link 来获取此程序的符号 table 或加载其中嵌入的一些数据,然后更改程序... table 将不正确,您甚至会使你的程序崩溃。您正在执行的程序的 executable 文件不再可用(您已将其删除)并且您放置在其位置的程序与您正在执行的二进制文件不对应。
当你unlink(2)一个正在执行的程序时,内核会在/proc
中标记那个symlink,所以程序可以
- 检测到二进制文件已被删除,无法再访问。
- 允许您仍然收集它的姓氏的一些信息(而不是从
/proc
树中删除 symlink)
您不能写入内核正在执行的文件,但没有人会阻止您删除该文件。只要您执行该文件,该文件就会继续存在于文件系统中,但没有名称指向它(它的 space 将在进程 exit(2) 后被释放) 在内核内存中的 inode 计数变为零之前,内核不会擦除其内容,这发生在对该文件的所有使用(引用)到期时。
一个解决方案是在可执行文件启动时(例如在 main()
的开头附近)读取 link /proc/self/exe
的值一次并将其静态存储以备将来使用:
static string savedBinary;
static bool initialized = false;
// To deal with issue of long running executable having its binary replaced
// with a newer one on disk, we compute the resolved binary once at startup.
if (!initialized) {
const size_t bufSize = PATH_MAX + 1;
char dirNameBuffer[bufSize];
// Read the symbolic link '/proc/self/exe'.
const char *linkName = "/proc/self/exe";
const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));
savedBinary = dirNameBuffer;
// On at least Linux, if the executable is replaced, readlink() of
// "/proc/self/exe" gives "/usr/local/bin/flume (deleted)".
// Therefore, we just compute the binary location statically once at
// startup, before it can possibly be replaced, but we leave this code
// here as an extra precaution.
const string deleted(" (deleted)");
const size_t deletedSize = deleted.size();
const size_t pathSize = savedBinary.size();
if (pathSize > deletedSize) {
const size_t matchPos = pathSize - deletedSize;
if (0 == savedBinary.compare(matchPos, deletedSize, deleted)) {
// Deleted original binary, Issue warning, throw an exception, or exit.
// Or cludge the original path with: savedBinary.erase(matchPos);
}
}
initialized = true;
}
// Use savedBinary value.
这样一来,原始可执行文件就不太可能在 main() 缓存其二进制文件路径的微秒内被替换。因此,一个长 运行 的应用程序(例如数小时或数天)可能会在磁盘上被替换,但根据最初的问题,它可能 fork()
和 execv()
到可能有错误的更新二进制文件使固定。这具有跨平台工作的额外好处,因此 differing Macintosh code 读取二进制路径同样可以在启动后防止二进制替换。
WARNING 编者注:readlink
不会以 null 终止字符串,因此如果缓冲区之前未填充零,则上述程序可能会或可能不会意外运行致电 readlink
您可以直接在 /proc/self/exe
上调用 open
,而不是使用 readlink
来发现您自己的可执行文件的路径。由于内核已经为当前正在执行的进程打开了一个 fd,因此无论该路径是否已被新的可执行文件替换,这都会为您提供一个 fd。
接下来,您可以使用 fexecve
而不是 execv
,它接受 fd
参数而不是可执行文件的 filename
参数。
int fd = open("/proc/self/exe", O_RDONLY);
fexecve(fd, argv, envp);
为简洁起见,以上代码省略了错误处理。