Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stack overflow: Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d09aa00)

Crash description

Recently I'm facing kinda really strange memory issues in one of my iOS/Swift projects. I'm really not sure what's going on and feel it's also not quite easy to describe, but I'll try my best anyway.

It basically behaves like follows:

  • On a certain code base, the crash always occurs in the same place (100% reproducible)
  • Changes to the code base, may resolve the issue, but it may also just pop up somewhere else
  • Crashes only occur on real devices, never inside simulators

Currently the app crashes with following error (results from 3 different runs):

Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d09aa00)

Thread 1: EXC_BAD_ACCESS (code=2, address=0x16af46a00)

Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d526a00)


Reasoning about memory addresses

WWDC session

I found an interesting session (Understanding Crashes and Crash Logs) from WWDC 2018, where one of the guys points out that it's sometimes possible to derive more information from the specific memory addresses, the crashes occur.

Unfortunately the addresses it crashes in my app are somewhat completely different, but maybe we can get clues from them anyway? At least it's interesting, that they're all quite similar, or isn't it?

Changes due to Diagnostic options enabled

Further investigation shows that the first 2 bytes (16) stay always the same, followed by 4 random bytes followd by 3 bytes (a00). When activating diagnositcs (e.g. ASan or Scribble), the last 3 bytes change (e.g. 3a0 or 9e0). But maybe this is only a kind of shift due to more "debug stuff" being added? I'm really not that "memory guy", but just want to provide anything I noticed.


Trying "Diagnostic options"

I tried different Diagnostic options (from schemes), but none of them really changed the crash in any way, or provided any more information.

1. Scribble

Crashes do not reference 0xAA or 0x55, so it's nothing to be catched using Scribble? (Xcode - scribble, guard edges and guard malloc)

2. Malloc Guard Edges

Didn't notice any difference using this either.

3. Zombies

Using this guide.

malloc_info --type 0x16b15e9c0

error: error: Trying to put the stack in unreadable memory at: 0x16b15e920.

4. ASan

Using ASan just puts following entry on top of the stack trace. Unfortunately I didn't find anything helpful related to that.

#0 0x0000000109efbf60 in __asan_alloca_poison ()

5. TSan

Not available on real devices (crashes only occur there)


Recursion / BOF?

Could it be a recursion that is too long, or another kind of stack/heap buffer overflow? But it seems like the stack size on real devices as well as simulators is exactly the same with 524288 bytes (from Thread.main.stackSize).

So, as it doesn't crash in simulators, it's not a BOF? Or is the architecture difference too big, to make such conclusions here?


Disassembling

I also tried "disassembling".

disassemble -a 0x16d09aa00

error: Could not find function bounds for address 0x16d09aa00

Or disassemble -frame

But my assembler skills are really lacking behind, so currently there is nothing to get for me from that information.


Need help

As you can see I'm really running out of ideas. Either the crashes are really totally weird, or I just do not have enough knowledge/skills to use above tools, to get me any closer to the cause of those issues.

Either way... Any help, hints, ideas or whatever could point me in the right direction is highly appreciated!

Thanks in advance, guys.


Update May 19, 2020

I totally forgot to mention, that we're using ReSwift heavily in our app, and the crashes seem to be related to how we use the Middlewares there, I guess.

I'm also already in contact with the devs there: github.com/ReSwift/ReSwift/issues/271.

Here's finally some code. Unfortunately I can't share all the apps code (which may be necessary!?) and also don't want to overload you with way to much code.

Current issue

Thread 1: EXC_BAD_ACCESS (code=1, address=0x16ed82da0)

UserAccountMiddleware.swift

Note: Using those DispatchQueue.main.async actually makes the crashes go away. They indeed break the current cycle, so maybe there's some kind of recursion or timing issue happening?

func userAccountMiddleware() -> Middleware<AppState> {
    return { dispatch, getState in
        return { next in
            return { action in
                switch action {
                case _ as ReSwiftInit:
//                    DispatchQueue.main.async {
                        dispatch(UserAccountSetAuthToken(authToken: Defaults.customerAuthToken))
                        dispatch(UserAccountSetAvatar(index: Defaults.avatarIndex))
//                    }
                    if let data = Defaults.customer,
                        let customer = try? JSONDecoder().decode(Customer.self, from: data) {
//                        DispatchQueue.main.async {
                            dispatch(UserAccountSetCustomerLoggedIn(customer: customer))
//                        }
                    }

                // [...]

                default:
                    break
                }

                next(action)
            }
        }
    }
}

ReSwift Store.swift

    // [...]
    open func _defaultDispatch(action: Action) {
        guard !isDispatching else {
            raiseFatalError(
                "ReSwift:ConcurrentMutationError- Action has been dispatched while" +
                " a previous action is action is being processed. A reducer" +
                " is dispatching an action, or ReSwift is used in a concurrent context" +
                " (e.g. from multiple threads)."
            )
        }

        isDispatching = true
        let newState = reducer(action, state) // Thread 1: EXC_BAD_ACCESS (code=1, address=0x16ed82da0)
        isDispatching = false

        state = newState
    }
    // [...]

