Why does this code cause "EXC_BAD_INSTRUCTION"?

user805627 picture user805627 · Nov 27, 2011 · Viewed 11.5k times · Source
dispatch_semaphore_t aSemaphore = dispatch_semaphore_create(1);        
dispatch_semaphore_wait(aSemaphore, DISPATCH_TIME_FOREVER);
dispatch_release(aSemaphore);

When the program runs to dispatch_release(aSemaphore), it will cause "EXC_BAD_INSTRUCTION", and then crash. Why?

Answer

mattjgalloway picture mattjgalloway · Nov 27, 2011

I tried this code and it does indeed die with illegal instruction. So I did some digging and found that it's dying in _dispatch_semaphore_dispose. So let's look at what that is (ARMv7 here, because it's easy to understand!):

__dispatch_semaphore_dispose:
000040a0            b590        push    {r4, r7, lr}
000040a2            4604        mov     r4, r0
000040a4            af01        add     r7, sp, #4
000040a6        e9d40108        ldrd    r0, r1, [r4, #32]
000040aa            4288        cmp     r0, r1
000040ac            da00        bge.n   0x40b0
000040ae            defe        trap
...

It dies at 0x40ae, which is a duff instruction put there so that it crashes if the bge.n doesn't make us branch to jump over it.

The reason it's failing is because r0 must be less than r1. r0 and r1 are loaded from the memory at r4 + 32 which having gone back up the stack to figure it out I think r4 is aSemaphore in the example code, i.e. the thing passed into dispatch_semaphore_release. The + 32 signifies it is reading 32 bytes into the struct that aSemaphore is pointing to (it's a pointer to a dispatch_semaphore_s struct). So overall what it's doing it reading 4 bytes from aSemaphore + 32 and putting them into r0 and reading 4 bytes from aSemaphore + 36 and putting them into r1.

The compare is then effectively comparing the value of aSemaphore + 32 and aSemaphore + 36. Reading what dispatch_semaphore_create does I can see that it stores the value passed in to both aSemaphore + 32 and aSemaphore + 36. I also found that dispatch_semaphore_wait and dispatch_semaphore_signal touch the value at aSemaphore + 32, to increment and decrement it. This means that the reason it's breaking is because the current value of the semaphore is less than the value passed into dispatch_semaphore_create. So you can't dispose of a semaphore when the current value is less than the value it was created with.

If you've read to here and understood my ramblings then well done! Hope it helps!

UPDATE:

It's probably better to look at the source (pointed out by JustSid) here - http://opensource.apple.com/source/libdispatch/libdispatch-187.7/src/semaphore.c - looking at the _dispatch_semaphore_dispose function we see:

if (dsema->dsema_value < dsema->dsema_orig) {
    DISPATCH_CLIENT_CRASH("Semaphore/group object deallocated while in use");
}

So, yes, there you go, that's why it crashes!