In the course of bringing our Adobe PDF Library Java interface to Solaris for the 64-bit AMD/Intel platform, we encountered an interesting issue regarding exception handling in shared libraries. We share our story in case it helps someone in the future.
Symptoms: Our APDFL Java interface was nearly completed: code had been ported, examples and tests had been run, and the results were promising. There were several areas with unexpected failures, however. These all manifested as Java virtual machine crashes, and all attempting to handle a C++ exception raised internally inside one of the APDFL C++ shared libraries:
Stack: [0xfffffd7fffbff000,0xfffffd7fffe00000), sp=0xfffffd7fffdfdfd8, free space=2043k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code, …=previous line continue)
C [libc.so.1+0xd9579] _Unwind_RaiseException+0x46
C [libstdc++.so.6.0.9+0xf9cf5] __cxa_throw+0x55
C [libDL100pdfl.so.10.1.0.18+0x8ae446] ASPushExceptionFrame+0x136
C [libDL100pdfl.so.10.1.0.18+0x823cb7] PDEPrefGet+0x307
C [libDL100pdfl.so.10.1.0.18+0x7ece1d] PDEFontIsMultiByte+0x36dd
C [libDL100pdfl.so.10.1.0.18+0x7f00fb] PDEFontSetSysFont+0x6b
C [libDL100pdfl.so.10.1.0.18+0x495571] PDDrawCosObjToWindow+0x1461
C [libDL100pdfl.so.10.1.0.18+0x496182] PDDrawCosObjToWindow+0x2072
This was only an issue with the x64 version of Solaris 10; the x86 32-bit Intel worked OK, as did both versions on the SPARC platform. Nor was this an issue with C++ – based programs using the same PDF Library via its C language interface.
A clue: Digging through, we found a note in our C++ language sample makefile from 2012 detailing a need to explicitly add the GNU C++ libgcc_s library when linking our sample programs, and to place it before the C runtime linkage. This resolved some issues with crashing when handling exceptions. Some research into past notes indicated an ABI mismatch between the C++ exception handling mechanism in the Solaris C runtime and the mechanism used by GCC in its runtime.
There is a known issue with a slight mismatch in the ABI between these two (libgcc_s.so: _Unwind_RaiseException and Solaris libc.so: _Unwind_RaiseException). Binding symbols to the GCC runtime first causes it to be loaded before the Solaris runtime, and everything works out well. But, simply adding this explicitly to our shared library link line did not help anything.
The problem: After some thought, we realized that running the PDF Library through a Java interface is somewhat different than calling it directly from an executable program in one important way. When building a C++ program, the linker can prompt the runtime linker along and give some help in the order of shared libraries to link – this is manifested by the order of shared libraries specified on the link line. But in our case, the Java interface is a native shared library that is loaded by the Java Virtual Machine dynamically, when it is invoked by a call to initialize the PDF Library. The executable running is the JVM – so the runtime linker will have taken its shared library loading information already from the ‘java’ executable. This executable, built with Oracle’s Solaris Studio compiler (formerly known as Sun Studio), will have the Solaris C runtime and its incompatible C++ exception ABI loaded long before the PDF Library is ever requested to be loaded by the dynamic loader.
It was a catch-22: we had to have the GCC runtime loaded before the Solaris runtime, by a program that knew nothing of GCC. Recompiling the PDF Library and the Java interface with Sun Studio was not an option: the C++ language support was not up to the needs of the Java interface and would require significant rewriting. We were stuck – or were we?
The solution: While not known by many, most UNIX systems (including Solaris) include a mechanism to explicitly load one or more shared libraries before starting an executable program: the LD_PRELOAD environment variable. This defines a shared library or a list of shared libraries to be loaded before program execution, and allows users of the PDF Library Java interface to ensure that the GCC runtime and its exception handling is loaded before the Solaris runtime. This causes references at runtime to the exception handling to be routed correctly. While a somewhat drastic measure, this causes all the exception handling to be wired correctly and resolved this issue.
In brief: Shared libraries built with GCC on Solaris for the x64 platform, that are called from programs built with a different compiler, should use the LD_PRELOAD flag to load the GCC runtime. This includes shared libraries built with GCC and used by Java programs via JNI when running in Oracle’s JVM.