Skip to content

『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.soB.so
readelf -d xxx | grep NEED 输出的是这个程序【直接需要】的动态链接文件,例子中为 A.so

所以这里不行的原因是 libQt5Test.so 不是直接需要的动态链接文件。
patchelf 本质上修改的是 .dynsym 表,所以它查找发现没有 libQt5Test,就不做任何操作,所以就相当于没执行。


所以现在情况是我需要让程序动态链接到 understandlibQt5Test,而不会 /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_PATHRUN_PATH 以及 patchelf 修改它们的注意点

R_PATHRUN_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.soR_PATH 中去找了,所以失败。


后来我强制所有的 libA.soRPATH 都改变,这样就 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 相当于没有用了


所以解决方法就很清楚,因为 understandRUNPATH 不是空,因此只要设置 LD_LIBRARY_PATHunderstand 文件夹在 /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

Comments