Search code examples
ioscrashdyld

Why do crashes in iOS relating to dyld_stub_binder occur?


It's widely known that dynamic link libraries aren't allowed in iOS apps, they may only link to dynamic system libraries. But I do run into some pretty confusing crashes with the 3rd frame from the top of the stack being dyld_stub_binder.

It's tough to find some solid information, but I'm guessing that dyld_stub_binder actually performs late linking of a dynamic system library.

I tend to run into crashes where the exception is EXC_BREAKPOINT UNKNOWN and the crash always seems to occur in the context of dyld_stub_binder.

The implementation of dyld_stub_binder is on the apple open source website. I don't quite understand the assembly, but perhaps someone who does could interpret why this error happens or whether or not it's something that is out of the application's direct control. The assembly code may not be useful though, as I'm talking about the iOS (arm) implementation and this code is i386 and x86_64.

EDIT: An interesting piece of information is that I think I started seeing this crash during efforts for porting to arm64. Is it possible that a runtime exception like this is due to some kind of misalignment?


Solution

  • As you've stated, the asm for the ARM case is not available, but it's fairly straightforward to figure out since you can decompile fairly easily. What dyld_stub_binder does (on all architectures) is to handle the lazy symbols in a binary. For example, consider the following:

       $ cat a.c
    void main(int argc, char **argv)
    {
    
        printf("%s", argv[1]);
    
    }
    $ gcc-iphone a.c -o a 
    $ jtool -d a
    Disassembling from file offset 0x7f44, Address 0x100007f44 
    _main:
       100007f44    STP    X29, X30, [X31,#-16]!    
       100007f48    ADD    x29, x31, #0x0   ; ..R29 = R31 (0x0) + 0x0 = 0x1f 
       100007f4c    SUB    X31, X31, #32    
       100007f50    STUR   X0, X29, #-4     ; *((1) + 0x0) = ???
       100007f54    STR    X1, [ X31, #2]   ; *((2) + 0x0) = ???
       100007f58    LDR    X1, [X31, #0x10] ; R1 = *(10) = 0x100000cfeedfacf
       100007f5c    LDR    X1, [X1, #0x8]   ; R1 = *(100000cfeedfad7) = 0x100000cfeedfacf
       100007f60    ADD    x8, x31, #0x0    ; ..R8 = R31 (0x0) + 0x0 = 0x1f 
       100007f64    STR    X1, [ X8, #0]    ; *(0x0) = 0xfeedfacf
       100007f68    ADRP   x0, 0            ; ->R0 = 0x100007000 
       100007f6c    ADD    x0, x0, #0xfb4   ; ..R0 = R0 (0x100007000) + 0xfb4 = 0x100007fb4 "%s"
       100007f70    BL     _printf  ; 0x100007f84
    ; _printf("%s",arg..);
    
       100007f74    STR    X0, [ X31, #3]   ; *((254) + 0x0) = ???
       100007f78    ADD    x31, x29, #0x0   ; ..R31 = R29 (0x1f) + 0x0 = 0x1d 
       100007f7c    LDP    X29, X30, [X31],#16  
       100007f80    RET    
    

    see that printf up there? 0x100007f84? Let's see what that is (The built-in otool can't decompile that part, but jtool can:)

    _printf:
       100007f84    NOP                     
       100007f88    LDR    X16, #34         ; R16 = *(100008010) = 0x100007fa8
    
       100007f8c    BR     X16   
    

    So you just to 0x100007fa8. Once again applying jtool:

    $ jtool -d 0x100007fa8 a
    Disassembling from file offset 0x7fa8, Address 0x100007fa8 
       100007fa8    LDR    X16, #2          
       100007fac    B      0x100007f90
    

    And now we have 0x100007f90, which is ...

       100007f90    ADR    x17, 120         ; ->R17 = 0x100008008 
       100007f94    NOP                     
       100007f98    STP    X16, X17, [X31,#-16]!    
       100007f9c    NOP                     
       100007fa0    LDR    X16, #24         ; R16 = *(100008000) dyld_stub_binder
       100007fa4    BR     X16              
    

    Now, go back to that 0x...8010 which gets loaded - that will be the address of printf(), but it is only bound after the first "hit" or access. You can verify that with dyldinfo, or jtool -lazy_bind:

    $ jtool -lazy_bind a
    bind information:
    segment section          address        type    addend dylib            symbol
    __DATA  __la_symbol_ptr  0x100008010    ...     0 libSystem.B.dylib    _printf
    

    Meaning, on first access, the stub_binder finds the address of printf in lib system, and embeds it there.

    If the symbol cannot be bound, you get an exception. Though that can be for oh-so-many-reasons. You might want to add the crash log here. If it's a breakpoint, that's a voluntary crash by dyld which usually occurs when symbol was not found. If a debugger (lldb) is attached, it will break there and then. Else - with no debugger - it crashes.