cms*_*cms 5 linker common-lisp shared-libraries dlopen quicklisp
我有一个简单的 common lisp 服务器程序,它使用 osicat 库与 posix 文件系统交互。我需要这样做,因为系统创建到文件的符号链接,并使用 POSIX stat 元数据,而这些事情在可移植 Lisp 中都不是很容易做到的。
我正在使用 Quicklisp 管理依赖项,并且我将所有这些都固定到一个工作发行版上。该应用程序可在 CCL 和 SBCL 之间移植,我倾向于在第一个中构建它并使用后者进行部署。我使用 asdf 声明应用程序的依赖项defsystem,并且可以使用 Quicklisp 加载它,以便从本地项目轻松进行开发。
对于部署,我只是使用一些 ansible 剧本,在远程复制开发人员环境(例如设置 Quicklisp、将代码推送到本地项目、运行用户主目录),这很 hacky,但基本上没问题。最近,随着它变得更加稳定sb-ext:save-lisp-and-die,我一直在使用简单的编译脚本来编译它。这意味着我得到了一个可执行文件,我可以像服务器一样运行它,并使用服务管理脚本和匿名用户帐户。
这一直工作得很好,所以我最近将这一步移到了下一个级别,并且我正在使用我的编译脚本构建 .deb 包,因此我可以将所有内容捆绑到可重定位的二进制文件中。这也有点工作,但生成的二进制文件不能从原始构建主机重新定位。他们拒绝启动,并且似乎尝试为 osicat 动态加载共享库
Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
Mar 15 12:47:14 annie [479]: {10005C05B3}>:
Mar 15 12:47:14 annie [479]: Error opening shared object "libosicat.so":
Mar 15 12:47:14 annie [479]: libosicat.so: cannot open shared object file: No such file or directory.
Run Code Online (Sandbox Code Playgroud)
看起来图像希望在原始构建树的 Quicklisp 档案中找到它
(ERROR "Error opening ~:[runtime~;shared object ~:*~S~]:~% ~A." "/home/builder/buil...quicklisp/dists/quicklisp/software/osicat-20180228-git/posix/libosicat.so
(SB-SYS:DLOPEN-OR-LOSE #S(SB-ALIEN::SHARED-OBJECT :PATHNAME #P"
Run Code Online (Sandbox Code Playgroud)
因此,研究源代码时,我意识到当quicklisp获取osicat并执行其构建操作时,它会编译此DLL以包装其与系统库的接口,而不是直接直接连接到它们 - 可能是因为它使用cffi groveller,我不这样做(还)不太了解 cffi。这很好,但不是使用系统链接器链接到 .so,而是尝试dlopen从固定路径链接到 .so,这不太可移植,并且有点破坏了save-image
在这一点上我有点困惑,但在我进一步深入 QL 和 cffi 构建之前,我想知道是否缺少一些构建或编译配置,这会使其以更“静态”的方式引导或影响包装库的制作。理想情况下,我只想要一个可以包装在安装程序中的 blob,并将其链接到系统库,但如果我必须部署一些额外的工件,那可能没问题。我不知道如何使自动生成的共享对象发生在更受控制的路径上。
不过,到那时,我不妨为我的 posix 调用编写一个 .so,并将其与应用程序一起分发,并尝试更直接地对其进行 FFI。这会有点痛苦,所以我宁愿不这样做。
你是对的,当转储的映像启动时,它正在尝试重新加载共享库。正如您所经历的,如果映像未在转储的计算机上启动,则该映像将不起作用。
这几乎就是static-program-op想要解决的问题。像这样的简单系统定义应该可以帮助您编译静态程序:
(defsystem "foo"
:defsystem-depends-on ("cffi-grovel")
:build-operation "static-program-op" ; "asdf" package is implied
:build-pathname "foo" ; path of the generated binary
:entry-point "foo:main" ; function to use as the entry point
;; ... everything else ...
)
Run Code Online (Sandbox Code Playgroud)
如果您的系统依赖于 grovel 文件(由:cffi-wrapper-file、:c-file或定义:o-file),例如 osicat 提供的文件,那么它将静态地将这些文件链接到您转储的映像。
然而,它并不完美。
本质上,还是存在一些问题。有些是由 CFFI 本身在上游修复的(例如,不重新加载静态嵌入库的共享库),有些则有点困难。(例如,SBCL 默认编译选项不允许您static-program-op默认使用。这已在 SBCL 的 Debian 版本中得到修复,但其他发行版的响应速度较差。)
这显然是整个社区都遇到的问题,有几个库可以提供帮助:
static-program-op,但需要您构建自定义 SBCL。.deb然而,它会生成像 和一样的分发包.rpm,以便能够指定系统共享库的依赖项(例如,如果您依赖于 sqlite,它会找出哪个包提供它并将其添加为 中的依赖项.deb)。我强烈建议您查看.gitlab-ci.yml示例。我建议您阅读这两个库的网页来做出选择,它们都有各自的优点和缺点。<joke>显然,linux 封装更胜一筹。</joke>