Handling Segmentation Faults in userland PHP

I’ve been doing a lot of multi-process and signal programming PHP lately, and I found myself wondering which signals it’s possible to intercept and handle in userland PHP. Of my findings, the most interesting was that you can trap SIGSEGV (“segmentation fault”) and handle it yourself.

By default, when the kernel raises a SIGSEGV on a PHP process, PHP intercepts the signal and handles it by simply printing “Segmentation Fault” to stderr (standard error) and the process immediately exits. This is understandable behaviour, but in most applications, especially large ones, it’s useful to know what it was that caused the segfault to be raised so you can address the problem.

So I found myself wondering if it was possible to trap SIGSEGV and gather some debug information about it before exiting, and discovered that it’s much simpler than it sounds:

<?php
/**
* Demonstrates signal handling to trap Segmentation Fault using userland PHP.
*
* Useful for debugging larger applications that occasionally generate
* segfaults.
*
* You are free to modify and redistribute this code without attribution.
*
* @author Nick Telford <nick.telford@gmail.com>
* @copyright Nick Telford (c) 2010
*/

// you need this towards the start of your application in order for signal
// handlers to be executed
declare(ticks = 1);

/**
* Signal handler for segmentation faults.
*
* Since a segfault indicates a memory problem, it's wise to keep this separate
* from other signal handlers and to have it do as little as possible.
*
* Also, since there's a memory problem, this should call exit(1), otherwise
* you could end up with unpredictable behaviour. Only avoid exit(1) if you
* *really* know what you're doing.
*
* @param integer $sig The UNIX signal, will be SIGSEGV for a segfault.
*/
function handleSegfault($sig)
{
    // note: unless you're running the CLI SAPI, this will be sent to the
    // browser, fwrite() it to STDERR instead or better yet, log the info
    // somewhere (a file or syslog) for later analysis
    echo "Segmentation fault, printing backtrace:\n";
    debug_print_backtrace();
    exit(139);
}

// attach the segfault handler
pcntl_signal(SIGSEGV, 'handleSegfault');

// tests the segfault handling by generating one
posix_kill(getmypid(), SIGSEGV);

Update: I’ve changed the exit code to 139, as processes terminated by a UNIX signal should use an exit code that is the signal number + 128. The exit code for processes that terminate due to SIGSEGV is 139.

As my notes in the code suggest, you shouldn’t merely echo the information out, as by default it is sent to stdout (standard output), which any SAPI handling web requests will send to the browser. Something strongly inadvisable.

Instead, you’ll most likely either want to output the information to stderr (using fwrite()) or log the data using your application’s logging layer. It’s important that you execute as little code as possible, especially memory operations, as a segmentation fault indicates memory corruption that could have unpredictable effects on your application.

In PHP 5.3.0 PHP’s handling of signals was changed from implicit, using declare(ticks = n) to instruct the interpreter to automatically dispatch pending signals after n lines of code are executed; to explicit, by calling pcntl_signal_dispatch() yourself. Whilst the new explicit process is more flexible and provides for better performance, it does have the potential to decrease the durability of your application, especially if you’re trapping SIGSEGV, because the application will continue to run until you call pcntl_signal_dispatch(), even though memory corruption has occurred.

Finally a note about performance in PHP < 5.3.0: using declare(ticks = 1) tells PHP to check for pending signals after it executes every line of code. Obviously this adds an overhead (albeit a small one) to your application. For that reason, it's probably wise to have this disabled in production environments.

Creative Commons LicenseThis work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 2.0 UK: England & Wales License.

Leave a Reply