news 2026/10/3 6:12:54

PHP 扩展加载失败

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP 扩展加载失败

php -m报Unable to load dynamic library。答案其实就写在这条报错里,只是写在最里面那层括号里。

这个故障容易让人绕远。报错文本长、带路径、带括号嵌套,第一眼看不出该查什么。于是转头去翻面板、重装扩展、比对 php.ini,折腾一圈问题还在。

这台是 PHP 8.4.25 容器,镜像1panel-php-fpm:8.4.25。基础镜像 Debian 13(trixie)。方法不依赖 Docker,任何 Linux 上的 PHP 都适用。

exportPHP_CTN="php84"# 换成你的 PHP 容器名

先把那条报错读明白

完整的一整行长这样(2026-09-16 实测):

PHP Warning: PHP Startup: Unable to load dynamic library 'zip' (tried: .../zip.so (libzip.so.5: cannot open shared object file: No such file or directory)) in Unknown on line 0

这条报错是嵌套的,从外往里读三层。

第 1 层是Unable to load dynamic library 'zip'。说的是谁失败了:名叫zip的扩展。

第 2 层是tried: .../zip.so,说的是它去找了哪个文件。注意这个.so文件是存在的,否则不会说 tried。

第 3 层在最里面那层括号里。括号里写的是(libzip.so.5: cannot open shared object file: No such file or directory)。问题就在这一层:zip.so自己要用的libzip.so.5找不到。

前两层只回答了谁失败了、它叫什么名字。第三层才是答案。

这条报错给出的核心认知是:扩展的.so文件存在,不代表它能被加载。

.so是动态库,它自己也要依赖别的动态库。PHP 加载zip.so的流程是找到文件、交给动态链接器。动态链接器再去解析zip.so的依赖,依赖里有一个找不到,整个加载就失败。

所以扩展文件在不在和扩展能不能用,是两个独立的问题。面板只回答第一个,ldd才回答第二个。

用 ldd 把依赖关系摊开

ldd打印一个程序或者动态库需要哪些共享库,以及每个依赖最终解析到了哪个文件。

# 先问 PHP 自己要扩展目录在哪(别硬编码路径,见下方说明)dockerexec-i"$PHP_CTN"php-r'echo ini_get("extension_dir"), "\n";'# 然后对具体扩展跑 ldd,只挑出找不到的dockerexec-i"$PHP_CTN"bash-c' EXT_DIR="$(php -r "echo ini_get(\"extension_dir\");")" ldd "$EXT_DIR/zip.so" | grep "not found"'

别硬编码扩展目录。它的名字里带着 PHP 的 API 版本号,换个小版本就可能变。比如/usr/local/lib/php/extensions/no-debug-non-zts-20240924/。用php -r 'echo ini_get("extension_dir");'问 PHP 自己,永远是对的。

正常的ldd输出长这样,这是 glibc 的 man page 里给的标准示例:

linux-vdso.so.1 (0x00007ffcc3563000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f87e5459000) libc.so.6 => /lib64/libc.so.6 (0x00007f87e4e92000)

格式是名字 => 解析到的路径 (加载地址)。左边的名字是需要哪个库,这是写在文件里的 soname。=>右边是实际找到了哪个文件。末尾那个(0x...)是加载地址,排查缺库时不用管。

缺库的时候=>右边变成not found:

libzip.so.5 => not found

两个特殊项第一次看会疑惑,都不用管。linux-vdso.so.1没有=>,它是内核提供的虚拟 DSO。它不对应磁盘上的文件,所以没有路径可解析,这不叫缺库。ld-linux-x86-64.so.2是动态链接器自己,出现在依赖列表里是正常的。

我这次实测到的结果

四个扩展同时加载失败,一条命令列出全部缺失库(2026-09-16 实测):

--- gd --- libpng16.so.16 => not found libavif.so.16 => not found libwebp.so.7 => not found libjpeg.so.62 => not found libXpm.so.4 => not found libfreetype.so.6 => not found --- intl --- libicuio.so.76 => not found libicui18n.so.76 => not found libicuuc.so.76 => not found --- zip --- libzip.so.5 => not found --- memcached --- libmemcached.so.11 => not found

