我正在尝试从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,但是共享对象的实际内容为空?链接器是否锁定共享库并阻止读取?
如果我的代码不是非常错误,我该如何规避这种行为?
我不愿意睡觉和重试,因为这是一个相当瞬时的更新。
答案 0 :(得分:0)
如果我的代码不是完全错误
这是完全错误的:您的代码正在使用(静态)链接程序(由make
或cmake
调用)。
运行make
时,它最终会调用:
gcc -shared -o /home/bacon/dynamic.so foo.o bar.o ...
然后,链接程序将执行open("/home/bacon/dynamic.so", O_WRONLY|O_CREAT, ...)
(或等效操作),一段时间后将write
,最后是close
文件。
在m_time
之后open
发生更改后close
,您的程序将唤醒,并尝试复制文件。如果您的副本在最终Makefile
之前 之前的任何时间发生,那么您可能会得到部分副本(包括包含0字节的部分副本)。
最明显的解决方案是Zsigmond建议的解决方案:您必须修改mv
才能将正在观看的文件链接到一个不同文件,然后执行make
到最后一个目的地(原子)。
另一种解决方案是使dynamic.so
的目标依赖于dynamic.so.done: dynamic.so
touch dynamic.so.done
,例如
m_time
在程序中,您将在dynamic.so.done
中查看dynamic.so
,只有在该文件更新后,才执行close
的副本(即保证到那时new Date("2019-05-29")
Wed May 29 2019 05:30:00 GMT+0530 (India Standard Time)
new Date("11111-05-29")
Mon May 29 11111 00:00:00 GMT+0530 (India Standard Time)
已经结束。