Significant Symbols

London, UK

Last week a person on the #php IRC channel on freenode, mentioned that he had problems loading some extensions with his self-compiled PHP binary. For example, trying to activate the timezonedb PECL extension failed with:

sapi/cli/php: symbol lookup error: /usr/local/php/extensions/debug-non-zts-20180731/timezonedb.so: undefined symbol: php_date_set_tzdb

Which is odd, as the php_date_set_tzdb is a symbol that PHP has made available since Date/Time support was added. I asked the user to check whether his PHP binary exported the symbol by using nm , and the answer was:

$ nm sapi/cli/php | grep php_date_set_tzdb 000000000018bc75 t php_date_set_tzdb

The small letter t refers to a local only text (code) section: the symbol was not made available to shared libraries to use. In other words, extensions that make use of the symbol, such as the timezonedb extension can not find it, and hence fail to load.

In PHP, the php_date_set_tzdb function is defined with the PHPAPI prefix, which explicitly should mark the symbol as a global symbol, so that shared libraries can find it:

PHPAPI void php_date_set_tzdb(timelib_tzdb *tzdb)

The PHPAPI macro is used, because on Windows it is required to explicitly make symbols available:

#ifdef PHP_WIN32 # define PHPAPI __declspec(dllexport)

On Linux (with GCC) symbols are made available unless marked differently (through for example the static keyword).

When looking into this, we discovered that his PHP binary had no exported symbols at all.

After doing a bit more research, I found that more recent GCC versions support a specific compiler flag that changes the default behaviour of symbol visibility: -fvisibility=hidden . In recent versions of PHP, we enable this flag if it is supported by the installed GCC version through a check in the configure system:

dnl Mark symbols hidden by default if the compiler (for example, gcc >= 4) dnl supports it. This can help reduce the binary size and startup time. AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [CFLAGS="$CFLAGS -fvisibility=hidden"])

As this makes all symbols hidden by default, the same commit also made sure that when the PHPAPI moniker is used, we set the visibility of these specific symbols back to visible :

# if defined(__GNUC__) && __GNUC__ >= 4 # define PHPAPI __attribute__ ((visibility("default"))) # else # define PHPAPI # endif

When the original reporter saw this, he mentioned that was using an older GCC version: 3.4, and that he could see the -fvisibility=hidden flag when running make , just like here:

… cc -Iext/date/lib … -fvisibility=hidden … -c ext/date/php_date.c -o ext/date/php_date.lo

Because his GCC supported the -fvisibility=hidden flag, the check in the configure script enabled this feature, but because his GCC version was older than version 4, the counter-acting ((visbility("default"))) attribute was not set for symbols that are explicitly marked with the PHPAPI specifier. Which means that no symbols were be made available for shared PHP extensions to use.

The user created a bug report for this issue, but as GCC 3.4 is a really old version, it seems unlikely that this issue will get fixed, unless somebody contributes a patch. In the end, it was quite a fun detective story and to get to the bottom of this!