一个简单的音视频解析go
程序需要放到一台没有go
环境的机器中去运行,都是linux
环境,本来以为是可以无缝迁移的。但实际上却发现运行报错,glibc
版本不一致。。。
因此打算直接编一个纯静态的可执行程序,依赖库都直接编译进去,这样就可以做到真正的无视平台限制。谁知道静态编译直接报错,好吧,那就总结一下静态编译相关知识点,并记录一下排查流程吧
博主使用的是manjaro
版本的linux
,目标服务器是ubuntu
版本且版本比较老。
go
默认是使用静态编译的方式,如果go
代码中使用的库不依赖C
库的话。不过复杂点的go
程序使用的包大概率是依赖系统C
库的,所以编译出来的文件是动态的,例如可以通过ldd
命令查看可执行程序以来的.so
文件。
ldd 可执行程序
linux-vdso.so.1 (0x00007ffeeaee7000)
libresolv.so.2 => /usr/lib/libresolv.so.2 (0x00007ff3838a6000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ff3836bf000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ff3838d3000)
具体为什么会动态编译,原理可以参考:
【go可执行文件的外部依赖】
静态编译有两种方式
默认情况下,go
的runtime
环境变量CGO_ENABLED=1
,即默认开始cgo
,允许你在go
代码中调用C
代码,go
的pre-compiled
标准库的.a
文件也是在这种情况下编译出来的。
我们可以在命令行指定CGO_ENABLED=0
就可以静态编译
CGO_ENABLED=0 go build .
go
默认是使用internal linking
,而无需启动外部external linker(如:gcc、clang等)
。而external linking
机制则是cmd/link
将所有生成的.o
都打到一个.o
文件中,再将其交给外部的链接器,比如gcc
或clang
去做最终链接处理。
我们想要静态编译的话,需要在 -ldflags
中指定linkmode
参数为external
,并且指定是静态链接。
-ldflags '-linkmode "external" -extldflags "-static"'
忽略'-linkmode "external" ,只设置-extldflags 也是ok的
静态编译一个项目,编译命令:
go build -o myapp -ldflags '-w -s -extldflags "-static"'
编译报错
/usr/lib/go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/bin/ld: cannot find -lopus: No such file or directory
collect2: error: ld returned 1 exit status
/usr/bin/ld 是 Linux 系统中的链接器(linker),用于将目标文件和库文件等链接起
来,生成最终的可执行文件或共享库。在大多数情况下,这个链接器已经默认设置
好,并且可以自动被编译器调用。
而对于 Go 语言的静态编译过程,我们需要在编译命令中加入相应的选项,指定使用
外部链接模式和静态链接方式,并将必要的库文件链接到生成的二进制文件中。具体
来说,可以使用 -ldflags 选项传递参数给链接器,包括 -linkmode external 表示启用
外部链接模式、-extldflags "-static" 表示启用静态链接方式等。
看起来是没找到libopus
,我们先确认本机上有没有安装libopus
ldconfig -p | grep libopus
通过包管理器查询libopus
pacman -Ql opus | grep libopus
opus /usr/lib/libopus.so
opus /usr/lib/libopus.so.0
opus /usr/lib/libopus.so.0.8.0
这个环境变量用于指定程序在运行时动态加载共享库(也称为动态链接库)时所要搜索的路径。当程序需要加载某个共享库时,它会按照以下顺序搜索路径:
程序中已经指定的库路径(如使用 -L 参数指定的路径)
LD_LIBRARY_PATH 中指定的路径
export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH
继续静态编译,还是寻找libopus
失败。 ok,开始有点意思了,我们下面详细排查下。
需要指定版本的话,使用这个命令:
sudo /usr/bin/pip install -i https://pypi.org/simple bcc==0.27.0
bcc
是一种跨平台的工具集,可用于在Linux
系统上进行动态追踪和探查。其中的opensnoop
工具可以用于监视应用程序打开、读取或写入文件的系统调用,以了解系统中哪些文件被访问,以及它们是如何被访问的。主要监听open()、read()、write()
等与文件操作相关的系统调用。
strace
也能查看系统调用函数,这里使用opensnoop
来进行排查。
# 开启一个窗口,输入这个命令
sudo opensnoop
# 开启另一个窗口,进行编译
go build -o myapp -ldflags '-w -s -extldflags "-static -lm"'
# 查看opensnoop的输出
结果发现编译过程中查找的是libopus.a文件,我们只有libopus.so文件
# 查看安装opus都会安装什么东西
sudo pacman -Ql opus
# 结果是没有.a文件
看来只能自己编译出来.a
文件或者去其他包管理平台下载了。。。
# 查看.so的版本
sudo pacman -Qo /usr/lib/libopus.so
/usr/lib/libopus.so is owned by opus 1.3.1-3
https://ubuntu.pkgs.org/20.04/ubuntu-main-amd64/libopus-dev_1.3.1-0ubuntu1_amd64.deb.html
查询到ubuntu
上的libopus
包是带有libopus.a
文件的。版本也能对得上,下载即可。
# 下载地址
https://www.cyberciti.biz/faq/how-to-extract-a-deb-file-without-opening-it-on-debian-or-ubuntu-linux/
# 查看下载的.deb包
file libopus-dev_1.3.1-0ubuntu1_amd64.deb
libopus-dev_1.3.1-0ubuntu1_amd64.deb: Debian binary package (format 2.0), with control.tar.xz, data compression xz
# 解压缩deb包
ar x libopus-dev_1.3.1-0ubuntu1_amd64.deb
# 解压完毕后会出现几个文件,主要用到data.tar.gz包,这个是存放二进制文件的压缩包
# 解压缩tar
tar -xvf data.tar.xz
# 可以发现libopus.a文件
./usr/lib/x86_64-linux-gnu/libopus.a
直接把libopus.a
文件放到当前目录,并设置搜索共享库路径。
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
执行编译依然报错,好像没有生效。
# 执行静态编译成功
# 查看静态编译文件
file myapp
myapp: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=8afbe7cba97e5f860ac49cfb6692e5eb5ec18cd5, for GNU/Linux 4.4.0, stripped
LD_LIBRARY_PATH
和 LIBRARY_PATH
都是用于指定共享库搜索路径的环境变量,但有一些细微的区别。
LD_LIBRARY_PATH
主要用于控制程序运行时加载共享库的路径,
而 LIBRARY_PATH
则主要用于控制编译器在编译时寻找共享库的路径。
静态编译要链接的是.a
文件,LD_LIBRARY_PATH
主要是设置.so
文件的搜索路径,所以就不生效
end