十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PHP 7.1.31源码编译与老项目维护实践

PHP 7.1.31源码编译与老项目维护实践 简介php-7.1.31.tar.gz 是 PHP 7.1.31 在 Linux 环境下的完整源码压缩包面向需要在 Linux 上编译部署 PHP、学习解释器实现或进行扩展开发的运维与开发人员可解决从源码构建 PHP 运行环境的问题。压缩包共含 19510 个文件以 15594 个 phpt 测试用例、1009 个 C 源码文件、774 个头文件为主体同时包含 PHP 脚本、XML 数据、构建配置脚本与文档资源整体大小约 18.8MB目录结构便于按模块查找。已有 285 人学习浏览适合需要从源码层面理解 PHP 7.1 新特性的读者。包内丰富的 phpt 测试样例与 C 语言实现可帮助掌握 void 返回类型、类常量可见性等版本特性也能用于研究扩展开发、编译参数配置和性能优化思路同时 configure 脚本、makefile 模板与扩展模块目录有助于梳理 Linux 下从编译到安装的完整链路并理解 OPcache、错误日志、扩展加载等实际运维关注点整体上是一份适合 Linux 运维与 PHP 进阶开发者系统学习的源码素材。 php-7.1.31.tar.gz 这个文件名但凡还要维护老项目的朋友看一眼就知道意味着什么又得在一台不能随便动 PHP 版本的服务器上手工编译一个早已停止安全支持的 PHP 版本。我上一次处理它是在一个跑了四五年的电商业务上订单代码一直停在不兼容 PHP 8 的状态系统库升级又绕不开 OpenSSL 1.1.1前后折腾了整整一个下午才把环境复现完整。这篇东西不是劝你升级的也不是劝你别升级的。它更像一份老 PHP 源码包处理笔记适合所有还在跟遗留系统做斗争的朋友运维、后端开发、接手祖传项目的同学。我会把从下载校验、编译安装、跑起来之后的常见维护坑到不能升级时怎么止损的完整链路过一遍全是实操层面能直接用的东西。1. 一个 EOL 版本为什么还在被反复下载PHP 7.1.31 的真实处境1.1 7.1 系列的定位与生命周期PHP 7.1 发布于 2016 年底那会儿 PHP 刚完成从 5.x 到 7.x 的性能跨越7.0 把 zval 结构重写了一遍7.1 则在这个基础上补了不少语法糖nullable type、void 返回类型、iterable 伪类型、多 catch 异常捕获还有 list() 支持键名解析。说实话放到今天看这些都是很基础的东西但如果你接手过 PHP 5.6 时代的代码再看 7.1 会觉得整个语言突然讲武德了。不过时间不饶人PHP 7.1 系列在 2018 年 12 月结束主动支持2019 年 12 月安全支持也正式停止。7.1.31 就是这一系列收尾期的补丁版本之后官方只在归档仓库里保留它不再做任何安全修复。也就是说今天你拿着 php-7.1.31.tar.gz 去编译装出来的是一个有已知 CVE、却无法获得官方补丁的运行环境。这个背景必须清楚后面所有安全加固动作都是基于这个现实做的。1.2 都是什么项目还赖在 7.1 上按理说 EOL 版本应该被淘汰但实际情况是有大量项目至今还跑在 7.1 甚至更低版本上。我接过的单子里最常见的原因有三类。第一类是老框架绑定典型的就是 ThinkPHP 3.2.3 这种当年红极一时的 MVC 框架。它最初就是为 PHP 5.2/5.3 设计的里面不少写法依赖旧版行为直接扔到 PHP 8 上会报一堆致命错误。第二类是商业加密扩展绑定比如项目里装了 Swoole Loader 加密过的业务模块这种 loader 对 PHP 版本有严格匹配PHP 升级后加密文件可能直接跑不起来厂商更新又慢项目只能停在旧版本。第三类纯粹是历史包袱老的第三方 SDK、写死在代码里的函数调用、没人维护的支付接口改起来风险太大业务方又不肯停下来,于是只能继续在老版本上续命。这些处境我们都理解但正因为如此把 PHP 7.1.31 的编译和维护流程跑通才成了很多人的硬需求。下面就从拿到源码包之后的第一步开始。2. 拿到 php-7.1.31.tar.gz 的前一步下载渠道、签名校验和解压确认2.1 从哪下载怎么确认文件没被换网上搜 php-7.1.31.tar.gz 会出现一堆下载站我的建议是别碰来路不明的源码包可能被植入了后门而源码包后门是最难排查的因为它藏在 C 代码里用肉眼看 configure 脚本根本发现不了。优先从官方渠道拿。老版本官网 distributions 目录可能已经撤掉了但 PHP 官方历史归档一直很全museum.php.net 的 php7 目录下有完整的 7.1.x 系列地址是这样# 官网发行目录如果还能访问 wget https://www.php.net/distributions/php-7.1.31.tar.gz # 官方历史归档 wget https://museum.php.net/php7/php-7.1.31.tar.gz下载完先别急着解压我习惯第一时间做一个 SHA256 校验。PHP 官网 releases 页面会列出每个发行版的哈希值把本地算出来的值和官方值逐位比对任何一个字符对不上都说明文件有问题。sha256sum php-7.1.31.tar.gz这一步五秒钟就能做完却能避免后面所有基于错误源码的排查属于性价比极高的操作。2.2 GPG 签名校验这一步很多人跳过了除了哈希比对PHP 官方还提供 GPG 签名文件后缀是 .asc。哈希校验能防下载文件被意外篡改但理论上攻击者可以同时改掉网页上的哈希值所以 GPG 签名验证才是更可靠的信任链。操作不复杂wget https://www.php.net/distributions/php-7.1.31.tar.gz.asc # 拉取 PHP 官方发布签名公钥从 php.net/gpg-keys.php 页面上获得 key ID gpg --keyserver keyserver.ubuntu.com --recv-keys PHP官方发布公钥ID # 用公钥验证签名 gpg --verify php-7.1.31.tar.gz.asc php-7.1.31.tar.gz看到Good signature字样才是安全的如果出现Cant check signature: No public key或者 BAD signature那就立刻停手。我见过不少教程把这一步省了但在源码包这种信任链关键环节上GPG 验证值得每次都做。一句经验之谈签名的公钥指纹一定要和官网公布的核对别只用一个红框框住的字符串就信了。2.3 解压后的目录结构速览校验完成后解压进目录tar -xzf php-7.1.31.tar.gz cd php-7.1.31/ ls稍微熟悉一下源码结构后面排查编译问题会快很多。最上层的Zend/目录是 Zend 引擎本体也就是 PHP 解释器的核心main/放的是 SAPI 层和扩展管道的公共代码ext/下是所有标准扩展的源码curl、mbstring、openssl、pdo 这些都在里面sapi/目录里能看到 cli、fpm 等不同运行方式的实现根目录的php.ini-production和php.ini-development是两个基础配置模板一个偏保守一个偏调试编译完成后要拷到配置目录去用。把这里摸熟后面遇到某个扩展怎么没编进去类的问题时直接去 ext 目录看有没有对应源码目录就知道了。3. 编译安装全记录configure 参数这样定make 才不会来回折腾3.1 前置依赖先解决 libxml2、OpenSSL、curl 这些系统库PHP 源码编译最烦人的不是 PHP 本身而是系统的开发库。configure 阶段很多报错都是因为少了某个-dev或-devel包。以最常见的 Ubuntu/Debian 为例先把基本依赖装上apt update apt install -y build-essential autoconf libxml2-dev libssl-dev libcurl4-openssl-dev \ libzip-dev libpng-dev libjpeg-dev libonig-dev libsqlite3-dev pkg-configCentOS/RHEL 系则对应yum install gcc gcc-c make libxml2-devel openssl-devel libcurl-devel libzip-devel libpng-devel等。我的经验是宁可一次多装几个库也别在 configure 报错时一个个补那种装一个跑一次 configure的流程极其消磨耐心。如果你是在本地 Windows 上用集成面板比如小皮面板这类工具直接选 PHP 7.1.31 版本加上对应扩展即可不需要自己编译。但生产环境跑在 Linux 服务器上时源码编译依然是最常见、最可控的路径。3.2 configure 参数逐项拆解为什么这么配PHP 源码编译的 configure 参数非常灵活但很多教程直接让你复制一大串根本没讲每个参数干什么。我这里给一份针对老项目常用场景的参数并逐个解释./configure \ --prefix/usr/local/php-7.1 \ --with-config-file-path/usr/local/php-7.1/etc \ --enable-fpm \ --with-fpm-userwww \ --with-fpm-groupwww \ --with-mysqlimysqlnd \ --with-pdo-mysqlmysqlnd \ --with-mysql-sock/var/run/mysqld/mysqld.sock \ --with-openssl \ --with-zlib \ --enable-mbstring \ --with-curl \ --with-gd--prefix安装根目录。我习惯把 PHP 装到独立的/usr/local/php-7.1而不是/usr/local/php这样以后升级路径、回滚路径都清晰直接换目录切版本就行。--with-config-file-pathphp.ini 的位置。PHP 7.1 默认会把 php.ini 找在prefix/lib下显式指定到etc目录更符合运维习惯。--enable-fpm启用 PHP-FPM。如果你用 Nginx 跑 PHP这个必须开如果只跑 CLI 脚本可以不开。--with-fpm-user/groupphp-fpm 子进程运行的系统身份按最小权限原则用专用账号我一般不用 root 跑 PHP。--with-mysqlimysqlnd和--with-pdo-mysqlmysqlnd老项目连接 MySQL 的两种接口mysqlnd 是官方驱动的实现比指向旧 libmysqlclient 更省心不需要额外装 MySQL 客户端库。--with-opensslHTTPS、签名、加密相关功能的底层依赖很多扩展和框架没有它直接起不来。--with-zlib压缩支持PHP 的压缩相关函数依赖它。--enable-mbstring多字节字符串扩展中文环境下基本必开后面很多坑都跟它有关。--with-curl给 PHP 提供 HTTP 请求能力的扩展各种 SDK 都依赖它。--with-gd图片处理扩展老项目里做缩略图、验证码基本都靠它。如果你不确定某个参数要不要开一个保守的判断法则是老项目跑起来缺什么再补什么别一次全开。扩展越多编译时间越长而且每个扩展都意味着新的攻击面。3.3 从 make 到 make install真实编译输出里的警示信号configure 通过之后进入 make 阶段make -j4-j4后面的数字是并行编译的进程数别超过 CPU 核心数否则内存不够会报virtual memory exhausted: Cannot allocate memory。遇到这种报错要么降低并行度到-j2要么先加 swap 再继续。make 过程常见的 configure 报错基本都是依赖问题configure: error: xml2-config not found缺 libxml2 开发包补装libxml2-dev或libxml2-devel。configure: error: Please reinstall the libcurl distribution - easy.h should be in curl-dir/include/curl/缺 libcurl 开发包。configure: error: Cannot find OpenSSLs evp.h缺 openssl 开发包。这些报错信息虽然英文看着玄乎但指向都很明确就是你的系统没装某个开发头文件。装了对应开发包后再重新跑 configure 基本都能过。make 成功后执行make install这个命令把 PHP 二进制、php-fpm、扩展、php-config 这些装到之前 prefix 指定的目录里。装完别忘了把配置模板拷过去cp php.ini-production /usr/local/php-7.1/etc/php.ini cp /usr/local/php-7.1/etc/php-fpm.conf.default /usr/local/php-7.1/etc/php-fpm.conf到这一步PHP 7.1.31 本身是装好了但真正难受的是跑起来之后的各种兼容问题下一节展开说。4. 跑起来之后的老版本维护mbstring 警告、OpenSSL 升级和 php-fpm 的坑4.1 Module mbstring is already loaded 到底在说什么很多人在老版本 PHP 上都会碰到这条警告PHP Warning: Module mbstring is already loaded in Unknown on line 0字面意思是mbstring 模块被加载了两次。问题是 PHP 的模块加载是幂等的重复加载会直接报这个警告虽然不影响业务但日志里刷屏容易把真正的问题淹没。常见原因有两个。第一种你在 configure 时已经通过--enable-mbstring把 mbstring 编进了 PHP又在 php.ini 里写了extensionmbstring.so这就重复了。第二种系统里有多个配置文件同时加载了它比如 php.ini 里写了一次/etc/php/7.1/conf.d/下的某个 ini 文件里又写了一次。排查方法php --ini php -i | grep mbstringphp --ini会列出所有被加载的配置文件逐个检查有没有重复的 extension 行把多余的那行注释掉重启 php-fpm警告就消失了。4.2 给 PHP 7.1.31 接上 OpenSSL 1.1.1热词里有个php升级openssl至1.1.1这是老版本 PHP 最典型的痛点之一。PHP 早期版本对 OpenSSL 1.1.0/1.1.1 的适配不完善编译时或者运行时可能出现SSLv3_method之类的 undefined reference。PHP 7.1.31 已经是 7.1 系列后期版本对 OpenSSL 1.1.1 的兼容比早期版本好很多但仍然建议在 configure 前先确认系统 OpenSSL 版本openssl version如果系统里同时装了多个 OpenSSL 版本比如默认是 1.0.2你自己编译的 1.1.1 在/usr/local/openssl-1.1.1那 configure 时就要显式指定路径./configure --with-openssl/usr/local/openssl-1.1.1 ...这样 PHP 的 openssl 扩展才会链接到正确的库上。一个容易忽略的坑是编译完成后用php -i | grep OpenSSL看的是 OpenSSL 扩展支持但 HTTP 客户端实际使用的 TLS 版本还取决于系统的 openssl 二进制和 curl 的编译链接。如果线上对外请求是低版本 TLS 被对方拒绝可能需要同时升级系统的 openssl而不仅仅是 PHP 扩展。4.3 php-fpm 最基本要检查的那几项配置编译装完后php-fpm 是另一个容易出问题的地方。我每次装完老版本都会先检查这几个点。第一是进程运行身份。--with-fpm-user/group指定的用户要真实存在否则 php-fpm 起不来。而且这个用户对项目目录至少要有读权限否则请求进来直接 500。第二是监听地址。php-fpm 默认监听 127.0.0.1:9000Nginx 通过fastcgi_pass 127.0.0.1:9000;转发。如果要用 Unix socket要在 php-fpm 配置里改 listen 并注意 socket 文件的权限Nginx 用户的组要能访问到 socket。第三是pm.max_children的设置。这个值决定 php-fpm 最多能起多少个子进程有些同学图省事直接填 200结果 PHP 单进程吃 50MB 内存十来个请求就把 2G 内存干满了。经验公式是先算单进程平均内存占用用可用内存 / 单进程内存得出合理值留出系统余量。第四是request_terminate_timeout。老项目里偶尔会有慢接口不设置超时的话一个卡住的请求能把整个 worker 池拖死。测试环境设 30s 没问题生产按业务实际设一个合理值。5. 老 PHP 的安全底线不能升级时的止损操作与迁移路线5.1 禁用危险函数和隐藏版本号既然 PHP 7.1.31 已经断更我们就必须假设它存在未修复的安全漏洞能做的是把攻击面尽量缩小。第一步在 php-fpm 层面禁用高危函数。我推荐至少禁用这批disable_functions exec,passthru,shell_exec,system,proc_open,popen,show_sourceexec系列能直接执行系统命令被攻击者拿到之后等于直接得手show_source能高亮显示 php 文件源码也不该在生成环境开放。这里有一个我自己踩过的坑如果这几个函数写进全局 php.ini那么命令行下跑 composer、artisan 这类工具也会被影响因为它们也可能依赖 exec。所以建议把 disable_functions 写在 php-fpm 的 pool 配置里用php_admin_value[disable_functions]只限制 Web 请求CLI 保持独立。第二步隐藏版本号expose_php Off这不能阻止攻击者通过探测确定版本但能增加一点扫描成本让批量漏洞扫描器少拿到一个信息点。同类的还有在 Nginx 隐藏 X-Powered-By 头都是低成本高回报的基础操作。第三步登录后台时加访问控制和限流老系统如果没有二次验证至少把后台路径改复杂一点别用 admin、manage 这类默认路径。5.2 反序列化和文件上传接口要格外小心老的 PHP 项目里反序列化漏洞和文件上传漏洞是我最担心的两个点。反序列化相关PHP 的unserialize()如果直接处理用户输入攻击者通过构造对象序列化串可能触发魔术方法调用链造成代码执行或者文件操作。ThinkPHP 3.2.3 当年出过不少这类问题。止损方案是所有接收外部数据的地方如果必须用反序列化至少限制允许的反序列化类型。PHP 7.1 可以这样处理$data unserialize($input, [allowed_classes false]);能不用反序列化就尽量不用改用 JSON 传数据这是最省心的。文件上传方面老项目要检查上传目录是否可执行 PHP 代码常见的做法是把上传目录的php_flag engine off或者让上传文件落到独立域名下并禁掉脚本执行权限这样就算攻击者传了 webshell 也起不了作用。5.3 迁移路线图从 7.1 到 8.x 的建议说句实在话PHP 7.1.31 这种老版本只适合作为过渡期的止损方案不适合作为长期目标。我在处理完那台老服务器之后给自己定了一条更明确的工作路径。第一步是盘点。用 PHP 7.4 或 8.2 在本地把项目跑一遍把所有的 deprecation warning 和 fatal error 记录下来。大多数老框架升级工作量集中在几类mysql_*函数被移除、each()被移除、list()的赋值顺序变化、魔术引号假设被移除。先分析这份日志比盲目替换代码高效得多。第二步是按依赖外部条件排序。如果项目绑定了 Swoole Loader 加密模块就要先跟厂商确认它支持的目标 PHP 版本不然代码层面全改完了扩展加载不上一切都白搭。如果项目还在用 ThinkPHP 3.2.3可以先评估升级到 ThinkPHP 5.x 或者直接拆掉重写部分模块但这一步风险较大要有测试环境做完整回归。第三步是选目标版本。我的建议是不要停在 7.2、7.3、7.4 这些已经 EOL 的中间版本它们能解决的兼容性问题有限却只是把同样的安全风险推迟几年。一步跨到 PHP 8.2 或 8.3 虽然升级时疼一下但至少换来几年健康运行期。我在编译完 php-7.1.31.tar.gz 的最后那段时间每次跑监测日志都会看到几条 PHP 警告翻代码发现有些写法在 PHP 8 下根本跑不了那种随时可能出事的感觉才是老项目维护者最大的心理压力。这套编译、加固、迁移的流程虽然烦但它最大的价值不是让你停在过去而是让你在必须和旧系统共存的日子里心里有底。后续每遇到一个不兼容问题按这份清单一项项去消除慢慢你会发现老代码也没那么可怕。本文还有配套的精品资源点击获取
返回列表