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