困惑:PHP 致命错误:在第 0 行的未知中抛出没有堆栈帧的异常?

Posted

技术标签:

【中文标题】困惑:PHP 致命错误:在第 0 行的未知中抛出没有堆栈帧的异常?【英文标题】:Baffled: PHP Fatal error: Exception thrown without a stack frame in Unknown on line 0? 【发布时间】:2012-05-16 03:43:20 【问题描述】:

我发现错误的一个常见原因是an exception being thrown from within an exception handler。我很确定这不会发生在我正在尝试调试的应用程序中...但是我已将所有初始化处理行放在 index.php 的顶部的 try/catch 中。*

这显然也可能发生,因为some things cannot be serialized to be stored in a session。这个应用程序最多将数组存储到会话中(相当多),但我相信它不会在其中存储任何不寻常的东西。

有人评论说这发生在他们身上是因为他们的主键needed to be CHAR(32) instead of INT(11)。本应用中的PK都是INT。

其他建议是 PHP 5.3.3 fixed in 5.3.6、full disk 和 need to typecast a SimpleXML value 可能存在问题。我们确实正在运行 PHP 5.3.3,但在这种情况下升级必须是最后的手段。它并不总是这样做。

更新/注意:我自己实际上无法重现错误,只能在日志中看到它发生,请参阅下面的段落了解我相信错误发生的位置...

* 从错误日志中,似乎至少有一个地方发生了 index.php。我推断这一点只是因为它在某些条目中由引用 URL 指示。 try/catch 代码目前仅围绕脚本的“顶部”初始化部分,其下方主要是 html 输出。输出中有一些 PHP 代码(虽然很简单),所以我可能需要对其进行测试。这是捕获部分,它不会在日志中产生任何输出:

 catch (Exception $e) 
    error_log(get_class($e)." thrown. Message: ".$e->getMessage(). "  in " . $e->getFile() . " on line ".$e->getLine());
    error_log('Exception trace stack: ' . print_r($e->getTrace(),1));

非常感谢任何关于此的提示!

编辑:PHP 作为 Apache 模块运行(服务器 API:Apache 2.0 处理程序)。我认为没有使用任何 PHP 加速器,但可能只是我不知道如何判断。 listed on Wikipedia 中没有一个在 phpinfo() 中。

据我所知,MPM 是 prefork。这是我第一次研究 MPM:

# ./httpd -l
Compiled in modules:
  core.c
  prefork.c
  http_core.c
  mod_so.c

【问题讨论】:

你是否从析构函数中抛出异常? 你能可靠地重现这个吗?如果是这样,我会尝试在 Web 服务器之外运行有问题的代码,如果可能的话,或者在 CGI 等基本的东西下运行。我有一个模糊的回忆,我有这个错误,这与我运行 PHP 的 MPM 有关。 @DampeS8N,我相信析构函数中没有异常代码,如果有的话,应用程序并没有真正过多地使用析构函数.. 好吧,然后尝试找到相似之处。在您收到的每个请求开始时,将当前时间戳和 print_r(get_defined_vars()) 写入日志文件。甚至更好:让tcpflow 运行,这样您就可以完整地转储http 标头。然后,每当错误日志中发生错误时,尝试通过时间戳识别正确的日志文件和日志文件上的位置。重建确切的 http 请求标头并将其发送到您的服务器。也许这会帮助你重现。 @yankee,你就是那个男人!使用日志输出精确定位...会话中存储了一对SimpleXMLElements。但是,要注意的是,它只会在访问以前从未存在过的 IP 时发生,所以我不会注意到我自己的任何请求触发它!期待在某个时候尝试 tcpflow.... 现在,我没有看到再次“开始赏金”的选项,您知道是否有其他方式可以奖励您更多积分(有 50 个代表点没有补充的赏金)?您只想创建一个我会接受的答案吗? 【参考方案1】:

