IOS 13 once crash location – released nsurl.host

Time:2020-4-19

Every year’s IOS upgrade will bring some adaptation work to developers, and some normal code may crash. This article discusses the problems of memory management of core foundation objects on IOS 13.

1. problem

On the IOS 13 beta version, there is a necessary crash in hand Taobao:

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x00000001d6f9af20 objc_retain + 16
1   CFNetwork                       0x00000001d7843f60 0x1d77b0000 + 606048
2   CFNetwork                       0x00000001d780cec8 0x1d77b0000 + 380616
3   CFNetwork                       0x00000001d77dff24 _CFSocketStreamCreatePair + 56
4   xxxxxxxxxxxxxxxxx               0x000000010c2a44b4 0x10b46c000 + 14910644
5   xxxxxxxxxxxxxxxxx               0x000000010c2a6238 0x10b46c000 + 14918200
6   xxxxxxxxxxxxxxxxx               0x000000010c2a661c 0x10b46c000 + 14919196

It’s collapsing_CFSocketStreamCreatePairIn the method, and then collapsedobjc_retainInside, it is speculated that the object field pointer of an objc passed in is caused.

By tracing the source code, it is found thatCFStreamCreatePairWithSocketToHostThis method, and then find the definition of this method:

void CFStreamCreatePairWithSocketToHost(
    CFAllocatorRef _Null_unspecified alloc, 
    CFStringRef _Null_unspecified host, 
    UInt32 port,
    CFReadStreamRef _Null_unspecified * _Null_unspecified readStream, 
    CFWriteStreamRef _Null_unspecified * _Null_unspecified writeStream
);

According to the context, it is the second parameterCFStringRef _Null_unspecified hostThe wild pointer is gone.

And find thishostObject initialization:

NSURL *serverUrl = [NSURL URLWithString:@"xxxxx"];
CFStringRef hostRef = (__bridge CFStringRef)serverUrl.host;

This code seems to have no problem. How can it lead to wild pointer and crash?

This is to find out from the memory management of IOS.

2. Apple ‘s autorelease memory management optimization

We all know that Apple uses“Reference counting”Technology to manage memory, using“Auto release pool”Technology to solve the memory management problem of method return value. There are many articles on the related technical principles on the Internet. However, the crash encountered in this article is caused by Apple’s compilation optimization of arc code. So let’s talk about what this optimization is.

Consider the simplest case of memory management:

IOS 13 once crash location - released nsurl.host

Under the original arc mechanism, the left code in the figure above will be compiled into the right code, thus ensuring the objectbThe life cycle of is complete.

But let’s analyze this code in detail. Is it removed[b autorelease]Sum[b retain]With these two steps, can the code execute normally? The answer is yes, so this operation can be optimized. Apple takes this into account.

So how to achieve this optimization? Because this optimization needs to be considered at the same timeCalled partyfuncBSumCallerfuncAThese two methods cooperate to complete, because it needs to be based on the memory management code of the caller to decide whether my callee really removes the autorelease operation. But also in the ABI down adaptation. Apple does this:

IOS 13 once crash location - released nsurl.host

Code:

// Prepare a value at +1 for return through a +0 autoreleasing convention.
id 
objc_autoreleaseReturnValue(id obj)
{
    //Determine whether optimization is needed. If it is possible, return directly and don't do autorelease
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);
}

id
objc_retainAutoreleasedReturnValue(id obj)
{
    //Judge whether the optimization logic is gone. If it is gone, there is no need to retain it
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}

static ALWAYS_INLINE bool 
prepareOptimizedReturn(ReturnDisposition disposition)
{
    assert(getReturnDisposition() == ReturnAtPlus0);
    //Determine whether the return address of the method is a certain value. If yes, it can be optimized
    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
        //You can save returnatplus1 to TLS if you want to optimize it
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}

static ALWAYS_INLINE bool 
callerAcceptsOptimizedReturn(const void *ra)
{
    // fd 03 1d aa    mov fp, fp
    // arm64 instructions are well-aligned
    //Determine whether the return address is 0xaa1d03fd, which is the 'mov FP, FP' instruction on arm64
    if (*(uint32_t *)ra == 0xaa1d03fd) {
        return true;
    }
    return false;
}

static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{
    ReturnDisposition disposition = getReturnDisposition();
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

//In TLS, current thread related
static ALWAYS_INLINE ReturnDisposition 
getReturnDisposition()
{
    return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}

static ALWAYS_INLINE void 
setReturnDisposition(ReturnDisposition disposition)
{
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

From the above analysis, we can see that as long as we see the callobjc_msgSendThe next instruction ismov x29, x29, then it must be the optimization.

IOS 13 once crash location - released nsurl.host

So, when you assemble and debug, don’t be surprised to see such a line of instructionsmov x29,x29Didn’t you do anything? It’s actually for optimization here.

3. Crash root cause

After learning about objc’s autorelease optimization, let’s go back to the crash problem we encountered. There is reason to doubt[NSURL host]This method will not go through this optimization on the old version system, so the return value is put into theAutoreleasePoolSo it is normal to continue to use it later. But IOS 13 goes to this optimization logic and actually returnshostI didn’t joinAutoreleasePool. At this time, there is no objc object to receive, so you can directly use__bridgeTransferred to CF object. Leading to thishostDirectly released.

By viewing the[NSURL host]The calling code of proves this conjecture:

IOS 13 once crash location - released nsurl.host

  1. +312 line call[NSURL host]Get host
  2. Because the + 316 command ismov x29, x29So if[NSURL host]The implementation in is similar to the abovefuncBIt will go to autorelease optimization. That is to say, the returned host is not added to the autoreleasepool
  3. +In line 320, because optimization is enabled, it is also captured as retain
  4. +Line 328, release directly. At this time, the host is released
  5. Continue to visit it later, just crash.

What we need to prove is that[NSURL host]The realization of itself. Then the implementation of IOS 12 and IOS 13 are compared:

Internal call on IOS 12[NSURL _cfurl]Get, already joined the autoreleasepool.

IOS 13 once crash location - released nsurl.host

On IOS 13, the normal value is autorelease, so it will go to the optimization logic:

IOS 13 once crash location - released nsurl.host

4. summary

Use caution__bridgeTo perform direct strong conversion of OC and CF objects. Because of autorelease optimizations, this usage can make your code unsafe, so use as much as possibleCFBridgeRetain  __bridge_retainedTo convert and manage CF objects, avoid the problem of early release of objects due to inconsistent scope.

The source code of this article comes from: https://opensource.apple.com/tarballs/objc4/

Author: Nian Ji, from Taobao client IOS Architecture Group
Taobao basic platform team is holding 2019 interns (graduated in 2020) and social recruitment. The positions include IOS Android client development engineer, Java R & D Engineer, C / C + + R & D Engineer, front-end development engineer and Algorithm Engineer. Please send your resume to junzhan.yzw @ taobao.com
If you want to learn more about Taobao basic platform team, welcome to watch the team introduction video


Author: Nian Ji

Read the original text

This is the original content of yunqi community, which can not be reproduced without permission.

Recommended Today

A detailed explanation of the differences between Perl and strawberry Perl and ActivePerl

Perl is the abbreviation of practical extraction and report language “practical report extraction language”. Application of activestateperl and strawberry PERL on Windows platformcompiler。 Perl   The relationship between the latter two is that C language and Linux system have their own GCC. The biggest difference between activestate Perl and strawberry Perl is that strawberry Perl […]