Forced exit of the application will have abnormal crash data

environment:

macOS10.15.5 (19F101) and xcode11.4.1

crash:

when my applicatopn debug or export IPA is running on the iPhone, when I forcibly quit the operation, there will be an abnormal crash, the log is as follows

Thread 0 Crashed:
0   libsystem_kernel.dylib        0x0000000187c3ad88 __pthread_kill + 8
1   libsystem_pthread.dylib        0x0000000187b531e8 pthread_kill$VARIANT$mp + 136
2   libsystem_c.dylib              0x0000000187aa6644 abort + 100
3   libsystem_malloc.dylib        0x0000000187b40ba0 _malloc_put + 0
4   libsystem_malloc.dylib        0x0000000187b40d58 malloc_report + 60
5   libsystem_malloc.dylib        0x0000000187b3608c free + 536
6   libc++.1.dylib                0x0000000187cbd16c std::__1::basic_string<char, std::__1::char_traits<char="">, std::__1::allocator >::~basic_string() + 32
7   libsystem_c.dylib              0x0000000187a874c8 __cxa_finalize_ranges + 384
8   libsystem_c.dylib              0x0000000187a877d8 exit + 24
9   UIKitCore                      0x000000018bef58f0 -[UIApplication terminateWithSuccess] + 0
10  UIKitCore                      0x000000018b695b14 -[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:] + 124
11  UIKitCore                      0x000000018b69579c -[_UISceneLifecycleMultiplexer forceExitWithTransitionContext:scene:] + 216
12  UIKitCore                      0x000000018beeb98c -[UIApplication workspaceShouldExit:withTransitionContext:] + 212
13  FrontBoardServices            0x000000018cfb8490 -[FBSUIApplicationWorkspaceShim workspaceShouldExit:withTransitionContext:] + 84
14  FrontBoardServices            0x000000018cfe35ac __83-[FBSWorkspaceScenesClient willTerminateWithTransitionContext:withAcknowledgement:]_block_invoke_2 + 76
15  FrontBoardServices            0x000000018cfc97ec -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 232
16  FrontBoardServices            0x000000018cfe353c __83-[FBSWorkspaceScenesClient willTerminateWithTransitionContext:withAcknowledgement:]_block_invoke + 124
17  libdispatch.dylib              0x0000000187b0b524 _dispatch_client_callout + 16
18  libdispatch.dylib              0x0000000187ab4434 _dispatch_block_invoke_direct$VARIANT$mp + 224
19  FrontBoardServices            0x000000018d008440 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 40
20  FrontBoardServices            0x000000018d00810c -[FBSSerialQueue _queue_performNextIfPossible] + 404
21  FrontBoardServices            0x000000018d008634 -[FBSSerialQueue _performNextFromRunLoopSource] + 28
22  CoreFoundation                0x0000000187dc3b64 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
23  CoreFoundation                0x0000000187dc3abc __CFRunLoopDoSource0 + 80
24  CoreFoundation                0x0000000187dc3244 __CFRunLoopDoSources0 + 184
25  CoreFoundation                0x0000000187dbe274 __CFRunLoopRun + 788
26  CoreFoundation                0x0000000187dbdc34 CFRunLoopRunSpecific + 424
27  GraphicsServices              0x0000000191f0738c GSEventRunModal + 160
28  UIKitCore                      0x000000018bef022c UIApplicationMain + 1932
29  xxxxxxx                      0x0000000104d7e09c main (main.mm:32)
30  libdyld.dylib                  0x0000000187c45800 start + 4

but the same project running on macOS10.15 (19A583) and xcode11.3 (11C29) No problem

so did the system update to upgrade some configuration

thank you!!!

Replies

What’s happening here is as follows:

  1. You remove the app from the multitasking UI. This has three different behaviours depending on the state of the app:

    • If the app is running, it triggers programmatic termination. Specifically, it ends up calling the

      -applicationWillTerminate:
      app delegate method (a common feature of iOS development before we introduced multitasking but now only called in this specific edge case).
    • If the app is suspended in the background, it simply removes it from memory.

    • If the app is not running at all, it does nothing.

    In all three cases it then goes on to do all the other actions associated with this gesture, notably, removing the app from the multitasking UI and flagging it to not be launched in the background.

    In the second two cases we’re gone. In the first case, programmatic termination continues as follows.

  2. After your

    -applicationWillTerminate:
    method returns, UIKit starts terminating the app. This eventually calls
    exit
    , which you can see in frame 8 of the backtrace you posted.
  3. exit
    starts calling all the various
    atexit
    handlers. This includes C++ static destructors. Frame 7 is the C++ runtime, suggesting that one of those destructors is involved in this crash.
  4. One of the destructors is calling

    free
    (frame 5), which has noticed some some memory corruption and thus calls
    abort
    (frame 2).

