获取挂起的PHP脚本的堆栈跟踪

时间:2013-01-10 15:52:15

标签: php

我有一个每晚从cron作业运行的脚本。最近,它在剧本几分钟后开始完全冻结,我无法弄清楚原因。如果这是Java,我可以简单地运行kill -3 PID并在stdout中打印一个线程转储。在PHP中是否有任何等价物,我可以在正在运行的PHP脚本上获取当前堆栈跟踪(以及理想情况下的内存信息)的转储?

6 个答案:

答案 0 :(得分:7)

您可以做的最好的事情是在--enable-debug期间使用configure自行编译PHP。如果该过程仍然挂起,您可以使用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可以确保这一点。

答案 1 :(得分: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__');
}

答案 2 :(得分:2)

是。您可以使用debug_backtrace获得回溯。您还可能需要memory_get_usage

答案 3 :(得分:2)

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

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

如果您有configured your machine to produce core dumps,您甚至不需要运行该流程即可获得回溯。您可以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和调试符号

答案 4 :(得分:1)

如果PHP脚本超过最大运行时间,它应该超时。肯定会为你解决问题;只是等待它打破,并且有你的堆栈跟踪。

我想你的代码(或php.ini)中可能会有一些东西将max runtime设置为零以阻止它崩溃。如果你已经得到了它,那么就把它去掉(或者如果默认值太小则将其设置为一个非常大的超时)。

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

答案 5 :(得分:0)

我想提出一种避免使用php调试符号的替代方法。在某些情况下,安装调试符号会更加麻烦,即在非root 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”。
  来自/lib64/libc.so.6的flock()中的0x00007fe37f597d47
  {main} @ / opt / app-root / src / enterprisetracker / index.php:0
  global@/opt/app-root/src/enterprisetracker/index.php:117
  上传下载@ / 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,使其可执行并运行。