当可执行文件在执行过程中被替换时如何处理"/proc/self/exe"的readlink()?

How to handle readlink() of "/proc/self/exe" when executable is replaced during execution?

本文关键字:proc 可执行文件 self exe readlink 执行 处理 替换 何处理 过程中      更新时间:2023-10-16

在我的C++应用程序中,我的应用程序在fork()子进程中执行execv(),以使用相同的可执行文件来处理具有不同参数的新子进程中的某些工作,这些参数与管道通信到父进程。 为了获得 self 的路径名,我在 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()时,由于结果中的额外尾随" (deleted)",它现在因 errno 2 - No such file or directory. 而失败。

我希望execv()要么自己使用旧的可执行文件,要么使用更新的可执行文件。 我可以检测以 " (deleted)" 结尾的字符串并对其进行修改以省略它并解析为更新的可执行文件,但这对我来说似乎很笨拙。

当原始可执行文件在执行过程中被更新的可执行文件替换时,如何使用一组新的参数execv()当前可执行文件(或替换,如果更容易的话)?

无需使用 readlink 来发现您自己的可执行文件的路径,您可以直接在 /proc/self/exe 上调用 open。由于内核已经对当前正在执行的进程有一个开放的 fd,因此无论路径是否已替换为新的可执行文件,这都会为您提供一个 fd。

接下来,您可以使用 fexecve 而不是接受可执行文件的 fd 参数而不是 filename 参数的execv

int fd = open("/proc/self/exe", O_RDONLY);
fexecve(fd, argv, envp);

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

一种解决方案是在可执行启动时(例如在main()开头附近),读取链接/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()到可能具有错误修复的更新二进制文件。 这具有跨平台工作的额外好处,因此读取二进制路径的不同Macintosh代码同样可以在启动后免受二进制替换。

警告编辑器注意:readlink 不 null 终止字符串,因此如果在调用 readlink 之前缓冲区未填充零,则上述程序可能会也可能不会意外运行

(deleted)部分放入符号链接的原因是,您已将文件替换为正确的程序二进制文本,并且指向可执行文件的符号链接永远不会再次有效。 假设您使用此符号链接来获取该程序的符号表或加载嵌入在其上的一些数据,并且您更改了程序...该表将不正确,您甚至可能导致程序崩溃。 您正在执行的程序的可执行文件不再可用(您已经删除了它),并且您放置的程序与您正在执行的二进制文件不对应。

当你取消链接(2)正在执行的程序时,内核在/proc中标记该符号链接,所以程序可以

  • 检测二进制文件已被删除且无法再访问。
  • 允许您仍然收集其姓氏的一些信息(而不是从/proc树中删除符号链接)

您无法写入内核正在执行的文件,但没有人阻止您擦除该文件。 只要你执行它,该文件就会继续存在于文件系统中,但没有名称指向它(一旦进程退出,它的空间将被释放(2)) 内核不会擦除其内容,直到内核内存中的索引节点计数为零,当对该文件的所有使用(引用)到期时,就会发生这种情况。

相关文章: