• 5
name

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191

Backtrace:

File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

name Punditsdkoslkdosdkoskdo

Get a stack trace of a hung PHP script

I have a script that runs from a cron job every night. Recently, it has started totally freezing up after several minutes into the script, and I can't figure out why. If this was Java, I could simply run kill -3 PID and it would print a thread dump in stdout. Is there any equivalent in PHP, where I could get a dump of the current stack trace (and ideally memory info) on a running PHP script?

php

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.

  • 7
Reply Report
    • From top of my head: dump_ht executor_globals.active_symbol_table (to verify the name of the variable to use: go to lxr.php.net or your local PHP source tree and check the executor_globals definition)
      • 2
    • I'm using the pthreads extension (php multithreading) and for this to work I had to first use the command set_ts (else ____executor_globals crashed) with the 'tsrm_ls' argument value appearing in the stack frames

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__');
}
  • 3
Reply Report
      • 2
    • This might work if you can't add the --debug-enable flag to the configure script. But I don't think you'd be doing this level of debugging if you couldn't also play with the configure augments.

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.

  • 2
Reply Report

A PHP script should timeout if it exceeds the maximum runtime. Surely that would solve the problem for you; just wait for it to break, and there's your stack trace.

I guess it's possible that you've got something in your code (or php.ini) that sets the max runtime to zero to stop it breaking. If you've got that, then get rid of it (or set it to a very large timeout if the default really is too small).

You might also want to try running it with xDebug or a similar tool, which will give you a profiler trace that will give you a call tree of the program, and also allow you to step through the code in your IDE, so you can see exactly what is happening; if there's an infinite loop in there, you ought to be able to identify it pretty quickly from that.

  • 1
Reply Report
      • 1
    • This is a long running script. Some times it can take several hours. I regularly reset the timeout for an amount that's reasonable for the function that's running, but this somehow does not catch my problem.

I would like to propose alternative approach avoiding the use of php debug symbols. In some cases installing debug symbols are more troublesome, i.e. when we run httpd inside non-root docker container. This approach requires xdebug extension to be installed (which are already installed in, for example, Openshift-based PHP containers)

First, download this gdb script : dumpstack.gdbscript, and save it in /tmp/dumpstack.gdbscript.

Second, invoke gdb like this (substitute $pid with the php process id):

gdb --batch --readnever --pid=$pid --command=/tmp/dumpstack.gdbscript 2>/dev/null

The result will be similar to these :

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007fe37f597d47 in flock () from /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 [New LWP 57961]
[New LWP 57960]
[New LWP 57956]
[New LWP 57955]
[New LWP 57954]
[New LWP 57953]
[New LWP 57952]

If you need to dump all httpd processes running PHP scripts, download this bash script dumpphp.sh, make it executable, and run it.

  • 0
Reply Report

Warm tip !!!

This article is reproduced from Stack Exchange / Stack Overflow, please click

Trending Tags

Related Questions