Issues with crash handling for iOS for Rust 1.22.1

18be625
Opened by Michael Eisel at 2022-04-13 02:43:23

I'm using rustc 1.22.1 (05e2e1c41 2017-11-22) and I'm calling a simple rust function from my iOS app that just panics. The rust function is in a static library and is compiled cargo build (debug with -O0). The architecture is aarch64-apple-ios. Here's the rust code:

extern crate rand;

pub struct MyStruct {
    i: i32,
}

impl MyStruct {
    pub fn do_stuff(&mut self) -> String {
        panic!("that was a fail");
        return String::from("asdf");
    }
}

#[no_mangle]
pub extern fn panicky_fn() {
    let mut m = MyStruct {
        i: 0
    };
    m.do_stuff();
}

When I call it like this, from my root view controller:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [Fabric with:@[ [Crashlytics class]]]; // start the crash reporter
    CFTimeInterval start = CACurrentMediaTime();
    srand(time(NULL));
    if (rand() % 2 == 0) {
        panicky_fn();
    }
    // more code...
}

Then when the panic occurs, in the left-hand pane of Xcode, it just shows me this generic trace:

#0	0x00000001838e9348 in __pthread_kill ()
#1	0x00000001839fd354 in pthread_kill$VARIANT$mp ()
#2	0x0000000183858fd8 in abort ()
#3	0x00000001832bc068 in abort_message ()
#4	0x00000001832bc16c in default_terminate_handler() ()
#5	0x00000001832d454c in std::__terminate(void (*)()) ()
#6	0x00000001832d45c0 in std::terminate() ()
#7	0x00000001832e476c in objc_terminate ()
#8	0x0000000104651470 in _dispatch_client_callout ()
#9	0x000000010465db74 in _dispatch_block_invoke_direct ()
#10	0x00000001864a5a04 in __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ ()
#11	0x00000001864a56a8 in -[FBSSerialQueue _performNext] ()
#12	0x00000001864a5c44 in -[FBSSerialQueue _performNextFromRunLoopSource] ()
#13	0x0000000183d78358 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#14	0x0000000183d782d8 in __CFRunLoopDoSource0 ()
#15	0x0000000183d77b60 in __CFRunLoopDoSources0 ()
#16	0x0000000183d75738 in __CFRunLoopRun ()
#17	0x0000000183c962d8 in CFRunLoopRunSpecific ()
#18	0x0000000185b27f84 in GSEventRunModal ()
#19	0x000000018d243880 in UIApplicationMain ()
#20	0x0000000104219b68 in main at /Users/michael/Snapchat/Dev/Rotationization/Rotationization/main.m:15
#21	0x00000001837ba56c in start ()

And in the crash reporting service, Crashlytics, I just see a stack trace just like it. However, when I wrap the call to the rust function like so:

dispatch_async(dispatch_get_main_queue(), ^{
    panicky_fn();
}

Then both the left-hand pane of Xcode as well as the crash reporting service show the correct stack trace. Can anyone help with this? I want to start using Rust in my app, but I don't feel comfortable doing so until I can monitor crashes with it.

  1. Unwinding across an FFI call is undefined behaviour

    Jonas Schievink at 2017-12-18 00:25:38

  2. Would it be safe to at least call the C backtrace function at that moment to get a backtrace that I can then use for crash reporting?

    Michael Eisel at 2017-12-18 00:54:28

  3. Probably. I think the backtrace crate does this.

    Jonas Schievink at 2017-12-18 01:09:28

  4. According to the docs, that library doesn't support iOS.

    Michael Eisel at 2017-12-18 01:11:35

  5. Also, according to the docs for resume_unwind, it can be used to carry a panic across a layer of C, is that referring to if you have rust code -> C code -> rust code, then in that last layer it would use resume_unwind to jump to first layer somehow?

    The reason I'm so curious is that, without good crash reporting for rust in iOS, I don't see how I can deploy any sort of large-scale app.

    Michael Eisel at 2017-12-18 01:18:00

  6. Using panic = 'abort' seems to give a better stack trace. The stack trace printed by panic is useless if you can't access console logs, and unwinding destroys most of the useful information.

    Dusty DeWeese at 2022-04-12 23:23:25

  7. I thought this fixed it, but it wasn't enough. Based on a Stack Overflow thread, I think I've found a solution.

    In Rust:

    extern "C" {
        fn platform_panic();
    }
    
    pub fn main() {
        std::panic::set_hook(Box::new(|_| unsafe {
            platform_panic();
        }));
    
        // stuff that might panic
    }
    

    and in Swift:

    @_cdecl("platform_panic")
    func platform_panic() {
        DispatchQueue.global(qos: .userInteractive)
            .sync {
                fatalError()
            }
    }
    

    Now, the actual panic!() line is in the stack trace.

    Dusty DeWeese at 2022-04-13 02:43:23