错误地填充了 Illuminate Eloquent 模型的 Fillable 属性后,我遇到了同样的错误。请注意数组的最后 3 个元素,其中一个是缺少昏迷

protected $fillable = [
    'budget',        
    'routestatus' ,
    'userroutenumber'
    'totalmovingseconds',
    'totalidleseconds'
];

【讨论】:

感谢分享!我很惊讶这不是解析错误——也许 Illuminate 在模型的上下文中做了一些额外的事情。我想知道它是否与它的缓存方式有关【参考方案2】:

您的数据库中可能有一个损坏/不一致的表。尝试转储数据库。如果你得到一个错误,那就是时候了。修复那个表,问题就会消失。

这就是为什么全新安装有效的原因。全新安装就是那么干净。

mysqlcheck 应该可以工作,但如果它没有显示并且问题仍然在上面。

【讨论】:

【参考方案3】:

当我更改几个 Symfony 包的命名空间时,我遇到了这个问题。删除 symfony 缓存目录中的文件解决了这个问题。

【讨论】:

【参考方案4】:

我有完全相同的错误。一个非常特殊的情况:如果您将未命名的函数(闭包)挂钩连接到对象实例的挂钩点。之后,您尝试序列化此对象。

【讨论】:

【参考方案5】:

产生此错误的一个简单方法是使用 register_globals = On 的旧服务器。那么你只需要两行代码:

<?php
    $_SESSION["my_var"] = "string";
    $my_var = new MyClass(); //could be any class, i guess
?>

一旦您重新加载此页面,您就会收到 Exception thrown without a stack frame in Unknown on line 0 - 错误。似乎类的实例和(会话)变量之间存在冲突。 至少这就是我得到这个很难调试的恼人错误的原因。

【讨论】:

【参考方案6】:

我知道这个问题已经得到解答,但我会添加这个,因为它可能对某人有帮助:

我设法(无意中)在没有堆栈帧的情况下从一个函数产生了错误,该函数使用自己的错误处理程序来保持对执行的控制,同时调用一个潜在的“危险”函数,如下所示:

// Assume the function my_error_handler() has been defined to convert any
// PHP Errors, Warnings, or Notices into Exceptions.

function foo() 
    // maintain control if danger() crashes outright:
    set_error_handler('my_error_handler');

    try 
        // Do some stuff.

        $r = danger();
     catch (Exception $e) 
        $r = 'Bad Stuff, Man!';
    

    restore error_handler();
    return $r;

如果“做一些事情”中的逻辑直接从 foo() 返回,绕过对 restore_error_handler() 的调用,程序执行结束时会发生“无法追踪的故障”。我从这次经历中得到的是:

    PHP 维护一个 堆栈 错误处理程序,每次调用 set_error_handler() 时,该堆栈会变得更深/更高。 如果您将错误处理程序压入堆栈并且在程序“正常”退出之前没有自行清理,则可能会发生错误。

这是一个很难隔离的错误 - 我基本上将问题缩小到上述功能,然后盯着它直到我的眼睛流血。

那么,如果我知道我现在所知道的,我将如何追踪它呢?由于我不知道直接检查 PHP 错误处理程序“堆栈”的任何方法,我认为使用 Singleton 对象来封装 PHP 错误处理程序的所有设置/恢复操作可能是有意义的。至少可以在正常退出程序之前检查 Singleton 的状态,如果检测到“悬空”错误处理程序以在 PHP 崩溃之前生成合理的失败/警告消息。

【讨论】:

【参考方案7】:

对我们来说,这个错误是由于无意中序列化了 SimpleXML 对象。

如果您在 5.3.3 中使用 SimpleXML 对象,请确保在序列化会话中的值时将节点值转换为您需要的任何值(例如字符串)。

之前

  $token = $response->Token->Value;
  /* token saved in session, results in line 0 error */

之后

$token = (string) $response->Token->Value;
  /* token saved in session, no error */

【讨论】:

已经有一段时间了,但我认为这里也是如此【参考方案8】:

我遇到了同样的错误,似乎将服务器从 centos 5 升级到 centos 6 并将 PHP 从 5.4 降级到 5.3。实际问题是 PHP apc,未正确配置。检查您的 APC。我使用的是 Symfony2,所以你可以在 Symfony Unable to allocate memory for pool 找到一些帮助

【讨论】:

【参考方案9】:

这可能有点晚了,但我在将站点从本地服务器移动到远程服务器时发现了一个问题。我正在使用 Concrete5 cms 在本地开发了我的网站(xampp 中的 windows 8),然后上传到运行 Cent 0S 的远程服务器

Windows mysql 默认情况下不区分大小写并创建一个小写数据库。将其上传到远程服务器后,我收到“在第 0 行的 Unknown 中抛出没有堆栈帧的异常?”

然后我更正了数据库表的情况,我的网站又开始工作了。

【讨论】:

啊!我真的很讨厌不区分大小写/敏感的问题:(【参考方案10】:

问题

简而言之,您在某个地方抛出了异常,您不知道在哪里,直到现在您无法重现该错误:它只发生在某些人身上,而不发生在您身上。您知道它会发生在其他人身上,因为您会在错误日志中看到这一点。

重现问题

由于您已经消除了需要重现错误的常见原因。如果您知道哪个参数会导致错误,那么应该很容易找到错误。

如果您知道所有 POST/GET 参数,很可能就足够了。 如果您不能仅使用这些进行复制,则需要了解其他请求标头。比如用户代理,accept-encoding,... 如果还是不能复现,那就很困难了:错误可能取决于状态(会话)、当前时间、源IP地址等。

自定义日志方法

让我们从简单的开始:要获取所有参数,您可以在受影响的 php 文件的最开头编写如下内容:

file_put_contents("/path/to/some/custom_error_log", date()."\n".print_r(get_defined_vars(), true), FILE_APPEND | LOCK_EX);

不要忘记 custom_error_log 文件必须可写入您的 php 应用程序。然后,当错误日志中出现错误时,在您的 custom_error_log 文件中找到相应的行。希望每秒没有太多请求,以便您仍然可以识别请求。也许错误日志中的一些附加参数(如源 ip)可以帮助您识别请求(如果您的错误日志显示)。 根据该数据,重构具有相同 POST/GET 参数的请求。

tcpdump 方法

下一个选项也非常简单,但需要您在目标计算机上具有 root 访问权限,即安装 tcpflow。然后创建一个文件夹,cd 进入该文件夹并简单地执行(以 root 身份)tcpflow "port 80"。选项(端口 80)是一个 pcap 过滤器表达式。要查看您可以做的所有事情,请参阅man pcap-filter。这些过滤器表达式可以做很多事情。

现在 tcpflow 将记录端口 80 上的所有 tcp 连接,通过组合属于一个连接的包并将此数据转储到一个文件来重建完整的数据交换,每个连接创建两个新文件,一个用于传入数据,一个用于传出数据。现在再次根据错误日志中的时间戳和文件的最后修改时间戳查找导致错误的连接的文件。然后你会得到完整的 http 请求头。您现在可以完全重构 HTTP 请求,包括设置相同的接受编码、用户代理等。您甚至可以将请求直接通过管道传输到 netcat,重放确切的请求。请注意,某些参数(如 sessionid)可能会妨碍您。如果 php 发现会话已过期,您可能只会重定向到登录或其他意外。您可能需要交换会话 ID 等内容。

嘲笑更多的东西

如果这些都没有帮助,并且您无法在您的机器上重现错误,那么您可以尝试模拟所有难以模拟的内容。例如源 IP 地址。这可能需要一些特技,但这是可能的:您可以使用带有“-w”选项的 ssh 连接到您的服务器,创建一个隧道接口。然后将有问题的 ip 地址分配给您自己的机器并设置路由(路由添加主机)规则以使用特定 ip 的隧道。如果您可以将两台计算机直接连接在一起,那么您甚至可以不使用隧道。

不要忘记模拟应该是最简单的会话。您可以使用 print_r(get_defined_vars()) 方法读取所有会话变量。然后你需要创建一个具有完全相同变量的会话。

询问用户

另一个选项实际上是询问用户他在做什么。也许你可以按照他的相同步骤,并且可以重现。

如果这些都没有帮助

如果这些都没有帮助......好吧......那么它会变得非常困难。 IP 的事情已经非常不可能了。它可能是一个 GEO-IP 库,导致来自特定区域的 IP 出现错误,但这些都是不太可能发生的事情。如果以上都没有帮助您重现问题,那么您可能只是在 custom_log_file-call / tcpflow 生成的所有数据中没有找到正确的请求。尝试通过获取更准确的时间戳来增加机会。您可以在 php 中使用 microtime() 作为 date() 的替代。检查您的网络服务器,如果您可以在错误日志中获得比秒数更准确的信息。编写自己的“tail”实现,为您提供更准确的时间戳,...减少系统负载,这样您就不必从那么多数据中进行选择(尝试一天中的另一个时间,用户负载到不同的服务器,...)

一旦你能重现问题,就圈出问题

现在,一旦您可以重现它,就应该在公园里散步以找到真正的原因。您可以通过反复试验找到导致错误的参数,或者通过将其与其他导致错误的请求进行比较,寻找相似之处。然后你可以看到这个参数的作用,哪些库访问它等等。你可以一个一个地禁用每个使用该参数的组件,直到你不能再复制。然后你得到你的组件,可以更深入地研究问题。

告诉我们你发现了什么。我很好奇;-)。

