PHP 尝试使用 preg_match 和preg_replace分配 127 TB 的内存和内存泄漏


PHP trying to allocate 127 TB of memory and memory leak with preg_match and preg_replace

我想我发现了一个问题,当 unicode 字符作为分隔符或有时在带有 preg_matchpreg_replace的正则表达式中的任何位置时,似乎会在 Apache/PHP 中产生内存泄漏。这可能发生在更preg_*的方法中。

测试用例 1

创建一个包含以下内容的新 PHP 文件test.php

<?php
    preg_match( '°test°i', 'test', $matches );

测试用例 2

创建一个包含以下内容的新 PHP 文件test.php

<?php
    preg_match( '°', 'test', $matches );

用作分隔符° unicode 字符是度数符号。如果您愿意,请尝试任何其他 unicode 字符,看看会发生什么。

结果

将文件上传到具有Apache 2.4.10 (Debian)PHP 5.6.0-1+b1的网络服务器后,从您喜欢的浏览器运行它。预计会看到一个空白页或一条消息,指出"无效响应"或"无法加载此页面"。

这将导致 Apache 错误中出现以下两行.log(通常是/var/log/error.log(:

[Mon Dec 15 10:31:09.941622 2014] [:error] [pid 6292] [client ###.###.###.###:64413] PHP Warning:  preg_match():  in /path/to/test.php on line 2
[Mon Dec 15 10:31:09.941796 2014] [:error] [pid 6292] [client ###.###.###.###:64413] PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 139979759686648 bytes) in Unknown on line 0

请注意,PHP 尝试分配的字节量刚刚超过 127 TB。

你现在需要重新启动 Apache

在尝试上述脚本后运行PHP脚本将导致各种通知或致命错误,即使在甚至不应该生成它们的代码中也会弹出。例如,自动加载扩展类似乎不再正常工作,并且可能会显示如下错误:

Class MyClass not found in file MyExtendingClass.php on Line 3

文件MyExtendingClass.php看起来像这样:

<?php
    class MyExtendingClass extends MyClass
    {
    }

如您所见MyClass显然在第 2 行,即使它确实存在并且自动加载器已正确设置,PHP 也无法再找到它。

显然,不要在正则表达式中使用 unicode 字符。但是为什么 PHP 在使用某些 unicode 字符时会泄漏内存?这种行为有解释吗?我想知道为什么PHP认为它应该分配如此大量的字节。

系统信息

Apache/2.4.10 (Debian( PHP/5.6.0-1+b1 OpenSSL/1.0.1i 已配置

我遇到了类似的错误,PHP 试图将大于 127 TB 的东西加载到 RAM 中,但对我来说,它发生在脚本完成后。

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 140683487706824 bytes) in Unknown on line 0

我使用的是 PHP 版本 5.4.39-0+deb7u2,我看到它在脚本执行后发生,因为脚本本身工作正常并且没有显示任何问题。我实际上尝试记录脚本执行时间和RAM使用情况,一切都很好,直到脚本结束。

我的问题很可能与使用 dblib+freetds 组合有关,用于从 Debian 连接到远程 MSSQL 服务器,这是一个已知的错误 #64511。它实际上被报告为固定的,但就我而言,似乎并非如此。

实际上很难在这种行为中得到任何规则,但这是我所看到的。

  • 我连接到远程 MSSQL
  • 执行
  • 查询(实际上包括对存储过程的调用,然后在它之后立即执行 SELECT 查询(

执行后,如果我使用类似以下代码的内容来获取结果:

$sprocResultSet = $PDOquery->fetchAll(PDO::FETCH_ASSOC);
$PDOquery->nextRowset();
$sprocExitCode = $PDOquery->fetchAll(PDO::FETCH_ASSOC);

在大多数情况下,我会遇到奇怪的 RAM 分配错误,据说加载了 TB。

当我没有在 fetchAll(( 中指定 fetch 样式时,我使用了 setFetchMode((,如下所示:

$PDOquery->setFetchMode(PDO::FETCH_ASSOC);
$sprocResultSet = $PDOquery->fetchAll();
$PDOquery->nextRowset();
$sprocExitCode = $PDOquery->fetchAll();

然后我从来没有注意到分配 RAM 的相同错误。

我确实尝试关闭游标并将$PDOquery变量清空,或者只是让它在脚本结束时自动关闭 - 没有帮助。另外,也许值得一提 - sproc 和它之后的其他 SELECT 查询都只返回一行数据,因此绝对不会返回大的结果集。

所以。。。我不确定这是否在所有情况下都有帮助,但是如果您遇到与我类似的情况,请尝试提前为 PDO 设置默认获取模式。

还有两件事要补充。首先,在上面的代码中,使用了 PDO 的 nextResultSet((,它也被报告为一个错误 - 导致内存问题:nextRowset 会导致内存损坏。 其次,PDO的fetch((没有被使用,因为每当使用时,它都会死亡并报告DBLIB错误。