Geant4 shared libraries don't have rpath embedded

Hi

Issue

I have encountered a problem that Geant4 shared libraries, such as libG4materials.so, don’t have embedded rpath in the binary files. This causes the shared libraries can’t find its dependencies.

If you run ldd libG4materials.so, the linked shared libraries can’t be found:

[root@d3ef4098097d lib64]# ldd libG4materials.so
        linux-vdso.so.1 (0x00007fedf0e46000)
        libG4intercoms.so => not found
        libz.so.1 => /lib64/libz.so.1 (0x00007fedf0d47000)
        libG4global.so => not found
        libG4clhep.so => not found
        libG4ptl.so.3 => not found
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fedf0ace000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fedf09e0000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fedf09b4000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fedf07c2000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fedf0e48000)

By reading the elf file with readelf -d libG4materials.so, it lacks the rpath:

Dynamic section at offset 0xc5be8 contains 33 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libG4intercoms.so]
 0x0000000000000001 (NEEDED)             Shared library: [libz.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libG4global.so]
 0x0000000000000001 (NEEDED)             Shared library: [libG4clhep.so]
 0x0000000000000001 (NEEDED)             Shared library: [libG4ptl.so.3]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libG4materials.so]
 0x000000000000000c (INIT)               0x224
 0x000000000000000d (FINI)               0x6bb60
 0x0000000000000019 (INIT_ARRAY)         0xc65f0
 0x000000000000001b (INIT_ARRAYSZ)       24 (bytes)
 0x000000000000001a (FINI_ARRAY)         0xc6608
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x6c000
 0x0000000000000005 (STRTAB)             0x71f68
 0x0000000000000006 (SYMTAB)             0x6d420
 0x000000000000000a (STRSZ)              38363 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0xc6fe8
 0x0000000000000002 (PLTRELSZ)           9768 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x7d120
 0x0000000000000007 (RELA)               0x7bd10
 0x0000000000000008 (RELASZ)             5136 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x7bb90
 0x000000006fffffff (VERNEEDNUM)         4
 0x000000006ffffff0 (VERSYM)             0x7b544
 0x000000006ffffff9 (RELACOUNT)          29
 0x0000000000000000 (NULL)               0x0

Shared libraries can be found if user change the environment variable LD_LIBRARY_PATH. But this is undesirable as it’s very intrusive for other programs running in the same system.

How to reproduce

Create a docker image with:

docker build -t geant-image .

in the folder containing the following Dockerfile:

ARG BUILD_REPO=fedora
FROM ${BUILD_REPO}:latest

ARG THREAD_NUM=4

ENV SIMPATH="/opt/FairSoft"

RUN yes | dnf upgrade --refresh &&\
    dnf install -y util-linux gcc gcc-gfortran clang cmake ninja-build git wget &&\
    dnf install -y expat-devel xerces-c-devel openblas-devel openssl-devel python python-devel python3-numpy python3-pygments python3-pyyaml&&\
    dnf autoremove -y && mkdir -p /tmp/download && cd /tmp/download &&\
    export CC=clang && export FC=gfortran && export CXX=clang++ &&\
    mkdir -p ${SIMPATH} && cd /tmp/download &&\
    wget https://github.com/Geant4/geant4/archive/refs/tags/v11.3.2.tar.gz &&\
    tar xzf v11.3.2.tar.gz && cd geant4-11.3.2 &&\
    mkdir build && cd build &&\
    cmake -DCMAKE_INSTALL_PREFIX=${SIMPATH} -GNinja -DCMAKE_CXX_STANDARD=23 -DGEANT4_BUILD_MULTITHREADED=ON -DGEANT4_BUILD_TLS_MODEL=global-dynamic -DGEANT4_USE_SYSTEM_EXPAT=ON -DGEANT4_USE_G3TOG4=ON -DGEANT4_USE_GDML=ON -DGEANT4_USE_OPENGL_X11=OFF -DGEANT4_USE_RAYTRACER_X11=OFF -DGEANT4_USE_PYTHON=ON -DGEANT4_INSTALL_DATA=ON -DGEANT4_INSTALL_DATA_TIMEOUT=36000 -DGEANT4_BUILD_STORE_TRAJECTORY=OFF -DGEANT4_BUILD_VERBOSE_CODE=ON -DGEANT4_BUILD_BUILTIN_BACKTRACE=OFF .. &&\
    ninja -j${THREAD_NUM} && ninja install &&\
    rm -rf /tmp/download && dnf autoremove -y

