一、背景

作为鸿蒙PC生态的早期技术探索者,我们在将curl、xz等Linux经典命令行工具交叉编译至鸿蒙PC平台的过程中,这中间难免会遇到动态依赖库的查找加载问题。

稍有不慎,就会遇到类似如下报错:

Error loading shared library libcurl.so.4: No such file or directory (needed by ./curl_without_runpath)
Error loading shared library libssl.so.3: No such file or directory (needed by ./curl_without_runpath)
Error loading shared library libcrypto.so.3: No such file or directory (needed by ./curl_without_runpath)

虽然可以通过设置LD_LIBRARY_PATH环境变量来指定动态库查找路径,但这要每个lib目录都需要手动配置,比较繁琐。为了让查找so的过程更加优雅,我们转向编译时配置RUNPATH的方案。本文将系统梳理鸿蒙PC环境下musl动态库路径管理的核心逻辑,拆解autotools(configure+make)、CMake还有clang直接编译三种主流构建系统的适配方案,以curl交叉编译、自定义项目开发为例,提供可复用的动态库加载路径解决方案,为同类工具适配提供实践参考。

二、autotools构建系统适配方案(以curl为例)

对于curl、xz等采用autotools(configure+make)构建的开源项目,libtool由工具链自动调用,动态库路径配置的核心难点是解决$ORIGIN在多层脚本传递中的解析问题。

2.1 典型问题:参数传递中动态库路径被篡改

最初尝试在configure阶段直接传递-rpath=$ORIGIN/../lib参数,期望将相对路径嵌入程序的RUNPATH。但由于autotools会通过configure、Makefile、libtool多层脚本传递参数,最终编译完成后通过readelf验证。

# 配置LDFLAGS,增加rpath
export LDFLAGS="-Wl,-rpath=$ORIGIN/../lib"

# 生成Makefile
./configure --host=aarch64-linux-ohos

# make生成产物
make

# 编译后验证RUNPATH结果
readelf -d src/.libs/curl | grep runpath

如下图所示,从readelf命令可以看到$ORIGIN/../lib变成了/../lib,完全不符合我们的预期。

2.2 根因解析:shell脚本的符号展开

经分析,我们设置LDFLAGS变量时,因为$符在shell中是环境变量取值的意思,所以此时会把ORIGIN当做shell环境变量,直接进行取值替换,但因为此时并不存在名为ORIGIN的变量,所以取值为空,导致$ORIGIN/../lib只剩下了/../lib,如下图所示,验证了这一点。

2.3 解决方案:LDFLAGS双层转义保护

针对这个场景,需通过双层转义设置LDFLAGS环境变量,确保$ORIGIN在多轮脚本传递后仍能完整传递给链接器。具体操作如下:

# 配置LDFLAGS,双层转义,确保$ORIGIN在多轮脚本传递中不被展开
export LDFLAGS="-Wl,-rpath='\$\$ORIGIN/../lib'"

转义逻辑说明:

  • 第一层\$:保护$不被export命令所在的Shell解析;
  • 第二层\$:确保经过多层脚本传递后,最终传递给链接器的是$ORIGIN
  • 单引号:进一步屏蔽脚本解析过程中对$的意外展开,强化转义效果。

重新设置LDFLAGS之后,再次执行configure和make命令,然后再次检查编译产物的RUNPATH

# 生成Makefile
./configure --host=aarch64-linux-ohos

# make生成产物
make

# 编译后验证RUNPATH结果
readelf -d src/.libs/curl | grep runpath

如下图所示,可以看到编译过程中,原样将$ORIGIN/../lib进行了传递,没有进行变量取值展开。并且,通过readelf命令可以看到,最终产物的RUNPATH也已经正常携带了$ORIGIN,问题顺利解决!

2.4 鸿蒙pc运行验证

如上图所示,通过objdump -x命令可以确认curl_with_runpath是配置了$ORIGIN/../lib为动态库查找路径的。此时可以直接执行curl_with_runpath命令,它会根据runpath自动找到所需的动态库。

三、CMake构建系统适配方案

CMake是开源项目与自主开发项目中广泛使用的另一个构建工具,同样可以像autotools那样为编译产物指定-rpath。接下来以“自定义CMakeLists.txt”和“基于已有CMakeLists.txt编译”两种场景,分别进行讲解演示。

3.1 场景1:自定义CMakeLists.txt

自主编写的CMakeLists.txt可直接通过全局配置或目标级配置设置动态库路径,CMake会自动处理Shell解析问题,无需复杂转义。

方案A:全局配置CMAKE_EXE_LINKER_FLAGS

在CMakeLists.txt添加全局配置,所有目标继承规则:

# 设置CMAKE_EXE_LINKER_FLAGS,通过链接选项指定运行时库查找路径为$ORIGIN/../lib
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath=$ORIGIN/../lib")

# 定义可执行程序目标
add_executable(myapp main.c)

如下图所示,最终产物的RUNPATH也已经正常携带了$ORIGIN,问题顺利解决!

方案B:单个目标配置target_link_options

仅为特定程序配置RPATH,直接传递-rpath参数:

# 定义可执行程序目标
add_executable(myapp main.c)

# 直接传递-rpath参数,CMake自动处理$ORIGIN转义
target_link_options(myapp PRIVATE "-Wl,-rpath=$ORIGIN/../lib")

