-
Notifications
You must be signed in to change notification settings - Fork 254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dynamic_cast form pointers is not working when linked with libc++_shared (ndk r15, r16b1) #519
Comments
Just tested: the same behavior when compiled with r15c. |
Sorry, at first I've looked at another part of code and wrote about smart pointers, but it is std::list. Description is fixed now. |
Your test case is not valid C++. Could you upload a test case? |
I'm trying to reproduce the problem outside our environment... |
I still have no luck reproducing the issue outside our environment. Here is clang invocation command:
Can it be configured without flags overriding? I mean it inserts some default options like -fno-rtti and -std=c++11 and then they are overrided in Application.mk in APP_CPPFLAGS. |
For RTTI flags, the last one wins, so If you are trying to cast something from a type in a library that's a prebuilt that was not built with RTTI, maybe that's the problem, but I don't think that's the case based on the command line above. |
Yes, it is definitely compiled with -frtti. Btw, can rtti info be stripped somehow? We use -Wl,-gc-section and -Wl, But anyway, I my testcase I also used that options and it still cannot be reproduced. But in debugger in production code (I can attach screenshot) code looks like: A* a = * iter; // iter is std::list<A*>::iterator
B* b = dynamic_cast<B*>( a ); debugger shows that |
How I can ensure that a shared library really contains rtti? |
I'm pretty sure that RTTI across libraries does require exposing the RTTI data (which is no different from any other symbol), so if you're not exposing that in your version script, that's probably your issue.
$ readelf -sW libfoo.so | c++filt | grep typeinfo |
It was really stripped. How can I compare dynamic_cast implementations within gnustl & libc++? Where is actual for NDK source code located? Another idea: class StreamSocket : public NonBlockSocket -> libfoo.a (static) java: System.loadLibrary( "c++_shared") It's known that dynamic_cast<StreamSocket*>() fails in lib2.so which is loaded after lib1.so There are no dependencies between lib1.so and lib2.so and no C++ object of that type are passed. All communication is done via libjniproxy.so which is not aware about libfoo and its content.
|
I'm trying to reproduce this in test case and foo* symbols' typeinfo get LOCAL attribute instead of GLOBAL as in main project: with -fvisibility=hidden
without -fvisibility=hidden
and there is not typeinfo for class pointers in main project... |
Agreed that this looks like #533. Once I get the fix for that submitted, you should check your app against a canary build. |
I've tested with both: NDK r17 Canary Build 4380476 2017 Oct 6 05:27:34 Still no luck. Maybe your fix is not there yet? So, looking forward to the next build. Will it be available in r16 or should I check only r17 canary? |
Not in r16 yet, but it was in build 4380016 of r17 from a couple hours before the one you tried. I guess you managed to find an unrelated Keep trying to get a test case. If you manage to get a repro case I can take a look. |
I've posted an update on the other bug. Now that I understand the problem better, I think you do have a bug here:
|
Looks like it is not the case. For debug purposes I've added to every class in hierarchy type describing method like: my.h: class Basic {
public:
Basic();
~Basic();
virtual const char* Type();
//...
}
class Derived {
public:
Derived();
~Derived();
const char* Type() override;
//...
} my.cpp: Basic::Basic() {}
Basic::~Basic() {}
const char* Basic::Type() { return "Basic"; }
Derived::Derived() {}
Derived::~Derived() {}
const char* Derived::Type() { return "Derived"; } And latter in code: void myfunc(Basic *obj)
{
Derived* derived = dynamic_cast<Derived*>(obj);
if (!derived)
{
log("Cannot cast from %s to Derived", obj->Type());
}
}
//...
Derived obj;
myfunc(&obj); Results: Cannot cast from Derived to Derived And objects do not pass through dlopen boundary. Every shared library is isolated (no internally defined types exposed) and communicate with each other only by means of simple types and some std:: types (like string & list). |
The above readelf listings with A/B/C classes are from my test case, and dynamic_cast works well there despite Unfortunately I still cannot reproduce the problem in test environment, this issue occurs only in production code. |
System.loadLibrary("a");
System.loadLibrary("b"); and libb.so depends on liba.so, you won't be able to With the code above added to each of your classes, you shouldn't be getting |
Both libraries depend only on system libraries and libc++_shared.so. And do not depend on each other. |
Yeah, we'd already more or less shown that your bug was something different than the other one, but figured it was worth checking. |
I'd removed all of dlopen/dlclose and the problem have gone... When all libs are loaded once from java dynamic_cast works fine with libc++_shared. |
That's good to hear. I'd rather have a better understanding of the problem you were encountering, but given that you have a workaround and haven't managed to work out a shareable test case, I think we should just close this. Let us know if you get more information and we'll reopen. |
Is this solved in NDK update 16.1.4479499 (updated from SDKManager in Android Studio)? Current setup: That NDK, clang and libc++ shared. I'm still having a null returned by dynamic_pointer_cast which with ndk12, clang and gnustl did work (e.g.):
|
There's nothing we can do without a test case. If you have one, post it here and we'll reopen. |
Just to know if the fixes mentioned here made it to a stable r16 release. Either way definitely I'll have to isolate the problem for a test case. Thanks. |
I wrote a tool that might be useful for debugging issues with RTTI and multiple C++ shared objects. It's a single-header-file C++ library that prints the shared object where an e.g. in @Cristo86's case, it should be possible to write something like this: #include "rtti_dump.h"
...
// Dumps (into logcat) the shared library containing the std::type_info for the
// type we're casting *from*.
rtti_dump::dump_type(&typeid(decltype(*inputPlugin.get())), "src");
// Dumps the std::type_info for the type we're trying to cast *to*.
rtti_dump::dump_type(&typeid(TouchscreenVirtualPadDevice), "dst");
// Dumps a hierarchy of std::type_info objects, starting with the most-derived
// class of the inputPlugin object. __dynamic_cast traverses this hierarchy at
// run-time and expects to find both src and dst.
rtti_dump::dump_class_hierarchy(rtti_dump::runtime_typeid(inputPlugin.get())); Assuming a class hierarchy like so... struct TouchscreenVirtualPadDevice {
virtual ~TouchscreenVirtualPadDevice() {}
};
struct OtherBase {
virtual ~OtherBase() {}
};
struct Derived : TouchscreenVirtualPadDevice, OtherBase {};
std::shared_ptr<OtherBase> inputPlugin; ... it would dump something like this into the log:
The tool is documented here. Links:
Let me know if this is helpful. |
Great tool, thanks @rprichard. I have this output, where it seems that
I'll double check if for any reason I'm mistakenly building one of the libs with a different stl. |
gnustl treats different type_info objects as equivalent if they have the same name. libc++abi more strictly follows the "Itanium" C++ ABI. From http://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti-general: "It is intended that two type_info pointers point to equivalent type descriptions if and only if the pointers are equal. An implementation must satisfy this constraint, e.g. by using symbol preemption, COMDAT sections, or other mechanisms."
Yes, that's the problem. libc++abi's I expect that Suggestions:
|
InputPlugin did not have a "key function" so adding a destructor as the non-inline non-pure virtual function made it (I borrowed the idea from #533 @DanAlbert answer, as I couldn't find a reason to invent a function that wasn't there). Before
After InputPlugin.h (addition)
InputPlugin.cpp (just to have that destructor)
rtti_dump output:
So that's it, InputPlugin address matches and dynamic_pointer_cast worked! Thanks again for the explanation about type_info objects treatment by different ABIs. |
I've lost hours and hours and hours and hours today trying to do this on the latest NDK.
|
still an issue for me and i'm on |
Do you have a repro case? As I said up the thread, we don't have enough information to act on this. The most likely scenario is that your type is missing a key function. Otherwise, @rprichard posted some advice that might help, and we can look further if you can provide a test case. |
@hcwiley I managed to fix the error by adding the destructor and some extra code #519 (comment) (following some tips by DanAlbert) with the final rtti_dump output. Does it remotely match your case? |
Please help,this issue happen to me,dynamic_cast always return null in one .so. Description: build.gradle:
device system: dynamic_cast return null code:
class CBase and CSingle only used in the same .so. rtti_dump log:
And readelf :
The lower_type and upper_type look the same but dynamic_cast return null.Did I use rtti_dump.h in the wrong way? |
The shared library that dyanamic_cast always return nullptr(aka library B) use an third-party library A. Obviously,3rd party library A should not implement that symbol, but why Android Linker resolve symbol to A not to libc++_shared.so? |
annoyingly, ELF files don't actually say where to get a symbol from. they just have a list of symbol names to resolve. so you and i know that "printf" is in libc and "cosf" is in libm, but the ELF file doesn't say that, and the dynamic linker has to search. (and if you have a "printf" in your executable, say, it's actually the correct behavior to choose that one rather than the libc one. but for symbols found in libraries it's complicated, and depends on what order things were loaded in. which is why you really want to avoid it ever being the case that there is more than one place to find any symbol.) |
This is only occurring now in Android 5 (SDK 21/22) -fritti enabled Works on all other SDK's |
Did you follow the instructions above? There isn't anything more we can tell you without a test case. |
@DanAlbert i have a case were i need to download 2 libraries dynamically from my localserver(running inside a separate app) in runtime and load them using system.loadlibrary(library_path) / dlopen(library_path). I cannot keep these libraries as dependenicies to my app(its a special use case for testing purpose so this cannot be kept as app dependency). Thanks in advance, |
aiui it can't be done. The C++ ABI does not allow it. There is no way for the runtime to prove that those are the same type. |
Thanks for the response @DanAlbert, But all the shared libraries are compiled using android's ndk. So the ABI is ideally generated for a specific android architecture. So in runtime can't this be resolved, or someway were we can add this runtime loaded libraries abi information into symbol tables or something. Not sure about the approachh, just wanted to know whether there could be a possibility to do this. |
That is what happens (assuming you've followed the instructions above and have key functions). The problem is that each |
@DanAlbert thanks for the detailed explanation. |
Description
Please help, I'm trying to figure out the reason, but still no luck. Maybe it is a bug.
Our project contains several .so which parts are building in different ways (for example, boost and other 3dp libs with standalone toolchains, main part which is bundled in aar with ndk-build and the app itself and jni part with gradle/cmake.
When I've switched to ndk16-b1 and libc++_shared all dynamic_cast's in c++ code from pointers to derived_class stored in std::list<base_class*> turned to nullptr.
For example:
This is only when libc++_shared is used.
I've tested with libc++_static, gnustl_shared & gnustl_static - the problem does not appear.
bPtr as expected is a pointer to object B added to list.
Any ideas?
Environment Details
The text was updated successfully, but these errors were encountered: