如何转储使用 osicat 的可执行 SBCL 映像

How to dump an executable SBCL image that uses osicat

我有一个简单的通用 lisp 服务器程序,它使用 osicat 库与 posix 文件系统交互。我需要这样做,因为系统会为文件创建符号 links,并使用 POSIX 统计元数据,而这些事情在可移植的 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.

看起来图像希望在原始构建树的 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"

所以仔细研究源代码,我意识到当 quicklisp 获取 osicat 并执行其构建操作时,它编译这个 DLL 以包装它与系统库的接口,而不是直接 ffi 到它们 - 可能是因为它正在使用 cffi groveller,我真的不太了解 cffi(还)。这很好,但是不是 linking 到 .so 使用系统 linker 它试图从固定路径 dlopen 它,这不是很便携,有点中断save-image

的用处

此时我有点困惑,但在我进一步深入研究 QL 和 cffi 构建之前,我想知道是否缺少一些构建或编译配置可以使它 bootstrap 以更 'static' 的方式或影响包装库的生产。理想情况下,我只想要一个我可以包装在安装程序中的 blob,并且 link 它针对系统库,但如果我必须部署一些额外的人工制品,那可能没问题。我不知道如何使自动生成的共享对象出现在更可控的路径上。

尽管如此,我也可以为我的 posix 调用编写一个 .so 并将其与应用程序一起分发,并尝试更直接地对其进行 FFI。那会有点痛苦,所以我宁愿不这样做。

也许您可以在 SBCL 上使用 sb-posix:symlinksb-posix:fstat,通过功能切换移除 osicat 依赖。

你是对的,当转储图像启动时,它正在尝试重新加载共享库。如您所见,如果映像未在其转储到的计算机上启动,则该映像无法正常工作。

这几乎就是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 ...
  )

如果您的系统依赖于 grovel 文件(由 :cffi-wrapper-file:c-file:o-file 定义),例如 osicat 提供的文件,那么它将静态 link 那些到你转储的图像。

然而,它并不完美。

本质上还是有一些问题。有些是由 CFFI 本身在上游修复的(例如,不重新加载静态嵌入式库的共享库),有些则更难一些。 (例如,SBCL 默认编译选项不允许您默认使用 static-program-op。这已在 SBCL 的 Debian 版本中修复,但其他发行版的响应速度较慢。)

这显然是整个社区遇到的一个问题,有几个图书馆可以提供帮助:

  • 第一个已经存在了一段时间的是 Deploy。它采用的方法是将转储的图像和库嵌入到存档中,并重新排列二进制文件的内容,以便从提取到的任何地方加载它们。
  • 第二个,我比较偏向,因为我做了,例如linux-packaging. It takes the approach of fixing static-program-op by extending it, but requires you to build a custom SBCL. However, it generates distribution packages like .deb and .rpm, in order to be able to specify dependencies for system shared libraries (e.g. if you depend on sqlite, it will figure out which package provides it and add it as a dependency in the .deb). I highly recommend looking at the .gitlab-ci.yml

我建议阅读这两个库的网页来做出选择,它们各有优缺点。 <joke>显然,linux-packaging更胜一筹。</joke>