我有一个每天晚上从 cron 作业运行的脚本。最近,在脚本执行几分钟后,它开始完全冻结,我不知道为什么。如果这是 Java,我可以简单地运行kill -3 PID
,它会在标准输出中打印一个线程转储。PHP 中是否有任何等价物,我可以在其中获取正在运行的 PHP 脚本上的当前堆栈跟踪(理想情况下是内存信息)的转储?
6 回答
您可以做的最好的事情是--enable-debug
在configure
. 如果该过程仍然挂起,您可以使用 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
确保这一点。
我注意到有一个使用 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__');
}
是的。您可以使用debug_backtrace
. 您也可能需要memory_get_usage
.
如果您安装了 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 和调试符号。
如果 PHP 脚本超过最大运行时间,它应该超时。这肯定会为您解决问题;等它坏了,你的堆栈跟踪就出来了。
我想您的代码(或 php.ini)中可能有一些东西将最大运行时间设置为零以阻止它中断。如果你有这个,那么摆脱它(或者如果默认值真的太小,将它设置为一个非常大的超时)。
您可能还想尝试使用 xDebug 或类似工具运行它,这将为您提供探查器跟踪,该跟踪将为您提供程序的调用树,并允许您单步执行 IDE 中的代码,因此您可以看到到底发生了什么;如果那里有一个无限循环,你应该能够很快地从中识别出来。
我想提出避免使用 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,使其可执行并运行。