0x00 前言:为什么需要静态编译

简而言之,本文的实验目标是:在 Debian 13(2024/12/19 sid,内核 6.12.5,glibc 2.40)上,编译出一个带有大量依赖库的可执行文件, 放到 CentOS 5(2007 年发布,内核 2.6.18,glibc 2.5)执行。换句话说,我们要编译出可以直接运行于绝大部分 Linux 系统的程序。

笔者回想起数年之前,刚刚进入实验室的时候,研究组有一台公用服务器,但我们只有普通用户权限,所以无法给服务器添加 .so 动态链接库。从而,有些软件必须静态编译。在华为实习期间,服务器是 arm 指令集,所以不仅需要静态编译,还需要交叉编译。

静态编译是很现实的需求——作为软件开发者,我们当然希望所有客户都能直接运行自己的软件;身为服务器管理员,面对旧版系统,如果想安装 btop 这样的现代工具,我们也需要在自己电脑上编译出一个版本,传到服务器上执行。而且,我们希望对服务器的影响尽量小,所以我们不会在服务器上安装各种动态库,而是尽量把所有的库都打包进单一的可执行文件。

脚本语言当然不受系统版本的影响,基于 VM 的语言(例如 java)一般也无需关心这种问题,即所谓“一次编译,到处运行”。而 Golang 为了解决兼容性问题,直接把 runtime 打包进了程序,不依赖于任何外部库。我们讨论静态编译,主要是针对 C/C++ 程序来说的。在本文中,我们将会从“打包依赖库”开始,尝试一步步解决问题。

0x01 一次失败的软件分发

在编译复杂软件之前,我们先来做一点小实验:写一个程序,调用 Markdown 解析库 Orc/discount(它的 Debian 包是 discountlibmarkdown2-dev),把 hello, **world** 这个字符串解析成 HTML 输出。首先安装 discount 库:

sudo apt install discount libmarkdown2-dev

编写代码:

#include <mkdio.h>
#include <stdio.h>

int main(void) {
    const char a[] = "hello, **world**";
    char *html;

    MMIOT *doc = mkd_string(a, sizeof(a), 0);
    mkd_compile(doc, 0);
    mkd_document(doc, &html);

    puts(html);

    return 0;
}

在 Debian 13 上编译并运行:

gcc app.c -o app -lmarkdown

./app
# <p>hello, <strong>world</strong></p>

输出完全符合我们的要求,所以这个软件的开发算是完成了。现在,把软件传给一位 Debian 12 用户,他的运行结果是:

./app: error while loading shared libraries: libmarkdown.so.2: cannot open shared object file: No such file or directory

提示我们找不到 libmarkdown.so.2 库。我们可以在开发机、客户机上分别运行 ldd 指令,查看 app 程序的依赖库:

# 开发机
neko@dev ~/b/mymark> ldd ./app
        linux-vdso.so.1 (0x00007fb4429e6000)
        libmarkdown.so.2 => /lib/x86_64-linux-gnu/libmarkdown.so.2 (0x00007fb4429c0000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb4427ca000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fb4429e8000)
        
# 客户机
neko@center:~$ ldd app
        linux-vdso.so.1 (0x00007ffd54874000)
        libmarkdown.so.2 => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f67b4447000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f67b4632000)

可见,客户机上拥有 linux-vdso.so.1libc.so.6ld-linux-x86-64.so.2,但缺乏 libmarkdown.so.2,导致执行失败。我们必须把 libmarkdown 提供给用户,要么使用动态链接库,要么静态链接进程序。

0x02 动态链接库、PLT 和 GOT

先试着通过分发动态链接库解决问题。我们把开发机上的 libmarkdown.so.2 复制给客户,让客户设置 LD_LIBRARY_PATH 环境变量,即可正常工作:

LD_LIBRARY_PATH=. ldd ./app
#        linux-vdso.so.1 (0x00007ffe51d8d000)
#        libmarkdown.so.2 => ./libmarkdown.so.2 (0x00007f507c88c000)
#        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f507c6a8000)
#        /lib64/ld-linux-x86-64.so.2 (0x00007f507c8a7000)
LD_LIBRARY_PATH=. ./app
# <p>hello, <strong>world</strong></p>

当然,也可以使用 LD_PRELOAD 环境变量:

LD_PRELOAD="./libmarkdown.so.2" ./app
# <p>hello, <strong>world</strong></p>
💡
许多 Windows 软件的安装目录下,有大量的 .dll 文件。这是因为,Windows 会加载应用程序所在目录下的 .dll 库,所以 Windows 用户无需设置 LD_PRELOAD 之类的环境变量,就能使用这些第三方库。

我们来看看 libmarkdown API 的调用过程。反汇编 app 程序,我们看到三个 call:

被 call 的函数不是 mkd_string 本身,而是它对应的 PLT 表项:

可见它会跳转到 GOT 表记载的位置,而初始状态下,GOT 表中记录的是 plt 条目中的第二条指令:

于是程序跳回 mkd_string@plt+6 位置,执行以下两句汇编:

   0x555555555056 <mkd_string@plt+6>:   push   $0x2
   0x55555555505b <mkd_string@plt+11>:  jmp    0x555555555020

跟进:

跳转到 *0x2fcc(%rip),这个指针里的值是:

跟进:

这个地址在 ld-linux-x86-64.so.2 里面,可见我们已经来到了链接器的地盘。

链接器会去帮我们寻找 mkd_string 的地址,并写回到 GOT 表。以后我们进入 PLT,便会直接跳转到 mkd_string 函数地址。至此,我们追踪完了函数加载过程。

0x03 打包 libmarkdown.a

然而,我们的最终目标是把所有依赖都打包进单个文件。动态链接使用的是 .so 文件,而静态链接使用的是 .a 文件,我们没有 libmarkdown.a。这没关系,我们自己编译一个:

git clone https://github.com/Orc/discount.git --depth=1

cd discount
./configure.sh
make -j16

du -h libmarkdown.a
# 380K    libmarkdown.a

现在,把 libmarkdown.a 打包进软件:

gcc app.c ./discount/libmarkdown.a -o app

现在,我们发现 app 没有 libmarkdown.so 这项依赖了:

du -h app
# 200K    app

ldd app
#        linux-vdso.so.1 (0x00007fa475a28000)
#        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa47580e000)
#        /lib64/ld-linux-x86-64.so.2 (0x00007fa475a2a000)

在客户机上亦可正确工作:

neko@center:~$ ./app
# <p>hello, <strong>world</strong></p>

用 IDA 打开 app 看一眼,发现我们使用到的函数已经编译进来了,不再使用 PLT 跳转:

我们确实在 Debian 13 上编译出了 Debian 12 可以运行的程序,但当我们把程序放到 CentOS 5 上运行时,意外发生了。

0x04 从 glibc 到 musl

在 CentOS 5 客户机上的执行结果如下:

./app
Segmentation fault
ldd app
# ./app: /lib64/libc.so.6: version `GLIBC_2.7' not found (required by ./app)
# ./app: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./app)
# ./app: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by ./app)
#        linux-vdso.so.1 =>  (0x00007fff139fd000)
#        libc.so.6 => /lib64/libc.so.6 (0x00002ac25bf3f000)
#        /lib64/ld-linux-x86-64.so.2 (0x0000003444600000)

看起来是 libc.so 的版本太旧。那么,我们故技重施,把开发机上面的 glibc 2.40 拷到客户机上,再次运行:

LD_PRELOAD="./libc.so.6" ./app
# ERROR: ld.so: object './libc.so.6' from LD_PRELOAD cannot be preloaded: ignored.
# ./app: /lib64/libc.so.6: version `GLIBC_2.7' not found (required by ./app)
# ./app: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./app)
# ./app: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by ./app)

