10

我有一个每天晚上从 cron 作业运行的脚本。最近,在脚本执行几分钟后,它开始完全冻结,我不知道为什么。如果这是 Java,我可以简单地运行kill -3 PID,它会在标准输出中打印一个线程转储。PHP 中是否有任何等价物,我可以在其中获取正在运行的 PHP 脚本上的当前堆栈跟踪(理想情况下是内存信息)的转储?

4

6 回答 6

8

您可以做的最好的事情是--enable-debugconfigure. 如果该过程仍然挂起,您可以使用 gdb 和一些宏通过以下步骤获取 PHP 级别的堆栈跟踪:

$ gdb -p $PHP_PID
(gdb) bt     # Get a system-level stacktrace, might already give some info
(gdb) source /path/to/php-src/.gdbinit # Load some useful macros
(gdb) dump_bt executor_globals.current_execute_data
            # Macro from PHP's .gbinit giving PHP stack trace
            # If you for whatever reason are using a thread-safe PHP build you have to do this:
(gdb) ____executor_globals
(gdb) dump_bt $eg.current_execute_data

然后提前调试:-)

请注意,要使其正常工作,您必须拥有一个带有符号信息的 PHP 二进制文件,以--enable-debug确保这一点。

于 2013-01-10T16:04:38.957 回答
3

我注意到有一个使用 pcntl_signal 的可能解决方案。我自己没有试过,你可以在这里找到一些示例代码:

https://secure.phabricator.com/D7797

function __phutil_signal_handler__($signal_number) {
  $e = new Exception();
  $pid = getmypid();
  // Some phabricator daemons may not be attached to a terminal.
  Filesystem::writeFile(
    sys_get_temp_dir().'/phabricator_backtrace_'.$pid,
    $e->getTraceAsString());
}

if (function_exists('pcntl_signal')) {
  pcntl_signal(SIGHUP, '__phutil_signal_handler__');
}
于 2014-07-01T15:53:30.647 回答
2

是的。您可以使用debug_backtrace. 您也可能需要memory_get_usage.

于 2013-01-10T15:56:08.557 回答
2

如果您安装了 PHP 的 gdb 和调试符号,您可以获得完整的 PHP 回溯。

安装调试符号的方法可能因发行版而异。例如,我在 Amazon Linux 上运行rpm -qa | grep php56-common并将结果传递给debuginfo-install. 请注意,使用标准包管理器安装调试符号可能不会产生预期的结果 - 在 Amazon Linux 上,我在运行时得到了不同版本 PHP 的调试符号,yum install php56-debuginfo而 gdb 不喜欢这样。

如果您已将机器配置为生成核心转储,您甚至不需要运行该进程即可获得回溯。您可以kill -ABRT $pid稍后检查核心转储。

然后,您可以使用 开始调试正在运行的进程gdb -p $pid或使用gdb /usr/bin/php $path_to_core_dump.

键入bt将为您提供 C 堆栈跟踪。有时这足以让您知道可能出了什么问题。确保调试符号安装正确;bt应该指向 PHP 源代码中的文件名和行号。

现在试试p executor_globals.current_execute_data。它应该打印类似$1 = (struct _zend_execute_data *) 0x7f3a9bcb12d0. 如果是这样,这意味着 gdb 可以检查 PHP 的内部结构。

使用一点 Python 脚本,您可以生成完整的 PHP 回溯。输入python-interactive然后插入这个小脚本:

def bt(o):
  if o == 0: return
  print "%s:%d" % (o["op_array"]["filename"].string(), o["opline"]["lineno"])
  bt(o["prev_execute_data"])

bt(gdb.parse_and_eval("executor_globals.current_execute_data"))

请注意,此方法在很大程度上取决于 PHP 的内部结构,因此它可能不适用于早期或以后的版本。我用 php 5.6.14 做到了这一点。这种方法的优点是它适用于正在运行的进程和核心转储,而且您不必重新编译 PHP 或重新启动 PHP 脚本。您甚至可以在发现挂起进程后安装 gdb 和调试符号。

于 2016-01-18T11:40:04.677 回答
1

如果 PHP 脚本超过最大运行时间,它应该超时。这肯定会为您解决问题;等它坏了,你的堆栈跟踪就出来了。

我想您的代码(或 php.ini)中可能有一些东西将最大运行时间设置为零以阻止它中断。如果你有这个,那么摆脱它(或者如果默认值真的太小,将它设置为一个非常大的超时)。

您可能还想尝试使用 xDebug 或类似工具运行它,这将为您提供探查​​器跟踪,该跟踪将为您提供程序的调用树,并允许您单步执行 IDE 中的代码,因此您可以看到到底发生了什么;如果那里有一个无限循环,你应该能够很快地从中识别出来。

于 2013-01-10T15:58:37.387 回答
0

我想提出避免使用 php 调试符号的替代方法。在某些情况下,安装调试符号更麻烦,即当我们在非根 docker 容器中运行 httpd 时。这种方法需要安装 xdebug 扩展(例如,已经安装在基于 Openshift 的 PHP 容器中)

首先,下载这个 gdb 脚本:dumpstack.gdbscript,并将其保存在 /tmp/dumpstack.gdbscript 中。

其次,像这样调用 gdb(用 php 进程 ID 替换 $pid):

gdb --batch --readnever --pid=$pid --command=/tmp/dumpstack.gdbscript 2>/dev/null

结果将类似于这些:

[启用使用 libthread_db 的线程调试]
使用主机 libthread_db 库“/lib64/libthread_db.so.1”。
0x00007fe37f597d47 来自 /lib64/libc.so.6
{main}@/opt/app-root/src/enterprisetracker/index.php:0
global@/opt/app-root/src/enterprisetracker/index.php :117
UploadDownload@/opt/app-root/src/enterprisetracker/system/codeigniter/CodeIgniter.php:197 library@/opt/app-root/src/enterprisetracker/system/application/controllers/uploaddownload.php:5 _ci_load_class@ /opt/app-root/src/enterprisetracker/system/libraries/Loader.php:91 global@/opt/app-root/src/enterprisetracker/system/libraries/Loader.php:827 session_start@/opt/app-root /src/enterprisetracker/system/application/libraries/DX_Auth.php:15 [新 LWP 57961]
[新 LWP 57960]
[新 LWP 57956]
[新 LWP 57955]
[新 LWP 57954]
[新 LWP 57953]
[新 LWP 57952]

如果您需要转储所有运行 PHP 脚本的 httpd 进程,请下载此 bash 脚本dumpphp.sh,使其可执行并运行。

于 2018-10-30T01:53:23.603 回答