- 固件
- 操作系统
【免费下载链接】CloverBootloader
Bootloader for macOS, Windows and Linux in UEFI and in legacy mode
导读
OpenSSL 的 UNIX 构建系统(Configure+ Makefile)有一套独特且容易踩坑的链接机制:链接工作始终经由 C 编译器命令行完成,因此所有链接选项都必须写成编译器能接受的形态;同时,当把 OpenSSL 安装到非系统默认目录时,动态链接的程序会因找不到共享库而无法运行,必须通过 RPATH 等运行时搜索路径机制来解决。本文以 OpenSSL 官方文档 NOTES-UNIX.md 为骨架,结合本仓库(CloverBootloader,一个在 UEFI 与传统模式下引导 macOS、Windows 和 Linux 的开源 Bootloader)中携带的 OpenSSL 3.0.9 源码树及其配置脚本,系统讲解编译器链接、各平台 RPATH 选项、ELF 的DT_RPATH/DT_RUNPATH查找顺序,以及第三方应用链接与静态库混用等实战问题,读完即可独立完成 OpenSSL 的 UNIX 定制构建与排错。
一、仓库中的 OpenSSL:UEFI 静态构建 vs UNIX 动态构建
本仓库将 OpenSSL 作为密码学实现引入,用于 UEFI 固件环境。与本文档讨论的 UNIX 动态链接场景不同,UEFI 没有共享库的概念,因此 Library/OpensslLib/configure.py 在调用 OpenSSL 的Configure时显式传入了大量裁剪参数,其中最关键的是:
perl Configure \ --config=../UefiAsm.conf \ --api=1.1.1 \ --with-rand-seed=none \ ... no-shared \ # 强制静态构建,回避动态链接问题 no-dso \ no-module \ ...而 Library/OpensslLib/openssl/VERSION.dat 表明本仓库携带的是 OpenSSL3.0.9(2023-05-30 发布,SHLIB_VERSION=3)。生成的头文件与汇编文件被放置到 Library/OpensslLib/OpensslGen 下,并通过 Library/OpensslLib/OpensslLib.inf 以 EDK2 库的形式参与固件构建。
理解这一点很重要:NOTES-UNIX.md 面向的是在 Linux、BSD、Solaris、AIX、HP-UX 等传统 UNIX 类系统上构建与部署 OpenSSL 的场景,此时动态共享库才是默认形态,运行时搜索路径(RPATH)问题也随之而来。本文档即专门解答这类问题。
二、OpenSSL 使用编译器来链接:所有链接选项必须经编译器转交
OpenSSL 生成的 Makefile不是用ld直接链接,而是用 C 编译器命令行来完成程序、共享库以及可动态加载共享对象的链接。这一点在 NOTES-UNIX.md 中开宗明义。
其直接后果是:任何传给配置脚本的链接选项,都必须写成编译器能够接受的形态。各系统的差异在于:
- 有些系统的编译器直接接受链接器标志(如
-rpath); - 更多编译器要求用
-Wl,前缀把选项转交给背后的链接器。
你需要阅读自己编译器的文档了解可接受的写法,并查阅ld(1)手册了解可用的链接器选项。
⚠️ 注意:
-Wl中的l是小写字母 L(不是数字 1)。形如-Wl,xxx的选项会被 OpenSSL 配置脚本统一识别为链接器选项并透传给生成的 Makefile。
这一行为在源码中有直接证据。查看 Library/OpensslLib/openssl/Configure 的参数解析逻辑:
elsif (/^-L(.*)$/ or /^-Wl,/) { push @{$useradd{LDFLAGS}}, $_; } elsif (/^-rpath$/ or /^-R$/) # -rpath is the OSF1 rpath flag # -R is the old Solaris rpath flag { my $rpath = shift(@argvcopy) || ""; $rpath .= " " if $rpath ne ""; push @{$useradd{LDFLAGS}}, $_, $rpath; }可见Configure会:把所有以-Wl,开头的参数与-L参数收入LDFLAGS;并把-rpath(OSF1/Tru64 风格)和-R(旧 Solaris 风格)连同其取值一并加入LDFLAGS。这也印证了文档所述“配置脚本能识别所有这些选项”。
三、非默认安装位置:共享库找不到,程序就无法运行
每个 UNIX 系统都有自己默认的共享库目录集合,例如/lib、/usr/lib,或可能的/usr/local/lib。如果把共享库装到非默认位置,动态链接的二进制文件在运行时将找不到它们而直接启动失败,除非通过运行时共享库搜索路径(runtime shared library search path)给予帮助。
3.1 OpenSSL 应用默认不会替你设置运行路径
对于openssl命令行程序,OpenSSL 的配置脚本通常不会自动设置运行时共享库搜索路径。因此,除非你确认库会安装到默认搜索目录列表之内,否则强烈建议在配置阶段显式指定运行路径。
3.2 各平台设置运行时搜索路径的选项对照
文档给出的各平台可用选项如下(不同系统、不同版本写法各异,RPATH 是最常见的机制;注意 ELF 系统其实有两个运行时搜索路径标签,下文第四节详述):
| 平台/系统 | 运行时搜索路径选项 |
|---|---|
| Linux、*BSD 等 | -Wl,-rpath,/whatever/path |
| Solaris | -R /whatever/path |
| AIX | -Wl,-R,/whatever/path(内部会额外传入-bsvr4) |
| HP-UX | -Wl,+b,/whatever/path |
| Tru64、IRIX | -rpath /whatever/path |
OpenSSL 的配置脚本能够识别以上全部选项并传递到它所生成的 Makefile(事实上,所有以-Wl,开头的参数都会被识别为链接器选项)。
3.3 不要硬编码路径:使用 LIBRPATH 便利变量
文档特别提醒:不要在运行时共享库搜索路径中使用硬编码目录。因为部分 OpenSSL 配置目标(config target)会为 multilib 安装额外增加一级目录(例如lib64、libx32)。为帮助用户应对这种差异,生成的 Makefile 提供了一个便利变量LIBRPATH,其定义可在 Library/OpensslLib/openssl/Configurations/unix-Makefile.tmpl 中找到:
# Convenience variable for those who want to set the rpath in shared # libraries and applications LIBRPATH=$(libdir)也就是说LIBRPATH就是解析后的libdir(在$(INSTALLTOP)/$(LIBDIR)与 multilib 后缀解析完毕之后的值),因此应把它与运行时搜索路径选项组合使用,例如:
$ ./Configure --prefix=/usr/local/ssl --openssldir=/usr/local/ssl \ '-Wl,-rpath,$(LIBRPATH)'这样即使构建目标带有 multilib 后缀(默认libdir变为lib<multilib-postfix>,可参考 INSTALL.md 中--libdir的说明),RPATH 也会跟随实际的libdir而变化,不会写死出错。
四、ELF 系统的两个运行时搜索路径标签:DT_RPATH 与 DT_RUNPATH
现代基于 ELF 的系统中有两个运行时搜索路径标签需要区分:DT_RPATH和DT_RUNPATH。共享对象按以下顺序被搜索:
DT_RPATH中指定的目录——前提是DT_RUNPATH未被设置;- 环境变量
LD_LIBRARY_PATH中的目录; DT_RUNPATH中指定的目录;- 系统的共享对象缓存(如
/etc/ld.so.cache)与默认目录。
这个顺序带来的一个重要推论是:当库能在DT_RPATH(且未设置DT_RUNPATH)给出的路径中找到时,LD_LIBRARY_PATH的值将不起任何作用——因为DT_RPATH的优先级高于环境变量。这往往是调试“明明设了LD_LIBRARY_PATH却仍加载了错误版本库”的根源。
4.1 默认设置哪个标签取决于系统
文档指出,默认设置DT_RPATH还是DT_RUNPATH因系统而异:
- Solaris 上
DT_RPATH据文档已被弃用,转而推荐DT_RUNPATH; - Debian GNU/Linux 上两者皆可设置,且写作文档时默认是
DT_RPATH。
具体应选择哪个标签,请查阅你系统上的ld(1)手册。
4.2 强制使用 DT_RUNPATH:--enable-new-dtags
如果希望 Debian GNU/Linux 等系统设置DT_RUNPATH而非DT_RPATH,可以指示链接器开启新的 dtags,例如:
$ ./Configure --prefix=/usr/local/ssl --openssldir=/usr/local/ssl \ '-Wl,--enable-new-dtags,-rpath,$(LIBRPATH)'4.3 可执行文件相对搜索:$ORIGIN
文档还提醒:多数 ELF 系统支持相对于“当前可执行文件所在目录”的运行时搜索路径,其实现方式是解释$ORIGIN(以及其他一些内部变量)。这允许你把共享库放在可执行文件旁边实现“可移植的”动态部署。具体支持情况请查阅系统文档。
五、链接第三方应用程序:与 OpenSSL 动态库对接
与 OpenSSL 动态链接(或任何其他共享库)的第三方应用,在“库安装于非默认位置”时面临完全相同的问题——应用的可执行文件里也必须有对应的运行时搜索路径。
文档指出,上面介绍的 OpenSSL 配置选项对目标应用的链接“可能有影响也可能没有”:
“可能有影响”的情形:某些环境下,用自然的链接方式就足够,例如:
cc -o myapp myapp.c -L/whatever/path -lssl -lcrypto另一些情形下,你必须在链接自己的应用时显式指定运行时搜索路径,例如将
-Wl,-rpath,/whatever/path加入链接命令行。
判断哪种写法适用,请查阅系统文档,并以上文第三节的各平台选项表为参考。
六、共享构建中的静态库:链接器优先找共享库,AIX 需加 _a 后缀
共享版 OpenSSL 构建(shared build)同样会安装静态库。链接这些静态库需要特别小心,因为:
- 链接器通常优先寻找共享库,对同时存在的静态 OpenSSL 库“视而不见”(倾向于选择
.so而忽略.a); - 需要借助系统文档中的方法来强制使用静态库(例如显式指定
.a文件路径,或使用-static等标志)。
此外文档点名了一个角落情况:AIX 在共享构建下,静态库的命名不同——需要附加_a后缀才能链接它们,例如用-lcrypto_a而非-lcrypto。
顺带一提,在 UEFI 场景下本仓库完全没有这一层困扰:Library/OpensslLib/configure.py 的配置参数中包含了no-shared,所有算法均以静态库形态直接编入固件,这也从反面印证了共享/静态两种形态的差异所在。
七、完整实战示例:一次规范的 UNIX 定制构建
综合本文全部要点,一个把 OpenSSL 安装到/usr/local/ssl并确保程序自带运行路径的典型配置流程如下:
# 1. 列出本机可用的构建目标(linux-elf / linux-x86_64 等) $ ./Configure LIST # 2. 指定平台与安装目录,并用 LIBRPATH 设置 DT_RUNPATH $ ./Configure linux-x86_64 \ --prefix=/usr/local/ssl \ --openssldir=/usr/local/ssl \ '-Wl,--enable-new-dtags,-rpath,$(LIBRPATH)' # 3. 编译并安装 $ make -j$(nproc) $ make install # 4. 验证运行时确实能找到共享库 $ /usr/local/ssl/bin/openssl version -a其中:
--prefix=/usr/local/ssl指定安装目录树的顶层(UNIX 默认是/usr/local,参见 INSTALL.md);--openssldir=/usr/local/ssl指定 OpenSSL 配置文件与默认证书/密钥库目录(Unix 默认/usr/local/ssl,参见 INSTALL.md);- 若某个构建目标带有 multilib 后缀导致默认
libdir变成lib64等,可用--libdir=lib覆盖,或直接用$(LIBRPATH)让 RPATH 跟随实际目录。
如果你是为本仓库的 UEFI 固件重新生成 OpenSSL 源文件,则应参照 Library/OpensslLib/configure.py 的openssl_configure()方式:在openssl/目录内运行perl Configure并传入--config=../UefiAsm.conf、--api=1.1.1、no-shared以及大量no-*裁剪项,随后用make生成文件并由generate_files()复制到 Library/OpensslLib/OpensslGen 目录(详见generate_include_files()、generate_library_files()与copy_generated_file()的实现)。
八、常见排错速查
| 症状 | 可能原因 | 对策 |
|---|---|---|
openssl程序报error while loading shared libraries: libssl.so.3 | 库装到非默认目录,且未设置 RPATH | 配置时加入'-Wl,-rpath,$(LIBRPATH)' |
设置了LD_LIBRARY_PATH仍加载到旧版本库 | 目标库由DT_RPATH路径先命中(DT_RPATH优先于环境变量) | 用--enable-new-dtags改用DT_RUNPATH,或清理 RPATH |
-Wl,-rpath被当作普通编译选项处理 | 参数形态错误 | 确认小写l(非数字 1),且整段用引号包裹 |
| multilib 系统上 RPATH 指向错误目录 | 硬编码了/lib而非实际libdir | 改用$(LIBRPATH) |
AIX 上链接-lcrypto得到共享库而非静态库 | 静态库命名带_a后缀 | 使用-lcrypto_a |
以上表格内容均由 NOTES-UNIX.md 的说明与 Configure 的源码逻辑推导整理,可放心作为 UNIX 平台上 OpenSSL 构建与链接的排错参考。
- 固件
- 操作系统
【免费下载链接】CloverBootloader
Bootloader for macOS, Windows and Linux in UEFI and in legacy mode
相关推荐
OpenSSL 类 Unix 平台构建指南:链接选项透传、共享库运行时搜索路径与 RPATH 配置
OpenSSL 类 Unix 平台构建指南:链接选项透传、共享库运行时搜索路径与 RPATH 配置 本指南基于 OpenSSL 仓库根目录下的 NOTES UN
密码学网络安全通信CPython 在 Unix 平台上的安装、编译与自定义 OpenSSL 构建指南
CPython 在 Unix 平台上的安装、编译与自定义 OpenSSL 构建指南 本篇技术指南围绕当前 CPython 源码仓库( README.rst ht
编程语言语言运行时解释器标准库SE-0342 提案解读:SwiftPM 在受支持平台上默认静态链接 Swift 运行时库
SE 0342 提案解读:SwiftPM 在受支持平台上默认静态链接 Swift 运行时库 本篇技术指南以 swift evolution 仓库中的 SE 03
文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考