在执行过程中替换可执行文件时如何处理“/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);

为简洁起见,以上代码省略了错误处理。