Archive for September, 2007

FreeBSD + PHP + OCI8 segfault fix

September 12, 2007

At work I manage FreeBSD servers mostly running web applications. One of the databases we need to be able to talk to is an Oracle database, so all I need to be able to do is connect, perform queries, and retrieve results. We need to be able to do this from PHP.

On FreeBSD, one obvious option is to use oracle8-client, which is a binary port of Oracle’s client libraries for Linux. The PHP OCI8 extension port has this as a dependency, so with no configuration aside from building PHP with OCI8 support, you now have oracle8-client on your system.

Everything was incredibly simple to install, except for one problem. Now, with the OCI8 extension enabled in php.ini, the following occurs:


user@host:~> php hello.php
Hello world!
Segmentation fault
user@host:~>

The contents of hello.php don’t matter, but now all PHP scripts run from the command line always segfault on exit. This includes pear and other CLI utilities written in PHP. Using gdb I was able to trace unloading of the OCI8 library to a call in Zend/zend_API.c (DL_UNLOAD) which calls dlclose(3) on a FreeBSD system. After doing some reading, I can only assume that the Oracle library is doing something incorrectly such as registering a function to be called at exit via atexit(3). In that case, we would call dlclose(3) after the function(s) are registered, but then when we exit and those registered functions get called, we get a segmentation fault as we’ve unmapped the memory they live in! While that may not be precisely what is happening in this case, as far as I can tell I’m dealing with a bug in the Oracle library.

We were able to run like this (in production too) for quite some time before I found a solution, because everything in the script could run before the segfault occurred. However, the return code would always be nonzero (so scripts that properly checked return codes from CLI PHP scripts had to be modified), it would spam the kernel logs, and generally annoy myself and our developers. Also, OCI8 connections made through httpd and mod_php were unaffected as the PHP module and extensions get loaded into memory, but not unloaded during the normal course of execution.

My patch is not elegant but works fine:


--- Zend/zend_API.c.orig Wed Sep 12 12:40:21 2007
+++ Zend/zend_API.c Wed Sep 12 12:43:45 2007
@@ -1912,9 +1912,11 @@

#if HAVE_LIBDL || defined(HAVE_MACH_O_DYLD_H)
#if !(defined(NETWARE) && defined(APACHE_1_BUILD))
+/*
if (module->handle) {
DL_UNLOAD(module->handle);
}
+*/
#endif
#endif
}

which essentially stops PHP from calling dlclose(3) on modules when it is exiting. As far as I have tested this works just fine because PHP is exiting anyway, but I would not use this patch if the dlclose(3) was called normally during runtime without exiting immediately afterwards. If you care to apply this patch, I saved the above text in a file called patch-Zend::zend_API.c, placed it in /usr/ports/lang/php5/files, and rebuilt PHP by executing portupgrade -f php5. Ports will automatically patch zend_API.c appropriately, and you should be good to go. The system I tested all of this on:

FreeBSD 6.2-RELEASE-p6
PHP 5.2.3
oracle8-client-0.1.1_1

There are some other instructions out there but I have found no need for the instant client, nor can I see how the instant client is even used by PHP in the example given there. That means you don’t need the Linux compatibility module loaded into your FreeBSD kernel, a Linux base system, or any of Oracle’s instant client packages (which require registration to download anyway).