[Feelings & Sharing] 【小?技巧】为外部应用解决动态库问题
Tofloor
poster avatar
enforcee
deepin
2023-05-05 21:53
Author

序章

熟练的GNU/Linux玩家经常用系统的包管理器安装程序。虽然这应该是日复一日的平凡生活的一部分,意外又总是会在不经意间现身,比如,设想一下有一天你得到了一个神奇的小应用,他并不是按照系统的包管理器打包的,而是一个单独的可执行文件...

(故事纯属虚构,为了安全请不要运行来路不明的程序或脚本)

blush
图片.png

你会心一笑,开始标准流程,首先赋予可执行权限(右键菜单-属性-允许以程序执行,喜欢命令的可以用 chmod +x
图片.png

然后双击运行...
图片.png

点击运行...什么事情都没有发生。

糟糕了,程序运行不起来!

你会怎么办呢,再多尝试一遍直到扼腕长叹?上网发帖批判一下Linux的兼容性就是不好?卸载系统重装Arch?不过对于纵横开源系统的老将们,折腾之魂自然已经蠢蠢欲动...


第一章:获取信息

在下定决心之前,应该考虑的是解决这个问题真正对你有帮助吗?也许,我们可以在另一个地方得到一个可以正常执行的程序:

软件源里没有吗?使用系统自带的包管理器安装的官方软件源中的软件包,均使用了相同的构建环境,保证与系统完美兼容。(其实我这个程序就是在官方源下载的,只是为了给大家做案例才给他解包出来变成一个单独的程序)

用户软件源的不行吗?有时候你想要的软件包已经被一些经验丰富的用户打包好了。比如Ubuntu有ppa,Arch有AUR,很多系统都有相应的用户群体提供软件包来支持系统的发展。在deepin中我们可以用星火商店

Flathub有吗?他们采用了flatpak的打包方式,能兼容全部发行版。(虽然Flathub的网速很成问题,但是现在用上海交通大学的镜像能提升一些)

软件的官网是不是已经提供了可执行文件?虽然很多软件只对GNU/Linux提供源代码,但是仍然有一些软件提供了完整功能的可执行程序(比如firefox、blender等,下载后只需要解压缩就可以运行)。

再尝试一下AppImage?这种打包方式将程序的依赖库打包进来成单独的一个文件,只需要赋予执行权限就可以运行。很多网站都喜欢提供AppImage,比如说用来下载开源素材的Pling网站也推出了AppImageHub。(虽然AppImage的打包质量参差不齐,有些AppImage本身就有依赖问题)

兵法云过:知己知彼。为了解决问题,首先我们需要收集信息。最简单的就是倾听程序自己的心声,让我们在终端中执行他。

在文件夹的空白处点击右键,在终端中打开。(一个黑漆漆的框框,提示文字是你的用户名,计算机名和当前工作目录(Work Directory))
图片.png

然后我们在终端中输入程序的路径。最简单的方法就是从文件管理器把文件图标拖拽到终端窗口,这样我们会得到一个完整路径:
图片.png

或者我们可以采用相对路径的方法,记住一个单独的点号.表示当前的工作路径,这样我们只需要 ./desmume就能表示上面的一长串了(引号是什么作用请大家自行学习)。这回可以采用手动输入的方式,输入的时候可以随时点击键盘上的Tab键,他会自动补全输入的内容,可以省力很多。(我们之后为了看着简洁都使用相对路径)
图片.png

然后按下回车键(Return或者Enter)开始执行。在终端运行的时候程序会显示信息(这就和Windows不一样了),很多时候你可以从这里看到程序失灵的原因。动态库问题很多情况是能解决的(本文的主要内容)。而一些问题,比如说很常见的段错误(Segmentation Fault)通常是因为访问内存越界,是程序的质量问题,这个就要让程序的开发者修理了。

运行结果:
图片.png

这是在说他找不到一个名叫「libSoundTouch.so.1」的动态库了。当然我们知道这个信息以后就可以直接着手解决,不过很有可能我们解决完了以后他又报告另一个动态库找不到的问题。这里我们使用ldd命令洞察一下他想要的所有动态库:输入 ldd 文件名
图片.png

可以看到命令用箭头=>的方式把程序所需的所有依赖库都标注出来。左边是程序所需的动态库,右边则是ld.so(动态库连接器)为这个程序所选择的,系统中实际存在的动态库。我们看到一些动态库变成了not found,这些就是问题所在,为了看着干净一点,我们用grep命令把含有not found的几行单独提取出来。ldd 文件名 | grep 'not found'
图片.png

因此我们是一共缺失了三个动态库。下面我们来想办法解决他们。


第二章 安装缺失的库

我们尝试在软件源里搜索一下包含他们的软件包(如果有的话就用不着去别的地方找了,直接安装对应的包就可以了)。对于debian系列的操作系统,我们需要使用apt-file命令。这个命令是需要手动安装,如果大家之前没有用过就先执行以下的命令:

sudo apt install apt-file #安装apt-file

sudo apt-file update #更新apt-file的数据库

然后我们就可以搜索一下,先从第一个「libSoundTouch.so.1」开始,输入 apt-file search <文件名>
图片.png

可以看到,这个动态库是由软件包「libsoundtouch1」提供的。我们使用apt安装他。sudo apt install <软件包名>。在这个案例中,我们输入:

sudo apt install libsoundtouch1

用这个方法可以解决所有缺失的依赖库...

才怪呢!

deepin真是不负众望,为我撰写本文又提供了好素材。我刚才还说官方源的软件应该和系统完美兼容呢,现在问题就出现了。就在处理第二个库的时候,我们使用apt-file搜索一下:
图片.png

但是我们安装了这个包以后,却什么都没有发生?动态库仍然处于缺失状态?

我们来看一下/usr/lib/x86_64-linux-gnu/目录到底有没有我们要的动态库。使用ls命令列出文件,grep搜索。(也可以直接使用文件管理器进入目录搜索)
图片.png

果然是没有2.6.2这个库的,但是有2.6.2.1。这是怎么回事,难道apt-file在骗人吗?我们使用apt看一下这个软件包。

apt show libtinyxml2.6.2v5 -a

我们发现原来软件源里有两个同名的软件包,版本号分别是2.6.2.1-deepin1和2.6.2-4+apricot。我们应该能想到这两个软件包分别提供了两个不同版本后缀名的动态库。正常的系统不应该是这样的。deepin到底是怎么回事就去让开发人员研究吧。我们先来解决眼下的问题。我们可以通过apt来下载软件包(而不安装),用等号=指明版本:

apt download libtinyxml2.6.2v5=2.6.2-4+apricot

图片.png

于是我们在工作目录(别忘了,就是终端提示字符串中的那个目录)下就能获得一个deb包。注意,不要安装他,我们要做点别的事情...


第三章 连接到外部动态库

有的时候系统软件源提供的动态库不符合程序的要求,或者是根本没有包含想要的动态库。有的时候我们要到别的地方去下载他。比如说debian提供了一个在线检索软件包的页面,我们也可以直接用浏览器访问任何一个镜像源的网址,直接下载软件包(不过通常没有搜索的功能):
图片.png

我们从别的地方获得的动态库,无论是从别的系统中下载的,还是自己编译的,最好不要安装到系统中,因为他们有可能影响系统其他组件的正常运行,有时候会造成不可逆转的后果。因为我们还有一个极其简单的方法让程序找到我们自己获取的动态库。言归正传,我们现在得到了一个旧版本的deb包,里面有我们想要的东西。不要安装,我们给他解包把动态库取出来。

在deb包上点击右键,打开方式,选择归档管理器打开。
图片.png

找到我们想要的东西,解压出来就可以(右键提取。我们最好创建一个单独的目录用于存放程序和动态库。)
图片.png

获得了动态库。(也可以用命令 dpkg -x <文件名> <目标路径> 来解deb包)
图片.png

这样就可以了,我们现在想办法让ld.so(动态库连接器)找到这个动态库就可以了。这里有个小知识,动态库选择的顺序是:

(1)DT_RPATH定义的路径。这个是写死在elf可执行程序里的。这个字段现在已经弃用,新编译的程序应该用DT_RUNPATH替代。

(2)环境变量LD_LIBRARY_PATH中的路径。

(3)DT_RUNPATH定义的路径,只搜索DT_NEEDED中定义的动态库。这两个是写死在elf可执行程序里的。(其实是可以修改的,之后的案例中我们会做一个类似的操作)

(4)/etc/ld.so.cache文件中的动态库。这个通常是写在/etc/ld.so.conf中的路径,然后用ldconfig生成的。

(5)默认路径,比如/lib和/usr/lib。

此外还有一个环境变量LD_PRELOAD,他所指定的动态库会被最先加载,但是具有覆盖其他动态库的函数的功能。这个一般是魔改(hack)用的。

其实动态库选取的依据比较复杂,这里只是简要介绍一下。可以看出LD_LIBRARY_PATH具有很高的优先级并且容易修改。如果系统中有同名的动态库,ld.so也会先选取LD_LIBRARY_PATH中的。那么我们只需要用env命令(或者直接在终端里面定义变量)就可以让程序找到我们自己的动态库了。

env LD_LIBRARY_PATH=<路径> <程序命令>

比如我们这个案例中,可以使用 env LD_LIBRARY_PATH=. ./desmume (别忘了点号.表示当前工作路径)

图片.png

程序运行起来了。

yeah

别急着庆祝...我还准备了一个终极挑战!


第四章 解决glibc兼容性问题

我们会发现有些程序运行失败时会给出提示version `GLIBC_某某' not found。上网搜索一下就会知道,这是因为系统中的glibc版本低于程序所需的版本。这时着急的小白连忙下载(或者编译)了新版glibc,准备把他覆盖到系统的glibc上。随着回车键按下,shell应声倒塌,系统灰飞烟灭,满目疮痍之间,小白完成了老鸟的蜕变。终于知道学会守护glibc是系统管理员的必经之路...(以上纯属胡说八道)

大家要知道libc是系统中相当重要的组件,几乎所有程序都围绕他运行,乱动的话非常容易弄坏系统。尽管如此,还是有很多人前仆后继地升级glibc,网上也有很多教程教大家如何安全地升级glibc。但是大家要想明白一个问题,这个场景下我们想解决的问题是「程序不能运行」而非「升级glibc」,只是一种思维定势在影响着大家,「版本低」就要「升级」。经过上文的经验,大家应该知道我们只需要让程序选择我们提供的动态库,而非系统的动态库,就能解决动态库的依赖问题。那么这个操作对libc也适用吗?

在这个章节中,我用在Archlinux编译的desmume作示例,想办法让他在deepin运行起来。(为啥用这个软件其实只是碰巧我有一个他的源代码)首先,我们还用老方法,ldd一下看看:
图片.png

从这里我们可以看到,他报告了一个libSoundTouch.so.2的缺失,libc和libstdc++的不兼容。我们故伎重施,把这些动态库从arch系统复制过来,然后用LD_LIBRARY_PATH指定动态库位置。(有的动态库在原系统是链接文件,我们要复制原文件然后改名成所需的动态库名)

会成功吗?
图片.png

可以看出他又报告了一个libm的不兼容(这个是libc的数学库)。我们再复制一个libm试试。
图片.png

这回报告了一个以前没见过的错误。其实这个是因为ld.so(动态库连接器)与glibc不兼容。注意的是ld.so本身就是glibc的一部分,他们是相互依存的。因此不能单独通过替换libc.so.6来完成这个操作。我们无路可走了吗?

秉承着差什么就换什么的理念,我们再把arch的ld.so搬运过来(ld-linux-x86-64.so.2)。但是程序寻找ld.so的逻辑和寻找动态库不同,这个位置是写死在程序里面的。如果要修改elf可执行文件,我们可以用patchelf小工具。

第一次使用需要安装:sudo apt install patchelf

elf的机制还是比较复杂的,由于这个案例中只需要几个命令,也不过多介绍了。

首先我们可以用 patchelf <文件名> --print-interpreter 来读取程序内部的ld.so位置。我们来试一下。
图片.png

之后我们可以用 patchelf <文件名> --set-interpreter <值> 来修改程序(建议提前备份一下)。然后可以再执行一下上面的命令看看是否修改成功。
图片.png

再执行一下原文件呢?
图片.png

提示权限不够。其实并不是我们想执行的程序没有可执行权限(我们从第一步就已经做了),而是ld.so没有执行权限。我们为ld-linux-x86-64.so.2授予可执行权限。再执行一下试试。
图片.png

又一个动态库报告了个不兼容。我们从Arch上再搬运一个libpthread再运行。
图片.png

又多出一个不兼容...继续搬运librt.so.1。
图片.png

程序运行起来了!加载一个rom试试。
图片.png

没有多大问题。那么glibc的兼容性也在不修改系统的情况下解决了。至此,我们大概解决了常见的各种依赖问题。还有最后一件事。


第五章 AppImage

虽然AppImage创建的目的就是为了解决依赖问题,采取了自带依赖库的方式。但是毕竟打包质量不同,有些时候运行AppImage也会出现如上的报错。我们只需要知道一件事,就很容易处理了。那就是解包AppImage的命令:

--appimage-extract

执行这个以后就会解出AppImage内的所有文件。可以在里面找到一个包括各种动态库的目录,如果报错是缺失动态库,只需要把动态库放到这个目录里;如果是版本不兼容,可以尝试移除这个目录中对应的动态库(这样就会选择系统中的动态库代替)。期间不需要重新打包成AppImage,只需要找到目录中相应的可执行文件运行即可。

Reply Favorite View the author
All Replies
阿尼樱奈奈
Moderator
2023-05-05 22:08
#1

学习了

Reply View the author
DebuggerX
deepin
2023-05-05 22:12
#2

教程没毛病,比那些乱搞libc的强太多,不过不知道有几个普通用户能搞明白的。。

另外有个方便获取高版本so库的办法,不用自己去找deb包或者装其他系统复制,就是利用debootstrap,比如想获取一个基础版的debian11:

sudo debootstrap --arch=amd64 bullseye ./ https://mirrors.tuna.tsinghua.edu.cn/debian/

然后可以用:

sudo chroot ~/debian11/

进入这个系统环境,正常通过apt安装依赖,最后把需要的so库文件通过软链接搞出来,用LD_LIBRARY_PATH提供给程序运行就可以了

Reply View the author
liwl
deepin
2023-05-05 22:16
#3

你看,多好的文章

都被论坛的渣渣排版给毁了

Reply View the author
阿尼樱奈奈
Moderator
2023-05-05 22:19
#4
liwl

你看,多好的文章

都被论坛的渣渣排版给毁了

这是Markdown的原因?

Reply View the author
liwl
deepin
2023-05-05 22:33
#5
阿尼樱奈奈

这是Markdown的原因?

不知道,反正论坛对长文的排版,怎一个烂字了得

也许是对图片的展示的问题吧,感觉杂乱

Reply View the author
青稚°
Moderator
2023-05-05 22:34
#6

写的好,就是排版有问题。

Reply View the author
青稚°
Moderator
2023-05-05 22:34
#7
DebuggerX

教程没毛病,比那些乱搞libc的强太多,不过不知道有几个普通用户能搞明白的。。

另外有个方便获取高版本so库的办法,不用自己去找deb包或者装其他系统复制,就是利用debootstrap,比如想获取一个基础版的debian11:

sudo debootstrap --arch=amd64 bullseye ./ https://mirrors.tuna.tsinghua.edu.cn/debian/

然后可以用:

sudo chroot ~/debian11/

进入这个系统环境,正常通过apt安装依赖,最后把需要的so库文件通过软链接搞出来,用LD_LIBRARY_PATH提供给程序运行就可以了

说的好,加精选了。

Reply View the author
jjcui8595
Moderator
2023-05-05 22:35
#8

大赞!学习了

like

Reply View the author
明天会更好
deepin
2023-05-06 00:41
#9

我竟然看完了,然后还是一头雾水。

Reply View the author
enforcee
deepin
2023-05-06 00:54
#10
明天会更好

我竟然看完了,然后还是一头雾水。

图片里也是有信息的,不要漏掉了

blush

Reply View the author
hanzn-zzx
deepin
2023-05-06 03:39
#11

like

马上收录进常用资源整理 @pzm9012

Reply View the author
enforcee
deepin
2023-05-14 07:05
#12

关于第四章解决glibc兼容问题的补充:

还有一种方法,不修改可执行文件,只需要手动以可执行程序的方法调用自己补充的ld.so即可,如:

LD_LIBRARY_PATH=. ./ld-linux-x86-64.so.2 ./desmume

(注意这个程序应该是没被patchelf修改的版本)

Reply View the author
jjcui8595
Moderator
2023-05-29 22:14
#13

收藏备用

like

Reply View the author
nexfia
deepin
2023-12-29 03:50
#14

tail

好东西

Reply View the author
月夜残云
deepin
2024-02-22 15:37
#15

我只想运行一下CRT,就看完了这个贴子,然后还是不会运行

Reply View the author