cling::DynamicLibraryManager::loadLibrary severely broken

@Axel This is Ubuntu 20.04 / x86_64 / gcc 9.3.0 and ROOT 6.24/02.

I have a bunch of shared libraries which I try to load using:

root [0] gSystem->AddDynamicPath("/.../VTK-9.0/lib")
root [1] gSystem->Load("libvtkCommonCore-9.0")
cling::DynamicLibraryManager::loadLibrary(): libvtksys-9.0.so.1: cannot open shared object file: No such file or directory
(int) -1

However, the “missing” library DOES exist in the same directory, as can be proven by:

root [2] gSystem->Load("libvtksys-9.0.so.1")
(int) 0

Moreover, if I first:

export LD_LIBRARY_PATH="/.../VTK-9.0/lib:${LD_LIBRARY_PATH}"

then it works:

root [0] gSystem->Load("libvtkCommonCore-9.0")
(int) 0

I think, ROOT needs to be able to “automatically” load any of these:

[...] $ ls -l libvtkCommonCore-9.0*
... libvtkCommonCore-9.0.so -> libvtkCommonCore-9.0.so.1
... libvtkCommonCore-9.0.so.1 -> libvtkCommonCore-9.0.so.9.0.3
... libvtkCommonCore-9.0.so.9.0.3
[...] $ ls -l libvtksys-9.0*
... libvtksys-9.0.so -> libvtksys-9.0.so.1
... libvtksys-9.0.so.1 -> libvtksys-9.0.so.9.0.3
... libvtksys-9.0.so.9.0.3

Hi Wile_E,

gSystem->AddDynamicPath() does not add to LD_LIBRARY_PATH, it just adds to the directories ROOT searches as part of gSystem->Load().

When ROOT tries to dlopen vtkCommon, that depends on a different library, and dlopen not ROOT!) cannot find that dependency. When you ask ROOT to load it it’s all fine because of your call to AddDynamicPath - but dlopen doesn’t know about that.

So the right thing indeed is to export LD_LIBRARY_PATH, either ouside or inside ROOT with gSystem->SetEnvVar().

Cheers, Axel.

Should/Could AddDynamicPath also update the LD_LIBRARY_PATH variable (via TSystem::setenv?)

IMO no - that’s a backward incompatibility: people will set LD_LIBRARY_PATH (possibly through TSystem) to set LD_LIBRARY_PATH, and call AddDynamicPath to allow ROOT to find libraries. Maybe it’s enough to call this out in the doc?

So, I tried:

root [0] TString d = "/.../VTK-9.0/lib";
root [1] gSystem->Setenv("LD_LIBRARY_PATH", TString(gSystem->Getenv("LD_LIBRARY_PATH")) + ":" + d);
root [2] gSystem->Load("libvtkCommonCore-9.0")
Error in <TUnixSystem::FindDynamicLibrary>: libvtkCommonCore-9.0[.so | .dll | .dylib | .sl | .dl | .a] does not exist in ...
(int) -1

And then I tried:

root [0] TString d = "/.../VTK-9.0/lib";
root [1] gSystem->Setenv("LD_LIBRARY_PATH", TString(gSystem->Getenv("LD_LIBRARY_PATH")) + ":" + d);
root [2] gSystem->AddDynamicPath(d);
root [3] gSystem->Load("libvtkCommonCore-9.0")
cling::DynamicLibraryManager::loadLibrary(): libvtksys-9.0.so.1: cannot open shared object file: No such file or directory
(int) -1

In other words, the loading of shared libraries is really terribly broken.

The “TSystem::AddDynamicPath” is useless if it cannot properly manage dynamic linking.

When ROOT starts, the contents of the “LD_LIBRARY_PATH” environment variable is directly copied into the “gSystem->GetDynamicPath()” but, as demonstrated above, later modifications done from inside of the running application are improperly handled. Please fix it.

The contents of the “gSystem->GetDynamicPath()” should be passed to the “dlopen”.

That’s your case here, but might completely destroy the setup of experiments. We’re all the center of the world, but there are multiple centers :wink:

If you read what I explained above then the behavior you demonstrate is what I’d expect; thanks for proving it. In general, if you want dlopen to find a library in a custom / non-default path then you have to set LD_LIBRARY_PATH. Indeed, the ROOT library search path is not updated from LD_LIBRARY_PATH once set; we could do that, I see no issue with that. @pcanal what’s your take?

It appears that changing LD_LIBRARY_PATH after the start of the process in not working. Reading through google it “appears” that dlopen gets configured at process startup. If that is correct, what Wile request is “impossible” to implement (cheaply). Ideally I agree that SetDynamicPath ought to have the same effect as setting LD_LIBRARY_PATH (i.e find both top level and inner libraries) but implementing this would required to re-implement the dependency walk in dlopen (i.e. find the top library, find it dependency and open the most derived dependencies first).

I have found a brutal fix that may be of interest to other people.

It works on Linux for shared libraries in the default ELF format (I do not know any fix for macOS and / or Windows, sorry).

The trick is to add “$ORIGIN” to their “RUNPATH”.

After building the VTK software and executing “make install”, I have around 160 shared libraries in a single directory ("/.../VTK-9.0/lib" in my previous posts here). By default, these libraries do not have any “RUNPATH” set, so I could simply execute:

patchelf --set-rpath "\$ORIGIN" *.so

Note: the above command unconditionally sets the new “RUNPATH” so one should first make sure that it is really empty:

readelf -d *.so | egrep "File:|RPATH|RUNPATH"

If for “libSomeLibrary.so” one sees some non-empty “old_value” then for this library one should execute:

patchelf --set-rpath "old_value:\$ORIGIN" libSomeLibrary.so

You could set rpath at the build system level with the GCC flags -Wl,-rpath,/path/to/dir, which is less hacky than patching the binary directly.

But the actual feature of having this isn’t bad at all, it certainly isn’t a “brutal fix” :wink: rpath is a great solution here and LD_LIBRARY_PATH, although common, isn’t always the best choice.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.