3

I am currently building in C a complex executable that depends on multiple libraries. The executable and the some of the libraries it uses depend on a specific library, lets call it libXYZ.

I am trying to build the executable with a more recent version of libXYZ, libXYZ.2 while I can not rebuild the other libraries that depend on an older, partially binary incompatible version of libXYZ, libXYZ.1.

I am able to include both .so for libXYZ1 and libXYZ2 in the executable and the code compiles and runs, but when I check the generated executable, some of the chosen symbols for the calls by the executable are for libXYZ1, even if at compile time the headers are for libXYZ2.

Is there a way via linker options to force the executable being built, to always choose libXYZ2 while leaving libXYZ1 available for the other libraries being also linked? I can not rebuild the other libraries that depend on libXYZ, but I can rebuild the executable and libXYZ itself as needed. Thanks.

Pedro Perez
  • 330
  • 1
  • 2
  • 11
  • 1
    What compiler is used? – tstanisl Feb 25 '21 at 23:06
  • GCC and LD under Linux – Pedro Perez Feb 26 '21 at 08:54
  • 1
    I suppose you don't want/can't use `dlopen` and `dlsym` to load the shared library at runtime and resolve the functions from it. Or am I missing something? – icebp Feb 26 '21 at 10:36
  • That is correct, no *dlopen* or *dlsym*, just compilation and linking modifications. – Pedro Perez Feb 26 '21 at 14:35
  • 1
    It doesn't really reach your condition of "no dlopen or dlsym" (well, technically it does), but I think `dlmopen` is the closest you can get. Alternatively, implement something like [Android's linker namespaces](https://source.android.com/devices/architecture/vndk/linker-namespace), but I'm not sure how feasible that is when you don't also own the C library and linker implementation. :) – Snild Dolkow Mar 01 '21 at 21:12
  • 1
    Can the call sites that will depend on the shared library be changed? I was thinking of wrapping libXYZ2 in another library that exports wrappers for every libXYZ2 export, so `foo` becomes `wrapped_foo` in libXYZ2Wrapper. – icebp Mar 01 '21 at 21:33
  • Have you tried to set the RPATH, using -Wl,-rpath,/any_lib_dir ? I used this to make sure my executable would use the lib version in my application directory so that I could make it use the latest version. – Alberto Pires Mar 01 '21 at 22:48
  • This is a confusing question. You can only link with one of the import libraries, not both. Are you asking how to link the object files with one library but some libraries with the other version? Headers have nothing to do with it – Lewis Kelsey Mar 02 '21 at 10:02

1 Answers1

1

I'm expanding a bit on one of my comments so the idea is properly formatted and described. This might not be doable in your scenario.

To simplify, we have libx1 which exposes two functions: libx_api and libx1_api. This version of the library is used by libx1_user which exposes a single API: libx1_user which uses both of the libx1 APIs.

We also have libx2 which exposes two functions: libx_api and libx2_api.

We have a program (prog) that depends on libx1_user and libx2.

My suggestion is to wrap libx2 in a dynamic library libx2w which exposes the same APIs as libx2, only with a slight change in name: w_libx_api and w_libx2_api (only the functions that have common names between libx1 and libx2 need this change, but I added the w_ prefix to both functions for consistency).

Now, the code of prog is changed so that every place in which a libx2 version of an API needs to be used it will actually use the wrappers for libx2w. This should be a change that is easy to automate.

Example with code (I will omit the contents of the headers, there's nothing special there, just function prototypes):

libx1.c

#include <stdio.h>
#include "libx1.h"

void libx_api()
{
    printf("In libx_api() from libx1\n");
}

void libx1_api()
{
    printf("In libx1_api()\n");
}

libx2.c

#include <stdio.h>
#include "libx2.h"

void libx_api()
{
    printf("In libx_api() from libx2\n");
}

void libx2_api()
{
    printf("In libx2_api()\n");
}

libx1_user.c

#include <stdio.h>
#include "libx1_user.h"
#include "libx1.h"

void libx1_user()
{
    printf("Entering libx1_user()\n");
    libx_api();
    libx1_api();
    printf("Exiting libx1_user()\n");
}

libx2_wrapper.c

#include <stdio.h>
#include "libx2wrapper.h"
#include "libx2.h"

void w_libx_api()
{
    libx_api();
}

void w_libx2_api()
{
    libx2_api();
}

program.c

#include <stdio.h>
#include "libx1_user.h"
#include "libx2wrapper.h"

int main()
{
    printf("Using libx1_user()\n");
    libx1_user();
    printf("Done!\n");

    printf("Using libx2 wrappers\n");
    w_libx_api(); // this was previously libx_api()
    w_libx2_api(); // this was previously libx2_api()
    printf("Done!\n");

    return 0;
}

Sample CMakeLists.txt for compiling and linking

I'm more familiar with CMake than make so I found it easier to write the corresponding CMakeLists.txt instead of a Makefile.

project(prog)

add_library(x1 STATIC libx1.c)
add_library(x1_user STATIC libx1_user.c)
target_link_libraries(x1_user PRIVATE x1)

add_library(x2 STATIC libx2.c)
add_library(x2w SHARED libx2wrapper.c)
target_link_libraries(x2w PRIVATE x2)

add_executable(prog program.c)
target_link_libraries(prog PRIVATE x2w x1_user)

Running the resulting program

$ ./prog
Using libx1_user()
Entering libx1_user()
In libx_api() from libx1
In libx1_api()
Exiting libx1_user()
Done!
Using libx2 wrappers
In libx_api() from libx1
In libx2_api()
Done!

nm output

$ nm prog
0000000000003d98 d _DYNAMIC
0000000000003fa8 d _GLOBAL_OFFSET_TABLE_
0000000000002000 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
000000000000224c r __FRAME_END__
0000000000002088 r __GNU_EH_FRAME_HDR
0000000000004010 d __TMC_END__
0000000000004010 B __bss_start
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000004000 D __data_start
0000000000001140 t __do_global_dtors_aux
0000000000003d90 d __do_global_dtors_aux_fini_array_entry
0000000000004008 d __dso_handle
0000000000003d88 d __frame_dummy_init_array_entry
                 w __gmon_start__
0000000000003d90 d __init_array_end
0000000000003d88 d __init_array_start
00000000000012c0 T __libc_csu_fini
0000000000001250 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
0000000000004010 D _edata
0000000000004018 B _end
00000000000012c8 t _fini
0000000000001000 t _init
00000000000010a0 T _start
0000000000004010 b completed.8060
0000000000004000 W data_start
00000000000010d0 t deregister_tm_clones
0000000000001180 t frame_dummy
0000000000001234 T libx1_api
00000000000011e6 T libx1_user
000000000000121d T libx_api
0000000000001189 T main
                 U puts@@GLIBC_2.2.5
0000000000001100 t register_tm_clones
                 U w_libx2_api
                 U w_libx_api
icebp
  • 1,608
  • 1
  • 14
  • 24
  • 1
    Hi icep Thank you for your answer. Your answer is correct, however, libXYZ contains hundreds of functions exposed via its API, wrapping them all is impossible or impractical. Maybe there is no option, but your detailed, correct and useful answer merits the bounty :). – Pedro Perez Mar 02 '21 at 15:03
  • Thanks. This issue kept popping into my head in the last few days. I'm really curious if there is actually a better solution. The only way this can be made practical for large libraries is through some automated process, maybe with some clang tools (like clang-query) or something built on top of pycparser. For example [here](https://devblogs.microsoft.com/cppblog/exploring-clang-tooling-part-1-extending-clang-tidy/) is a way of adding a prefix to all functions, but that's not enough for what is needed here. – icebp Mar 02 '21 at 15:13