资讯动态

Ubuntu 18.04 安装 GLIBC 2.28:源码编译与动态链接器切换实践

发布时间:2026/10/5 2:54:34 来源:尧图企业网站定制
开头直接切入主题。我最近又双叒叕在 Ubuntu 18.04 上栽了一次 GLIBC 的坑。明明系统里跑着好好的服务结果同事扔过来一个二进制包一执行就报version GLIBC_2.28 not found。查了一圈18.04 自带的 GLIBC 是 2.27而很多 2019 年之后编译的闭源软件、商业工具链、甚至某些新版本数据库客户端都默认链接了 2.28 的符号。系统又不方便整体升级到 20.04于是我又把“源码编译安装 GLIBC 2.28”这条路完整走了一遍。这篇博客就把全过程拆开揉碎从原理到实操再到排坑一次性讲清楚。先说明一个重要前提我说的“安装 GLIBC 2.28”不是让你替换掉系统自带的/lib/x86_64-linux-gnu/libc.so.6而是把 2.28 编译到一个独立目录比如/opt/glibc-2.28再用具体程序的时候单独指定动态库路径。直接在 18.04 上覆盖系统 GLIBC基本等于自杀bash、ls、sudo全都会挂SSH 都连不进去。这个问题后面我会反复强调因为真有人这么干过然后只能进恢复模式救系统。本文适合这几类人看运维工程师给老服务器装新软件时遇到 GLIBC 版本不匹配开发者在 CI 镜像里想手动构建 GLIBC或者单纯好奇 GLIBC 编译流程的 Linux 爱好者。只要你能敲命令按下面步骤走大概率能一次成功。1. GLIBC 2.28 到底卡住了什么1.1 老系统和新二进制的兼容性矛盾先解释一下 GLIBC 版本不匹配的本质。GLIBC 是 GNU C Library也就是用户态程序几乎绕不开的 C 运行时库。Linux 下一个普通的可执行文件动态链接器会在启动阶段就加载它程序里用到的malloc、printf、pthread_create这些符号都在这个库里。问题是 GLIBC 的符号是带版本标签的比如memcpyGLIBC_2.14、getrandomGLIBC_2.25。程序在编译时如果链接器发现某个符号需要更高版本的 GLIBC就会在.gnu.version段里写下一个GLIBC_2.28标记。运行时动态链接器一看你要GLIBC_2.28我这个库最高只提供GLIBC_2.27直接报错拒绝启动。Ubuntu 18.04 默认的 GLIBC 是 2.27这是 2018 年初的快照。很多软件后来切换到更新的工具链比如 GCC 8 之后的版本配合较新的 binutils编出来的程序就可能会请求GLIBC_2.28甚至更高的符号。Debian 10 的 GLIBC 才是 2.28Ubuntu 18.10 也是 2.28但 18.04 LTS 因为发布时间早卡在了 2.27。所以你想在 18.04 上直接跑这些新二进制最常见的报错就是./some_tool: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.28 not found (required by ./some_tool)注意这个报错只说明某个二进制文件需要 GLIBC 2.28 的符号并不代表全系统都要升级。很多人第一步就理解错了以为要全盘升级。1.2 为什么不能替换系统自带的 GLIBCGLIBC 和普通软件有个本质区别它是几乎所有用户态程序的父依赖。你的bash、cp、grep、apt、python全部链接到同一个/lib/x86_64-linux-gnu/libc.so.6。如果你把 2.28 编译后直接覆盖系统路径会发生两件事新版 GLIBC 要求的内核版本、ld-linux-x86-64.so.2路径、某些内部数据结构可能和旧系统其他库不匹配导致旧程序崩溃。即使新版 GLIBC 大部分向后兼容但 18.04 上还有其他关键组件比如libpthread、libdl的拆分方式、NSS 模块、pam 模块是基于 2.27 构建的混合使用就会出现各种诡异问题。我见过有人在 16.04 上强行用.deb包装 2.28结果apt直接没法用所有和网络相关的命令都段错误。所以一定要把新版 GLIBC 隔离安装互不影响。1.3 两条公认的安全路线目前社区里常见的做法有两种。第一编译 GLIBC 到一个自定义前缀目录比如/opt/glibc-2.28然后通过LD_LIBRARY_PATH或修改二进制文件的 interpreter 来“临时”使用它只对特定程序生效系统其他部分不受影响。第二直接用 Docker 容器或者 chroot 环境在容器里跑一个 Debian 10 或 Ubuntu 18.10 的根文件系统。容器方案最干净但有些场景下你必须在宿主机上直接跑目标程序比如内核模块配套的工具、依赖宿主机设备节点的程序那就只能走第一条路。本文详细展开第一条路线因为它最灵活也最常被问到。2. 环境准备与核心原理2.1 确认当前 GLIBC 版本动手之前先看清楚底牌ldd --versionUbuntu 18.04 会输出最后一行GCC ld.so 2.27或类似信息。如果你想更精确地看程序到底缺什么可以用objdump -T /path/to/binary | grep GLIBC_ | sort -u | tail -n 10这会列出目标二进制引用的所有 GLIBC 符号版本。如果里面有GLIBC_2.28那基本确定要走本文的流程。我记得有一次帮客户排查他抱怨某个机器学习推理工具启动报错我objdump一看那个程序引用的符号列表里甚至有GLIBC_2.34那光装 2.28 也救不了得装更新版本或者换系统。所以第一步检查非常重要别装完 2.28 才发现程序需要的是 2.30白忙一场。2.2 准备编译环境和源码编译 GLIBC 对构建环境有要求不能直接 root 编译但在容器里可以用 root。这里在 Ubuntu 18.04 上先装基础工具链sudo apt update sudo apt install -y build-essential bison flex gawk python3 make file texinfo patchelf wget curlbison和flex用来生成 GLIBC 的一些解析器代码gawk是构建脚本需要的python3新版 GLIBC 构建也依赖。patchelf后面修改二进制文件时会用到强烈建议提前装。源码从 GNU 官方镜像站或 GNU FTP 下载。注意一定要选官方glibc-2.28.tar.gz不要随便去 GitHub 拉某个 commit因为标准 release 包带了完整的configure脚本开发分支往往需要 bootstrap 过程复杂得多wget https://ftp.gnu.org/gnu/glibc/glibc-2.28.tar.gz tar -xzf glibc-2.28.tar.gz2.3 为什么用/opt/glibc-2.28而不是默认/usr/local很多人习惯./configure --prefix/usr/local这在编译 GLIBC 时是一颗雷。因为/usr/local往往也在系统默认搜索路径里如果以后某个程序动态链接时优先找到了/usr/local/lib/libc.so.6而它又和系统里的其他库不匹配会出问题。更关键的是GLIBC 编译时必须指定一个独立前缀才能确保动态链接器路径被正确配置。我建议统一前缀为/opt/glibc-2.28这样生成的动态链接器就是/opt/glibc-2.28/lib/ld-linux-x86-64.so.2它和系统原来的/lib64/ld-linux-x86-64.so.2井水不犯河水。Greg KH 在他的博客里也强调过自定义前缀是隔离损坏系统的最佳手段。还要注意GLIBC 的 build 目录必须与源码目录分开在源码根目录里直接 configure 会提示不支持。规范流程是mkdir -p ~/glibc-build cd ~/glibc-build3. 从源码构建 GLIBC 2.28 的完整实操3.1 配置与参数选择进入 build 目录运行cd ~/glibc-build ../glibc-2.28/configure --prefix/opt/glibc-2.28 --disable-werror --enable-obsolete-rpc--disable-werror很重要。Ubuntu 18.04 默认 GCC 是 7.5但如果你装了新版 GCC 或者 binutils 版本不够某些警告会被当成错误导致编译中断。加上这个参数可以避免很多无谓的失败。--enable-obsolete-rpc是可选的但很多老项目需要 SUN RPC 接口GLIBC 2.28 默认没开加上它方便以后链接旧程序。有人问要不要加--enable-static-nss。如果目标程序要在 chroot 环境、或者没有 nss 模块的环境里跑再加这个选项否则不要加因为静态 NSS 会增加库的体积。我们这里先不加。3.2 编译过程的实际输出解读配置完成后直接make -j$(nproc)这一步会花 5 到 15 分钟取决于机器性能。编译过程中你可能会看到大量CC、CXX、AR的日志。这里提两个我踩过的坑第一如果机器内存小于 2Gmake -j并发数太高会被 OOM killer 干掉。这时候建议改成make -j2或者make -j1慢一点但稳。第二编译期间会生成本地化的 locale 数据比如/usr/lib/locale相关文件这一环节在某些精简系统上会因为缺少localedef工具而失败。解决方法是通过环境变量让构建系统跳过本地化生成或者先安装locales包sudo apt install -y locales sudo locale-gen en_US.UTF-8不过我们的目标前缀是/opt/glibc-2.28它不会覆盖系统 locale所以即使 localedef 失败也不影响最终运行。编译结束后执行make install注意make install的目标前缀是/opt/glibc-2.28因为我们在 configure 时指定了。这不会动系统库直接执行即可。3.3 安装后的目录结构长什么样安装完成后/opt/glibc-2.28/lib下应该有/opt/glibc-2.28/lib/libc.so.6 /opt/glibc-2.28/lib/ld-linux-x86-64.so.2 /opt/glibc-2.28/lib/libm.so.6 /opt/glibc-2.28/lib/libpthread.so.0 ...其中最关键的两个文件就是libc.so.6和ld-linux-x86-64.so.2。前者是核心库后者是动态链接器。当你用这个新的动态链接器去启动程序时它会优先搜索/opt/glibc-2.28/lib下的库而不是系统的/lib/x86_64-linux-gnu这就是隔离运行的原理。验证安装文件完整性可以这么做/opt/glibc-2.28/lib/ld-linux-x86-64.so.2 --version如果输出最后一行是GCC ld.so 2.28说明新的动态链接器已经就绪。4. 让目标程序改用 GLIBC 2.28 的三种方法这部分是关键中的关键。装好/opt/glibc-2.28只是第一步怎么让目标程序用上它有几种常见方案按适用场景排序。4.1 方案一用新版动态链接器直接启动假设你的二进制是/opt/apps/my_tool直接这样跑/opt/glibc-2.28/lib/ld-linux-x86-64.so.2 /opt/apps/my_tool这相当于手动指定了解释器跳过系统默认的/lib64/ld-linux-x86-64.so.2然后新的解释器会按它自己的规则寻找依赖库。由于/opt/glibc-2.28/lib在编译时被内置为默认搜索路径程序就会加载 2.28 的库。这个方案最保守不需要改动二进制文件适合临时验证或者写个 wrapper 脚本包一层#!/bin/bash exec /opt/glibc-2.28/lib/ld-linux-x86-64.so.2 /opt/apps/my_tool $缺点是每次都要带前缀而且如果目标程序内部又用exec启动了其他子进程子进程还是会用系统的解释器又打回原形。所以这个方案适合单文件工具。4.2 方案二patchelf 修改解释器路径用patchelf把二进制里的程序解释器直接改成新路径patchelf --set-interpreter /opt/glibc-2.28/lib/ld-linux-x86-64.so.2 /opt/apps/my_tool同时把RPATH或RUNPATH指到新库目录patchelf --set-rpath /opt/glibc-2.28/lib /opt/apps/my_tool修改之后这个程序启动时就会直接使用新解释器。好处是一劳永逸不用每次敲长前缀。坏处是程序文件被改了如果你需要保持原始文件的完整性记得先备份。另外patchelf不是所有 ELF 都能顺利改特别是有些程序带了严格的完整性校验或签名改完会无法运行。那种情况建议用方案一或方案三。我当时给客户处理一个商业闭源软件时就是用patchelf --set-interpreter一步搞定的。那个软件还会启动一个 web 服务子进程子进程是从主进程fork出来的不是重新exec所以继承了解释器没有出现问题。如果你遇到的是子进程重新exec的情况那就需要用下面的方案三。4.3 方案三指定 LD_LIBRARY_PATH 加上系统解释器如果你不想动解释器只想让程序用新版库可以这样LD_LIBRARY_PATH/opt/glibc-2.28/lib /opt/apps/my_tool但这里有一个隐蔽的坑动态链接器本身是系统的 2.27它是被/lib64/ld-linux-x86-64.so.2提前加载的它已经运行起来了你再通过LD_LIBRARY_PATH提供一个更高版本的libc.so.6这个高版本库里的符号和旧解释器之间可能发生 ABI 冲突。轻则程序正常重则直接段错误。所以这个方案只在程序只用到libm、libpthread等兼容性较好的接口时可靠不推荐作为首选。最稳的还是方案二。毕竟 GLIBC 的更换核心就是换解释器因为解释器决定整个用户态库的加载策略。4.4 验证是否真的生效不管用哪种方案跑起来之后用ldd检查实际加载路径LD_DEBUGlibs /opt/glibc-2.28/lib/ld-linux-x86-64.so.2 /opt/apps/my_tool 21 | grep libc.so.6输出会显示类似trying file/opt/glibc-2.28/lib/libc.so.6。或者直接用/opt/glibc-2.28/lib/ld-linux-x86-64.so.2 --list /opt/apps/my_tool--list是动态链接器的隐藏选项等价于ldd但它会从新解释器的视角去搜索路径能看到真实的依赖加载情况。多学这一招排错时非常有帮助。5. 编译后续的依赖库问题5.1 目标程序还依赖其他系统库怎么办大多数动态链接程序除了libc还会依赖libssl.so、libcurl.so、libz.so等。这些库通常还在系统的/usr/lib/x86_64-linux-gnu下而新的解释器/opt/glibc-2.28/lib/ld-linux-x86-64.so.2在运行时会不会去找系统路径答案是会但要满足条件。动态链接器的搜索顺序是RPATH/RUNPATH里的路径然后是LD_LIBRARY_PATH然后是缓存文件/etc/ld.so.cache最后是默认目录/lib、/usr/lib。系统库本身在/usr/lib/x86_64-linux-gnu一般都会通过/etc/ld.so.cache里的记录找到。所以只要目标程序不是强依赖一个更高版本的 GLIBC 专用接口它还是能混用系统库和新 GLIBC 的。但这种混用有时会出幺蛾子。因为系统库比如libssl.so.1.1当初是链接 2.27 的符号编译的现在换到 2.28 下运行大部分情况没问题因为 GLIBC 2.28 向后兼容 2.27。反过来才有问题如果一个系统库是 2.28 编译的现在放在 18.04 上才可能缺少 2.28 符号。所以我们的需求通常只是“把目标程序喂饱”而不是“给全系统换血”。5.2 依赖 Python 或 OpenSSL 的高层库如果目标程序是用 Python 3.6 嵌入的或者动态链接了libpython3.6m.so那么 Python 解释器本身也是基于系统库跑的。即使你给主程序换了新 GLIBCPython 的 C 扩展模块可能还会抱怨缺少 GLIBC 符号。这种多层嵌套场景最有效的做法不是单点替换而是直接上容器。容器方案其实特别简单docker run -v /opt/apps:/opt/apps -v /data:/data debian:10 /opt/apps/my_toolDebian 10 自带 GLIBC 2.28--mount把宿主机目录挂进去程序在容器内运行宿主机完全无感。这个方案还能解决系统库混用问题因为容器内有自己完整的/usr/lib。前提是目标程序需要访问宿主机硬件设备或内核模块时容器参数会复杂一点但很多场景足够用了。我个人的习惯是能上容器就上容器不能上容器才用/opt/glibc隔离方案。因为容器的隔离最彻底也不污染宿主机出了问题直接删容器重来。但如果客户的生产环境不允许装 Docker那也就只能老老实实走编译路线。6. 编译与运行中的常见问题排查6.1 符号版本报错GLIBC_2.28 not found这种报错最常见但要注意区分是哪个库报的。用之前说的命令objdump -T /opt/apps/my_tool | grep GLIBC_2.28如果输出为空报错可能是程序运行时动态加载的某个插件库比如.so插件引出的。那就用ldd -r /opt/apps/my_tool这个命令会检查所有依赖库的未定义符号能直接点名是哪个库要求 GLIBC_2.28。找到具体库之后要么给这个库单独做链接适配要么确认这个库确实需要 2.28 而系统库给不了。6.2 段错误Segmentation fault 或 Relocation error如果程序能启动但在某个特定功能上崩溃通常是新 GLIBC 与某些系统库的 ABI 互相踩踏了。比如libstdc.so.6是 GCC 7.5 编译的它内部调用了memcpyGLIBC_2.14在 2.28 下应该也能解析但如果内存布局或线程局部存储TLS发生变化就可能触发问题。这种问题排查起来很费劲。我的建议是先在纯净容器里测一遍确定程序本身是好的再回到宿主机用LD_DEBUGall跑一次把加载的每个.so文件路径打印出来人工对照哪些来自/usr/lib/x86_64-linux-gnu、哪些来自/opt/glibc-2.28/lib。如果发现同一个符号出现在两个库版本里优先考虑patchelf --set-rpath或者重建依赖库。6.3 编译时 make 中断错误指向 sysdeps 里的汇编文件这个多半是 binutils 版本兼容问题。Ubuntu 18.04 的 binutils 2.30 是支持 GLIBC 2.28 构建的但如果你之前手动升级过 binutils 到 2.34某些汇编指令生成会变。处理方式很简单sudo apt install binutils2.30-*固定版本或者干脆用系统默认环境编译不要启用update-alternatives里乱改的工具链。还有一种情况是make报错说缺少gnu/stubs-32.h这是因为没装 32 位开发库。虽然我们目标是 64 位但 GLIBC 构建时会检查 multi-arch 能力遇到这种就执行sudo apt install libc6-dev-i386 lib32gcc-7-dev再重新make即可。6.4 locale 生成警告安装完成后如果你用新解释器跑程序遇到 locale 相关警告比如cannot set locale可以在程序运行前设置export LC_ALLC因为新 GLIBC 在/opt/glibc-2.28/lib/locale下没有生成 locale 数据它会回退到系统 locale但有时候回退失败。LC_ALLC是最保险的做法代价是中文显示可能变乱但对于服务器端工具通常无所谓。7. 进阶把 GLIBC 2.28 集成到系统编译环境7.1 编译新软件时自动使用 2.28如果你想在 18.04 上编译新软件让新软件直接链接到 2.28而不是编译完再手动改可以在编译命令前指定编译器的 sysroot 或库路径./configure CCgcc -I/opt/glibc-2.28/include -L/opt/glibc-2.28/lib -Wl,-rpath,/opt/glibc-2.28/lib -Wl,--dynamic-linker/opt/glibc-2.28/lib/ld-linux-x86-64.so.2把CC设置成上面这段make出来的程序会直接使用新解释器和库路径。注意-I和-L必须在链接时同时生效。不过这样编译出的程序只适合在装有/opt/glibc-2.28的机器上运行拿到别的机器上还是会缺库。7.2 Makefile 级别的集成更规范的做法是环境变量export CFLAGS-I/opt/glibc-2.28/include export LDFLAGS-L/opt/glibc-2.28/lib -Wl,-rpath,/opt/glibc-2.28/lib然后make。注意-Wl,-rpath加不加取决于你是否希望程序在找不到库时仍然能靠 RPATH 搜索到。我建议加因为这样生成的二进制更健壮。不过这里有个细节如果你链接到新的libc.so但编译时用的头文件还是系统的旧头文件GLIBC 2.27 的头文件某些新的结构体定义可能对不上导致编译报错。所以CFLAGS里的-I/opt/glibc-2.28/include必须放在最前面优先于系统头文件路径。7.3 从 Makefile 源码编译的三角关系再补充一句GLIBC 的编译其实有个特殊的“理智检查”它要求你不能在编译器默认搜索路径里找到旧的libc.so或crt1.o否则构建脚本会报错比如configure: error: forced unwind support is not available。为了绕过这个问题GCC 套件里的libgcc是独立的它依赖libc但构建时用的是一个精简的 bare-metal 模式所以通常没问题。真正会出问题的是某些第三方构建脚本里显式指定-lc但链接器却搜到了系统库。这种时候可以先unset LD_LIBRARY_PATH保持环境干净再重新编译。8. 我的实际操作体会与最后提醒老实说GLIBC 编译安装本身并不难难的是对动态链接机制的理解。如果你连ldd输出都读不顺建议先把ld.so的搜索顺序背熟再来折腾老系统兼容问题。个人经验里有几条铁律写在这里给大家参考第一永远不要在系统目录里覆盖/lib/x86_64-linux-gnu/libc.so.6。如果真需要全局替换请直接升级发行版或者用容器、chroot 隔离。这不是怂是理智。新版 GLIBC 和旧内核、旧库之间的耦合太深手动替换等于给自己制造一台无法启动的服务器。第二编译时如果报错先查config.log不要盲目重跑make。GLIBC 的 configure 脚本会输出很长的检测日志里面通常明确写着缺失的头文件、函数或工具链限制。我之前有次编译失败就是少了libidn2-dev导致 configure 阶段安静跳过直到make出来才爆错。第三装完之后测试新解释器时不要直接拿系统里的/bin/ls来测因为它链接的是系统库用新解释器启动它虽然大概率能跑但没什么代表性。最好拿一个明确要求 2.28 的二进制来测。第四想验证你的/opt/glibc-2.28是否真的独立可以删掉LD_LIBRARY_PATH后执行strings /opt/glibc-2.28/lib/libc.so.6 | grep GLIBC_2.28能看到GLIBC_2.28字符串说明这个库本身就是 2.28 版本没编错。最后讲一个真实案例。我之前帮一个做金融风控的朋友处理过一套交易监控程序他们数据库服务器是 Ubuntu 18.04供应商发来的监控 agent 二进制是 CentOS 8 上编译的要求 GLIBC 2.28。当时我没编译而是直接在服务器上拉了 Debian 10 的容器把 agent 挂进去跑结果 agent 连接宿主机网卡和 NT 时钟源都没问题持续稳定运行了半年。后来另一台机器没法装 Docker我才用了本文的/opt/glibc-2.28编译方案配patchelf --set-interpreter同样很稳。所以你看GLIBC 2.28 安装这事其实是“系统兼容三板斧”里的一环。先用容器再用/opt隔离库最后才考虑是否要动用系统库路径。顺序别反了你的服务器就能少流点血。如果你在 18.04 或 Debian 老版本上遇到其他 GLIBC 版本不匹配的问题思路也是一样的找到目标程序需要的最高 GLIBC 版本符号编译对应的版本到/opt/glibc-XXX再用解释器切换。整个流程本质上没有变化变的只是版本号。希望这篇能帮你少踩几个坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