四个扩展,共缺 11 个共享库。到这里,问题已经从「扩展加载失败」变成了一个具体的、可执行的清单。

定位到库名只完成了一半,你还需要知道这个库属于哪个包。

最快的办法是按命名规律推。Debian 的包名和库名有大致对应关系。libzip.so.5通常对应libzip5。libpng16.so.16对应libpng16-16,libicuuc.so.76对应libicu76。规律是「lib + 名字 + .so. + 主版本号」对应「lib + 名字 + 主版本号」。能用,但不能写死,下面那个坑就是反例。

最准的是直接查:

# Debian/Ubuntu:查某个文件属于哪个包(需要 apt-file)apt-file search libzip.so.5# 已装的包反查它提供了哪些文件dpkg-Slibzip.so.5

apt-file search是最可靠的答案。代价是先装apt-file并跑一次apt-file update,会拉一份索引,有点重。

最省事的是试装循环,把候选包名逐个试,谁成功用谁,容忍不确定:

forpinlibzip5 libzip4;doapt-getinstall-y--no-install-recommends"$p"2>/dev/null&&{echo"libzip 来自$p";break;}done

Debian 正在做 64 位 time_t 过渡(t64),一批库改了包名。

libpng16.so.16对应的包,从libpng16-16变成了libpng16-16t64。后果很直接。要是你的补库脚本写死了libpng16-16,在 t64 之后的系统上它会直接报错。报的是E: Unable to locate package libpng16-16,而库名看起来一点没错。

所以候选名要兼容新旧:

forpinlibpng16-16t64 libpng16-16;doapt-getinstall-y--no-install-recommends"$p"2>/dev/null&&breakdone

这条给了一个更一般的教训:排查脚本里凡是按名字推断出来的东西,都要留一条退路。库名是事实,ldd读出来的;包名是推断,按规律猜的。两者的可靠度完全不同。

排查时通常不止一个扩展出问题,我这次是四个。与其一个个来,不如一次扫完:

# 遍历扩展目录,列出所有依赖缺失的扩展dockerexec-i"$PHP_CTN"bash-c' EXT_DIR="$(php -r "echo ini_get(\"extension_dir\");")" for so in "$EXT_DIR"/*.so; do [ -e "$so" ] || continue # 通配符没匹配到时的保护 miss=$(ldd "$so" 2>/dev/null | grep "not found") if [ -n "$miss" ]; then echo "--- $(basename "$so") ---" echo "$miss" fi done'

三个细节。

[ -e "$so" ] || continue。目录里没有.so时,$so会是字面量*.so,这行把它挡掉。

2>/dev/null。目录里可能有非动态库文件,ldd会对它们报错到 stderr。这里是故意静音,这类报错不是问题。

ldd只回答缺不缺,不回答该不该装。缺库的扩展不代表你都需要,补之前先确认哪些扩展是项目真在用的。

ldd 的两条边界

边界一:它默认只查找不找得到,不查符号。

ldd能告诉你libzip.so.5找不到。但如果库找到了,里面的符号版本却对不上,ldd会显示一切正常,程序照样起不来。比如系统升过库、扩展还是按老版本编译的。

glibc 的 man page 对-d和-r有明确说明。这两个选项会执行重定位并报告缺失的对象或函数:

# 更严格的检查:连符号一起查ldd-r"$EXT_DIR/zip.so"|grep-E"not found|undefined symbol"

排查时建议直接带上-r。反正都要跑,一次把两类问题都覆盖掉。

边界二:ldd会执行目标程序。

这一点很多人不知道,man page 专门用一整段警告了它。原文:

In the usual case,lddinvokes the standard dynamic linker
with theLD_TRACE_LOADED_OBJECTSenvironment variable
set to 1, which causes the linker to display
the library dependencies.
Be aware, however, that in some circumstances,
some versions oflddmay attempt to obtain
the dependency information by directly
executing the program.
Thus, you should never employldd
on an untrusted executable, since this may result
in the execution of arbitrary code.

对应的安全替代方案,man page 也直接给了:

