c++ - 如何在 Linux 上热重载共享库

标签 c++ c dll shared-libraries elf

我正在尝试从 Casey Muratori 的热门 Handmade Hero series 中复制一个很酷的技巧.在 win32 上,Casey 能够重新加载 DLL 并看到他的代码更改,而延迟只有几毫秒。

我正在尝试使用 dlopen、dlsym、dlclose 和 stat 在 Linux 上复制此行为,但我遇到了以下行为,而且我有一种预感,我要么误解了 ELF,例如,链接器,或者共享对象的概念。

我能够让他的代码在 win32 上毫不费力地工作,所以我觉得这是我所缺少的 Linux 特有的东西。

我正在使用 CMake 进行构建,但我并不特别认为 CMake 是罪魁祸首。

我复制了共享库 dynamic.so,并加载它。每当原始共享对象的 mtime 更新时,我关闭旧拷贝的句柄,创建一个新拷贝,然后尝试加载新拷贝。

我想指出,我打算在第一次更改后打破循环,因为我只是想弄清楚这一点。


#include <stdio.h>                                                                                                                                                                                                                                                                                                                                                                                                                                   
#include <dlfcn.h>                                                                                                                                                                                                                        
#include <time.h>                                                                                                                                                                                                                         
#include <sys/stat.h>                                                                                                                                                                                                                     
#include <unistd.h>      

void
CopyFile(const char* src, const char* dest)
{
  FILE* fsrc;
  FILE* fdest;
  unsigned char buffer[512];
  size_t bytes;

  fprintf(stderr, "copy from: %s to %s!\n", src, dest);

  fsrc = fopen(src, "rb");
  if ( fsrc == NULL )
    ┆   fprintf(stderr, "failed to open file: %s for reading\n", src);

  fdest = fopen(dest, "wb");
  if ( fdest == NULL )
    ┆   fprintf(stderr, "failed to open file: %s for reading\n", src);

  while ( (bytes = fread(buffer, 1, sizeof(buffer), fsrc)) > 0 )
    {
    ┆   fwrite(buffer, 1, bytes, fdest);
    }

  fclose(fsrc);
  fclose(fdest);

  fprintf(stderr, "copy complete!\n");
}

int main(int argc, char** argv)
{

const char* libpath = "/home/bacon/dynamic.so";
const char* copypath = "/home/bacon/dynamic-copy.so";
CopyFile(libpath, copypath);

void* handle = dlopen(copypath, RTLD_NOW | RTLD_GLOBAL);
if ( handle == NULL )
    fprintf(stderr, "failed to load %s, error = %s\n", copypath, dlerror());

struct stat s;
stat(libpath, &s);
time_t oldtime = s.st_mtime;
while (true)
{
    stat(libpath, &s);
    if ( oldtime != s.st_mtime )
    {
        if ( handle != NULL )
        {
            if ( dlclose(handle) )
                fprintf(stderr, "dlclose failed: %s\n", dlerror());
            else
                handle = NULL;
        }

        CopyFile(libpath, copypath);

        handle = dlopen(copypath, RTLD_NOW | RTLD_GLOBAL);
        if ( handle == NULL )
            fprintf(stderr, "failed to load %s, error = %s\n", copypath, dlerror());

        break;
    }
}
}

至于动态库,什么都应该做(例如标题):

#ifndef DYNAMIC_HEADER
#define DYNAMIC_HEADER 1

#define DYNAMIC_API __attribute__ ((visibility("default")))

extern "C" DYNAMIC_API int
Add(int x, int y);

#endif /* DYNAMIC_HEADER */

和源文件:

#include "Dynamic.h"

int
Add(int x, int y)
{
    return x + y;
}

共享库只是提供了一些例程来将几个数字加在一起,并且我已经验证了我能够在没有热重载技巧的情况下使用 dlopen 和 dlsym。

我还验证了我的复制例程实际上复制了共享对象。

我希望最初的 dlopen 能够成功,并且 dlsym 能够正确链接 Add(它会)。然后我会编辑 Dynamic.cpp 并可能返回 x + x + y 或其他东西,保存文件并重新编译,期望 while 循环在 st_mtime 中获取更改。

我注意到当我运行代码并更新时我收到了错误:

dlopen: file too short

果然,当我 ls -la 包含共享对象的目录时,拷贝大小为 0。

不知何故,stat报告的st_mtime更新了,但是共享对象的实际内容是空的?链接器是否锁定共享对象并阻止读取?

如果我的代码没有严重错误,我该如何避免这种行为?

我不愿意休眠并重试,因为这是一个相当即时的更新。

最佳答案

If my code isn't horribly wrong

这是非常错误的:您的代码正在与(静态)链接器(由 makecmake 调用)竞争。

make 运行时,它(最终)调用:

gcc -shared -o /home/bacon/dynamic.so foo.o bar.o ...

然后链接器将执行open("/home/bacon/dynamic.so", O_WRONLY|O_CREAT, ...)(或等效的),一段时间后将写入,最后关闭文件。

m_time 发生变化时,您的程序将被唤醒,这是 open 之后的任何时间,并且将尝试复制文件。如果您的复制发生在最终关闭之前的任何时间,那么您可能会得到部分拷贝(包括包含 0 字节的部分拷贝)。

最明显的解决方案是 Zsigmond 建议的解决方案:您必须修改 Makefile 以链接一个与您正在观看的文件不同的文件,然后执行 mv 作为最后(原子)步骤到达最终目的地。

另一种解决方案是拥有一个依赖于dynamic.somake 目标,例如

dynamic.so.done: dynamic.so
        touch dynamic.so.done

在您的程序中,您会观察 m_time 以获取 dynamic.so.done,只有当 那个 文件更新时,才执行dynamic.so 的拷贝(保证到那时已经closed)。

关于c++ - 如何在 Linux 上热重载共享库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56334288/

相关文章:

c++ - 如何在动态链接库中处理 'extern' 变量?

java - DLL文件放在哪里?

c++ - 求和 float c++

c - 为什么这个按位运算符返回负数?

c++ - 为什么符号 malloc、__malloc 和 __libc_malloc 指向相同的代码地址?

将温度 F 更改为 C 有问题

dll - Pylab导入错误dll加载失败

c++ - C++中的构造函数列表

c++ - 对 C++ 中的类型双关有何看法?

c++ - 为什么这个重载的 operator<< 以意想不到的方式工作?