关于C ++:如何在Linux上热重载共享库

我正在尝试从Casey Muratori受欢迎的Handmade Hero系列中复制一个很酷的技巧。在win32上,Casey能够重新加载DLL,并且仅几毫秒的延迟就可以看到他的代码更改。

我正在尝试使用dlopen,dlsym,dlclose和stat在linux上复制此行为,但是我遇到了以下行为,并且我有一种直觉,就是我误解了ELF,示例,链接器,或共享对象的概念。

我能够在win32上轻松完成他的代码工作,所以我觉得这是我所缺少的特定于Linux的东西。

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

我将共享库复制为dynamic.so,然后加载它。每当原始共享库的mtime更新时,我都会关闭旧副本的句柄,制作一个新副本,然后尝试加载新副本。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#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!\
"
, src, dest);

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

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

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

  fclose(fsrc);
  fclose(fdest);

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

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\
"
, 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\
"
, 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\
"
, copypath, dlerror());

        break;
    }
}
}

至于动态库,任何事情都应该做(示例标头):

1
2
3
4
5
6
7
8
9
#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 */

和源文件:

1
2
3
4
5
6
7
#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中的更改。

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

1
dlopen: file too short

果然,当我在包含共享库的目录中执行ls -la时,副本的大小为0。

某种程度上,由stat报告的st_mtime已更新,但是共享库的实际内容为空?链接器是否锁定共享对象并防止读取?

如果我的代码不是完全错误,我该如何规避这种行为?

我不愿意睡觉和重试,因为这是一个相当瞬时的更新。


If my code isn't horribly wrong

完全错误:您的代码正在使用(静态)链接程序(由make或cmake调用)。

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

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

然后,链接器将执行open("/home/bacon/dynamic.so", O_WRONLY|O_CREAT, ...)(或等效功能),一段时间后将执行write,最后是close文件。

m_time更改后(在open之后的任何时间),程序都会唤醒,并尝试复制文件。如果您的副本在最后一个close之前的任何时间发生,那么您可能会得到部分副本(包括包含0字节的部分副本)。

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

另一种解决方案是使make目标取决于dynamic.so,例如

1
2
dynamic.so.done: dynamic.so
        touch dynamic.so.done

在您的程序中,您将在m_time中监视dynamic.so.done,并且只有在更新该文件时,才执行dynamic.so的副本(保证此时已被close d复制)。

原文地址:https://www.cnblogs.com/lidabo/p/15508797.html