November 30, 2022

Debugging PHP Segmentation Faults

Debugging

Debugging PHP can be hard, but debugging PHP segmentation faults (segfaults) in the PHP interpreter or its modules is even harder. In this article, we look at how PHP developers (with no prior C knowledge) can analyze, understand, and fix PHP segfaults. 

PHP Segmentation Fault Overview

A segfault in the PHP interpreter or one of its modules is something that rarely happens. But, when it happens, it can cause a lot of frustration and confusion. A PHP developer might feel left in the dark when a segfault happens.

What Is a PHP Segmentation Fault?

A segmentation fault, or segfault, is any condition whereby the application has attempted to access a restricted area of memory.

These crashes are often associated with a file named core. Segfaults are often caused by a program trying to read or write an illegal memory location.

Are PHP Segmentation Faults Dangerous?

No, but they require a bit of preparation to analyze — and it is rarely possible to analyze completely with PHP debuggers.

How to Analyze a PHP Segmentation Fault

A segmentation fault can be due to a hardware issue, software issue (e.g., library incompatibilities), or a code issue. To successfully analyze a segfault, you should be able to reproduce the issue on the same server where it happened (or still happens) and with the same software environment. This means that you should be able to isolate the server where the issue happens, determine if the issue is reproducible throughout a cluster of PHP servers, and add instrumentation to that server.

How to Find the Cause of the Segmentation Fault

Finding the cause of a segmentation fault in PHP is dependent on that fault being reproducible. For example, if you make a POST request to a specific URL with special form data, then you can have a runtime debugging session. If such issues are happening very rarely then you can try to analyze the segfault after it has happened.

Installing the GNU Debugger (GDB)

For the purposes of this article, we will be installing GDB to find segfaults. According to the GDB entry on the Sourceware website, GDB  "allows you to see what is going on `inside' another program while it executes -- or what another program was doing at the moment it crashed."

Most of the popular operating systems have already pre-built packages for GDB and can be installed with a simple command. For the purposes of this article, we will use Ubuntu as our operating system (OS).

In Ubuntu GDB can be installed using the command below:

sudo apt-get install gdb

GDB has to be installed and run on the server where the issue is happening.

NOTE

If PHP is running on an embedded system then it might not be possible to run GDB directly on the same device. For such cases it will suffice to compile and run a gdbserver on the embedded system. In this article we will not cover this topic. Those of you that are interested can find more information on gdbserver here

Debugging PHP Builds

In order to get meaningful information from a binary, you will also need to have the debug symbols for the PHP binary and its modules.

Use Prebuilt Debug Symbols

If you are using PHP distributions like Zend Server, you can just install additional packages with all pre-built debug symbols.

In the case of Zend Server this can be done by typing:

sudo apt-get install zend-server-dbg

For ZendPHP, it would look similar to this:

bash
    apt install $(apt list --installed | grep php8.2 | cut -d"/" -f 1 | awk '{print $0"-dbgsym"}')

Note that you would need to substitute in your desired PHP version into the command line based on what you are running in production.

It is worth checking if your distribution provides debug symbols for PHP before trying to compile a debug build. If the OS does not provide them, then you can read the next option below.

Compile a Debug Build of the PHP Interpreter

If your operating system does not provide easy access to debug symbols for the PHP interpreter then you can try to build a PHP debug build yourself. Be warned, tt requires more steps than just installing the debug symbols and additional software is needed for the compilation of the PHP interpreter.

Read more about setting up a PHP build environment >>
 

The step below installs the additional software for Ubuntu:

sudo apt-get install build-essential autoconf automake bison flex re2c \
	              libtool make pkgconf git libxml2-dev libsqlite3-dev

The steps below describe how to download the PHP source code for the version that you are interested in. In this article we will be using PHP version 8.2.0.

cd <your-development-directory>/
git clone https://github.com/php/php-src.git --branch=PHP-8.2.0

A new php-src directory will be created. In this directory, you can execute the following commands to prepare the compilation:

cd php-src
./buildconf --force
./configure \
	--disable-all \
	--enable-debug \
	--enable-cgi \
	--enable-shared \
	--enable-json=shared \
	CFLAGS="-DDEBUG_ZEND=2"

make && make install

The important piece from the snippet above are `--enable-debug`, which instructs the build system to create a debug version of the PHP interpreter and the desired modules; and `CFLAGS="-DDEBUG_ZEND=2"`, which instructs the compiler to build the interpreter with more debugging statements. This can help us better analyze the root cause of the problem.

It's important to note that the command above compiles only PHP-CGI binary and the json module. If you need more modules or other binary, e.g. fpm, then be sure to read the article linked above.

Analyzing Your PHP Segmentation Faults

For both runtime and post-mortem analysis we will be using GDB. In the root of the PHP sources directory you will find a file named .gdbinit. This file helps us understand the PHP functions that were involved in the event leading to a segfault. For PHP version 8.2.0 the .gdbinit file can be downloaded here.

The analysis must be done on the same machine where the segfault happens. This is because the machine will have the same hardware and memory layout, operating system, binary files, libraries, PHP version, extensions, and configurations.

Runtime / Live Debugging PHP Segmentation Faults

Runtime debugging is useful when the segfaults happen frequently, or are reproducible.

In order for a runtime debugging to be successful we have to limit the number of PHP processes responsible for our segfaulting application to a minimum. It is possible to instruct apache or FastCGI to have only one process.

Once this is done we can run the following command to debug a running PHP process:

gdb -x .gdbinit -p <pid-of-php-process>

In the command above, <pid-of-php-process> has to be replaced with the actual Process ID (PID) of the running PHP process. When the GDB attaches to a running process the process will be paused. To unpause it you have to type the following command in GDB:

continue

Now reproduce the steps that are leading to a segfault. At some point GDB will notify you that a segfault has occurred. You can use the following commands to understand what has caused the segfault from the PHP application.

zbacktrace

A sample output looks like:

example zbacktrace output for a PHP segmentation fault

If the command above does not produce a helpful information then the following two will definitely have a lot of useful information

backtrace
backtrace full

The newly gathered information can be sent to the PHP Core developers or a module developer in order to help them fix the issue. Or you can work-around the issue by using other PHP functions that can produce the same results as the ones causing segfault.

Post-Mortem Debugging PHP Segmentation Faults

Post-mortem debugging involves inspecting the state of the PHP interpreter and its modules that have crashed, in order to determine the conditions that lead to the failure.

The default action for a segmentation fault or bus error is abnormal termination of the process that triggered it. 

As the Wiki entry for Segmentation Faults explains, “a core file may be generated to aid debugging, and other platform-dependent actions may also be performed.”                     

A generated code dump file can be quite big in size. In most operating systems generating a core dump file is disabled by default. To enable it on Ubuntu (or any other Linux distribution) you have to run the following command before running the application:

ulimit -c unlimited

If your PHP interpreter is running as a service then you have to instruct the service to allow core dumps. For further information, we recommend reading this article on how to understand and configure core dumps.

The rest of the commands are similar to the runtime debugging. In order to start GDB, we need to pass the executable as the first parameter, then the code dump as the second parameter:

gdb -batch -x .gdbinit $binary $dump

As an example, in situations where the PHP interpreter is running as a module in Apache you would use:

gdb -batch -x .gdbinit /usr/sbin/apache2 /var/c/ore123

Or, for situations where PHP is running as a CGI/FastCGI service:

gdb -batch -x .gdbinit /usr/bin/php-cgi /var/c/ore123

Final Thoughts

In this article we showed how to find, analyze, and debug PHP segmentation faults using GDB and Zend Server. Stay tuned to the Zend blog for future articles on how to debug PHP using ZendPHP and ZendHQ.

Try ZendPHP and Zend Server for Free

Zend Server ZRay and ZendPHP ZendHQ offer advanced debugging functionality for a seamless PHP development experience. Try free today to see how they work in action.

Try ZendPHP  Try Zend Server

Additional Resources