Xcode console:

(lldb) po state
error: warning: couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value couldn't be evaluated

error: Trying to put the stack in unreadable memory at: 0x16d95ad00.

Assembler (very last step of crash):

myapp`type metadata accessor for GlobalState:
    0x101f6ac10 <+0>:  sub    sp, sp, #0x30             ; =0x30 
->  0x101f6ac14 <+4>:  stp    x29, x30, [sp, #0x20] // Thread 1: EXC_BAD_ACCESS (code=1, address=0x16ed82da0)
    0x101f6ac18 <+8>:  adrp   x8, 3620
    0x101f6ac1c <+12>: add    x8, x8, #0x148            ; =0x148 
    0x101f6ac20 <+16>: ldr    x8, [x8]
    0x101f6ac24 <+20>: mov    x9, #0x0
    0x101f6ac28 <+24>: mov    x1, x8
    0x101f6ac2c <+28>: str    x0, [sp, #0x18]
    0x101f6ac30 <+32>: str    x1, [sp, #0x10]
    0x101f6ac34 <+36>: str    x9, [sp, #0x8]
    0x101f6ac38 <+40>: cbnz   x8, 0x101f6ac54           ; <+68> at <compiler-generated>
    0x101f6ac3c <+44>: adrp   x1, 2122
    0x101f6ac40 <+48>: add    x1, x1, #0x1dc            ; =0x1dc 
    0x101f6ac44 <+52>: ldr    x0, [sp, #0x18]
    0x101f6ac48 <+56>: bl     0x102775358               ; symbol stub for: swift_getSingletonMetadata
    0x101f6ac4c <+60>: str    x0, [sp, #0x10]
    0x101f6ac50 <+64>: str    x1, [sp, #0x8]
    0x101f6ac54 <+68>: ldr    x0, [sp, #0x8]
    0x101f6ac58 <+72>: ldr    x1, [sp, #0x10]
    0x101f6ac5c <+76>: str    x0, [sp]
    0x101f6ac60 <+80>: mov    x0, x1
    0x101f6ac64 <+84>: ldr    x1, [sp]
    0x101f6ac68 <+88>: ldp    x29, x30, [sp, #0x20]
    0x101f6ac6c <+92>: add    sp, sp, #0x30             ; =0x30 
    0x101f6ac70 <+96>: ret     
like image 803
d4Rk Avatar asked May 14 '20 09:05

d4Rk


1 Answers

TL;DR

Just move huge structs to the heap, by wrapping them inside arrays. Using @propertyWrappers, this can be an at least partly elegant solution.

@propertyWrapper
struct StoredOnHeap<T> {
    private var value: [T]

    init(wrappedValue: T) {
        self.value = [wrappedValue]
    }

    var wrappedValue: T {
        get {
            return self.value[0]
        }

        set {
            self.value[0] = newValue
        }
    }
}

// Usage:
@StoredOnHeap var hugeStruct: HugeStruct

https://gist.github.com/d4rkd3v1l/ab582a7cafd3a8b8c164c8541a3eef96


Long version

I'm almost 100% certain now, that this is a stack overflow, as I (finally) managed to reproduce this in a little demo project: https://github.com/d4rkd3v1l/ReSwift-StackOverflowDemo

Now I will just provide some more details and solutions for anyone else may running into this or similar issues.

The stack size on iOS (as of iOS 13) is 512kb and should apply to both, devices and simulators. Why did I say "should"? Because it almost certainly is somewhat different on simulators, as I did not see those crashes there. So maybe Thread.main.stackSize just tells 512kb but is in fact larger? IDK 🤷‍♂️


Here are some indicators, you may face the same issue:

  • You get EXC_BAD_ACCESS crashes with code 1 or 2**. And the crashes occur in high memory addresses, or at least completely out of where the rest of your app/stack normally "lives". Something like 0x16d95ad00 in my case.
  • Reducing the stuff you put on the stack (value types, e.g. very very large structs) or breaking the call stack down into smaller pieces (e.g. dispatch async) to give the stack some "time to breathe" prevents this crash.

And here at the latter we're already in the middle of the solution for that issue. As the stack size cannot (and probably even should not) increased, you must reduce the load you put there, like described in the 2nd point.

At least that's the solution we will probably go for. 🤞


*This is true at least for the main thread, other threads may be different.

**I think code 0 is kinda null pointer exceptionand therefore doesn't apply here. Please correct me if I'm wrong about this.

like image 167
d4Rk Avatar answered Oct 22 '22 05:10

d4Rk