
这套报错我太熟了。凡是拿 ThinkPHP 二开商业源码做部署的朋友十有八九会遇到这个红脸打开网站首页浏览器直接甩出一句Fatal error: Uncaught think\exception\ErrorException: SourceGuardian Loader后面跟一大段文件路径和行号。我第一次见的时候也愣了几秒明明本地跑得好好的怎么上服务器就炸了。这里面有两层信息要拆开看SourceGuardian是 PHP 的商用源码加密扩展而think\exception\ErrorException是 ThinkPHP 框架统一拦截错误后包装出的异常对象。换句话说真正的问题不是 ThinkPHP 逻辑写错了而是 PHP 在启动阶段加载 SourceGuardian 扩展时失败框架只是把底层的 warning/error 捕获后重新抛了出来。本文我就按实际排查的顺序把 SourceGuardian Loader 从原理、检测、安装到装完之后的连环坑完整过一遍。1. 先搞清楚 SourceGuardian 到底在项目里扮演什么角色1.1 这个报错不是 ThinkPHP 主动抛的是 PHP 启动时就出问题了很多人一看到think\exception\ErrorException就以为是框架代码里某个函数写错了于是翻控制器、翻模型查了半天找不到头绪。实际上ErrorException是 ThinkPHP 的异常处理器在工作——当 PHP 引擎产生一个非致命错误比如扩展加载失败时的 warning、notice时框架通过自定义的错误处理函数把它转成了异常对象。所以这个报错的本质是PHP 环境中缺少 SourceGuardian Loader或者 Loader 版本与当前 PHP 版本不匹配导致所有被 SourceGuardian 加密过的 PHP 文件无法被解密执行。SourceGuardian 的工作原理简单说就是给 PHP 源码做了一层加密壳。开发者用 SourceGuardian Encoder 把原本的明文 PHP 文件编码成一段二进制内容文件头部带有 SG 标记。服务器上的 PHP 要运行这种文件必须在php.ini里安装对应的 Loader 扩展PHP 进程启动时把扩展加载进内存遇到 SG 加密文件时现场解密再字节码执行。没有 Loader 或者 Loader 版本不对PHP 就把这个文件当成非法内容直接报错。这也是为什么很多商业 PHP 系统尤其是 ThinkPHP 老项目会强制要求服务器装 SG Loader——不装你连源码都看不了更别说跑了。1.2 什么场景最容易碰上这个报错我总结了一下碰到这个报错的人基本逃不出下面几种情况拿到一套二开过的商业源码对方用 SourceGuardian 加密了核心文件部署文档里写着“请安装 SourceGuardian Loader”但你忽略了直接传到服务器开跑首页秒红。服务器迁移或重装环境老服务器上装了对应版本的 Loader没觉得有什么特殊换到新服务器后只装了 PHP 和 Nginx没补 Loader结果网站起不来。宝塔面板切换 PHP 版本这种情况最多。宝塔里可以同时装 PHP 5.6、7.1、7.4、8.0 等多个版本网站原本用的是 7.1你为了性能或兼容性切到 8.0但新版本的 Loader 没装报错立刻出现。PHP 版本升级后 Loader 没同步升级SourceGuardian 官方从 SG11 开始逐步支持 PHP 7.1SG12 才支持 PHP 8.1。如果 Loader 停留在老版本PHP 升到 8.2 就会直接罢工。如果你正好属于其中一种那这篇文就是写给你的。1.3 网上搜到的 lms001、mark-compacts 跟它有没有关系我在搜索这个报错的时候发现一个很有意思的现象相关搜索里会出现fatal error[lms001]: license check failed. use the iar license manager to re、ineffective mark-compacts near heap limit allocation failed这些关键词。这里必须先帮你排个雷免得排查方向跑偏。lms001是 IAR 嵌入式开发工具的许可证校验报错跟 PHP 八竿子打不着mark-compacts near heap limit allocation failed是 Node.js/V8 引擎内存回收失败的提示也不是 PHP 环境的东西。搜索引擎之所以把它们关联在一起只是因为开头都顶着fatal error而已。真正的排查入口只有一个确认 SourceGuardian Loader 在目标 PHP 环境下是否加载成功。2. 确诊比治疗重要先确认 Loader 状态再动手2.1 一条命令看清扩展有没有加载不要上来就重装 PHP 或者换环境先花三十秒做确诊。打开终端切到项目所用的 PHP 版本命令行执行php -v正常情况下如果 SG Loader 已经加载你会看到类似这样的输出PHP 7.4.33 (cli) (built: Feb 20 2023 12:28:40) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with SourceGuardian v11.0.0, Copyright (c) 2003-2022, by SourceGuardian注意看最后一行的with SourceGuardian。有这行说明扩展本身已经挂上了没有这行说明 Loader 没安装或者装了但 PHP 没加载。如果输出里出现Failed loading /usr/local/lib/php/extensions/ixed.7.1.lin这样的提示那就是 php.ini 里写了extension配置但文件不存在属于路径问题。更直观的方式是用 PHP 的模块列表php -m | grep SourceGuardian或者干脆写一个探针文件在浏览器里看phpinfo()echo ?php phpinfo(); /网站根目录/info.php然后访问http://你的域名/info.php按下 CtrlF 搜SourceGuardian。搜到了就能看到 Loader 版本和支持状态搜不到说明这个 PHP-FPM 进程根本没加载 SG 扩展。2.2 不同报错形态对应的真实原因同样的“SG 相关”报错形态其实不止一种对应的处理方向也不完全一样。我把常见的几种列出来方便你对号入座报错形态真实原因处理方向打开网站直接Fatal error提示SourceGuardian Loader环境中没有安装 SG 扩展下载对应 PHP 版本的 loader装进 php.ini浏览器显示一个纯英文页面写着This file was encoded by SourceGuardianSG 加密文件存在但扩展未加载同上重点检查 CLI 和 FPM 是否一致PHP Warning: PHP Startup: Unable to load dynamic library ixed.7.1.linphp.ini 里配置了 extension但文件不存在或位数不对检查 extension_dir 和文件是否真实存在接口/页面提示SourceGuardian Loader 版本过低Loader 版本早于加密端 Encoder 版本升级到官网最新 Loader同时提示需要ionCube PHP Loader源码在 SG 基础上还套了一层 ionCube 混淆额外安装对应版本的 ionCube Loader最后一种情况在 ThinkPHP 商业源码里也偶尔出现需要一并处理别装完 SG 以为万事大吉。2.3 FPM 与 CLI 环境不一致的问题这里有一个非常隐蔽的坑你在终端执行php -v看到 SourceGuardian 正常但网站依然报错。原因通常是命令行使用的 PHP 和 Web 服务PHP-FPM使用的 PHP 不是同一个。这在宝塔面板、OneinStack、LNMP 一键包环境下非常常见——系统里可能存在多个 PHP 版本命令行默认调用的可能是/usr/bin/php而 FPM 跑的是/www/server/php/74/sbin/php-fpm两者的 php.ini 路径完全不同。确诊方法也简单命令行里用php --ini看当前 CLI 加载的配置文件路径再用一个 phpinfo 探针看 FPM 的Loaded Configuration File。两者不一致就要以 FPM 的配置文件为准。宝塔用户直接在面板的“软件商店 → PHP 项目 → 配置文件”里改别去改/etc/php.ini那是系统自带的另一个 PHP 用的。3. 下载 Loader 的版本选择这一步错了后面全白搭3.1 SG Loader 和 PHP 版本的对应关系SourceGuardian Loader 有多个大版本每个大版本支持的 PHP 范围不同。选错版本是翻车率最高的环节。老版本的 SG1/SG2/SG5/SG6 支持 PHP 4/5SG9 支持 PHP 5.3-5.6SG10 支持 PHP 7.0SG11 支持 PHP 7.1-8.0SG12 支持 PHP 8.1。Loader 文件命名也很有规律Linux 平台是ixed.PHP版本号.linWindows 平台是ixed.PHP版本号.win比如PHP 版本Linux Loader 文件名对应 SG 大版本PHP 5.2 - 5.5ixed.5.2.lin / ixed.5.5.linSG 较早版本PHP 5.6ixed.5.6.linSG 9/10PHP 7.0ixed.7.0.linSG 10PHP 7.1 - 7.4ixed.7.1.linSG 11PHP 8.0ixed.8.0.linSG 11PHP 8.1 - 8.2ixed.8.1.lin / ixed.8.2.linSG 12注意新版本 SourceGuardian 的下载页会自动推荐最新的 SG12但 SG12 只支持 PHP 8.1 以上。如果你的服务器是 PHP 7.4从官方下载页下载时要手动找 SG11 目录下的文件别直接用默认的最新版。3.2 官方下载页的正确用法SourceGuardian 的 Loader 可以从官网获取不需要付费。访问官网后找 Loader 下载入口通常是sourceguardian.com/ixed/页面页面会让你填写服务器 PHP 版本、操作系统类型、位数等信息提交后返回对应的下载链接。这里有两个容易忽略的细节区分 32 位和 64 位现在绝大多数服务器是 64 位但老机器上仍有少量 32 位系统。用uname -m看一下x86_64是 64 位i386/i686是 32 位。下载不对加载时直接报Invalid library或Unable to load dynamic library。填写系统提示时不要选错 Web 服务器类型下载链接本质上只是把对应的ixed文件打包给你但如果你填错系统类型给的是 Windows 的 dllLinux 下同样加载不了。3.3 白嫖的 Loader 和加密端 Encoder 的版本关系最后一个版本相关的知识很多人不理解为什么我 Loader 装的是官网最新版还是提示版本过低因为加密文件是用 SourceGuardian Encoder 生成的而 Encoder 版本可能比你装的 Loader 版本还要新。Loader 向下兼容旧加密文件但不保证支持比自己更新的 Encoder 产物。所以如果你从某个渠道拿到的源码是近期用最新 Encoder 加密的服务器 Loader 就必须也用对应的最新版不能用三年前的安装包。这个逻辑就相当于压缩软件解压新格式压缩包一样解压端版本得跟得上压缩端。4. 两种常见环境下安装 SourceGuardian Loader4.1 宝塔面板修改配置文件后重启宝塔面板是国内部署 ThinkPHP 项目最常见的方式操作路径很固定。首先找到当前网站的 PHP 版本进入“软件商店 → PHP 项目”点击对应版本右边的“设置”切到“配置文件”选项卡。在php.ini的最末尾追加一行extension ixed.7.1.lin文件名要跟你的 PHP 版本对应如果你选的是 8.0那就要写ixed.8.0.lin。保存之后先不要急着重启先把下载好的 Loader 文件放进扩展目录。扩展目录怎么找还是在这个配置文件里搜extension_dir通常会看到extension_dir /www/server/php/74/lib/php/extensions/用 SFTP 或宝塔文件管理器把下载的ixed.7.1.lin上传到这个目录下。如果你不确定扩展目录也可以用命令行快速定位/www/server/php/74/bin/php -i | grep extension_dir然后回到面板在 PHP 设置页点“重启”按钮重启 PHP-FPM。重启完执行/www/server/php/74/bin/php -v确认能看到with SourceGuardian那一行。再访问网站的探针文件phpinfo()里搜SourceGuardian显示Loader Support enabled就通关了。4.2 原生 LNMP/编译安装手动下载复制扩展如果你不是宝塔而是自己用 LNMP 一键包或者手动编译的 PHP流程也差不多只是命令要手敲。第一步找到 PHP 的扩展目录和配置文件php -i | grep extension_dir php --ini第二步从 SourceGuardian 官网下载对应版本的 Loader 文件。假设 PHP 是 7.4Linux 64 位下载后得到一个ixed.7.1.lin注意 SG11 对 PHP 7.1-7.4 共用同一个文件把它复制到刚才查到的 extension_dir 目录cp ixed.7.1.lin /usr/local/lib/php/extensions/ixed.7.1.lin第三步修改 php.inivim /usr/local/php/etc/php.ini在文件末尾加extension ixed.7.1.lin这里有个小技巧如果 php.ini 里已经设置了extension_dir你只需要写文件名PHP 会自动去那个目录找如果没设置建议写绝对路径比如extension /usr/local/lib/php/extensions/ixed.7.1.lin避免路径解析问题。第四步重启 PHP-FPMsystemctl restart php-fpm或者按 PHP 版本号重启service php-fpm-74 restart4.3 验证 Loader 是否生效的几个姿势安装完不能光看“没报错”就收工我建议做完三层验证CLI 验证php -v输出里带with SourceGuardian。探针验证写一个phpinfo()文件通过浏览器访问搜SourceGuardian确认SourceGuardian Loader Support enabled。业务验证访问之前报错的那个页面。这一步最根本——Loader 装好页面能正常出内容才算真的解决了。如果三层验证中有任何一层失败回到上一节检查文件放没放对目录、php.ini 改没改对、重启有没有生效。5. Loader 装好了还报错往下游查5.1 授权证书与域名绑定lgpl 和 lms001 是另一条线Loader 安装成功后还有一种报错很让人头大SG 扩展加载正常但加密程序启动时提示授权失败比如出现license check failed、unable to checkout a viewer license之类。有些系统还会弹一个“SG Licensing”的提示页。这里要分两种情况。第一种是 SourceGuardian 扩展自身在个别老版本上对线程安全模式TS或非线程安全模式NTS有要求装错版本会报加载失败这属于扩展层第二种是源码业务层自带的授权逻辑——开发者用 SG 加密后又在代码里写了一套域名授权、IP 授权或到期时间校验服务器不满足条件程序故意抛错。这种情况 Loader 本身没问题问题在授权文件或源码逻辑上你需要联系源码作者重新授权别在服务器层面瞎折腾。还有一种容易混淆的情况网上搜索时总会看到lms001这个报错。它是 IAR 的许可证管理器提示跟 PHP 的 SG 扩展没关系我当初也差点被它带偏这里提醒你直接忽略。5.2 加密文件是否真的被 SG 加密文本头判断如果你 Loader 装好了、也确认加载了但个别文件还是报错可以检查一下这个文件到底是不是 SG 加密的。用文本编辑器或 vim、cat打开报错文件看一眼文件开头。普通 PHP 文件开头是?php明文可读SG 加密过的文件开头通常是一串二进制乱码文件头部会有SG字样标记。如果文件本来就是明文 PHP那这个报错跟 SG 没关系是代码本身的语法或逻辑问题排查方向要转回 ThinkPHP 应用层。另外提醒一句不要用记事本或 Windows 自带的编辑器打开 SG 加密文件去“修复”二进制内容一改文件直接损坏神仙也救不回来。5.3 ThinkPHP 3.2 在 PHP 8 下的兼容残局这是一个更大的背景性问题。很多 SG 加密的 ThinkPHP 项目是 3.2 时代的产物当时主流 PHP 是 5.3/5.4/5.6。如果客户服务器用的是 PHP 8.0就算你装好了 SG Loader下一步也会撞上 ThinkPHP 3.2 本身在高版本 PHP 下的兼容性错误比如each()函数在 PHP 8.0 中被移除create_function()被移除mcrypt_*系列函数被移除花括号访问字符串偏移$str{0}不再支持部分 MySQL 扩展方法需要改写为 PDO 或 mysqli所以遇到 ThinkPHP 3.2 项目我通常建议先确认服务器 PHP 版本。如果业务允许尽量用 PHP 7.4 跑老项目SG Loader 用 SG11兼容性最好如果必须用 PHP 8.0那要做好心理准备SG 只是第一道坎后面还有 ThinkPHP 内核语法兼容的长期战斗。5.4 OPcache 干扰升级 loader 后要清缓存最后一个坑是部署到启用了 OPcache 的环境时容易踩的。PHP 的 OPcache 会缓存编译后的字节码如果你在 Loader 升级前后没有清缓存PHP 进程可能还在用旧的字节码导致 SG 扩展加载了但业务依然报错或者报错信息跟之前一模一样。宝塔面板里OPcache 的设置页有“清理缓存”按钮命令行可以用php -r opcache_reset();或者直接重启 PHP-FPM一劳永逸systemctl restart php-fpm我个人的习惯是每次修改 php.ini 相关配置后不直接看页面而是先重启 PHP-FPM 再验证能避开绝大多数缓存问题。6. 我的一些部署习惯和建议踩过这么多坑之后我给自己定了一套固定流程现在分享给你。以后凡是拿到 SG 加密的 ThinkPHP 项目按这个顺序走先用php -v和phpinfo()确认当前服务器 PHP 版本、CLI/FPM 是否一致。去 SourceGuardian 官网下载对应版本的 Loader不要贪新PHP 版本决定 SG 版本。备份 php.ini再改配置加extension行。重启 PHP-FPM先看php -v再看phpinfo()最后访问业务页面。如果依然报错查 PHP 错误日志tail -f /var/log/php-fpm/error.log别只看浏览器表面信息。排掉 SG 之后再跑一遍项目所有核心页面确认没有 ThinkPHP 框架本身的兼容性问题。另外如果你是在帮别人处理这个问题处理完之后最好把 Loader 文件和 php.ini 配置一起打包进部署文档。因为 SG 加密项目换服务器很频繁很多人半年后又来问一遍同样的问题。这套流程看起来不复杂但每一条都是我拿真实项目的 downtime 换来的。尤其是版本对应关系和 FPM/CLI 不一致这两个点排查起来最耗时提前避掉能省下大半天时间。