如下图所示:我们在CMakeLists.txt中定义了2个目标:myappmyapp1,但是我们只给myapp目标设置了-rpath参数。编译完成之后,分别查看myappmyapp1RUNPATH信息,可以看到我们在CMakeLists.txt里的rpath设置只影响到了myapp没有影响myapp1

3.2 场景2:基于已有CMakeLists.txt编译

对于已有的CMake开源项目(如xz、zlib的CMake版本),无需修改源码,重新设置LDFLAGS参数,然后正常执行cmake即可。

注意用于CMake的**LDFLAGS**命令设置方法和用于autotools的有区别。

export LDFLAGS="-Wl,-rpath='\$ORIGIN/../lib'"

四、直接使用clang编译器的适配方案

对于无需构建系统(autotools/CMake)的简单项目,或需要手动控制编译流程的场景,可直接调用鸿蒙PC交叉编译工具链中的clang编译器,通过直接传递-rpath参数配置动态库路径。核心是正确处理$ORIGIN的转义,确保其完整传递给链接器。

假设存在简单源文件main.c,需链接自定义路径下的动态库(如openssl),且期望程序运行时自动查找自身同级lib目录下的库文件,具体编译命令如下:

# 直接调用clang交叉编译器,单引号保护$ORIGIN避免被展开
clang main.c -lssl -lcrypto -lz -o myapp -Wl,-rpath='$ORIGIN/lib'

编译完成后,通过readelf验证RUNPATH配置结果:

readelf -d myapp | grep runpath

如下图所示,最终产物的RUNPATH也已经正常携带了<font style="background-color:rgba(0, 0, 0, 0.06);">$ORIGIN</font>

五、鸿蒙PC动态库路径管理方案

$ cat /etc//etc/ld-musl-aarch64.path
/system/lib64:/vendor/lib64:/vendor/lib64/chipsetsdk:/system/lib64/ndk:/system/lib64/chipset-sdk:/system/lib64/chipset-pub-sdk:/system/lib64/platformsdk:/system/lib64/priv-platformsdk:/system/lib64/priv-module:/system/lib64/module:/system/lib64/module/data:/system/lib64/module/multimedia:/system/lib:/vendor/lib:/system/lib/ndk:/system/lib/chipset-sdk:/system/lib/chipset-pub-sdk:/system/lib/platformsdk:/system/lib/priv-platformsdk:/system/lib/priv-module:/system/lib/module:/system/lib/module/data:/system/lib/module/multimedia:/lib64:/lib:/usr/local/lib:/usr/lib:/lib64/platformsdk:/lib64/chipset-pub-sdk:/chip_prod/lib64

从/etc/ld-musl-aarch64.path文件内容看,程序运行时,系统会从包括/system/lib64/system/lib/lib64/lib/usr/local/lib/usr/lib在内的目录下查找动态依赖库。但是这些目录,对于用户来说,是无法进行操作的,所以如果需要使用到自己编译的动态库,就需要通过其他方法来做到。除了编译时配置RUNPATH的核心方案,针对不同调试/部署需求,还可采用以下两种补充方式。

4.1 LD_LIBRARY_PATH设置动态库路径

musl支持LD_LIBRARY_PATH环境变量,程序运行时,如果需要加载动态库,会优先在设置的路径下查找依赖库。

export LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH

如下图所示,在设置LD_LIBRARY_PATH之后,程序就可以顺利到动态依赖库了。

4.2 运行时直接调用musl链接器

musl动态链接器(/lib/ld-musl-aarch64.so.1)支持--library-path参数,可临时指定库查找路径,替代LD_LIBRARY_PATH的功能:

# 格式:链接器路径 --library-path 自定义路径 程序路径
ld-musl-aarch64.so.1 --library-path ../lib ./curl

核心优势:无需重新编译,纯运行时调整,适配多版本库测试、临时路径替换。

但需要注意一点:受鸿蒙PC权限限制,/lib/目录下的ld-musl-aarch64.so.1是无法直接运行的,会报bash: /lib/ld-musl-aarch64.so.1: Permission denied。需要将其拷贝到其它目录下,然后自签名之后才可以运行。

如上图所示,对于没有设置runpath的curl命令开始,直接执行是会提示找不到依赖库而无法运行的,但通过./ld-musl-aarch64.so.1 --library-path ../lib ./curl_without_runpath --version命令指定了动态库加载路径之后,curl命令就可以正常运行了。

4.3 如何查看加载动态库的路径

musl动态链接器(/lib/ld-musl-aarch64.so.1)除了支持--library-path参数外,还支持通过--list参数查看依赖库的加载路径。

./ld-musl-aarch64.so.1 --list ./curl_with_runpath

如下图所示,可以看到curl_with_runpath所有依赖的动态库的查找路径。

七、总结

鸿蒙PC平台动态库路径管理的核心,是适配musl libc与glibc的本质差异——放弃对LD_LIBRARY_PATH的依赖,转向编译时RUNPATH+$ORIGIN的核心方案,同时结合musl链接器的--library-path参数满足调试灵活性需求。这一适配思路可复用于curl、xz、rsync、zstd等各类开源命令行工具。掌握musl的动态库查找规则,以及不同构建系统下的路径配置方式,能大幅降低鸿蒙PC平台开源工具适配成本,提升调试与部署效率,为鸿蒙PC生态的命令行工具适配提供可落地的技术支撑。

欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