Как сделать горячую перезагрузку общей библиотеки в Linux - PullRequest
0 голосов
/ 28 мая 2019

Я пытаюсь повторить классный трюк из популярной серии Кейси Муратори 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, обновляется, но фактическое содержимое общего объекта пусто? Связывает ли компоновщик общий объект и предотвращает чтение?

Если мой код не ужасно неправильный, как я могу обойти такое поведение?

Я не склонен спать и повторять попытки, поскольку это было предназначено для довольно мгновенного обновления.

1 Ответ

1 голос
/ 30 мая 2019

Если мой код не ужасно неправильный

Это ужасно неверно: ваш код работает с (статическим) компоновщиком (вызывается 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, то вы можете получить частичную копию (включая частичную копию, содержащую 0 байтов).

Наиболее очевидное решениеэто то, что предложил Зигмонд: вы должны изменить Makefile, чтобы связать файл , отличный от того, который вы смотрите, и выполнить mv до конечного пункта назначения в качестве последнего (атомарного) шага.

Альтернативное решение состоит в том, чтобы иметь цель make, которая зависит от dynamic.so, например,

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

В вашей программе вы бы смотрели m_time для dynamic.so.done, итолько когда этот файл обновлен, выполняется копирование dynamic.so (которое гарантированно было close d к этому моменту).

...