Я пытаюсь повторить классный трюк из популярной серии Кейси Муратори Handmade Hero . На win32 Кейси смог перезагрузить DLL и увидеть изменения своего кода с задержкой всего в несколько миллисекунд.
Я пытаюсь повторить это поведение в linux, используя dlopen, dlsym, dlclose и stat, но я сталкиваюсь с приведенным ниже поведением, и у меня есть догадка, что я либо неправильно понимаю что-то об 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;
}
В общей библиотеке просто есть несколько подпрограмм для сложения нескольких чисел, и я подтвердил, что смог выполнить dloym и dlsym без хитрости горячей перезагрузки.
Я также проверил, что моя процедура копирования фактически копирует общий объект.
Я ожидал, что начальный dlopen будет успешным, и dlsym правильно связал Add (что и было бы). Затем я отредактирую Dynamic.cpp и, возможно, верну x + x + y или что-то еще, сохраню файл и перекомпилирую, ожидая, что цикл while поймает изменение в st_mtime.
Я заметил, что когда я запустил код и обновился, я получил ошибку:
dlopen: file too short
Конечно, когда я ls -la каталог, содержащий общие объекты, копия была размером 0.
Каким-то образом st_mtime, о котором сообщает stat, обновляется, но фактическое содержимое общего объекта пусто? Связывает ли компоновщик общий объект и предотвращает чтение?
Если мой код не ужасно неправильный, как я могу обойти такое поведение?
Я не склонен спать и повторять попытки, поскольку это было предназначено для довольно мгновенного обновления.