神乎其技!
这才是真正的大佬,我虽然没有完全弄懂操作方法,解决思路大体还是看清楚了,有时间自己动手试试。
再看一遍还是感叹,魔法
好文!ABL非常好用!
看了个寂寞
好用心
搬个椅子,好好学习一下。。。
喜欢看这种干货
精品,拜读一下,看能不能和当前的distrobox的互补下,感谢大佬
大神讲的明明白白。
ablerun=bwrap三件套封装(ld/libc6/stdc++),解决dist-time滞后版本的兼容性最大问题。
ablrun只需要定时维护(URL,pseudo-env)这些对deb/rpm run-time二次封包脚本。
精品,拜读一下,看能不能和当前的distrobox的互补下,感谢大佬
abl主打轻量,主要是为了无损性能运行单纯是libc版本问题的软件,多为appimage,优势是性能几乎无任何折损,劣势是只能解决libc版本问题
distrobox主打大而全,主要是为了能提供一个完整的运行环境,能够运行大多数的应用,解决大部分的兼容问题,劣势是部署复杂而且启动容器比较缓慢
ACE 兼容环境是二者的结合,利用轻量的bwrap容器提供了一个完整的运行环境,优势是如同abl一样的几乎无任何折损的性能,秒速启动,同时也有distrobox的大而全的运行环境,劣势是缺乏完整的权限管理体系,容器内无法进行提权操作且无独立daemon(但这也算是一种优势?不需要常驻后台,打开应用才开启,关闭之后就退出,干干净净)
abl主打轻量,主要是为了无损性能运行单纯是libc版本问题的软件,多为appimage,优势是性能几乎无任何折损,劣势是只能解决libc版本问题
distrobox主打大而全,主要是为了能提供一个完整的运行环境,能够运行大多数的应用,解决大部分的兼容问题,劣势是部署复杂而且启动容器比较缓慢
ACE 兼容环境是二者的结合,利用轻量的bwrap容器提供了一个完整的运行环境,优势是如同abl一样的几乎无任何折损的性能,秒速启动,同时也有distrobox的大而全的运行环境,劣势是缺乏完整的权限管理体系,容器内无法进行提权操作且无独立daemon(但这也算是一种优势?不需要常驻后台,打开应用才开启,关闭之后就退出,干干净净)
了解,之前关注过ace和bwrap技术,当时没时间,结合abl和bwrap还是很牛,感谢,有空看看
Popular Events
More
glibc兼容问题一直是困扰GNU/Linux爱好者的一个难点,如果在旧版的系统使用自己安装的较新的应用,就很容易遇到应用无法运行,控制台提示类似的内容:
/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.xx' not found (required by /path/to/xxx)
实际原因是开发者构建应用时使用的glibc版本高于当前用户运行应用时使用的glibc版本所引发的。如果是反过来,开发者使用旧版系统构建应用,用户在新版系统上运行的话,就不会有问题,因为glibc设计中有对旧版本的兼容能力。遇到这种情况,除了自己更换操作系统外,最妥善的解决办法应该是让开发者(或者有自己构建应用能力的用户)在旧版系统上重新构建软件并打包,但是这相当浪费时间和资源,也要看人家的心情。于是有很多用户决定自己动手升级新版的glibc,结果弄得系统崩溃。这是因为glibc在GNU/Linux系统中是如此重要,几乎所有应用都需要依靠glibc来发挥功能,一旦不慎破坏了glibc,那么系统中所有程序都将全部失灵。尽管这种操作如此危险,依然有不少用户为了体验新版程序冒险升级。glibc兼容问题变成一种梦魇,很多人认为冒险升级glibc是用上新版应用的唯一方法,没有其他路可以走。
为了破除包括glibc问题在内的一些谣言,也想让一些出问题的应用程序在我的电脑上运行起来,我曾经花了一段时间研究各种动态库问题,最终发现其实GNU/Linux的动态库使用也十分灵活,glibc问题也有不需要修改系统的解决方法(原帖:https://bbs.deepin.org/post/256081)。其实可能这些内容本来就是GNU/Linux开发者的基础知识,但是由于用户学习使用操作系统的方式从来都是由表及里的,很难接触到这些底层原理,才诞生了这些偏见。
当然在那篇文章中提到的关于glibc问题的解决方法仍然有些复杂,之后我又借助轻量级容器应用bubblewrap设计了一个简单程序来方便用户和打包者使用,就是附加基础库Additional Base Lib(https://gitee.com/spark-store-project/additional-base-lib)。本来是纯粹当作给我提出的解决方案做试验了,但是真的解决了许多用户的困难,因此随后又花了一段时间改善功能。但是由于一些原因我没办法继续更新这个小工具,所以来写一篇文章来尽可能详细地解释这个问题,以免后来者再误入歧途。
什么是库?
随着应用程序的设计越来越复杂,要方便管理,要逻辑清晰,要满足多个开发者合作的需要,要后续添加和改变功能容易,想开发一个复杂的应用,就得拆成几部分来完成。不考虑脚本或者其他类型的程序,只看最常见的二进制程序的话,包含入口的程序,就是当用户想运行应用时所启动的程序,我们称为可执行文件(Executable)。另一些程序并非由用户去主动运行,而是由其他程序来利用其中包含的功能,我们称为库文件(Library)。程序想使用库中包含的功能,就要进行链接(Link),而链接又分为静态链接和动态链接,静态链接库是在应用程序构建的时候完成链接的,库也融合进可执行文件里了,所以用户平常是见不到的。而动态链接库(Dymanic Link Library/DLL),是在运行程序后才进行链接的,动态库也是独立于可执行程序之外的文件,因此用户最常见的库就是这种。
在类Unix系统中,二进制程序的格式被称为elf(Excutable and Linkable Format),可执行文件通常被存放在名为bin这样的目录,不使用扩展名,而动态库被存放在lib这样的目录,动态库通常命名前面加上lib,后面扩展名为.so(Shared Object)后面可以再加一个点数字表示api版本。
使用库的好处:
不过另一方面,主流的GNU/Linux的设计大量依靠动态库,尽管减少了存储和网络成本,但是也让系统和应用环环相扣,有一处出现问题会造成多个应用同时出现故障,这是比较违反用户直觉的。现代一些新设计的打包技术则是尽量规避系统与应用之间、应用与应用之间的耦合来减少问题,不过那就不是本文的重点了。
对于常见的动态库问题,缺库在系统软件源中安装对应的软件包即可,如果遇到与系统动态库不兼容的问题,只需要在其他系统中下载对应的库放到一个地方,然后用LD_LIBRARY_PATH环境变量指定动态库搜索路径,让程序优先加载指定的库,具体参见之前的文章。
想了解库加载顺序的话,可以使用
man ld.so
阅读手册,或者直接上网搜索相关资料。glibc问题的特殊之处
那么glibc不兼容问题能套用上述方法,通过搬运一个新版本的libc.so.6和其他glibc动态库解决吗?答案是否定的。其根本原因在于动态库链接器ld.so和libc.so.6基本是耦合的,如果用非原装的libc.so.6,ld.so就无法工作,那么任何程序都不能运行。
那么你应该会想,只需要再搬运一个对应的新版ld.so就能解决问题了吧。结果是,真就能解决问题,就是这么容易。
那么ld.so到底是何方神圣呢?实际上这个名字只是一个简称,在不同架构上,他的名称和路径不同。x86-64架构上,他是
/lib64/ld-linux-x86-64.so.2
。ld.so本身也是glibc中的一个组件,他被称为动态库链接器,也可以叫elf文件的解释器(interpreter),他表面上的功能是为程序加载动态库,但实际上他的工作模式是类似bash运行shell脚本、python运行python脚本一样,elf文件是由ld.so运行的。因此运行任何可执行程序都会先运行ld.so,其重要性不言而喻。就如同我们会在shell脚本的第一行写上
#!/bin/bash
来提示系统选择哪个命令作为解释器运行,elf文件内也有一个.interp
字段指示所选择的ld.so。不过用平常的方法不太容易读取和修改,用patchelf工具会更加方便。如此,我们便得到了解决glibc兼容问题的两个途径。第一种是采用patchelf工具修改elf文件内部的解释器字段使其指向自己搬来的ld.so,然后运行程序时再用LD_LIBRARY_PATH提供配对的libc.so.6和其他需要的动态库。
第二种则是用解释器的原始定义,直接调用自己搬运的ld.so运行程序,即:
/路径/ld-linux-** <要运行的命令>...
,同时使用LD_LIBRARY_PATH提供配对的libc.so.6和其他需要的动态库。ABL的工作
以上方法能解决glibc兼容问题,但是也比较麻烦,面对比较复杂的程序可能需要改的地方很多,也不利于保持应用的纯洁。于是采用bubblewrap便是一个比较不错的方案。bubblewrap是一个轻量化的容器应用,由于不需要授予root权限也能运行,不需要另外下载镜像,能实现在当前根目录上替换文件等等特性,是解决兼容问题的利器。flatpak做跨发行版运行应用,容器技术也是选用了bubblewrap。过去社区里的打包家也曾用bubblewrap做过一些伪装系统的小技巧。实际上最早的ABL就是如此简单,就是搬运了高版本的glibc文件(把自带的动态库都放在系统库目录中additional-base-lib目录里),然后用可以算是一条命令的脚本创造了一个和本机根目录相同,只不过替换了ld.so和libc.so.6两个文件的容器,然后用LD_LIBRARY_PATH寻找其他的glibc动态库。安装之后,如果遇到不兼容的情况出现,只需要在相关的命令前面加上ablrun和一个空格,就能神奇地消除glibc不兼容问题。
不过为了满足更多系统和应用的需求,这个项目逐渐扩充,虽然脚本增长了不少,但核心仍然和之前没什么区别。
打包脚本详解
一开始我是通过手动下载debian的几个glibc相关包然后手动拆包打包来制作ABL,不过后来发现经常这样做比较麻烦,所以我做了
make-deb.sh
用于自动下载、解包、获取信息、生成deb包。每次debian更新后,只需要替换掉下载链接,基本上自动打包就没什么问题。之后我又为采用rpm包管理的操作系统设计了make-rpm.sh
。之后在不同CPU架构上想用ABL,只要debian或fedora支持,都可以用来快速生产。这两个脚本从头到脚的内容:
~/rpmbuild/BUILD
目录中进行,创建路径,将rpm包解包。/usr/lib/x86_64-linux-gnu
,ABL就会把库安装到/usr/bin/x86_64-linux-gnu/additional-base-lib
里。这个字符串可以用命令dpkg-architecture -A <架构简称>
看到。而rpm平台就简单得多,32位系统就是/lib
,64位系统就是/lib64
。/lib
路径,之后的是在/usr/lib
。所以增加了个检测。这个只在打包时需要找到解包出来的各种动态库时用,反正ABL最后都会安装到上面说的路径里。/usr/bin/x86_64-linux-gnu/additional-base-lib/
里 ,我就得在脚本里面记录两个变量,一个是ABL自带的ld.so的位置/usr/bin/x86_64-linux-gnu/additional-base-lib/ld-linux-x86-64.so.2
,一个是ld.so的默认位置/lib64/ld-linux-x86-64.so.2
。最后所以实际上我把ld.so放到/usr/bin/x86_64-linux-gnu/additional-base-lib/lib64/ld-linux-x86-64.so.2
里面,这样我只需要记录一个变量/lib64/ld-linux-x86-64.so.2
,然后把他和动态库目录拼接一下就是ABL自带的ld.so位置。(还有一个是单用cp只能移动到已有的文件夹,又不清楚变量里面到底是lib还是lib64,又懒得截取字符串一个一个创建,所以先拿ld.so的路径用mkdir --parent
建成了个文件夹,然后用rm删掉,这么一操作就保留了父级文件夹,然后再cp过去。)ablrun详解
在打包过程中会生成ablrun脚本,也是实际使用时执行的文件。其实除了开头的两个变量是打包时生成的,其他内容全在ablrun_part里。
从头到脚的内容入下:
sudo bash -c "echo 1 > /proc/sys/user/max_user_namespaces"
,就能启用Linux的user namespace功能,这种情况下ablrun就会自动切换成另外一种模式,让那些应用可以运行。另外,如果使用root权限运行ablrun,也会使用ablrun_nocap模式。CAP_SYS_ADMIN
权限,这是想运行自带容器的程序(如AppImage,electron开发的程序等)必须的权限。应该所有应用都能运行。CAP_SYS_ADMIN
权限了,就能允许自带容器的程序运行。希望后来者补全的功能
ABL只采用bwrap绑定ld.so和libc.so.6,其他动态库使用LD_LIBRARY_PATH寻找,在大多情况下是完全没有问题的,但是有些不太讲武德的开发者把LD_LIBRARY_PATH在运行脚本里面写死了,这样ABL自带的其他动态库就没有作用。其实一开始设计的方案完全是为了证明我的想法(即只需要绑定ld.so和libc.so.6即可解决glibc兼容问题,其他动态库都可以通过LD_LIBRARY_PATH解决),并没有考虑太多,但是后来的版本为了尽量防止出错,我觉得有必要把所有自带的动态库挂载上。
如果ABL带的glibc库版本还不如系统自带的glibc版本高,那样的话用ablrun运行反而会出现glibc兼容问题(不用反而会正常)。本来我觉得这不是个问题,毕竟应用能运行还为啥要安装ABL,但是后来发现星火商店不少应用都在默认加ablrun,这种情况也不是完全没可能发生。需要做一个glibc版本判别功能,如果系统自带的版本更高一些就自动不使用ABL。其实只要把libc.so.6当作可执行程序运行,就能看到版本号了。