cgo运行时无法找到动态库问题记录

前段时间有同学在用CoralReefPlayer的go语言绑定的时候遇到了些问题,他是macos,在运行go test的时候找不到CoralReefPlayer的动态库:Go绑定动态连接失败 · Issue #15 · DawningW/CoralReefPlayer

其实我是知道这个问题的,之前的解决方法一直都是加LD_LIBRARY_PATH,但是不能指望用户自己加这个,总得找找更优雅的解决办法,所以就趁他提单了的机会好好研究一下吧

运行go test的报错如下:

build/install/gcrp on  HEAD (170014d) via C v17.0.0-clang via 🐹 v1.25.3
❯ ls
coralreefplayer.h		gcrp.go				libCoralReefPlayer.dylib
gcrp_test.go			go.mod

build/install/gcrp on  HEAD (170014d) via C v17.0.0-clang via 🐹 v1.25.3
❯ go test
dyld[88967]: Library not loaded: @rpath/libCoralReefPlayer.dylib
  Referenced from: <32AB29F6-8B9B-319A-D929-8AB64CD7A3E4> /private/var/folders/x3/p_mvf5p973d0_nvxxkfmvgzr0000gn/T/go-build2135229572/b001/gcrp.test
  Reason: no LC_RPATH's found
signal: abort trap
FAIL	oureda.cn/gcrp	0.003s

从go test的报错可以看出问题是加载@rpath/libCoralReefPlayer.dylib的过程中找不到rpath,因为cgo的链接器参数里没有配置rpath,所以编出来的二进制里也没有rpath,自然找不到动态库了

Tips: 什么是rpath
rpath 指的是可执行文件或库中硬编码的运行时搜索路径,动态链接加载器使用 rpath 来查找所需的库,想了解更多可以自己百度或者问AI😋

那么接下来就用otool命令看一下rpath和动态库搜索路径,可以看到确实没有rpath

~/Codes/CoralReefPlayer/build/binding/go/gcrp $ go test -c -o test
~/Codes/CoralReefPlayer/build/binding/go/gcrp $ otool -l test | grep LC_RPATH
~/Codes/CoralReefPlayer/build/binding/go/gcrp $ otool -L test
test:
        @rpath/libCoralReefPlayer.dylib (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)

这类问题实际上在很多语言的binding里都出现过,因为运行时的工作目录和动态库所在的目录可能不是同一个,又或者某个语言的动态库搜索策略是自己写的,要挨个去修很费劲,所以我在ci的测试里采用了偷懒的办法,就是配置LD_LIBRARY_PATH(linux和mac默认都不加载当前目录下的动态库,所以单纯放同一个目录里是没用的),无论是python这种自己加载库的,还是go这种通过libc去加载库的,都会遵循LD_LIBRARY_PATH,而在mac上这个环境变量叫DYLD_LIBRARY_PATH,把DYLD_LIBRARY_PATH配置为libCoralReefPlayer.dylib所在目录后,运行起来确实没问题了

但是这样每次运行前都要配一次环境变量,有没有更简单的办法呢,有的兄弟有的,对cgo这种用了c语言链接器的来说,可以在链接的时候配置一下rpath,linux上可以把rpath配置为$ORIGIN,表示工作目录;mac上可以配成@loader_path表示该库的加载者的所在目录,@executable_path表示可执行文件所在目录,这样这两个平台就能和windows一样在可执行文件的目录里加载动态库了

所以在cgo的LDFLAG里加上-Wl,-rpath,@loader_path,结果编译的时候又报错了

oureda.cn/gcrp: invalid flag in #cgo LDFLAGS: -Wl,-rpath,@loader_path (see https://go.dev/s/invalidflag)

按理说这不就是把选项透传给编译器/链接器,而且这么常用的选项怎么可能不支持,去报错信息里的文档一看才知道是google担心有安全问题,给选项设成了白名单。但是早在10年前rpath就加到了白名单里,那为什么还不行呢,于是接着在github上搜,发现是把@给屏蔽了,原因是gcc会把@开头的参数替换为@后面字符串所指向的文件内容,有安全问题,issue上讨论了4年才决定改成特判mac平台,然后合入了没到两周就发现苹果的旧链接器有漏洞,又回退了…

issue链接在这里:cmd/go: go tool rejects #cgo LDFLAGS: -Wl,-rpath,@executable_path/../Frameworks · Issue #40559 · golang/go

既然@loader_path暂时是没法用了,那就只能先用相对路径凑合一下了,目前库上的cgo链接选项已经改成了下面这样,这样在linux上是在可执行文件的目录里找,在mac上暂时是在工作目录里找,等上面的issue修好之后再换回@loader_path

#cgo darwin LDFLAGS: -Wl,-rpath,.
#cgo linux LDFLAGS: -Wl,-rpath,$ORIGIN

最后总结一下,找不到动态库是由于linux和mac默认都不加载可执行文件目录下的动态库,可以通过以下方式解决:

  1. 必须关闭BUILD_MACOS_FRAMEWORK选项,只生成单个动态库而不是framework
  2. 如果上述修改后,还是找不到动态库,那么有三种办法让编出来的可执行文件找到动态库(其实是两种)
    a. 偷懒法:配DYLD_LIBRARY_PATH
    b. 绕过cgo选项检查:google还是提供了把选项加到了白名单的方法,把CGO_LDFLAGS_ALLOW环境变量配成允许使用的选项的正则表达式即可
    c. 手动新增rpath:编译完可执行文件后,用mac的install_name_tool -add_rpath命令手动添加rpath
标题: cgo运行时无法找到动态库问题记录
作者: QingChenW
链接: https://dawncraft.cc/2026/03/627/
本文遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 许可
禁止商用, 非商业转载请注明作者及来源!
上一篇
隐藏