【问题标题】:Generate shim `.so` to rename symbols of existing `.so`生成 shim `.so` 以重命名现有 `.so` 的符号
【发布时间】:2019-05-29 22:04:39
【问题描述】:

我有一个现有的预构建.so 共享库(我们称之为libjniopenssl.so)。它是由第三方在Debian LinuxOpenSSL 1.0.1k-3+deb8u4 上构建的。 我必须使用OpenSSL 1.0.2k 包在CentOS 上运行libjniopenssl.so。 尽管OpenSSL 版本略有不同,libjniopenssl.so 使用的 API 在1.0.1k1.0.2k 之间没有变化——所以我希望它们在我的场景中是源代码和二进制兼容的。 不幸的是,仅在 CentOS 上运行 libjniopenssl.so 不起作用。

libjniopenssl.soJVM 通过System.loadLibrary 加载,但在CentOS 上运行时失败并出现以下错误:

Unable to load libjniopenssl: java.lang.UnsatisfiedLinkError: /tmp/jna-112200956/jna6604950569974562639.tmp:
libcrypto.so.1.0.0: cannot open shared object file: No such file or directory

原因很简单,CentOS 上没有libcrypto.so.1.0.0 这样的文件,因为OpenSSL 1.0.2k 16.el7 只提供了以下.so 的:

$ ls -l /lib64/libcrypto*
lrwxrwxrwx. 1 root root      19 Jun  5  2018 /lib64/libcrypto.so.10 -> libcrypto.so.1.0.2k
-rwxr-xr-x. 1 root root 2512832 Apr 11  2018 /lib64/libcrypto.so.1.0.2k

出于某种原因,CentOSintentionally renamed .so 文件名从默认 libcrypto.so.1.0.0libcrypto.so.1.0.2k,即使从 source 编译该版本时,它仍使用名称 libcrypto.so.1.0.0

此错误消息建议尝试创建一个名称为 /lib64/libcrypto.so.1.0.0 的符号链接,该链接指向 /lib64/libcrypto.so.1.0.2k

这导致运行时出现以下错误:

Unable to load libjniopenssl: java.lang.UnsatisfiedLinkError: /tmp/jna-112200956/jna2564265718506275007.tmp: 
/lib64/libcrypto.so.1.0.0: version `OPENSSL_1.0.0' not found (required by /tmp/jna-112200956/jna2564265718506275007.tmp)

检查libjniopenssl.so 发现OpenSSL 引用了以下符号:

$ readelf -Ws libjniopenssl.so

Symbol table '.dynsym' contains 40 entries:
Num:    Value          Size Type    Bind   Vis      Ndx Name
    0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
    1: 0000000000000e98     0 SECTION LOCAL  DEFAULT    9
    2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND EVP_CIPHER_CTX_init@OPENSSL_1.0.0 (2)
    5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc@GLIBC_2.2.5 (3)
    6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND OPENSSL_add_all_algorithms_noconf@OPENSSL_1.0.0 (2)
    7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND HMAC_CTX_cleanup@OPENSSL_1.0.0 (2)
    8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND EVP_CipherUpdate@OPENSSL_1.0.0 (2)
    9: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTable
    10: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND ERR_load_crypto_strings@OPENSSL_1.0.0 (2)
    11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5 (3)
    12: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    13: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (3)
    14: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND HMAC_CTX_init@OPENSSL_1.0.0 (2)
    15: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND HMAC_Final@OPENSSL_1.0.0 (2)
    16: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND EVP_sha1@OPENSSL_1.0.0 (2)
    17: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND HMAC_Init_ex@OPENSSL_1.0.0 (2)
    18: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND HMAC_Update@OPENSSL_1.0.0 (2)
    19: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND EVP_CIPHER_CTX_cleanup@OPENSSL_1.0.0 (2)
    20: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND EVP_MD_size@OPENSSL_1.0.0 (2)
    21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND EVP_aes_128_ctr@OPENSSL_1.0.1 (4)

但是CentOS 上的libcrypto.so.1.0.2k 有不同后缀的符号,例如:

readelf -Ws /lib64/libcrypto.so.1.0.2k | grep -iE "EVP_CIPHER_CTX_init"
705: 000000000012a6b0   151 FUNC    GLOBAL DEFAULT   13 EVP_CIPHER_CTX_init@@libcrypto.so.10

所以,CentOS 上的后缀是 @@libcrypto.so.10,但 libjniopenssl.so 链接到共享库,该库具有后缀 @OPENSSL_1.0.0 的符号。这可能就是为什么仅添加符号链接不起作用的原因。

进一步调查显示Debian版本的OpenSSL 1.0.1kincluded many patches,其中一个adds symbol versioning。它仅在 OpenSSL 1.1.0 中引入,但由向 1.0.1 提供 Debian 软件包的人反向移植。

相反,CentOS OpenSSL 1.0.2k 软件包不包含用于符号版本控制的补丁(但它们包含来自 1.1.0 的其他补丁,根据 .so 中的符号)。

我可以根据OpenSSL 的副本在CentOS 上重新构建libjniopenssl.so,但它实际上涉及维护(构建、存储、部署)自己的libjniopenssl.so 副本,它实际上来自第三方git 存储库.我不喜欢重建解决方案,所以我正在寻找更优雅的解决方案。但是我不得不说re-buildingCentOS论坛上推荐的。

我能想到的一个解决方案是在构建或部署期间生成一个特殊的 shim libcrypto.so.1.0.0(基于目标系统提供的 libcrypto 副本),它已根据 libjniopenssl.so 的需要重命名符号,但在后台它将所有调用转换为原始系统提供的libcrypto.so.1.0.2k

一般来说,我正在寻找一些工具或一组工具,它们能够从指定的“实现”.so 中自动生成这样的“垫片/适配器/代理”.so,但能够重新定义一些或所有符号。 我发现有objcopy 工具有--redefine-symbol old=new,但它并不完全符合我的要求:我不想从原始.so 复制任何代码,我确实想将调用转换为原来的“实现”.so

UPD:运行本地实验导致objcopy不支持动态库中符号的重命名。从这个mail thread确认了问题。

总结一下,我的问题是:

  1. 是否有现有的工具能够为指定的.so 文件生成这样的共享 shim 库?
  2. 或者也许有更好的方法来解决CentOSDebian OpenSSL 包之间的这种不兼容性,假设我不想修改第三方提供的libjniopenssl.so 和操作系统提供的@987654397 的副本@?

【问题讨论】:

  • Debian 和 CentOS 的 OpenSSL 似乎是二进制不兼容的。通过重命名符号来强制链接可能会导致运行时错误。
  • 他们建议重建它是有充分理由的。这是一个合理的建议,无论你是否想做这件事,你都应该遵循。如果它们不兼容,不要指望任何实用程序来“解决”问题。在编程中记住这一点:“工作”的外观并不意味着它是正确的。有很大的不同。不兼容就是不兼容,无论你多么不喜欢它。简而言之,答案是你走错了路,事情就是这样。
  • @Vi。这是真的,但实际上简单的符号链接通常就足够了,因为应用程序不会调用不兼容的 ABI 子集。我知道这是有风险的、不正确的等等,但这仍然是一种常见的做法。

标签: java linux openssl shared-libraries


【解决方案1】:

Implib.so 能够为任意 .so 库生成 shim 包装器。跑步

$ implib-gen.py --dlopen-callback=mycallback libcrypto.so.1.0.0

将生成libcrypto.so.1.0.0.tramp.Slibcrypto.so.1.0.0.init.c,它们将对libcrypto.so.1.0.0 中的所有符号实现无版本包装。您还需要一个用于特定操作系统实现的小型加载程序(即libcrypto.so.1.0.2k):

$ cat mycallback.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __cplusplus
extern "C"
#endif

// Load OS-specific implementation underneath
void *mycallback(const char *lib_name) {
  void *h = dlmopen(LM_ID_NEWLM, "libcrypto.so.1.0.2k", RTLD_LAZY | RTLD_DEEPBIND);
  if (h)
    return h;
  fprintf(stderr, "dlmopen failed: %s\n", dlerror());
  exit(1);
}

将代码与自动生成的文件链接,mycallback.c-ldl 应该可以解决版本不兼容的问题。

【讨论】:

    猜你喜欢
    • 2013-12-27
    • 1970-01-01
    • 1970-01-01
    • 2017-11-14
    • 1970-01-01
    • 1970-01-01
    • 2013-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多