# 静态读取 ELF 头里的 NEEDED 段,完全不执行文件objdump-p/path/to/program|grepNEEDED

但别以为 objdump 能整体替代 ldd

这里有个容易被忽略的差别。objdump -p | grep NEEDED只列直接依赖,ldd会展开整棵依赖树。

对排查缺库这件事来说差别很关键。我自己的文件、要查某个库为什么找不到,用ldd,因为需要完整依赖树,还要看实际解析路径。来源不明、不放心的二进制,用objdump -p | grep NEEDED或readelf -d,只读头部,不执行。

一个实测发现:不同实现报错文本不一样

我在本机 Cygwin 的ldd上实测了两种异常输入:

$ ldd /tmp/plain.txt ldd: /tmp/plain.txt: Exec format error $ ldd /tmp/nonexistent.so ldd: /tmp/nonexistent.so: No such file or directory

而 glibc 的ldd碰到静态链接的程序,报的是not a dynamic executable。

同一件事,不同实现、不同文本。Cygwin 报的是Exec format error,glibc 报的是not a dynamic executable。为什么会有这个差别,我没往下挖。这条我到现在也没查明白。

所以看这类输出别死记某一句报错,要看它在说什么。not found和No such file or directory说的是缺库或缺文件。Exec format error和not a dynamic executable说的是给它的不是动态库。可能是脚本、文本、或者静态二进制。

完整排查路径

有

没有

php -m 报 Unable to load dynamic library

读报错最内层括号
拿到缺失的库名

对扩展的 .so 跑 ldd
grep not found

有 not found 吗?

库名反推包名
命名规律 + apt-file + 试装循环

库都在
带上 -r 再查一遍符号

装包 → 重启容器 → 复验 php -m

拿这个坑走一遍,就是这次的实际路径。读报错,ldd出 11 个缺失库,逐库反推包名,装包,复验php -m零告警。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 6:11:38

VSC-HVDC四端系统实战解析:控制逻辑、通信协议与调度落地

1. 这不是教科书里的概念图,而是真实电网调度室正在跑的拓扑你打开电力系统仿真软件,看到四个变流站像四颗行星绕着中心母线旋转——这不是教学演示动画,而是华东某省级电网2024年夏季负荷高峰期间实际投运的VSC-HVDC交直流混合并网系统的实时…

作者头像 李华
网站建设 2026/10/3 6:11:34

Python+OpenCV+GPT:从零搭建虚拟数字人直播系统实战

1. 虚拟数字人直播的底层逻辑与方案选型1.1 为什么选择 Python Pygame OpenCV GPT 这套组合做虚拟数字人直播,核心要解决三件事:形象渲染、环境感知、智能对话。市面上成熟的商业方案不少,但如果你想从零搭一套自己能完全掌控、成本可控、…

作者头像 李华
网站建设 2026/10/3 6:11:32

用Dify打造AI复盘助手:从日志到结构化结论的自动化工作流

说实话,我第一次看到"hindsight"这个词蹦到我工作台旁边的时候,第一反应是:这不就是"事后诸葛亮"的英文版吗?后来真把它当个项目名来用,才发现这名字起得特别妙。hindsight 的意思是"后见之明…

作者头像 李华
网站建设 2026/10/3 6:09:55

Word转竖屏视频全自动流程:HTML+edge-tts+Remotion+FFmpeg实战

1. 从一份 Word 稿到 142 秒竖屏视频,这套流程到底解决了什么问题手里有一份写好的 Word 口播稿,想把它变成一条能直接发出去的竖屏短视频,这件事听起来简单,做起来全是坑。我最初的想法也很朴素:把稿子念一遍录下来&a…

作者头像 李华
网站建设 2026/10/3 6:09:49

Claude Code 成本优化实战:从400元到80元的Token节省策略

1. 从400到80,账单是怎么被吃掉的先交代背景。我用 Claude Code 做日常开发辅助,主要场景是读代码、改 bug、写测试、偶尔让它帮忙整理文档。第一个月账单出来的时候,400 多块,说实话有点肉疼。不是付不起,是觉得不值—…

作者头像 李华