查找资料,从一篇 stackoverflow 问答中得知,ld.so 的版本需要与 libc.so.6 版本匹配。我们看看客户机上的 ld 版本:

[root@localhost ~]# ld -v
GNU ld version 2.17.50.0.6-26.el5 20061020

[root@localhost ~]# /lib64/libc.so.6
GNU C Library stable release version 2.5, by Roland McGrath et al.
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.1.2 20080704 (Red Hat 4.1.2-54).
Compiled on a Linux 2.6.9 system on 2014-09-16.
Available extensions:
        The C stubs add-on version 2.1.2.
        crypt add-on version 2.1 by Michael Glad and others
        GNU Libidn by Simon Josefsson
        GNU libio by Per Bothner
        NIS(YP)/NIS+ NSS modules 0.19 by Thorsten Kukuk
        Native POSIX Threads Library by Ulrich Drepper et al
        BIND-8.2.3-T5B
        RT using linux kernel aio
Thread-local storage support included.
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

而开发机的 ld 版本是 2.40:

neko@dev ~ [1]> ld.so --version
ld.so (Debian GLIBC 2.40-4) stable release version 2.40.
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

按照一篇 stackexange 问答的指引,我们把新版本的 ld-linux-x86-64.so.2 也送来,然而:

[root@localhost ~]# LD_LIBRARY_PATH="./" ./ld-linux-x86-64.so.2 ./app
Segmentation fault

我们似乎陷入了僵局。再看一眼开发机的 glibc:

neko@dev ~/b/mymark> /lib/x86_64-linux-gnu/libc.so.6 
GNU C Library (Debian GLIBC 2.40-4) stable release version 2.40.
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 14.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
Minimum supported kernel: 3.2.0
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.

注意这一句 Minimum supported kernel: 3.2.0,似乎从这个 glibc 编译出的程序,对内核版本是有要求的。而我们客户机的内核是 2.6.18-398.el5,低于 3.2.0,所以跑不起来也算是预料之中。这篇 stackexange 问答详细解释了这里为什么是 3.2.0,值得一读。

我们是不可能升级内核的,所以通过动态链接库提供高版本 glibc 的路线走到了尽头。那么,我们能把 glibc 像 libmarkdown 一样静态编译进程序吗?很遗憾,这也是不可行的,详情参考这篇 stackoverflow 问答

既然问题出在 glibc 上,我们考虑用其他可以静态链接的 libc 换掉 glibc。musl 就是一个不错的选择。按照指引,我们用 musl-gcc 工具代替 gcc

sudo apt install musl musl-dev musl-tools

musl-gcc app.c ./discount/libmarkdown.a -o app -static
# app.c:1:10: fatal error: mkdio.h: No such file or directory
#     1 | #include <mkdio.h>
#       |          ^~~~~~~~~
# compilation terminated.

gcc 能找到的头文件,musl-gcc 没有找到。我们通过 -I 参数手动提供 header 目录:

musl-gcc app.c ./discount/libmarkdown.a -I /usr/include/x86_64-linux-gnu -o app -static

du -h app
# 256K    app

musl-ldd app
# musl-ldd: app: Not a valid dynamic program

终于,我们的 CentOS 5 也能执行程序了:

[root@localhost ~]# ./app
<p>hello, <strong>world</strong></p>

回顾一遍我们干的事情。首先,我们编译出依赖库的 .a 文件,以便静态链接;接下来,我们用 musl 取代了 glibc,最终获得了一个完全静态链接的程序。不过,上述实验是针对一个小规模程序进行的,只使用了 libmarkdown 这一个依赖;后文将静态编译一些其他的程序。

0x05 静态编译 htop

现在为 CentOS 5 编译一个最新版的 htop。在静态编译之前,我们先走一遍常规编译流程,看看它依赖哪些库:

sudo apt install libncursesw5-dev autotools-dev autoconf automake build-essential

