Ruby借助Fiddle加载C共享库时,如何正确加载项目内的依赖.so文件
问题核心根因
根据你提供的strace跟踪结果,这个问题完全是第三方ServerApi.so的内部实现逻辑导致的:
- 它没有走Linux系统动态链接器的标准依赖查找流程,而是自己调用
readlink("/proc/self/exe")获取当前进程可执行文件的绝对路径 - 拿到Ruby解释器的路径后,直接拼接同目录下的
Network.so、Protobuf.so完整路径发起文件加载 - 运行Ruby脚本时,
/proc/self/exe指向的是Ruby解释器的安装目录(也就是报错里的.rvm/rubies/ruby-2.6.5/bin/路径),不是你存放项目文件的目录,自然会报文件找不到的错误
你之前把依赖库放到Ruby安装目录就能跑,本质上是刚好命中了这个库自己拼出来的查找路径,但这种方式会把项目文件散落到Ruby安装路径里,完全不符合项目集中管理的要求。
可落地的解决方法
方案1:提前预加载所有依赖(推荐,零额外配置)
Linux的glibc动态链接器有个原生机制:如果某个共享库已经被加载到当前进程的全局符号表,后续其他模块请求加载同名库时,会直接复用已有的内存句柄,不会重新去文件系统查找路径。
利用这个特性,只要在加载ServerApi.so之前,用项目目录下的绝对路径把所有依赖的.so提前加载到进程全局命名空间,就能跳过ServerApi.so内部的硬编码路径查找逻辑。
修改后的可用代码:
require 'fiddle' require 'fiddle/import' # 获取脚本所在目录的绝对路径,避免相对路径受进程工作目录变动影响 LIBS_DIR = File.expand_path(__dir__) # 预加载底层依赖,显式指定全局可见的加载标记 Fiddle.dlopen( File.join(LIBS_DIR, 'Network.so'), Fiddle::RTLD_LAZY | Fiddle::RTLD_GLOBAL ) Fiddle.dlopen( File.join(LIBS_DIR, 'Protobuf.so'), Fiddle::RTLD_LAZY | Fiddle::RTLD_GLOBAL ) module Era extend Fiddle::Importer # 用绝对路径加载主库,替代原来的相对路径 dlload File.join(LIBS_DIR, 'ServerApi.so') extern 'int era_init_lib()' extern 'void era_deinit_lib()' extern 'int era_process_request(const char* request, char** response)' extern 'void era_free(char* response)' end Era.era_init_lib begin # 业务逻辑 ensure Era.era_deinit_lib end
操作注意事项:
- 所有
.so的路径都要基于脚本位置转成绝对路径,不要用./这类相对路径,否则进程工作目录变动时依然会加载失败 - 预加载顺序要符合依赖关系:先加载无其他依赖的底层库(Network、Protobuf),再加载上层的主库ServerApi
- 预加载时一定要加
RTLD_GLOBAL标记,确保库的符号对后续加载的ServerApi.so可见,否则会触发符号未定义的加载错误
方案2:通过LD_PRELOAD环境变量加载(适合不想改代码的场景)
如果不想改动现有Ruby脚本,可以在启动进程时,通过系统的LD_PRELOAD机制提前把依赖库加载到进程空间,效果和代码内预加载完全一致:
LD_PRELOAD="$PWD/Network.so:$PWD/Protobuf.so" ruby your_script.rb
这种方式不需要修改现有代码结构,只要启动时能正确定位到当前目录下的依赖库即可。
内容的提问来源于stack exchange,提问作者3limin4t0r
相关产品推荐
相关产品推荐

