golang静态编译及编译失败排查步骤

一、背景

      一个简单的音视频解析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可执行文件的外部依赖】

1、执行静态编译

静态编译有两种方式

设置CGO_ENABLED方式

      默认情况下,goruntime环境变量CGO_ENABLED=1,即默认开始cgo,允许你在go代码中调用C代码,gopre-compiled标准库的.a文件也是在这种情况下编译出来的。
我们可以在命令行指定CGO_ENABLED=0就可以静态编译

CGO_ENABLED=0 go build .
指定link方式

      go默认是使用internal linking,而无需启动外部external linker(如:gcc、clang等)。而external linking机制则是cmd/link将所有生成的.o都打到一个.o文件中,再将其交给外部的链接器,比如gccclang去做最终链接处理。

我们想要静态编译的话,需要在 -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   

2、编译报错分析

/usr/bin/ld 是 Linux 系统中的链接器(linker),用于将目标文件和库文件等链接起
来,生成最终的可执行文件或共享库。在大多数情况下,这个链接器已经默认设置
好,并且可以自动被编译器调用。

而对于 Go 语言的静态编译过程,我们需要在编译命令中加入相应的选项,指定使用
外部链接模式和静态链接方式,并将必要的库文件链接到生成的二进制文件中。具体
来说,可以使用 -ldflags 选项传递参数给链接器,包括 -linkmode external 表示启用
外部链接模式、-extldflags "-static" 表示启用静态链接方式等。

看起来是没找到libopus,我们先确认本机上有没有安装libopus

(1)确认系统上有没有安装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

(2)设置LD_LIBRARY_PATH

这个环境变量用于指定程序在运行时动态加载共享库(也称为动态链接库)时所要搜索的路径。当程序需要加载某个共享库时,它会按照以下顺序搜索路径:

程序中已经指定的库路径(如使用 -L 参数指定的路径)
LD_LIBRARY_PATH 中指定的路径
export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH  

继续静态编译,还是寻找libopus失败。 ok,开始有点意思了,我们下面详细排查下。

三、详细排查过程

1、下载bpf排查工具bcc, bcc-tools,python-bcc

      需要指定版本的话,使用这个命令:

sudo /usr/bin/pip install -i https://pypi.org/simple bcc==0.27.0 

bcc是一种跨平台的工具集,可用于在Linux系统上进行动态追踪和探查。其中的opensnoop工具可以用于监视应用程序打开、读取或写入文件的系统调用,以了解系统中哪些文件被访问,以及它们是如何被访问的。主要监听open()、read()、write()等与文件操作相关的系统调用。

strace也能查看系统调用函数,这里使用opensnoop来进行排查。

2、使用opensnoop 排查编译过程查找共享库的路径都有哪些

# 开启一个窗口,输入这个命令
sudo opensnoop

# 开启另一个窗口,进行编译
go build -o myapp -ldflags '-w -s -extldflags "-static -lm"'

# 查看opensnoop的输出
结果发现编译过程中查找的是libopus.a文件,我们只有libopus.so文件

3、pacman下载opus包

# 查看安装opus都会安装什么东西
sudo pacman -Ql opus

# 结果是没有.a文件

看来只能自己编译出来.a文件或者去其他包管理平台下载了。。。

4、查看当前的libopus.so文件的版本

# 查看.so的版本
sudo pacman -Qo /usr/lib/libopus.so
/usr/lib/libopus.so is owned by opus 1.3.1-3

5、下载libopus.a

https://ubuntu.pkgs.org/20.04/ubuntu-main-amd64/libopus-dev_1.3.1-0ubuntu1_amd64.deb.html

查询到ubuntu上的libopus包是带有libopus.a文件的。版本也能对得上,下载即可。

6、.deb文件解压缩

# 下载地址
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

7、添加LD_LIBRARY_PATH

直接把libopus.a文件放到当前目录,并设置搜索共享库路径。

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

执行编译依然报错,好像没有生效。

8、添加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

9、LD_LIBRARY_PATH和LIBRARY_PATH的区别

      LD_LIBRARY_PATHLIBRARY_PATH 都是用于指定共享库搜索路径的环境变量,但有一些细微的区别。

LD_LIBRARY_PATH 主要用于控制程序运行时加载共享库的路径,
LIBRARY_PATH 则主要用于控制编译器在编译时寻找共享库的路径。

静态编译要链接的是.a文件,LD_LIBRARY_PATH主要是设置.so文件的搜索路径,所以就不生效

end

猜你喜欢

转载自blog.csdn.net/LJFPHP/article/details/132031641