git clone https://github.com/htop-dev/htop.git --depth=1

cd htop
./autogen.sh
./configure
make -j16

du -h ./htop
# 1.5M    ./htop
ldd ./htop
#        linux-vdso.so.1 (0x00007fd833f7c000)
#        libncursesw.so.6 => /lib/x86_64-linux-gnu/libncursesw.so.6 (0x00007fd833ee0000)
#        libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007fd833eab000)
#        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd833dc5000)
#        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd833bcf000)
#        /lib64/ld-linux-x86-64.so.2 (0x00007fd833f7e000)

尝试打开 --enable-static 选项,静态链接:

./configure --enable-static
#
#  htop 3.4.0-dev-762acbe
#
#  platform:                  linux
#  os-release file:           /etc/os-release
#  (Linux) proc directory:    /proc
#  (Linux) openvz:            no
#  (Linux) vserver:           no
#  (Linux) ancient vserver:   no
#  (Linux) delay accounting:  no
#  (Linux) sensors:           no
#  (Linux) capabilities:      no
#  unicode:                   yes
#  affinity:                  yes
#  unwind:                    no
#  hwloc:                     no
#  debug:                     no
#  static:                    yes


make -j16

du -h ./htop
# 2.9M    ./htop
ldd ./htop
#        not a dynamic executable

确实是静态链接了,然而在 CentOS 上仍然无法运行:

[root@localhost ~]# ./htop
Segmentation fault

考虑使用 musl 代替 glibc。

# ./configure CC=musl-gcc
configure: error: cannot find required curses/ncurses library

提示找不到 ncurses 库,我们需要提供对应的 .a 文件和 header 文件。然而,我们手头只有 /lib/x86_64-linux-gnu/ 里面的 libncursesw.a,它是在 glibc 上编译的。我们需要在 musl 上编译 libncursesw 等依赖库,这是庞大的工作量。换一种思路——既然 Debian 软件仓库有预编译的 glibc 版依赖库,那 Alpine Linux 的软件仓库里面应该也能找到 musl 版依赖库。

我们看一眼 Alpine Linux 里的 htop 包构建脚本构建日志

# Contributor: Sören Tempel <soeren+alpine@soeren-tempel.net>
# Maintainer: Carlo Landmeter <clandmeter@alpinelinux.org>
pkgname=htop
pkgver=3.3.0
pkgrel=0
pkgdesc="Interactive process viewer"
url="https://htop.dev/"
arch="all"
license="GPL-2.0-or-later"
makedepends="ncurses-dev python3 linux-headers lm-sensors-dev"
subpackages="$pkgname-doc"
source="https://github.com/htop-dev/htop/releases/download/$pkgver/htop-$pkgver.tar.xz"
options="!check" # no upstream/available test-suite

build() {
	./configure \
		--build=$CBUILD \
		--host=$CHOST \
		--prefix=/usr \
		--sysconfdir=/etc \
		--mandir=/usr/share/man \
		--localstatedir=/var \
		--enable-cgroup \
		--enable-taskstats
	make
}

package() {
	make DESTDIR="$pkgdir" pixmapdir=/usr/share/icons/hicolor/128x128/apps install
}

sha512sums="
f98d4a4370954969d0ae16993d80ca5ce48670a711f17445de979513ac9caf2b197291732d937ae07d48709ded660ea09601b3a41ad7c48b3abb87e7a67deb65  htop-3.3.0.tar.xz
"

我们跟随这个步骤来操作:

apk add build-base autoconf automake
apk add ncurses-dev python3 linux-headers lm-sensors-dev

./configure
make -j4

du -h htop
# 1.5M    htop
ldd htop
#        /lib/ld-musl-x86_64.so.1 (0x7f6e9e7c7000)
#        libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x7f6e9e717000)
#        libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f6e9e7c7000)

动态编译成功。现在静态编译:

make clean
./configure --enable-static=yes
# configure: error: cannot find required curses/ncurses library

