Sharing empty strings between exectuables causes segfault

713 views
Skip to first unread message

x.c...@actisec.com

unread,
Aug 11, 2014, 5:05:30 PM8/11/14
to andro...@googlegroups.com
Hello,

I encountered a problem sharing std::string's across library boundaries. 

We have a shared object that loads other shared objects at runtime using dlopen(). Part of the interface passes back a pointer to an object allocated with new. This object holds a std::string. We've been getting segfaults when the string in that object is empty and is being destructed.

It seems that when an empty string is created, the data is set to the static member &std::string::_Rep::_S_empty_rep_storage. When the string is deleted, it is checked against this address, and if it differs the data is free'd. 

The problem seems to be that the empty string is created in the plugin, setting the address of _S_empty_rep_storage is the plugin one. Then when it is deleted in the main shared object the address checked in the dtor is the main shared object's _S_empty_rep_storage. It is different, so the dtor assumes that it is an allocated string and tries to delete it. I've confirmed this to be happening by stepping through and taking note of the addresses compared.

Both the library and plugin SO are compiled using the libgnustl_static.a library provided by the NDK. Deploying with the libgnustl_shared.so fixes the issue, but I would like to avoid that if possible. Any ideas on workarounds? 

As an aside, it seems in other GNU distributions of the static libstdc++ libraries STB_GNU_UNIQUE is set on _S_empty_rep_storage (as it is a static member variable), and so only one address exists for the whole process. At least that's the case on the one from the Linux Mint repo.

The logcat errors are something like this:

libc    : @@@ ABORTING: LIBC: ARGUMENT IS INVALID HEAP ADDRESS IN dlfree addr=0x00006ccc
DEBUG  
: backtrace:
DEBUG  
:     #00  pc 0000f15c  /system/lib/libc.so
DEBUG  
:     #01  pc 00011f03  /system/lib/libc.so (dlfree+1458)
DEBUG  
:     #02  pc 0000d073  /system/lib/libc.so (free+10)


David Turner

unread,
Aug 12, 2014, 4:39:43 AM8/12/14
to andro...@googlegroups.com
On Mon, Aug 11, 2014 at 11:05 PM, <x.c...@actisec.com> wrote:
Hello,

I encountered a problem sharing std::string's across library boundaries. 

We have a shared object that loads other shared objects at runtime using dlopen(). Part of the interface passes back a pointer to an object allocated with new. This object holds a std::string. We've been getting segfaults when the string in that object is empty and is being destructed.

It seems that when an empty string is created, the data is set to the static member &std::string::_Rep::_S_empty_rep_storage. When the string is deleted, it is checked against this address, and if it differs the data is free'd. 

The problem seems to be that the empty string is created in the plugin, setting the address of _S_empty_rep_storage is the plugin one. Then when it is deleted in the main shared object the address checked in the dtor is the main shared object's _S_empty_rep_storage. It is different, so the dtor assumes that it is an allocated string and tries to delete it. I've confirmed this to be happening by stepping through and taking note of the addresses compared.

Both the library and plugin SO are compiled using the libgnustl_static.a library provided by the NDK. Deploying with the libgnustl_shared.so fixes the issue, but I would like to avoid that if possible. Any ideas on workarounds? 


The problem is that both SOs have their own independent copy of the STL, which is why you're seeing this problem (and potentially a few others that would occur when you try to pass objects created by one to the other).

You really need a single copy of the STL that all code uses. One way to do that would be to do something like:

1/ Statically link your main library with LOCAL_WHOLE_STATIC_LIBRARIES := libgnustl_static, to ensure that no symbols are removed from its SO.

2/ Ensure your main library also exports the include path for the STL headers (e.g. LOCAL_EXPORT_C_INCLUDES := ....)

3/ Compile your plugin with a link-time dependency to your main library, this will get the right include paths, and the right symbols at link time.
 
As an aside, it seems in other GNU distributions of the static libstdc++ libraries STB_GNU_UNIQUE is set on _S_empty_rep_storage (as it is a static member variable), and so only one address exists for the whole process. At least that's the case on the one from the Linux Mint repo.


Yes, but this is not supported by the Android system's dynamic linker, as far as I know.

Hope this helps
 
The logcat errors are something like this:

libc    : @@@ ABORTING: LIBC: ARGUMENT IS INVALID HEAP ADDRESS IN dlfree addr=0x00006ccc
DEBUG  
: backtrace:
DEBUG  
:     #00  pc 0000f15c  /system/lib/libc.so
DEBUG  
:     #01  pc 00011f03  /system/lib/libc.so (dlfree+1458)
DEBUG  
:     #02  pc 0000d073  /system/lib/libc.so (free+10)


--
You received this message because you are subscribed to the Google Groups "android-ndk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to android-ndk...@googlegroups.com.
To post to this group, send email to andro...@googlegroups.com.
Visit this group at http://groups.google.com/group/android-ndk.
For more options, visit https://groups.google.com/d/optout.

jeff shanab

unread,
Aug 12, 2014, 6:57:08 AM8/12/14
to andro...@googlegroups.com
If this works consider yourself lucky. On windows it will for for strings under a certain size that store the data in the string instance. above that it dynamically stores it on the heap and you could be in for the slicing issue. No guarantee at all same heap. You need to ensure the data gets copied accross the shared library boundry. char*

Deallocation must always occur in the same heap that it was allocated. I have used boost shared pointer with a custom deleter to ensure that and make it easy to pass around.


x.c...@actisec.com

unread,
Aug 12, 2014, 6:06:33 PM8/12/14
to andro...@googlegroups.com
Thanks for the replies! About 10 minutes after I submitted I found this in the NDK's C++Support docs:

II.3. Static runtimes:

Please keep in mind that the static library variant of a given C++ runtime SHALL ONLY BE LINKED INTO A SINGLE BINARY for optimal conditions.

What this means is that if your project consists of a single shared library, you can link against, e.g., stlport_static, and everything will work correctly.

On the other hand, if you have two shared libraries in your project (e.g. libfoo.so and libbar.so) which both link against the same static runtime, each one of them will include a copy of the runtime's code in its final binary image. This is problematic because certain global variables used/provided internally by the runtime are duplicated.

This is likely to result in code that doesn't work correctly, for example:

  • memory allocated in one library, and freed in the other would leak or even corrupt the heap.
  • exceptions raised in libfoo.so cannot be caught in libbar.so (and may simply crash the program).
  • the buffering of std::cout not working properly

This problem also happens if you want to link an executable and a shared library to the same static library.

In other words, if your project requires several shared library modules, then use the shared library variant of your C++ runtime.


Both of your solutions are good, but as I'm able to I've decided to just use the shared version of the STL. Thanks again!
Reply all
Reply to author
Forward
0 new messages