【问题标题】:Making executable to prefer a shared object library over older version also available in same executable使可执行文件更喜欢共享对象库而不是旧版本也可以在同一个可执行文件中使用
【发布时间】:2023-04-01 05:17:01
【问题描述】:

我目前正在用 C 语言构建一个依赖于多个库的复杂可执行文件。可执行文件和它使用的一些库依赖于特定的库,我们称之为 libXYZ。

我正在尝试使用更新版本的 libXYZ libXYZ.2 构建可执行文件,而我无法重建依赖于较旧的、部分二进制不兼容的 libXYZ 版本 libXYZ.1 的其他库。

我可以在可执行文件中同时包含 libXYZ1 和 libXYZ2 的 .so,并且代码编译并运行,但是当我检查生成的可执行文件时,可执行文件调用的某些选定符号是针对 libXYZ1 的,即使在编译时,头文件用于 libXYZ2。

有没有办法通过链接器选项强制构建可执行文件,始终选择 libXYZ2,同时让 libXYZ1 可用于也链接的其他库?我无法重建依赖于 libXYZ 的其他库,但我可以根据需要重建可执行文件和 libXYZ 本身。谢谢。

【问题讨论】:

  • 使用什么编译器?
  • Linux下的GCC和LD
  • 我想您不想/不能使用dlopendlsym 在运行时加载共享库并从中解析函数。还是我错过了什么?
  • 它并没有真正达到“没有 dlopen 或 dlsym”的条件(嗯,从技术上讲它确实如此),但我认为 dlmopen 是你能得到的最接近的条件。或者,实现 Android's linker namespaces 之类的东西,但我不确定当您不拥有 C 库和链接器实现时这有多可行。 :)
  • 能否更改依赖共享库的调用站点?我正在考虑将 libXYZ2 包装在另一个库中,该库为每个 libXYZ2 导出导出包装器,因此 foo 在 libXYZ2Wrapper 中变为 wrapped_foo

标签: c linker shared-libraries


【解决方案1】:

我正在对我的一个 cmets 进行一些扩展,以便正确格式化和描述这个想法。这在您的场景中可能不可行。

为了简化,我们有libx1,它公开了两个函数:libx_apilibx1_api。此版本的库由 libx1_user 使用,它公开了一个 API:libx1_user,它同时使用了 libx1 API。

我们还有libx2,它公开了两个函数:libx_apilibx2_api

我们有一个程序 (prog),它依赖于 libx1_userlibx2

我的建议是将libx2 包装在一个动态库libx2w 中,它公开了与libx2 相同的API,只是名称略有不同:w_libx_apiw_libx2_api(仅具有共同的函数) libx1libx2 之间的名称需要进行此更改,但我为这两个函数添加了 w_ 前缀以保持一致性。

现在,prog 的代码已更改,因此每个需要使用 API 的libx2 版本的地方都将实际使用libx2w 的包装器。这应该是一个易于自动化的更改。

代码示例(我将省略标题的内容,没有什么特别之处,只是函数原型):

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;
}

用于编译和链接的示例 CMakeLists.txt

我比 make 更熟悉 CMake,所以我发现编写相应的 CMakeLists.txt 而不是 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)

运行生成的程序

$ ./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 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

【讨论】:

  • 嗨,icep 谢谢你的回答。您的答案是正确的,但是,libXYZ 包含通过其 API 公开的数百个函数,将它们全部包装起来是不可能或不切实际的。也许没有选择,但您的详细、正确和有用的答案值得赏金:)。
  • 谢谢。在过去的几天里,这个问题一直在我脑海中浮现。我真的很好奇是否真的有更好的解决方案。对于大型库而言,唯一可行的方法是通过一些自动化过程,可能使用一些 clang 工具(如 clang-query)或构建在 pycparser 之上的东西。例如here 是一种为所有函数添加前缀的方法,但这还不足以满足这里的需要。
猜你喜欢
  • 2020-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-20
  • 2022-09-28
  • 1970-01-01
  • 2019-10-03
  • 2012-08-13
相关资源
最近更新 更多