我们仍然缺少 libncursesw.a 文件。在 Alpine Linux 官网上搜索

发现 ncurses-static 包里面有我们所需的文件。继续编译:

apk add ncurses-static

./configure --enable-static=yes
#  htop 3.4.0-dev
#
#  platform:                  linux
#  os-release file:           /etc/os-release
#  (Linux) proc directory:    /proc
#  (Linux) openvz:            no
#  (Linux) vserver:           no
#  (Linux) ancient vserver:   no
#  (Linux) delay accounting:  no
#  (Linux) sensors:           yes
#  (Linux) capabilities:      no
#  unicode:                   yes
#  affinity:                  yes
#  unwind:                    no
#  hwloc:                     no
#  debug:                     no
#  static:                    yes

make -j4


du -h htop
# 2.4M    htop
ldd htop
# /lib/ld-musl-x86_64.so.1: htop: Not a valid dynamic program

至此静态编译完成。CentOS 5 成功运行:

0x06 静态编译 btop

htop 是纯 C 写的,而 btop 的主要语言是 C++,因此本文选择 btop 作为 C++ 静态编译的例子。先动态编译:

apk add coreutils-fmt lowdown linux-headers

git clone https://github.com/aristocratos/btop.git --depth=1
cd btop

make ADDFLAGS="-fno-ipa-cp"
# 83%  -> obj/btop_tools.o              (2.1MiB) (09s)
# 90%  -> obj/linux/btop_collect.o      (3.2MiB) (12s)
# 
# Linking and optimizing binary...
# 100% -> bin/btop                      (1.8MiB) (17s)
#
# Build complete in (46s)


du -h ./bin/btop 
# 1.8M    ./bin/btop
ldd ./bin/btop 
#        /lib/ld-musl-x86_64.so.1 (0x7fa06187b000)
#        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x7fa061400000)
#        libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7fa0616c9000)
#        libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7fa06187b000)

改用静态编译:

make clean
make STATIC=true ADDFLAGS="-fno-ipa-cp"

du -h ./bin/btop 
# 4.1M    ./bin/btop
ldd ./bin/btop 
# /lib/ld-musl-x86_64.so.1: ./bin/btop: Not a valid dynamic program

CentOS 运行成功,不过有点 bug,无法展示程序名:

0x07 静态编译 net-snmp

接下来,我们静态编译一份 net-snmp,包含 snmpd 和 snmpwalk 等工具。先准备源码:

apk add file perl-dev openssl-dev perl-net-snmp perl-tk linux-headers

wget https://github.com/net-snmp/net-snmp/archive/refs/tags/v5.9.4.tar.gz
tar -zxvf v5.9.4.tar.gz
cd net-snmp-5.9.4

尝试动态编译:

./configure --with-defaults
make -j4

编译失败:

libtool: compile:  gcc -I../include -I. -I../snmplib -g -O2 -DNETSNMP_ENABLE_IPV6 -fno-strict-aliasing -DNETSNMP_REMOVE_U64 -g -O2 -Ulinux -Dlinux=linux -D_REENTRANT -D_GNU_SOURCE -D_GNU_SOURCE -fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -I/usr/lib/perl5/core_perl/CORE -Wall -Wextra -Wstrict-prototypes -Wwrite-strings -Wcast-qual -Wimplicit-fallthrough=2 -Wlogical-op -Wundef -Wno-format-truncation -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -c text_utils.c -o text_utils.o >/dev/null 2>&1
In file included from large_fd_set.c:12:
large_fd_set.c: In function 'LFD_SET':
../include/net-snmp/net-snmp-config.h:1614:30: error: unknown type name 'unknown'; did you mean 'union'?
 1614 | #define NETSNMP_FD_MASK_TYPE unknown
      |                              ^~~~~~~
