让 ld 选择正确的库

Pen*_*lec 3 compiling dynamic-linking openssl

我正在尝试编译一个程序prog并将其链接到 OpenSSL 的 1.0.2 测试版,从源代码构建并安装在/usr/local/ssl-1.0.2. 在使用 0.9.8 的旧系统上,这没有太多麻烦。在安装了 1.0.1 的较新系统上,这需要做更多的工作。我想知道为什么。

1) 在 Ubuntu 10.04 上,使用 OpenSSL 0.9.8:

以下是我按照 1.0.2 编译和链接的步骤。

$ ./config shared --openssldir=/usr/local/ssl-1.0.2 && make && make install
$ ldconfig
$ ldconfig -p | grep libcrypto
Run Code Online (Sandbox Code Playgroud)

=> 只显示 0.9.8 文件,所以我添加了 1.0.2 文件的路径...

$ ldconfig /usr/local/ssl-1.0.2/lib
$ ldconfig -p | grep libcrypto
Run Code Online (Sandbox Code Playgroud)

=>

 libcrypto.so.1.0.0 (libc6) => /usr/local/ssl-1.0.2/lib/libcrypto.so.1.0.0
 libcrypto.so.0.9.8 (libc6, hwcap: 0x0008000000008000) => /lib/i686/cmov/libcrypto.so.0.9.8
 libcrypto.so.0.9.8 (libc6, hwcap: 0x0004000000000000) => /lib/i586/libcrypto.so.0.9.8
 libcrypto.so.0.9.8 (libc6, hwcap: 0x0002000000000000) => /lib/i486/libcrypto.so.0.9.8
 libcrypto.so.0.9.8 (libc6) => /lib/libcrypto.so.0.9.8
 libcrypto.so.0.9.8 (libc6) => /usr/lib/libcrypto.so.0.9.8
 libcrypto.so (libc6) => /usr/local/ssl-1.0.2/lib/libcrypto.so
Run Code Online (Sandbox Code Playgroud)

所以我可以编译prog...

$ gcc -o prog ... -L/usr/local/ssl-1.0.2/lib -lcrypto
$ ldd prog
Run Code Online (Sandbox Code Playgroud)

=>

    libcrypto.so.1.0.0 => /usr/local/ssl-1.0.2/lib/libcrypto.so.1.0.0 (0x0083b000)
Run Code Online (Sandbox Code Playgroud)

...并且它与 1.0.2 正确链接。

2) 在 Debian Wheezy 上,使用 OpenSSL 1.0.1:

同样的步骤,不同的结果。

$ ./config shared --openssldir=/usr/local/ssl-1.0.2 && make && make install
$ ldconfig
$ ldconfig -p | grep libcrypto
Run Code Online (Sandbox Code Playgroud)

=>

 libcrypto.so.1.0.0 (libc6, hwcap: 0x0008000000008000) => /usr/lib/i386-linux-gnu/i686/cmov/libcrypto.so.1.0.0
 libcrypto.so.1.0.0 (libc6, hwcap: 0x0004000000000000) => /usr/lib/i386-linux-gnu/i586/libcrypto.so.1.0.0
 libcrypto.so.1.0.0 (libc6) => /usr/lib/i386-linux-gnu/libcrypto.so.1.0.0
Run Code Online (Sandbox Code Playgroud)

同样,我将路径添加到 1.0.2 ...

$ ldconfig /usr/local/ssl-1.0.2/lib
$ ldconfig -p | grep libcrypto
Run Code Online (Sandbox Code Playgroud)

=>

 libcrypto.so.1.0.0 (libc6, hwcap: 0x0008000000008000) => /usr/lib/i386-linux-gnu/i686/cmov/libcrypto.so.1.0.0
 libcrypto.so.1.0.0 (libc6, hwcap: 0x0004000000000000) => /usr/lib/i386-linux-gnu/i586/libcrypto.so.1.0.0
 libcrypto.so.1.0.0 (libc6) => /usr/local/ssl-1.0.2/lib/libcrypto.so.1.0.0
 libcrypto.so.1.0.0 (libc6) => /usr/lib/i386-linux-gnu/libcrypto.so.1.0.0
 libcrypto.so (libc6) => /usr/local/ssl-1.0.2/lib/libcrypto.so
Run Code Online (Sandbox Code Playgroud)

然后我尝试编译...

$ gcc -o prog ... -L/usr/local/ssl-1.0.2/lib -lcrypto
$ ldd prog
Run Code Online (Sandbox Code Playgroud)

=>

    libcrypto.so.1.0.0 => /usr/lib/i386-linux-gnu/i686/cmov/libcrypto.so.1.0.0 (0xb7591000)
Run Code Online (Sandbox Code Playgroud)

但在这里它没有与 1.0.2 相关联。编译时库路径是正确的(用 指定-Lgcc否则会失败,因为 中使用的某些函数prog特定于 1.0.2),但不是运行时的。

3) 如何让它在 Wheezy 上工作

运行或不运行ldconfig /usr/local/ssl-1.0.2/lib

$ gcc -o prog ... -Wl,--rpath=/usr/local/ssl-1.0.2/lib -L/usr/local/ssl-1.0.2/lib -lcrypto
$ ldd prog
Run Code Online (Sandbox Code Playgroud)

=>

    libcrypto.so.1.0.0 => /usr/local/ssl-1.0.2/lib/libcrypto.so.1.0.0 (0xb7592000)
