-
Notifications
You must be signed in to change notification settings - Fork 298
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
Game packaged as an ipa crashes at startup on iOS #1609
Comments
Here's another Unreal plugin with the same problem: |
This has come up a lot:
Not a lot of solutions, though. |
Kicking this around in my head over the weekend, I almost had myself convinced that it's impossible to solve. If there's any possibility of the constructor of std::string (or anything) to be in our binary while the destructor is in libc++, then this allocator mismatch will happen, and there's nothing we can do to reliably fix it. But then I realized I'm stuck in a Windows mindset. In Windows, functions shared between DLLs must be both explicitly exported and explicitly imported. There's no way that something like libc++ could import a symbol from our binary because the arrow of dependency just doesn't go that way. But in Linux and macOS and iOS, it works differently. This is going to be an oversimplification, but an intuitive way to think of it is that dynamic linking on these platforms is just static linking that happens at runtime. When libc++ calls So I now think that this is the problem. The If I run
However, when I run it on the binary in the IPA (which crashes!), those symbols are gone. I found this post with almost the same problem, that suggests these symbols are being stripped, and that it's possible to exclude them from stripping: So I'm going to try that next. |
It worked! If I open up
Then the generated IPA runs just fine on the iPhone! It should be possible strip everything except the new and delete operators, rather than remove the symbol stripping entirely, but I haven't tried yet. This strip command is added by UnrealBuildTool here: So I can edit UBT to fix this, but that's super hacky. But at least it's a workaround for people running into this. I guess maybe I'll report this to Epic, and maybe they'll be willing to fix it in a future UE release, because AFAICT any Unreal app on iOS that tries to construct a std::string will crash in a fully store-ready build. Still thinking about if there's some way to fix this without a change to Unreal Engine and without requiring the end-user to do hacky things. |
New workaround posted here: Use that instead of commenting out the |
Mentioned on the community forum here:
https://community.cesium.com/t/solved-ios-apps-distributed-to-the-app-store-crash/29401
https://community.cesium.com/t/issues-with-cesium-in-ue5-4-on-ios/37756/3
Packaging a game and running it on an iPhone from Xcode works fine. But if we create an "archive" (ipa) and then run that on the device, it crashes very quickly after showing the splash screen. The immediate cause of the crash is that a std::string's destructor is called, and the memory it holds was not allocated by the standard
operator new
. That's because it was allocated from Cesium3DTileset.cpp, which, being compiled in the usual Unreal way, overridesoperator new
with its own custom allocator.The fundamental problem is that the
basic_string
constructor that gets called is found in our Unreal game, so it uses Unreal's custom allocator, while thebasic_string
destructor is found inlibc++.dylib
, so it uses the standard allocator.I spent a lot of time trying to understand why the constructor/destructor end up coming from two different places. Based on stepping through the disassembly with the xcode debugger (with the app running on the iPhone), I can see:
Why does this happen? Well, in Apple's libc++ (taken from iOS SDK v18.2), the relevant std::string constructor is declared/defined like this:
The destructor looks like this:
So the constructor has
_LIBCPP_HIDE_FROM_ABI
. Among other things, that adds the__exclude_from_explicit_instantiation__
attribute. Here's the clang docs on what that means:https://clang.llvm.org/docs/AttributeReference.html#exclude-from-explicit-instantiation
Long story short, methods marked with this attribute will be implicitly instantiated in every translation unit (cpp file) where they're used. Never will an implementation from libc++.dylib be used. That's consistent with what I'm seeing. Why? I have no idea!
Meanwhile, the destructor is declared
inline
. Well, it's not getting inlined. If it were, there wouldn't be a problem, because both the constructor and destructor would then use Unreal's allocator. But no, it's not inlined, despite that fact that it's declared inline. That's a little odd, but not shocking. For one thing, I reckon thatinline
here doesn't actually do anything at all, because the method implementation is found inside the class definition in this case, which impliesinline
. And second,inline
is doesn't actually mean inline, cause C++ is like that. See here:https://en.cppreference.com/w/cpp/language/inline
So clang isn't necessarily doing anything wrong by not inlining the destructor. 🤷
Alright, so the compiler is doing what it should, and it's easy to see why that causes a crash. The problem is either in Unreal Engine or in libc++, or in any case the interaction between the two of them. What can we do about it? Well, one solution is to tell Unreal not to use a custom allocator. This is done by defining
FORCE_ANSI_ALLOCATOR=1
. That reportedly worked in older versions of Unreal Engine, but not in newer ones (I haven't tried myself). It's not surprising that there would be problems when compiling the plugin and app with the ANSI allocator and the rest of Unreal Engine with its own allocator. Perhaps compiling all of Unreal Engine with ANSI would work, but that's a drastic step for people that just want to package their game for iOS.I'm low on other ideas. Perhaps there are some compiler flag tweaks that would cause that destructor to get inlined. But even if we found such a solution, it feels precarious. Or go the other way, and construct all of our strings with a custom STL allocator that is guaranteed not to use Unreal's custom new/delete functions. But the allocator is part of std::string's type, so we'd basically have to specify it everywhere in cesium-native's public API.
Can we statically-link the C++ runtime library on iOS?
What other possibilities am I missing?
The text was updated successfully, but these errors were encountered: