We provide Android libraries for use with the Dalvik virtual machine as a part of our ASN1C and XBinder products, but we also build our C/C++ runtimes using the Android NDK as well.  These libraries can be used through the Android native interface.

We recently received a report that there were missing symbols in our runtimes when using JNI.  It took a bit of effort to track down the failure, but thanks to a dedicated reseller and some internal testing, we were able to isolate and resolve the issue.  This blog post helps to summarize some of the issues and suggests some workarounds for those considering using our software with JNI, but it should be more broadly applicable to anyone who is using the Android NDK for their own purposes. It's a fairly lengthy take on the issue, but we hope it illuminates some of the pitfalls that can come with building native Android applications.

The Error

The reported error looked a bit like this:

Test report.... Android Simulator -> UnsatisfiedLinkError: rtpv664androidarmubp(libasn1rt.a + libasn1per.a) -. Using /Users/.../rtpv664androidarmubp_obj_2013_0822/asn1c-v664/cpp/libgpp/ (libasn1rt.a + libasn1per.a) -. ...: E/AndroidRuntime(1265): Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: reloc_library[1285]: 36 cannot locate 'rtxMarkPos'...

This indicated the missing symbol was the rtxMarkPos function.  We were a bit mystified since the symbols were certainly in the library.  Their application compiled and linked without any problems, so clearly something else was to blame.

How Does This Happen?

Those of you who have used dynamically-linked applications can recognize the basic problem:  the library in question doesn't provide the requested function at run time. It's common with applications whose dependencies are satisfied by the build host but not the target deployment system. (This is why we provide our own versions of the Qt libraries when distributing our GUIs: the target systems often have a different version of Qt whose symbols differ from the ones we link against.)

Usually this problem arises when an application is successfully built on a host system and deployed to a target whose libraries don't match. But it can also happen when attempting to dynamically load a library from within an application. In Java, this is done via System.loadLibrary. Unfortunately, these libraries can be incorrectly linked very easily—even by automated build tools. (In this case, it appears to be an issue with Eclipse, which successfully resolved the functions in our libraries when compiling the application and failed to export them into a custom library that our customer created.)

We'll demonstrate the issue by showing progressive attempts to compile an application against an improperly linked library here, but your experience with JNI will probably look rather more like the error above.

The Diagnosis

Dynamically linked libraries will happily ignore missing symbols (tools like nm use the U to mark them as undefined), so it's easy to accidentally create broken ones if you're not careful. This becomes especially problematic with optimizing linkers, since they remove functions that aren't directly referenced. Library link order is very significant in this scenario.

A Static Linkage Example

Let's assume that we've used ASN1C to generate a small test writer application. If we attempt to link the application with the wrong library order, we'll generate an error at link time:

$ g++ -static -o writer writer.o Test*.o -lasn1rt -lasn1per

The writer fails to link because the PER runtime library uses symbols that are defined in the common runtime library—but gcc doesn't know that the symbols are needed because it hasn't identified any undefined symbols when processing the libraries. It discards the objects it finds in libasn1rt.a and then moves on to libasn1per.a, where it reports missing symbols:

../../cpp/lib/libasn1per.a(asn1PerCppTypes.o): In function `ASN1PERDecodeBuffer::peekByte(unsigned char&)' ...

When we reverse the link order, the error disappears:

$ g++ -static -o writer writer.o Test*.o -L ../../cpp/lib -lasn1per -lasn1rt

Dynamic Linkage

This is especially important to remember when we are creating a dynamic library. Let's say, for example, that we want to make a single shared library to link our writer application against:

$ g++ -shared -o libasn1rtper.so libasn1rt.a libasn1per.a writer.o Test*.o $ ls -l *.so -rwxr-xr-x 1 ethan ethan 97940 Aug 22 14:40 libasn1rtper.so

So far, so good, right? No errors were reported, so we must be okay! Not so fast:

$ g++ -o writer -L . -lasn1rtper ./libasn1rtper.so: undefined reference to `OSRTCtxtHolder::getCtxtPtr()' ./libasn1rtper.so: undefined reference to `OSRTCtxtHolder::printErrorInfo()' [...]

We are clearly missing symbols here, even though there was no link error when we created the shared library. Why? The problem is the link order. Some symbols were clearly exported to the shared library, but not all of the ones that are needed. Since gcc knows what is needed at link time by the order of objects specified, we should do better by moving our generated object files to the front:

$ g++ -shared -o libasn1rtper.so writer.o Test*.o libasn1rt.a libasn1per.a $ ls -l *.so -rwxr-xr-x 1 ethan ethan 809518 Aug 22 14:43 libasn1rtper.so

This is more promising, but attempting to compile the writer application results in the following:

$ g++ -o writer -L . -lasn1rtper ./libasn1rtper.so: undefined reference to `rtxEncBitsFromByteArray' ./libasn1rtper.so: undefined reference to `rtxResetToPos' ./libasn1rtper.so: undefined reference to `rtxDecBitsToByteArray' [...]

The link order again causes problems; in this case, the libasn1rt.a and libasn1per.a libraries are in the wrong order and need to be switched:

$ g++ -shared -o libasn1rtper.so writer.o Test*.o libasn1per.a libasn1rt.a $ ls -l *.so -rwxr-xr-x 1 ethan ethan 844439 Aug 22 14:45 libasn1rtper.so

$ g++ -o writer -L . -lasn1rtper

The generated objects depend on the PER runtime library, and the PER runtime library depends on the common runtime library; they must be specified in this order to properly link the application.

The Solution

Clearly the solution is to make sure that the library link order is correct whenever you create a dynamic library. Your IDE may need to be configured differently to ensure that it doesn't accidentally misorder the libraries.

Alternatively, you can also use the -Wl,-whole-archive switch when creating the library to tell ld to include all of the objects in the relevant libraries. (Obviously this won't work if your backend linker isn't ld or one of its drop-in replacements.) If you use this switch when linking an application, you'll need to make sure that you add `-Wl,-no-whole-archive`` after the libraries, too <http://stackoverflow.com/a/2657390>`__.

We generally wouldn't recommend this latter option because it produces larger libraries than may be needed—it doesn't optimize, after all—and space is at a premium on embedded devices. Various problems using it have also been reported.


Published

Category

ASN1C

Tags