尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PHP 扩展加载失败

PHP 扩展加载失败 php -m报Unable to load dynamic library。答案其实就写在这条报错里只是写在最里面那层括号里。这个故障容易让人绕远。报错文本长、带路径、带括号嵌套第一眼看不出该查什么。于是转头去翻面板、重装扩展、比对 php.ini折腾一圈问题还在。这台是 PHP 8.4.25 容器镜像1panel-php-fpm:8.4.25。基础镜像 Debian 13trixie。方法不依赖 Docker任何 Linux 上的 PHP 都适用。exportPHP_CTNphp84# 换成你的 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_CTNphp-recho ini_get(extension_dir), \n;# 然后对具体扩展跑 ldd只挑出找不到的dockerexec-i$PHP_CTNbash-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 foundlibzip.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-16libicuuc.so.76对应libicu76。规律是「lib 名字 .so. 主版本号」对应「lib 名字 主版本号」。能用但不能写死下面那个坑就是反例。最准的是直接查# Debian/Ubuntu查某个文件属于哪个包需要 apt-fileapt-file search libzip.so.5# 已装的包反查它提供了哪些文件dpkg-Slibzip.so.5apt-file search是最可靠的答案。代价是先装apt-file并跑一次apt-file update会拉一份索引有点重。最省事的是试装循环把候选包名逐个试谁成功用谁容忍不确定forpinlibzip5 libzip4;doapt-getinstall-y--no-install-recommends$p2/dev/null{echolibzip 来自$p;break;}doneDebian 正在做 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$p2/dev/nullbreakdone这条给了一个更一般的教训排查脚本里凡是按名字推断出来的东西都要留一条退路。库名是事实ldd读出来的包名是推断按规律猜的。两者的可靠度完全不同。排查时通常不止一个扩展出问题我这次是四个。与其一个个来不如一次扫完# 遍历扩展目录列出所有依赖缺失的扩展dockerexec-i$PHP_CTNbash-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-Enot found|undefined symbol排查时建议直接带上-r。反正都要跑一次把两类问题都覆盖掉。边界二ldd会执行目标程序。这一点很多人不知道man page 专门用一整段警告了它。原文In the usual case,lddinvokes the standard dynamic linkerwith theLD_TRACE_LOADED_OBJECTSenvironment variableset to 1, which causes the linker to displaythe library dependencies.Be aware, however, that in some circumstances,some versions oflddmay attempt to obtainthe dependency information by directlyexecuting the program.Thus, you should never employlddon an untrusted executable, since this may resultin 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 errorglibc 报的是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 跑 lddgrep not found有 not found 吗库名反推包名命名规律 apt-file 试装循环库都在带上 -r 再查一遍符号装包 → 重启容器 → 复验 php -m拿这个坑走一遍就是这次的实际路径。读报错ldd出 11 个缺失库逐库反推包名装包复验php -m零告警。
返回列表