“soname”选项用于构建共享库,其作用是什么?

72

我学习了《程序库HOWTO》。它提到可以使用soname来管理版本,如下所示。

gcc -shared -fPIC -Wl,-soname,libfoo.so.1  -o libfoo.so.1.0.0 foo.c
ln -s libfoo.so.1.0.0  libfoo.so.1
ln -s libfoo.so.1 libfoo.so

而我得到的信息是,如果未设置soname,则它将等于libfoo.so.1.0.0,可以在这里查看答案。

而我发现即使没有soname也可以工作,如下所示:

 gcc -shared -fPIC -o libfoo.so.1.0.0 foo.c
 ln -s libfoo.so.1.0.0  libfoo.so.1
 ln -s libfoo.so.1 libfoo.so

所以我认为唯一有用的点是,当你使用 readelf -d libfoo.so 命令检查共享库时,soname 选项可以告诉你共享库的版本。

它还能做什么?

5个回答

72

soname用于指示您的库支持哪些二进制API兼容性。

SONAME在编译时由链接器使用,从库文件中确定实际目标库版本。gcc -lNAME将寻找libNAME.so链接或文件,然后捕获它的SONAME,这肯定会更具体(例如 libnuke.so 链接到包含 SONAME libnuke.so.0 的 libnuke.so.0.1.4)。

在运行时,它将链接到此,然后设置为ELF动态部分NEEDED,然后应该存在具有此名称的库(或其链接)。 在运行时,SONAME被忽略,因此仅需要链接或文件存在即可。

备注:SONAME仅在链接/构建时强制执行,而不是在运行时。

可以使用'objdump -p file | grep SONAME'查看库的'SONAME'。 可以使用'objdump -p file | grep NEEDED'查看二进制文件的'NEEDED'。

[编辑]警告:以下是一般性说明,不是Linux中部署的说明。请参阅结尾。