【讨论】:

【参考方案11】:

注册异常处理程序时会发生什么,而不是将代码包装在 try/catch 块中?显然,您的 try/catch 块没有捕获异常,从而导致错误记录到 Apache。通过注册处理程序,您可以确保处理任何未捕获的异常。

另外,如果您在应用程序中使用命名空间,请确保在 catch 块中写入 \Exception(或通过 use 语句包含 Exception 类)。

【讨论】:

该死!我把它放在初始化脚本的顶部:function my_exception_handler($e) error_log(get_class($e)." thrown. Message: ". $e-&gt;getMessage(). " in " . $e-&gt;getFile() . " on line ". $e-&gt;getLine()); error_log('Exception trace stack: ' . print_r($e-&gt;getTrace(), 1)); set_exception_handler('my_exception_handler'); 验证它确实捕获了异常,就在几分钟前,另一个进来了! [Mon May 21 09:48:39 2012] [error] [client x.x.x.x] PHP Fatal error: Exception thrown without a stack frame in Unknown on line 0我也对这个寄予厚望..【参考方案12】:

我也有这样的错误。发现我在我的会话类(由 session_handler 使用)中返回了一个 sql 对象,而不是返回任何内容或至少不返回 sql 对象。首先检查你的 _write 和 _read 方法,如果你也返回了一些不正确的东西。

Notice: ... Unknown on line 0 - How to find correct line, it's NOT "line 0"

【讨论】:

这是一个很好的提示,谢谢...虽然我们没有为这个应用程序使用任何自定义会话处理程序:(

以上是关于困惑:PHP 致命错误:在第 0 行的未知中抛出没有堆栈帧的异常?的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 错误 - 未知:需要打开失败'。 ./public' (include_path='.;C:\php\pear\') 在第 0 行的未知

PHP文件不在Windows机器中执行

PHP 警告:模块已在第 0 行的未知中加载

警告:文件上传错误 - 无法在第 0 行的未知中创建临时文件

致命错误:在第 2 行的 index.php 中调用未定义函数 get_header()

PHP 致命错误:在第 45 行的 admin\includes\html\database.class.php 中的非对象上调用成员函数 prepare()