After the image is build, create its container:

docker run -it --name test geant-image "/bin/bash"

Then inside the container, go to the library folder:

cd /opt/FairSoft/lib64

Query the needed shared libraries with:

ldd libG4materials.so

Then the missing shared libraries could be seen.

Potential fix

In the CMake file, specify the rpath of the library targets:

include(GNUInstallDirs)
file(RELATIVE_PATH relDir
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
)
set(CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/${relDir})

Please refer to section 27.2.2 of the book “Professional CMake: A Practical Guide” by Craig Scott about the installation rpath.

Geant4 Version: v11.3.2
Operating System: Fedora 42
Compiler/Version: gcc 15.0
CMake Version: 4.1


There is a deliberate decision not to force use of RPATH as this is not what packages do typically, and of we did, we’d get complaints in the opposite direction (so this is principle of least surprise), especially from package managers.

In any case, there’s no need to change the build scripts themselves. Just configure Geant4 with the appropriate CMake arguments. I think the correct invocation given the need to escape the dollar sign is:

$ cmake -DCMAKE_INSTALL_RPATH="\$ORIGIN" <otherargs>

Thanks for your reply.

There is a deliberate decision not to force use of RPATH as this is not what packages do typically

Could you elaborate on this and what kind of issues could be caused by the RPATH? As far as I know, shared libraries from ROOT do have RPATH embedded in the binaries.

See the Debian docs on RPATH searching Google for “use of rpath discouraged” will bring up various other results. One of the key things is that it can be difficult to change afterwards, and there are various cases where it prevents things working as expected (in a general case).

ROOT may well encode the RPATH in the binaries they themselves distributed, but those are probably built using CMAKE_INSTALL_RPATH or similar builtin functionality that CMake has to set this up exactly as the use case needs, or doesn’t. That provides all the functionality needed to enable use of RPATH if wanted.

Hi,

Thanks for your information. I did some research regarding this issue and found one of my misconception about rpath.

I thought rpath and runpath are the same thing. But this is wrong. The difference (see this) is:

The major difference between RPATH and RUNPATH is that the RPATH is searched before the directories given by LD_LIBRARY_PATH while the RUNPATH is searched after.

Due to this reason, rpath is largely discouraged and deprecated in the most of loaders and linkers nowadays. Instead, RUNPATH should be preferred.

For the CMAKE_INSTALL_RPATH, what it does (see this issue) is actually setting up the RUNPATH instead of rpath. Take a look at ROOT installation with CMAKE_INSTALL_RPATH set:

Tag        Type                         Name/Value
0x0000000000000001 (NEEDED)             Shared library: [libThread.so.6.36]
0x0000000000000001 (NEEDED)             Shared library: [libCore.so.6.36]
0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
0x0000000000000001 (NEEDED)             Shared library: [ld-linux-x86-64.so.2]
0x000000000000000e (SONAME)             Library soname: [libRIO.so.6.36]
0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/]
0x000000000000000c (INIT)               0x2cc
0x000000000000000d (FINI)               0x31b2a8
0x0000000000000019 (INIT_ARRAY)         0x4a8e38
0x000000000000001b (INIT_ARRAYSZ)       288 (bytes)
0x000000000000001a (FINI_ARRAY)         0x4a8f58
0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
0x0000000000000004 (HASH)               0x31c000
0x000000006ffffef5 (GNU_HASH)           0x326860
0x0000000000000005 (STRTAB)             0x358670
0x0000000000000006 (SYMTAB)             0x3314c0
0x000000000000000a (STRSZ)              540426 (bytes)
0x000000000000000b (SYMENT)             24 (bytes)
0x0000000000000003 (PLTGOT)             0x4b8fe8
0x0000000000000002 (PLTRELSZ)           31992 (bytes)
0x0000000000000014 (PLTREL)             RELA
0x0000000000000017 (JMPREL)             0x40f160
0x0000000000000007 (RELA)               0x3dfb50
0x0000000000000008 (RELASZ)             194064 (bytes)
0x0000000000000009 (RELAENT)            24 (bytes)
0x000000006ffffffe (VERNEED)            0x3df9a0
0x000000006fffffff (VERNEEDNUM)         5
0x000000006ffffff0 (VERSYM)             0x3dc57a
0x000000006ffffff9 (RELACOUNT)          88
0x0000000000000000 (NULL)               0x0

