我正在尝试从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, 1, sizeof(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 -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字节的部分副本)。
最明显的解决方案是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复制)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2019-11-04 git clone和git pull的区别
2019-11-04 git fetch & pull详解
2019-11-04 用一个bat文件调用另外两个bat文件,当1.bat执行完后再执行2.bat
2019-11-04 bat 批处理切换到当前脚本所在文件夹
2017-11-04 C++ ORM ODB入门
2015-11-04 Linux上使用Qt Creator进行C/C++开发
2013-11-04 VC使用双缓冲避免绘图闪烁的正确使用方法【转】