有没有一种开源的方法可以从没有源代码可用的动态可执行文件中生成静态文件?

让我用一个例子来解释这个问题。我在日常工作中使用一些旧的程序,比如xfigpdfedit
现在,这些程序相当古老,更新频率也不高;我担心总有一天它们会因为缺少某个库或者某个不兼容的更新而无法正常工作。
如果这个程序在当前系统上很容易编译,解决方案就很简单:稍微修改一下源代码,静态编译它——生成的可执行文件会很大,效率也不高,但是可以在可预见的未来内正常工作(1)。这似乎适用于xfig,我会尽快尝试一下。
例如,pdfedit 依赖于Qt3,在此时设置编译系统相当复杂。幸运的是它现在可以运行,因为它需要的库与任何其他库不冲突。但是这可能会在未来发生变化,所以我想解决这个问题: 如果我在Ubuntu上有一个动态二进制文件(或类似的文件),并且拥有所有库但没有源代码,我该如何制作静态二进制文件? 我搜索了一下。Statifier(2) 是一种可能性,但它有很多地址随机化方面的问题,所以不可取。非免费版本Ermine似乎可以工作,但我真的更喜欢开源选项。

另一个可能性是使用Docker或类似的打包系统。但是,我找到的教程都非常面向Red Hat;而且,老实说,跟随起来相当复杂。


脚注:
(1) 并不是那么疯狂。我举一个例子,使用静态的ffmpeg,运行良好且没有任何兼容性问题...
(2) 要编译statifier,请参考https://stackoverflow.com/questions/23498237/compile-program-for-32bit-on-64bit-linux-os-causes-fatal-error
4个回答

您可以用另一种更简单的方式解决问题: 在可执行文件上使用"ldd"命令查看链接库,例如:
$ ldd /bin/bash
linux-vdso.so.1 =>  (0x00007fffb2fd4000)
libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fac9ef91000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fac9ed8d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fac9e9c6000)
/lib64/ld-linux-x86-64.so.2 (0x00007fac9f1e1000)

然后将所有库文件收集到一个文件夹中,并在运行程序之前设置LD_LIBRARY_PATH环境变量,使其指向该文件夹:
$ LD_LIBRARY_PATH="/opt/my_program/lib" /opt/my_program/start

或者您可以将lib文件夹添加到/etc/ld.so.conf.d/中。

但这将对整个系统产生影响。


1这是一个好主意,虽然我真的很想找到一种将所有内容打包成可执行文件的方法;但这个解决方案可能会受到加载器变化的影响(尽管我希望没有人以不向后兼容的方式做出这样的改变)。如果没有更好的解决方案出现,我会授予奖励的,谢谢。 - Rmano
你可以将这个添加到一个小的安装脚本中,并将其链接到本地路径。我喜欢这个解决方案,以前可能用得上它。 - WalyKu
1@Klaus,linux-vdso.so.1 看不到任何地方,我猜它应该在内核中,对吗? - Rmano
1是的。从man 7 vdso中可以得知:““vDSO”(虚拟动态共享对象)是一个小型的共享库,内核会自动映射到所有用户空间应用程序的地址空间中。” - Klaus D.
虽然这并不是严格意义上的答案,但它是一个合理的解决方法。谢谢。 - Rmano
在OSX上,使用otool -L而不是ldd。顺便说一句。 - keen

我根据Klaus D的想法写了packelf.sh

你可以这样使用它:

~ # packelf.sh `which perf` /root/perf
~ # /root/perf --version
perf version 3.10.0-1160.49.1.el7.x86_64.debug

packelf.sh:

#!/bin/bash
set -eo pipefail

[ $# -lt 2 ] && {
    echo "usage: $0 <ELF_SRC_PATH> <DST_PATH> [ADDITIONAL_LIBS ...]"
    exit 1
}

src="$1"
dst="$2"
shift
shift

cat >"$dst" <<EOF
#!/bin/bash
tmp_dir="\$(mktemp -d)"
check_path="\$tmp_dir/__check_permission__"
trap 'rm -rf \$tmp_dir' 0 1 2 3 6
if ! (touch "\$check_path" && chmod +x "\$check_path" && [ -x "\$check_path" ]); then
    rm -rf "\$tmp_dir"
    tmp_dir="\$(TMPDIR="\$(pwd)" mktemp -d)"
fi
sed '1,/^#__END__$/d' "\$0" | tar -xz -C "\$tmp_dir"
"\$tmp_dir/LD_SO" --library-path "\$tmp_dir" "\$tmp_dir/PROGRAM" "\$@"
exit \$?
#__END__
EOF

libs="$(ldd "$src" | grep -F '/' | sed -E 's|[^/]*/([^ ]+).*?|/\1|')"
ld_so="$(echo "$libs" | grep -F '/ld-linux-')"
sed -E -i -e 's|PROGRAM|'"$(basename "$src")"'|' -e 's|LD_SO|'"$(basename "$ld_so")"'|' "$dst"
tar -czh --transform 's/.*\///g' "$src" $libs "$@" >>"$dst" 2> >(grep -v 'Removing leading' >&2)
chmod +x "$dst"

这个脚本会创建一个bash脚本,并附加一个tgz文件。它会执行以下操作:1. 将shell和tar文件解压到tmp目录;2. 运行tmp文件。(每次执行时都会执行这些操作) - undefined

开源工具mkblob可以创建一个新的二进制可执行文件,其中包含您的程序所需的所有依赖项,并且您可以将其分发给其他(甚至是以后的)发行版,而不仅仅是编译时所使用的那个。它的工作原理有点像之前提到的Statifier和Ermine。

嗨!看起来不错,但我在Ubuntu上无法使用它。你有什么提示吗? - Rmano
在 https://github.com/sigurd-dev/mkblob/tree/master/binary_X86_64 中有一个预编译的二进制和deb/rpm包。同时,似乎我可以使用 makeall_el8.sh 在 Ubuntu 18.04 中进行编译。 - JoeMittile
谢谢 - 是的,我安装了.deb文件,但它似乎不能与我检查过的一些二进制文件一起正常工作...我的意思是,这个二进制文件可以运行,但无法成功创建一些程序的静态二进制文件。好吧,是时候问另一个问题了!不管怎样,还是谢谢。 - Rmano

关于 statifier 的一个建议:
如果地址空间布局随机化(ASLR)导致其失败,你并不需要关闭整个机器的 ASLR。你可以将其仅针对该进程关闭:
$ setarch `uname -m` -R statified_pdfedit [args...]

它将以禁用随机布局的方式运行该命令(无需root权限)。

哇,有趣。现在如果我能编译statifier就好了... - Rmano
已编译并检查。xfig_statified 仍然核心转储...遗憾。无论如何,还是谢谢。 - Rmano
是的,真可惜。我在想这是否可能是一个64位的问题,或许可以尝试在32位的环境下运行statifier? - lemonsqueeze
在32位机器上进行了检查,仍然出现核心转储。 - Rmano