Run Code Online (Sandbox Code Playgroud)

或者,在运行export LD_LIBRARY_PATH=/usr/local/ssl-1.0.2/lib之前运行gcc

我想知道什么

LD_DEBUG=libs ./prog按照 Mr.spuratic 的建议使用,我发现在/etc/ld.so.cache. 我打开那个文件,发现 .so 的查找顺序对应于 .so 的输出ldconfig -p

所以实际的问题是:

  • 为什么 1.0.2 文件在 1) 中位于 ldconfig 列表的顶部,但在 2) 中没有?纯随机?由于 1.0.1 和 1.0.2 文件具有相同的后缀而导致混淆?(“1.0.0”)

或者换一种说法,

  • 为什么在 3) 中添加的标志在 1) 中不是必需的?

mr.*_*tic 5

在针对非默认包编译/链接时,您需要注意三件事:

  • 标题(通常CFLAGS
  • 编译时库路径(通常为LDFLAGS
  • 运行时库路径(rpath via LDFLAGS, LD_RUN_PATH, LD_LIBRARY_PATHor ld.so.conf

你还没有说是什么prog,所以我不知道它的配置有多好(或者它是否使用 autoconf?),我见过很多只可靠地执行前两个步骤。

在链接阶段,库路径顺序是相关的,假设您使用的是 GNU 工具链(gcc 和 binutils),您可能可以通过CFLAGS之前设置configure(或Makefile直接设置)来查看发生了什么:

export CFLAGS="-Wl,-t"
Run Code Online (Sandbox Code Playgroud)

这会将-t跟踪选项传递给链接器。如果您在 make 期间仅获得简洁的“CC”和“LD”行输出,则可能需要将V=1或添加VERBOSE=1到 make 命令。)

在运行时,您可以ld.so通过仔细设置来查看尝试的内容LD_DEBUG,例如

LD_DEBUG=libs ./myprog
Run Code Online (Sandbox Code Playgroud)

(或尝试的值filessymbols更多的细节)

要在构建时正确指定所有三个参数,您应该能够:

  • export CFLAGS="-I/usr/local/ssl-1.0.2/include"
  • export LDFLAGS="-L/usr/local/ssl-1.0.2/lib -R/usr/local/ssl-1.0.2/lib"

然后重新配置/重新编译。

您正在使用--openssldir而不是更传统的--prefix(我推荐后者,并且仅make install_sw在您不需要默认安​​装提供的 1000 个左右的手册页和符号链接时才使用)。这可能是问题的一部分。出于某种原因,您展示的 .so 库已知ld.so没有版本后缀(例如.so.1.0.2),正确的“ make install”应该为您设置(通过link-sharedmain 中的目标Makefile)。

-R选项指示链接器在特定 OpenSSL 库的可执行输出中嵌入 RPATH,这样它就不需要依赖运行时链接器 ( ld.so) 通常会提供的默认值。您可以chrpath改为修改现有的二进制文件。

这或多或少相当于导出LD_LIBRARY_PATH=/usr/local/ssl-1.0.2/lib. 您可以在此处阅读有关 RPATH 和相关 RUNPATH 的更多信息:http : //blog.tremily.us/posts/rpath/

作为最后的手段,您可以在没有“shared”或“noshared”的情况下构建 OpenSSL,这将为您提供不会出现此问题的静态库(但很可能存在其他问题,例如在 ELF .so 中使用,导致 PIC /PIE 问题)


根据更新的细节,我认为问题在于 1.0.1 和 1.0.2beta 都将 .so 版本后缀(SONAME)设置为 1.0.0。在第一个只有 0.9.8 的系统上,这不会引起任何问题;在第二个版本为 1.0.1 和 1.0.2 的版本都为 1.0.0 时,它是基于ld.so.{conf,d}排序的“第一场比赛获胜” 。请记住,ld编译时链接器ld.so与运行时链接器是不同的程序,并且可以有不同的行为(通常会导致符号错误或更糟,如您所见)。

$ cd /usr/local/src/openssl/openssl/1.0.2beta1
$ readelf -a libssl.so | grep SONAME
0x0000000e (SONAME)                     Library soname: [libssl.so.1.0.0]

$ cat verchk.c
int main(int argc, char *argv[]) {
    printf("build: %s\n",OPENSSL_VERSION_TEXT);
    printf("run  : %s\n",SSLeay_version(SSLEAY_VERSION));
    return 0;
}

$ gcc -Wall  -I/usr/local/src/openssl/openssl-1.0.2-beta1/include \
    -Wl,-rpath /usr/local/src/openssl/openssl-1.0.2-beta1/ \
    -o verchk /usr/local/src/openssl/openssl-1.0.2-beta1/libcrypto.so verchk.c

$ ./verchk
build: OpenSSL 1.0.2-beta1 24 Feb 2014
run  : OpenSSL 1.0.2-beta1 24 Feb 2014

$ grep SHLIB_M...R= Makefile
SHLIB_MAJOR=1
SHLIB_MINOR=0.0
Run Code Online (Sandbox Code Playgroud)

更新 OpenSSL-1.1 进行了一些 API 级别的更改,上面的代码将无法使用 v1.1 头文件和旧库 ( undefined reference to `OpenSSL_version')进行编译。

SSLeay_version()现在已弃用,并且(取决于OPENSSL_API_COMPAT)可能会被#define-d 到适当的 API 函数OpenSSL_version()