『007』安装 understand 艰难过程记录: 如何修改动态链接优先级
安装 understand 艰难过程记录: 如何修改动态链接优先级
关键词: RUN_PATH, R_PATH, LD_LIBRARY_PATH
前言
understand 是一个代码阅读器,偶然在知乎上看到有好几个人推荐,据说比 Vim+Ctag
还要好。
虽然现在我这样菜狗的水平看的代码都是直接 vim
,甚至不需要 ctag
,但还是想尝尝看,结果一尝试折腾了一下午。
问题发生
首先从官网中下载后,解压后是一个已经编译好的文件夹,里面有一些 libxxx.so
文件和最终运行的 understand 程序。
understand 已经动态链接到这些 libxxx.so
了,程序还有些这里面没有的、但是程序需要的 libxxx.so
,如 libc.so
,这些程序是动态链接到 /usr/lib/libxxx.so
中。
understand 运行出了问题。
./understand: /home/allen/understand/bin/linux64/libQt5Core.so.5: version `Qt_5.15' not found (required by /usr/lib/libQt5Test.so.5)
./understand: /home/allen/understand/bin/linux64/libQt5Core.so.5: version `Qt_5.15' not found (required by /usr/lib/libQt5QuickWidgets.so.5)
排查过程
一: 分析动态链接情况
这个以前遇过,当时是 openssl 有问题,解决方法是下载旧版本,然后用 patchelf
链接到那个旧版本。
首先看 understand 的动态链接情况。
ldd understand
libQt5Concurrent.so.5 => /home/allen/understand/bin/linux64/./libQt5Concurrent.so.5 (0x00007f88ebd4d000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f88ebd47000)
libcrypt.so.1 => /usr/lib/libcrypt.so.1 (0x00007f88ebd0d000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f88ebceb000)
libQt5WebEngine.so.5 => /home/allen/understand/bin/linux64/./libQt5WebEngine.so.5 (0x00007f88eba8d000)
libQt5WebEngineWidgets.so.5 => /home/allen/understand/bin/linux64/./libQt5WebEngineWidgets.so.5 (0x00007f88eb855000)
...
我一开始认为是
/usr/lib
中的 libxxx.so
太新了,所以要把 /usr/lib
中的文件下载旧版本。但是这也太多了吧,好像有二三十个,所以肯定不行。
二: 替换出错库
但是注意看,引发问题的 libQt5Test
这个文件明明在 understand
文件夹中,但是动态链接却在 /usr/lib
中!
所以先尝试修改可执行文件加载这个库的路径: patchelf --replace-needed libQt5Test.so.5 xxx/libQt5Test.so.5 understand
,但是发现这条语句没有效果!
这是为什么??
【第一个知识点】: ldd 和 readelf 区别
假设程序需要 A.so
,而加载 A.so
需要 B.so
。
ldd
输出的是这个程序【所有需要】的动态链接文件,例子中为 A.so
和 B.so
。
readelf -d xxx | grep NEED
输出的是这个程序【直接需要】的动态链接文件,例子中为 A.so
。
所以这里不行的原因是 libQt5Test.so
不是直接需要的动态链接文件。
patchelf
本质上修改的是 .dynsym 表,所以它查找发现没有 libQt5Test
,就不做任何操作,所以就相当于没执行。
所以现在情况是我需要让程序动态链接到 understand
的 libQt5Test
,而不会 /usr/lib
中。
但是 patchelf --replace-needed
无效(就像上面所说,找不到这个间接库),有一种朴素的方法就是手动找到哪些 libxxx.so
动态链接 libQt5Test
,然后用 patchelf
一个个改变它们。
不过这也太蠢了。所以我想到可以把 /usr/lib
中的 libQt5Test.so
删掉或者改名,然后指定动态链接文件夹为 /usr/lib:understand/
此时程序当想要链接 libQt5Test.so
时,会在 /usr/lib
中找,发现找不到了就会在 understand
中找。
但是我发现差点就运行了,闪了一下报错了
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
...
【第二个知识点】: 如何找到 QT 程序的错误原因
需要使用 export QT_DEBUG_PLUGINS=1
,然后再运行文件,这样会显示比较详细的信息告诉你错在哪里。
结果发现错误原因还是 undefined xxxx
,所以乖乖把之前的设置改回去。不知道怎么办的时候,发现了一个很重要的知识。
三: 程序链接路径
【第三个知识点】: R_PATH
和 RUN_PATH
以及 patchelf
修改它们的注意点
R_PATH
和 RUN_PATH
就是指明动态链接文件夹的位置,前者比后者优先级更高,即前者如果存在就不用找后者了。
值得注意的是 patchelf
修改这两有细微区别:
# 查看 R_PATH 和 RUN_PATH
readelf -d understand | grep 'R*PATH'
# 设置 RUN_PATH
patchelf --set-rpath xxxx/lib understand
# 设置 R_PATH
patchelf --force-rpath --set-rpath xxxx/lib understand
那么修改完这两个变量就可以了吗?不可以!下面是解释,有点绕,但也好理解:
虽然 understand
需要 libA.so
时确实从我设置的 R_PATH
中找,但是如果 libA.so
还需要 libB.so
时,此时是从 libA.so
的 R_PATH
中去找了,所以失败。
后来我强制所有的 libA.so
的 RPATH
都改变,这样就 OK 了,具体实现看下面,不过还有更好的做法。
# 解压后的文件夹有些 libxxx.so 是不需要使用的
mkdir libQt && mv *.so.5 bQt*.so libQt
cd libQt && patchelf --force-rpath --set-rpath xxxx/libQt *
cd .. && patchelf --force-rpath --set-rpath xxxx/libQt understand
四: 最终做法
现在回顾一下:我就是想让动态链接先找 understand
文件夹,如果没有就找 /usr/lib
文件夹。其实有个更好的解决方法。
【第四个知识点】: LD_LIBRARY_PATH
,这个和 R_PATH
, RUN_PATH
有着很密切的关系
同样地,这个变量是由多个路径组成,表示动态链接去哪个文件夹找。
那么: LD_LIBRARY_PATH
, RPATH
, RUNPATH
优先级是什么呢?【【非常重要】】
- 如果 RUNPATH
为空,那么为 RPATH
, LD_LIBRARY_PATH
- 如果 RUNPATH
不空,那么为 LD_LIBRARY_PATH
, RUNPATH
,此时 RPATH
相当于没有用了
所以解决方法就很清楚,因为 understand
的 RUNPATH
不是空,因此只要设置 LD_LIBRARY_PATH
中 understand
文件夹在 /usr/lib
文件夹前面就行。
# 为了清晰一点,我把 understand 中的 libxxx.so 放到 libQt 文件夹中
mkdir libQt && mv libQt*.so libQt
LD_LIBRARY_PATH=./libQt:$LD_LIBRARY_PATH ./understand
参考
1. https://blog.csdn.net/zhanghm1995/article/details/106474505