Get a stack trace of a running or hung PHP script

The best thing you can do is compile PHP yourself using --enable-debug during configure. If the process then still hangs you can use gdb and some macros to get a PHP-level stacktrace using these steps:

$ 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

And then debug ahead :-)

Note that for this to work you have to have a PHP binary with symbol information, --enable-debug ensures that.


I've noticed that there is a possible solution using pcntl_signal. I haven't tried it myself, you can find some sample code here:

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__');
}

Yes. You can get a backtrace with debug_backtrace. Also you may need memory_get_usage.


Provided you have gdb and debugging symbols for PHP installed, you can get a full PHP backtrace.

The method of installing debugging symbols may vary from distro to distro. For instance on Amazon Linux I ran rpm -qa | grep php56-common and passed the result to debuginfo-install. Note that installing debugging symbols using the standard package manager might not give the desired result - on Amazon Linux I got debugging symbols for a different version of PHP when running yum install php56-debuginfo and gdb didn't like that.

If you have configured your machine to produce core dumps you don't even need the process to be running to get a backtrace. You can kill -ABRT $pid and inspect the core dump later.

You can then start debugging a running process with gdb -p $pid or a core dump with gdb /usr/bin/php $path_to_core_dump.

Typing bt will give you a C stack trace. This will sometimes be enough to give you a hint of what might be wrong. Ensure that debugging symbols are correctly installed; bt should point at file names and line numbers in the PHP source code.

Now try p executor_globals.current_execute_data. It should print something like $1 = (struct _zend_execute_data *) 0x7f3a9bcb12d0. If so, it means gdb can inspect the internals of PHP.

Using a little Python scripting you can produce a full PHP backtrace. Type python-interactive and then insert this tiny script:

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"))

Note that this method depends heavily on the internals of PHP, so it might not work on earlier or later versions. I did this with php 5.6.14. The advantage of this method is that it works for both running processes and core dumps, plus you don't have to recompile PHP or restart your PHP script. You can even go install gdb and debugging symbols after you discover a hanging process.