Here it’s RUNPATH that is set to be $ORIGIN/.

ROOT may well encode the RPATH in the binaries they themselves distributed

Yes, and RUNPATH is enabled by default.

So would it be viable to enable RUNPATH of Geant4 libraries by default as well?

Wouldn’t that just be done with -DCMAKE_INATALL_RPATH=ON when you build? Or are you asking for it to be enabled by default in the prebuilt binaries?

Hi,

Wouldn’t that just be done with -DCMAKE_INATALL_RPATH=ON when you build?

I assume you actually meant -DCMAKE_INATALL_RPATH="\$ORIGIN"? If so, well, that still doesn’t solve the problem for the binaries in the bin folder. To make this correct, it has to be something that I mentioned in the top post:

file(RELATIVE_PATH relDir
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
)
set(CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/${relDir})

Here both the binaries in bin and lib64 folder should be linked correctly. And in this way, it’s also independent on whether it’s lib or lib64.

Or are you asking for it to be enabled by default in the prebuilt binaries?

No, prebuilt is prebuilt and nothing can be enabled or disabled for the prebuilt. What I am suggesting is to enable runpath in the CMake installation process. I’m not aware of any disadvantage when using runpath. But you could also have a toggle about it, like ROOT.

Cheers

The toggle is the CMAKE_INSTALL_RPATH option. I can guarantee if we do enable it by default, there will be someone else who doesn’t want that and will complain. Since it’s trivial to enable RPATH/RUNPATH if required using well documented, standard, CMake options, we leave the choice to the person building Geant4.

I’m also confused about why the path to the bin folder is needed. Geant4 doesn’t install anything there (at least nothing that is dependent on anything the lib dir), and it’s up to the application using the libraries to set its R/RUNPATH as needed, not the libraries (who can’t know where a dependent application ends up.

Hi,

The toggle is the CMAKE_INSTALL_RPATH option.

Sorry, by “toggle”, I meant a boolean value with “ON/OFF”. The CMAKE_INSTALL_RPATH from the documentation here:

A semicolon-separated list specifying the rpath to use in installed targets (for platforms that support it). This is used to initialize the target property INSTALL_RPATH for all targets.

It’s a list of strings, not an option.

I can guarantee if we do enable it by default, there will be someone else who doesn’t want that and will complain.

Like I said, most of libraries have this runpath enabled by default, like ROOT, Boost, Google’s protobuf, etc. Thus, we could have a cmake option to toggle it on or off, like what ROOT has.

I’m also confused about why the path to the bin folder is needed.

Sorry, I mistaked them for something else.

Anyway, if it’s undesirable to default the correct way for the installation. Would it be possible to add the setup (Linux, MacOS and Windows(?)) of CMAKE_INSTALL_RPATH to the installation documentation?

I know, but it still acts as a switch, toggle, on using R/RUNPATH or not. Configure with -DCMAKE_INSTALL_RPATH=<yourlist> you get it set. Don’t configure with that CMake argument, you don’t get it.

That’s not the case for an out the box build/install of Boost as least on the current develop branch and following their install instructions. If you’re seeing an R/RUNPATH set in that, it’s almost certainly from a package manager setting the build up or patching the build to add it. Protobuf does appear to set it, but there are protobuf installs (e.g. on LCG/CVMFS) that don’t have R/RUNPATH set, presumably because the package manager stripped it out or setup or patched the build scripts not to add it.

Again, the point is that there isn’t really an absolute standard or “correct” choice here, some projects do set it by default, others don’t, and package managers swap/adjust these anyway. The decision in Geant4 is based on least surprise and opt-in which is you get R/RUNPATH if you want it, and all you have to do then is use the known, familiar CMake mechanisms which are already well documented. This also gives the freedom to choose the best setting: $ORIGIN? or an absolute path to the install location? or a padded path to allow later fixes? (and that’s just on Linux/ELF)

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