Introduction

Ruby's Fiddle library provides FFI (Foreign Function Interface) capabilities for calling C functions from Ruby. When Fiddle cannot find a symbol in a loaded shared library (Fiddle::DLError: dlsym() symbol not found), the native extension or FFI call fails. This commonly occurs with gems that bundle native libraries, system library version mismatches, or when the library was compiled with symbol visibility restrictions.

Symptoms

  • Fiddle::DLError: dlsym(): symbol not found when loading a gem
  • cannot load such file for native extension gems
  • LoadError for .so or .dylib files after system update
  • Native gem works on one machine but fails on another
  • ffi gem cannot find library function by name

Error output: `` /usr/lib/ruby/3.2.0/fiddle.rb:47:in initialize': dlsym(0x7f8b3c004000, custom_function): symbol not found (Fiddle::DLError) from /usr/lib/ruby/3.2.0/fiddle.rb:47:in new' from /home/user/.gem/ruby/3.2.0/gems/my-native-gem/lib/native.rb:12

Common Causes

  • Library compiled without exporting required symbols
  • System library version incompatible with gem's expectations
  • Library path not in LD_LIBRARY_PATH or DYLD_LIBRARY_PATH
  • Gem's native extension compiled against different library version
  • macOS System Integrity Protection blocking library loading

Step-by-Step Fix

  1. 1.Check which symbols are exported by the library:
  2. 2.```bash
  3. 3.# List symbols in a shared library
  4. 4.nm -D /path/to/library.so | grep custom_function
  5. 5.# Or
  6. 6.objdump -T /path/to/library.so | grep custom_function

# If symbol not found, it was not exported during compilation # If symbol shows as 'U' (undefined), it needs to be linked ```

  1. 1.Fix library path for dynamic loading:
  2. 2.```bash
  3. 3.# Linux
  4. 4.export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
  5. 5.ldconfig # Rebuild library cache

# macOS export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH

# Verify library can be found ldd /path/to/gem/native_extension.so # Check for "not found" entries ```

  1. 1.Reinstall gem with correct library paths:
  2. 2.```bash
  3. 3.# Uninstall and reinstall native gem
  4. 4.gem uninstall my-native-gem

# Set library path during installation gem install my-native-gem -- \ --with-opt-dir=/usr/local \ --with-opt-lib=/usr/local/lib \ --with-opt-include=/usr/local/include

# Or use bundler config bundle config build.my-native-gem --with-opt-dir=/usr/local bundle install ```

  1. 1.**Debug Fiddle loading in Ruby":
  2. 2.```ruby
  3. 3.require 'fiddle'

# Try loading library manually begin lib = Fiddle.dlopen('/usr/local/lib/libcustom.so') puts "Library loaded successfully"

# Check specific symbol func = Fiddle::Function.new( lib['custom_function'], [Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT ) puts "Symbol found and function wrapped" rescue Fiddle::DLError => e puts "Failed: #{e.message}"

# List available libraries puts "Search paths: #{ENV['LD_LIBRARY_PATH']}" end ```

  1. 1.Fix macOS-specific library loading issues:
  2. 2.```bash
  3. 3.# Check library is properly code-signed
  4. 4.codesign --verify /usr/local/lib/libcustom.dylib

# Fix library install names install_name_tool -id /usr/local/lib/libcustom.dylib \ /usr/local/lib/libcustom.dylib

# Re-link dependent libraries install_name_tool -change /old/path/libcustom.dylib \ /usr/local/lib/libcustom.dylib \ /path/to/gem/native_extension.bundle ```

Prevention

  • Pin system library versions in Docker images and CI
  • Use gem install with explicit --with-opt-dir for native gems
  • Add library path checks to application startup
  • Document required system libraries in project README
  • Use ruby-vips or similar well-maintained native gems instead of custom FFI
  • Test native extension loading in CI on all target platforms