large_fd_set.c:97:5: note: in expansion of macro 'NETSNMP_FD_MASK_TYPE'
   97 |     NETSNMP_FD_MASK_TYPE *fds_array = p->fds_bits;
      |     ^~~~~~~~~~~~~~~~~~~~
large_fd_set.c:97:39: error: initialization of 'int *' from incompatible pointer type 'long unsigned int *' [-Wincompatible-pointer-types]
   97 |     NETSNMP_FD_MASK_TYPE *fds_array = p->fds_bits;
      |                                       ^
large_fd_set.c: In function 'LFD_CLR':
../include/net-snmp/net-snmp-config.h:1614:30: error: unknown type name 'unknown'; did you mean 'union'?
 1614 | #define NETSNMP_FD_MASK_TYPE unknown
      |                              ^~~~~~~
large_fd_set.c:105:5: note: in expansion of macro 'NETSNMP_FD_MASK_TYPE'
  105 |     NETSNMP_FD_MASK_TYPE *fds_array = p->fds_bits;
      |     ^~~~~~~~~~~~~~~~~~~~
large_fd_set.c:105:39: error: initialization of 'int *' from incompatible pointer type 'long unsigned int *' [-Wincompatible-pointer-types]
  105 |     NETSNMP_FD_MASK_TYPE *fds_array = p->fds_bits;
      |                                       ^
large_fd_set.c: In function 'LFD_ISSET':
../include/net-snmp/net-snmp-config.h:1614:30: error: unknown type name 'unknown'; did you mean 'union'?
 1614 | #define NETSNMP_FD_MASK_TYPE unknown
      |                              ^~~~~~~
large_fd_set.c:113:11: note: in expansion of macro 'NETSNMP_FD_MASK_TYPE'
  113 |     const NETSNMP_FD_MASK_TYPE *fds_array = p->fds_bits;
      |           ^~~~~~~~~~~~~~~~~~~~
large_fd_set.c:113:45: error: initialization of 'const int *' from incompatible pointer type 'const long unsigned int *' [-Wincompatible-pointer-types]
  113 |     const NETSNMP_FD_MASK_TYPE *fds_array = p->fds_bits;
      |                                             ^
make[1]: *** [Makefile:100: large_fd_set.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make[1]: Leaving directory '/root/work/net-snmp-5.9.4/snmplib'
make: *** [Makefile:671: subdirs] Error 1

应用一个 Alpine Linux 打包团队的 patch

wget https://gitlab.alpinelinux.org/alpine/aports/-/raw/master/main/net-snmp/fix-fd_mask.patch
patch -p1 < fix-fd_mask.patch 

./configure --with-defaults
make -j24

动态编译成功。现在改为静态编译:

./configure --with-defaults --with-systemd --enable-static=yes --enable-shared=no

make -j24

du -h ./agent/snmpd
# 8.4M    ./agent/snmpd
ldd ./agent/snmpd
#        /lib/ld-musl-x86_64.so.1 (0x7f4222478000)
#        libcrypto.so.3 => /usr/lib/libcrypto.so.3 (0x7f4221a00000)
#        libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f4222478000)

发现 libc 和 libcrypto 还没有被静态链接进去。我们指定 -static 这个 CFLAGS 再试试:

./configure --with-defaults --with-systemd --enable-static=yes --enable-shared=no --with-openssl --disable-embedded-perl CFLAGS="-static"

然而,编译结果仍然动态链接了 libcrypto.so。辗转找到一个 iperf 项目的 issue,提到使用 LDFLAGS=--static 以解决问题。重新尝试,果然成功:

./configure --with-defaults --with-systemd --enable-static=yes --enable-shared=no --with-openssl LDFLAGS="--static"
make clean
make -j24

du -h agent/snmpd
# 21.8M   agent/snmpd

ldd agent/snmpd
# /lib/ld-musl-x86_64.so.1: agent/snmpd: Not a valid dynamic program

CentOS 可以正常执行 snmpwalk 程序获取其他服务器的信息: