【问题标题】:Get a stack trace of a running or hung PHP script获取正在运行或挂起的 PHP 脚本的堆栈跟踪
【发布时间】:2021-02-14 12:33:42
【问题描述】:

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

【问题讨论】:

  • 简单的解决方案,将一些日志记录语句放入您的代码中,并查看它们在执行中停止的位置。

标签: php debugging stack-trace


【解决方案1】:

您能做的最好的事情是在configure 期间使用--enable-debug 自己编译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 可以确保这一点。

【讨论】:

  • 嗨。是否可以以相同的方式打印 PHP 变量的当前值?
  • 从我的头顶:dump_ht executor_globals.active_symbol_table(验证要使用的变量的名称:转到 lxr.php.net 或您本地的 PHP 源代码树并检查 executor_globals 定义)跨度>
  • 我正在使用 pthreads 扩展(php 多线程),为此我必须首先使用命令 set_ts(否则 ____executor_globals 崩溃)和堆栈帧中出现的 'tsrm_ls' 参数值
【解决方案2】:

我注意到有一个使用 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-enable 标志添加到配置脚本中,此可能会起作用。但如果你不能同时使用配置增强,我认为你不会进行这种级别的调试。
【解决方案3】:

如果您安装了 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】:

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

    【讨论】:

    • 我的问题是我不知道它挂在哪里。如果我这样做了,这将是一个简单的解决方案。
    【解决方案5】:

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

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

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

    【讨论】:

    • 这是一个长时间运行的脚本。有时可能需要几个小时。我会定期将超时重置为对正在运行的函数而言合理的数量,但这并没有解决我的问题。
    【解决方案6】:

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

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

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

    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,使其可执行并运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 1970-01-01
      • 2014-03-09
      • 2012-12-12
      • 1970-01-01
      • 1970-01-01
      • 2015-01-04
      相关资源
      最近更新 更多