CMake-built executable is missing rpath for shared lib it uses

Greetings!

I’m writing an application that links against a shared library libaes.so. libaes.so is available here:

/usr/local/include/libaes/aes.hpp
/usr/local/include/libaes/message.hpp
/usr/local/include/libaes/octet_string.hpp
/usr/local/lib/libaes.so.1
/usr/local/lib/libaes.so
/usr/local/lib/pkgconfig/libaes.pc

CMake finds the pkg-config file and builds my project just fine. I can call into libaes.so from my code. But when running the built executable, it tells me it cannot find libaes.so. When providing the path using LD_PRELOAD it works.

$ tests/secure-tcp-socket
tests/secure-tcp-socket: error while loading shared libraries: libaes.so.1: cannot open shared object file: No such file or directory

$ $ ldd tests/secure-tcp-socket
        linux-vdso.so.1 (0x00007ffc3bdde000)
        libaes.so.1 => not found
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc6c8e00000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc6c90f5000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc6c8c1f000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc6c8b3f000)

$ LD_PRELOAD=/usr/local/lib/libaes.so.1 tests/secure-tcp-socket
Server: Running on port 34461. Awaiting connections...
Client: Connecting to 127.0.0.1:34461...

What’s the canonical way of baking the path to libaes.so into the executable? I’m wondering if I made a mistake because CMake should know where to find libaes.so. I’d assume it would use this info if it could.

Being a new user, I cannot yet upload attachments, so please find the relevant CMakeLists.txt below.

Thanks in advance!

cmake_minimum_required( VERSION 3.13.4 )

## ========================= ###
## --- secure-tcp-socket --- ###
## ========================= ###

add_library( secure_tcp_socket STATIC )

set_target_properties( secure_tcp_socket
    PROPERTIES
    CXX_STANDARD   11
    PUBLIC_HEADER  "inc/ecdh_handshaker.hpp;inc/secure-tcp-server.hpp;inc/secure-tcp-client.hpp" # for install()
)

# target_compile_definitions( secure_tcp_socket PUBLIC BOOST_ASIO_ENABLE_HANDLER_TRACKING )

find_package( PkgConfig REQUIRED )
pkg_check_modules( AES REQUIRED libaes )

target_include_directories( secure_tcp_socket
    PUBLIC      inc
    PRIVATE     ${AES_INCLUDE_DIRS} )

target_sources( secure_tcp_socket
    PRIVATE     ecdh_handshaker.cpp
    PRIVATE     secure-tcp-server.cpp
    PRIVATE     secure-tcp-client.cpp )

# Available in Debian package `libboost-system-dev`.
find_package( Boost CONFIG REQUIRED
    COMPONENTS system )

set( THREADS_PREFER_PTHREAD_FLAG ON )
find_package( Threads REQUIRED )

target_link_libraries( secure_tcp_socket
PRIVATE     Threads::Threads
PRIVATE     Boost::headers
PRIVATE     ${AES_LIBRARIES} )

## ============================== ###
## --- secure_tcp-socket_test --- ###
## ============================== ###

add_executable( secure_tcp_socket_test )

set_target_properties( secure_tcp_socket_test
    PROPERTIES
    CXX_STANDARD   11 )

target_sources( secure_tcp_socket_test
    PRIVATE     test.cpp )

target_link_libraries( secure_tcp_socket_test
    PRIVATE     secure_tcp_socket )

add_test( secure-tcp-socket-test secure_tcp_socket_test )

# Copy executable to root of build tree
add_custom_command(
    TARGET secure_tcp_socket_test POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_BINARY_DIR}/secure_tcp_socket_test
            ${CMAKE_BINARY_DIR}/tests/secure-tcp-socket )

From what I know about that, it has to do with BUILD_RPATH, since you are not using install() and just copy the executable with add_custom_command() (otherwise it would have been INSTALL_RPATH).

You should be able to set it manually:

set_target_properties(secure_tcp_socket_test
    PROPERTIES
        BUILD_RPATH "/usr/local/lib"
)

or instead this should work too, but it needs to be done before the target creation:

list(APPEND CMAKE_BUILD_RPATH "/usr/local/lib")

I’m just guessing here, I’ve never tried/needed that myself.

Thank you, @retif, that indeed fixes the problem!

Still, I was puzzled why this manual intervention is necessary. I used pkgconfig to include libpng in my build, to check wether it is suffering from the same problem. That is, if I manually need to set the path to libpng as RPATH as well. Turns out, no, it worked without problems. ldd listed the path just fine. So it seems there is some crucial difference between libpng and my libaes. After ruling out that the pkgconfig files trigger this change in behaviour, it figured it must be the install location: libpng installs in /usr/lib/x86_64-linux-gnu, my libaes in /usr/local/lib.

So I reconfigured my project to install libaes in /usr/lib/x86_64-linux-gnu as well and tada, everything works without problems.

/usr/include/libaes/aes.hpp
/usr/include/libaes/message.hpp
/usr/include/libaes/octet_string.hpp
/usr/lib/x86_64-linux-gnu/libaes.so.1
/usr/lib/x86_64-linux-gnu/libaes.so
/usr/lib/x86_64-linux-gnu/pkgconfig/libaes.pc

ldd lists the correct path and the executable runs without LD_PRELOAD. Adapting CMakeLists.txt the way you suggested is not necessary.

$ ldd tests/secure-tcp-socket
        linux-vdso.so.1 (0x00007ffdc31e2000)
        libpng16.so.16 => /lib/x86_64-linux-gnu/libpng16.so.16 (0x00007f4be067a000)
        libaes.so.1 => /lib/x86_64-linux-gnu/libaes.so.1 (0x00007f4be066d000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f4be0400000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f4be064d000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4be021f000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f4be062c000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4be013f000)
        libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f4bdfc00000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f4be0713000)

$ tests/secure-tcp-socket
Server: Running on port 34369. Awaiting connections...
Client: Connecting to 127.0.0.1:34369...

Just sharing this information in case someone finds this thread later on. I’m also curious if this is really how it’s supposed to work, because I can’t see any reason why you’d have to manually set the RPATH for shared libraries in /usr/local/lib, but not for those in /usr/lib.

Still, I was puzzled why this manual intervention is necessary

I couldn’t find this in documentation (although there is this wiki entry), but it seems that only /lib/ and /usr/lib/ paths are added by default, so /usr/local/lib/ is not there and requires manual adding.

1 Like