【问题标题】:How to handle readlink() of "/proc/self/exe" when executable is replaced during execution?在执行过程中替换可执行文件时如何处理“/proc/self/exe”的readlink()?
【发布时间】:2015-05-11 06:37:54
【问题描述】:

在我的 C++ 应用程序中,我的应用程序在 fork()ed 子进程中执行 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() 时,它现在失败了,errno 2 - No such file or directory. 由于到结果中额外的尾随 " (deleted)"

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

当原始可执行文件在执行期间被更新的可执行文件替换时,我如何execv() 当前的可执行文件(或者如果更容易的话,则替换它)与一组新的参数?

【问题讨论】:

  • 为什么不总是使用argv[0],传递给main
  • @JonathonReinhart 因为 argv[0] 并不总是提供可执行文件的路径名,可能只命名可执行文件。
  • 有趣的问题。我几乎在想修剪 ` (deleted)` 将是这里最直接的解决方案。
  • 你可以在分叉后放弃 execv 吗?你可以直接调用你想跳转到的任何函数,而不是重新执行相同的程序。
  • 如果有人从以 (deleted) 结尾的路径名运行您的程序,则修剪 (deleted) 将不起作用。

标签: c++ linux exec fork self-reference


【解决方案1】:

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

警告 编者注:readlink 不会以空值终止字符串,因此如果在调用 readlink 之前缓冲区未填充零,上述程序可能会或可能不会意外运行。

【讨论】:

  • 不太可能并不意味着不可能。我仍然会对这种情况进行错误检查。但这很酷
  • 我相信我有一个完美的解决方案 - 请参阅我刚刚添加的答案。
  • @andrewrk 谢谢,我喜欢你的解决方案。您是否确认它在可执行文件中工作?我目前无法,但在阅读时,我的眼睛说它有效!
  • 我还没有测试过,只是通过查看手册页和在 IRC 上四处询问拼凑出来的。
【解决方案2】:

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

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

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

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

【讨论】:

  • 还没有机会确认,但读起来听起来很棒!
【解决方案3】:

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

当您unlink(2)正在执行的程序时,内核会在/proc 中标记该符号链接,因此程序可以

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-11
    • 2010-11-04
    • 2013-05-27
    • 2021-11-24
    相关资源
    最近更新 更多