假设您有一个名为 libnuke.so.1.2 的库,并且您开发了一个新的 libnuke 库:

  • 如果您的新库是对以前版本进行修复,而没有 API 更改,则应保持相同的 soname,增加文件名的版本。即文件将是 libnuke.so.1.2.1,但 soname 仍将是 libnuke.so.1.2。
  • 如果您有一个只添加了新功能但不会破坏功能且仍与先前版本兼容的新库,则应使用与先前版本相同的 soname 加上新后缀,例如 .1。即文件和 soname 将是 libnuke.so.1.2.1。任何链接到 libnuke.1.2 的程序仍将与该程序一起工作。新程序链接到 libnuke.1.2.1,只能使用该程序(直到出现新的子版本,例如 libnuke.1.2.1.1)。
  • 如果您的新库与任何libnuke: libnuke.so.2不兼容
  • 如果您的新库与旧版本裸机兼容:libnuke.so.1.3 [即仍与libnuke.so.1兼容]
  • [编辑]完整版:Linux案例。

    在Linux的实际应用中,SONAME具有特定的形式: lib[NAME][API-VERSION].so.[major-version] major-version只是一个整数值,每次主要库更改时增加。 默认情况下,API-VERSION为空。

    例如libnuke.so.0

    然后真实的文件名包含次要版本和子版本,例如:libnuke.so.0.1.5

    我认为不提供SONAME是一种不良做法,因为重命名文件将改变其行为。


    1
    根据David A. Wheeler(http://bit.ly/1CkQJmR)的说法,soname只有一个版本号,例如libnuke.so.1或libnuke.so.4。你知道是否可以在soname中加入第二个版本号,例如libnuke.so.1.2吗? - Gabriel F. T. Gomes
    存在具有多个数字的SONAME,例如:openssl库的SONAME为libssl.so.0.9.8,但你是正确的,它比“程序库HOWTO”更通用,这似乎更加严格。 - philippe lhardy
    你似乎在描述文件名和soname应该设置不同,但你没有描述后面的情况下soname应该如何设置。 - poolie
    1
    @Gabriel 我会更新我的答案(稍后),实际上这个答案对于大多数Linux情况来说太笼统了,而且事实上是错误的(很遗憾,没有其他的答案)。 - philippe lhardy
    如果有了解这个问题的人有时间,能否更新一下这个答案? - firedrillsergeant
    您可以拥有名为libx.so的文件,并使用SONAME libx.so.1编译和链接,直到您在Android上运行它时才会出错。因为Android始终期望文件名以.so结尾,如果SONAME是带版本号的,则加载该库将失败。 如果这是一个开源库,您可以修改构建文件,否则rpl可能是您唯一的选择。 - vesperto

    7
    你创建了一个名为libx.1.0.0的动态库,遵循命名传统libname.{a}.{b}.{c}。
    {a} stand for primary version, should changes when APIs changes(which making things incompatible).
    {b} stand for sub version, should changes by adding APIs.
    {c} stand for mirror version, should changes by bug fixing or optimizing
    

    现在您正在发布libx.1.2.0版本,您需要声明libx.1.2.0与libx.1.0.0兼容,因为只是添加功能和可执行文件不会崩溃,只需像以前一样链接即可:将libx.1.0.0和libx.1.2.0设置为具有相同的soname,例如libx.1。这就是soname的作用。

    1
    但是仍然需要手动创建符号链接,对吗? soname 是符号链接名称,这样说正确吗? - daparic

    6

    这是一个支持Johann Klasek的回答的例子。

    简而言之,SONAME在运行时需要。在编译时,只需要链接器名称或真实名称(例如g++ main.cpp -L. -laddg++ main.cpp -L. -l:libadd.so.1.1)。链接器名称和真实名称的定义请参见《程序库指南:3.共享库》

    源树:

    ├── add.cpp
    ├── add.h
    ├── main.cpp
    └── Makefile
    

    Makefile:

    SOURCE_FILE=add.cpp
    # main.cpp includes `add.h`, whose implementation is `add.cpp`
    MAIN_FILE=main.cpp
    SONAME=libadd.so.1
    REAL_NAME=libadd.so.1.1
    LINKER_NAME=libadd.so
    OUTPUT_FILE=a.out
    
    all:
       g++ -shared -fPIC -Wl,-soname,${SONAME} -o ${REAL_NAME} ${SOURCE_FILE}
       ln -s ${REAL_NAME} ${LINKER_NAME}
       g++ main.cpp -I. -L. -ladd -o ${OUTPUT_FILE} 
       # Same as `ldconfig -n .`, creates a symbolic link
       ln -s ${REAL_NAME} ${SONAME}
       #./a.out: error while loading shared libraries: libadd.so.1: cannot open 
       # shared object file: No such file or directory
       LD_LIBRARY_PATH=. ./${OUTPUT_FILE}
    clean:
       rm ${SONAME} ${REAL_NAME} ${LINKER_NAME} ${OUTPUT_FILE}
    

    1
    显然,这个答案与被接受的答案不同,在'SONAME'何时生效,运行时还是编译时。 - diverger

    4
    假设libA.so依赖于libB.so,并且它们都在同一目录中(当然,动态链接器无法找到该目录)。如果你没有设置soname,那么dlopen就无法工作。
    auto pB = dlopen("./libB.so", RTLD_LAZY | RTLD_GLOBAL);
    auto pA = dlopen("./libA.so", RTLD_LAZY | RTLD_GLOBAL);
    

    由于运行时链接器找不到libB.so,因此pA被设置为NULL。
    在这种情况下,soname将拯救你免于苦难...

    1
    另一个方面:至少在Linux上,SONAME条目为运行时链接器系统提供了提示,以便在/lib、/lib64等目录中创建适当的链接。运行ldconfig命令尝试创建一个名为SONAME的符号链接,该链接也被纳入运行时链接器缓存中。标记相同SONAME的库中最新的一个赢得链接竞赛。如果某些软件依赖于特定的SONAME,并且您想要更新库,则必须提供此SONAME才能使ldconfig粘附在此新库上(如果使用ldconfig重新构建缓存和链接)。例如,libssl.so.6和libcrypto.so.6就是这样的情况。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接