This raises a bunch of questions:

  • Do you need to run these destructors at all? In most cases, the answer is “No.”

  • Still, the fact that they’re crashing due to corrupted memory is not good. What’s causing that.

To move forward with this I recommend that you start by making it easier to reproduce the problem. If you add a (temporary, just for testing) button to your app that calls

exit
, does that also crash in this way?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
I'm having what I suspect is the same problem.  I have a multithreaded app, and one of the threads decides to shut the app down, and it sometimes crashes.  I've narrowed down the crash to this example C++ app:

Code Block
#import <dispatch/dispatch.h>
#import <iostream>
#import <regex>
int main() {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Main app code goes here
        // Spawn a background thread to do some work:
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            static const std::regex pattern("hello world", std::regex_constants::icase);
            std::cmatch match;
            for (int x = 0; x < 1000; ++x) {
                //std::cout << x << std::endl; // To make sure exit() is called somewhere inside this loop
                std::regex_search("example", match, pattern);
                std::regex_search("hello", match, pattern);
                std::regex_search("world", match, pattern);
                std::regex_search("hello world", match, pattern);
                std::regex_search("hello beautiful world", match, pattern);
            }
        });
        // App continues on this thread
        // Try to delay exit until somewhere inside the regex work:
        usleep(5000);
        exit(0);
    });
    dispatch_main();
    return -1;
}

(Enabling the Address Sanitizer makes it easier to hit the crash)

The ways this app exits are any of the following:

(clean exit)
or
Code Block
libcabi.dylib: terminating with uncaught exception of type std::1::regex_error: Unknown error type
Abort trap: 6

or
Code Block
libcabi.dylib: terminating with uncaught exception of type std::1::regex_error: Unknown error type

If you run this example app through lldb, the stack trace indicates that one thread is using the STL while another is inside the STL's destructor. I'd paste a stack trace here, but at the moment, using lldb with this example app is hanging my entire computer...

Anyways, what's a little interesting is that if you move the std::regex constant from the stack to the heap, the crash goes away. So, at the very least, a possible workaround is to never store any C++ object on the stack.

Unfortunately, with legacy codebases and third party libraries we don't control, that's not really a feasible option. I like what you describe iOS does in some cases (unloading the app from memory)... is that possible in a traditional tool on macOS?

It seems to me that the root problem here is that invoking exit() causes all destructors for all statically allocated objects (regardless of thread) to be invoked, regardless of whether or not those threads are still running and are still using those objects. To make matters more complicated, destructors take non-zero time to finish, and invoking the destructors runs in parallel with all other threads in the app, none of which have any clue that the world is getting burned down. This leads to unreproducible crashes caused by what are essentially race conditions.

One interesting possibility is to (1) iterate over the list of all threads in the app, (2) suspend all of them except your own, and (3) exit(). However, doing so has the same underlying problem as signal handling; there is no way to control exactly where/when a thread is suspended relative to critical sections of code. For all you know, you suspended a thread in the middle of reallocing a std::map, and when exit() ultimately gets around to invoking the destructor for that object, it's in an undefined state, and we crash. So although it may be an interesting academic exercise, it's not really helpful, even at all. Ideally, we want the entire app to halt, exactly where it is, wherever it is, and then force-unload the entire app from memory, without invoking any destructors at all.

What is the correct way for a modern multi-threaded C++ app to shut down without crashing?
  • I stumbled across this, looking for a different answer. The problem is libdispatch continues to execute tasks concurrent with the exit handlers. Without a way to close the libdispatch queues, you need a handle of some sort to the task that you can wait on before exiting (do this in ApplicationWillTerminate). You could use a C++ promise/future, condition variable, or a libdispatch task group.

Add a Comment

I've narrowed down the crash to this example C++ app

Unfortunately I don’t know enough about the C++ standard library to tell you whether that code is supposed to be legal or not.

I like what you describe iOS does in some cases … is that possible in
a traditional tool on macOS?

The traditional UNIX way of terminating without running atexit handlers is to call the _Exit system call rather than exit library function. See its man page for details.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"