pnoltes commented on PR #441: URL: https://github.com/apache/celix/pull/441#issuecomment-1241134232
> > In practice this means that every bundle activator library (and ideally every private bundle library) should have a unique SONAME and although in theory this is doable ("just configure the SONAME"), I think that in many cases this adds too much complexity. > > Bundle activator library can have the same SONAME, provided that we install them into different location of the global cache, and provide the full path to `dlopen`. `man 2 dlopen` says: > > > If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname. Otherwise, the dynamic linker searches for the object as follows (LD_LIBRARY_PATH thing) > > I worked out a minimal example and tested it on my Ubuntu machine: > > ```cmake > # CMakeLists.txt > cmake_minimum_required(VERSION 3.23) > project(hello_solib C) > > set(CMAKE_C_STANDARD 99) > > > add_library(hello_impl1 SHARED hello1.c) > set_target_properties(hello_impl1 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/impl1) > set_target_properties(hello_impl1 PROPERTIES OUTPUT_NAME hello) > add_library(hello_impl2 SHARED hello2.c) > set_target_properties(hello_impl2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/impl2) > set_target_properties(hello_impl2 PROPERTIES OUTPUT_NAME hello) > > add_executable(hello_solib main.c) > target_compile_definitions(hello_solib PRIVATE HELLO1_LOC="$<TARGET_[FILE:hello_impl1](file:///hello_impl1)>") > target_compile_definitions(hello_solib PRIVATE HELLO2_LOC="$<TARGET_[FILE:hello_impl2](file:///hello_impl2)>") > target_link_libraries(hello_solib PRIVATE dl) > > ``` > > ```c > //main.c > #include "hello.h" > #include <assert.h> > #include <dlfcn.h> > #include <stdio.h> > > int main() { > int (*funcp1)(void); > int (*funcp2)(void); > void *handle1; > void *handle2; > printf("impl1=%s\n", HELLO1_LOC); > handle1 = dlopen(HELLO1_LOC, RTLD_LAZY); > assert(handle1 != NULL); > printf("impl2=%s\n", HELLO2_LOC); > handle2 = dlopen(HELLO2_LOC, RTLD_LAZY); > assert(handle2 != NULL); > *(void **)(&funcp2) = dlsym(handle2, "hello"); > funcp2(); > *(void **)(&funcp1) = dlsym(handle1, "hello"); > funcp1(); > dlclose(handle2); > dlclose(handle1); > return 0; > } > ``` > > ```c > //hello.h > #ifndef HELLO_SOLIB_HELLO_H > #define HELLO_SOLIB_HELLO_H > #ifdef __cplusplus > extern "C" { > #endif > > int hello(void); > > #ifdef __cplusplus > } > #endif > #endif //HELLO_SOLIB_HELLO_H > ``` > > ```c > //hello1.c > #include "hello.h" > #include <stdio.h> > > int hello(void) { > printf("hello1\n"); > } > ``` > > ```c > //hello2.c > #include "hello.h" > #include <stdio.h> > > int hello(void) { > printf("hello2\n"); > } > ``` > > Running the binary produces the following console output: > > ``` > /home/peng/Downloads/hello_solib/cmake-build-debug/hello_solib > impl1=/home/peng/Downloads/hello_solib/cmake-build-debug/impl1/libhello.so > impl2=/home/peng/Downloads/hello_solib/cmake-build-debug/impl2/libhello.so > hello2 > hello1 > > Process finished with exit code 0 > ``` Nice, this is new for (even though it is documenten in the man dlopen ..). I also gonna try this on a Mac and then think about how we can use this. > > > The challenge I see here is the same as with importing/exporting libraries from bundles: If there are libraries which are different, but have the same SONAME header, a call to dlopen will reuse a (transitive) library with the same SONAME instead of loading a new lib. This is even true if DL_LOCAL is used. > > At first, I thought the importing/exporting problem touches the intrinsic difference between JAVA and C++, and thus is impossible to solve. It turns out that I was wrong. > > Instead of `dlopen/dlmopen`, we should really manipulate `ld.so`. According to `man ld.so`: > > > When resolving shared object dependencies, the dynamic linker first inspects each dependency string to see if it contains a slash (this can occur if a shared object pathname containing slashes was specified at link time). > > IIRC, shared object's dependency is encoded in the NEEDED entry in ELF dynamic section: > > ``` > peng@hackerlife:~/Downloads/hello_solib/cmake-build-debug/impl1$ readelf -d libhello.so > > Dynamic section at offset 0x2e10 contains 25 entries: > Tag Type Name/Value > 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] > 0x000000000000000e (SONAME) Library soname: [libhello.so] > ``` > > If we could modify NEEDED entries of installed shared object at runtime, use relative/absolute path instead, `ld.so` will be guided to load whatever we want. And it turns out that we can do that! > > https://github.com/NixOS/patchelf > > ``` > $ patchelf --remove-needed libhello.so.1 hello > $ patchelf --add-needed ./libhello.so.1 hello > ``` > > What we need to do is to implement `patchelf` inside the Celix framework. Then IMPORT/EXPORT in the manifest become the definitive source of shared object interdependence. The framework does the hard work of wiring shared objects together at appropriate stage (e.g. when bundle get resolved?). > > If we do implement this, I bet every C/C++ programmer will be shocked. > > Currently SONAME + strict semantic version scheme should work properly in most corporate working environment, though it may be difficult to enforce in the open source world. Besides, conan makes integration test against various version of dependencies relatively easier. I suggest we finish the cache work first. > > WDYT? Nice, yes this can work. Around 2011-2013 we did some experiment with updating the NEEDED and SONAME flags and in that experiment it did work. But updating the NEEDED/SONAME entry is difficult of the string size needs to increased, so for the experiment we only updated NEEDED to something small (libfoo->ab1, libbar->ab2, etc). If I remember correctly we did not follow up this approach, because we had bigger things to tackle. But I agree if we can tackle this we are creating something "magical" for the C/C++ word :). This should indeed be done in the resolving of a bundle and ideally be done by a resolver. There is a resolver in Celix, but that is outdated and not something I have touched(https://github.com/apache/celix/blame/master/libs/framework/src/resolver.c). But If we refactor this, it should be able to work out which libs are EXPORTED and IMPORTED and define a wire (e.g. how the NEEDED (and maybe SONAME) should be updated) between the export and import libs. Also note that patchelf is GPL and if I am correct not useable for a ASF project. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@celix.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org