如何在执行期间替换可执行文件时处理“/ proc / self / exe”的readlink()?

时间:2015-03-09 23:00:11

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

在我的C ++应用程序中,我的应用程序在execv() ed子进程中执行fork()以使用相同的可执行文件来处理新子进程中的一些工作,这些进程具有与管道通信的不同参数父进程。要获取自己的路径名,我在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()能够将旧的可执行文件用于self,或者使用更新的可执行文件。我只能检测以" (deleted)"结尾的字符串并修改它以省略它并解析为更新的可执行文件,但这对我来说似乎很笨拙。

当原始可执行文件在执行期间被更新后的可执行文件替换时,如何使用一组新参数execv()当前可执行文件(或更换它时更换它)?

3 个答案:

答案 0 :(得分:6)

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

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

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

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

答案 1 :(得分:2)

一个解决方案是在可执行启动时(例如,在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不会终止字符串,因此如果在调用{{1}之前缓冲区未填充零,则上述程序可能会意外地工作,也可能无法正常工作}

答案 2 :(得分:1)

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

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

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

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