iOS13 一次Crash定位 - 被释放的NSURL.host

每年一次的iOS升级,都会给开发者带来一些适配工作,一些原本工作正常的代码可能就会发生崩溃。 本文讲到了一种 CoreFoundation 对象的内存管理方式在iOS13上遇到的问题。

1. 问题

iOS 13 Beta 版本上,手淘出现了一个必现的崩溃:

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

崩溃在了 _CFSocketStreamCreatePair  方法里面, 然后崩溃在了 objc_retain  里面,推测是传入的某个ObjC的对象野指针了导致的。

通过追溯源码,发现调用的是 CFStreamCreatePairWithSocketToHost 这个方法,然后找到这个方法的定义:

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

根据上下文判断,是第二个参数 CFStringRef _Null_unspecified host  野指针了。

然后找到这个 host 对象的初始化:

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

这段代码看起来好像并没有问题,怎么会导致野指针,然后Crash呢?

这要从iOS的内存管理上找答案。

2. 苹果的autorelease内存管理优化

我们都知道苹果使用 “引用计数” 技术来管理内存, 使用 “自动释放池AutoreleasePool” 技术来解决方法返回值的内存管理问题。 相关技术原理网上都有很多文章。但是本文中遇到的Crash是由苹果对使用 ARC 代码进行的编译优化从而引发的。所以先讲一下这个优化是什么。

考虑一个内存管理的最简单的case:

在最初的 ARC 机制下,上图中的左边代码会编译成右边这样的代码,从而保证了对象 b 的生命周期完整。

但是我们再详细分析下这个代码,是不是去掉 [b autorelease]  和 [b retain] 这两步操作的话,代码也是可以正常执行的呢? 答案是肯定的, 那么这个操作其实就是可以优化掉的。苹果考虑到了这一点。

那么要怎么样做到这个优化呢? 因为这个优化是需要同时考虑 被调用方funcB 和 调用方funcA 这两个方法配合来完成,因为需要根据调用方的内存管理代码才能决定我被调用方要不要真的去掉autorelease操作。 而且还要在ABI上向下适配。 苹果是这样做的:

 

代码:

// Prepare a value at +1 for return through a +0 autoreleasing convention.
id 
objc_autoreleaseReturnValue(id obj)
{// 判断是否需要优化, 如果可以,就直接return,不做autoreleaseif (prepareOptimizedReturn(ReturnAtPlus1)) return obj;return objc_autorelease(obj);
}id
objc_retainAutoreleasedReturnValue(id obj)
{// 判断是否走了优化逻辑,如果走了就不用retainif (acceptOptimizedReturn() == ReturnAtPlus1) return obj;return objc_retain(obj);
}static ALWAYS_INLINE bool 
prepareOptimizedReturn(ReturnDisposition disposition)
{assert(getReturnDisposition() == ReturnAtPlus0);// 判断方法返回地址是不是某个值,是的话就认为可以优化if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {// 可以优化就把ReturnAtPlus1 存起来,存到了tls里面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// 判断return address是不是 0xaa1d03fd, 在arm64上就是 `mov fp, fp` 指令if (*(uint32_t *)ra == 0xaa1d03fd) {return true;}return false;
}static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{ReturnDisposition disposition = getReturnDisposition();setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized statereturn disposition;
}// 存在当 tls中,当前线程相关的
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);
}

从上面的分析中,我们可以得出,只要看到调用 objc_msgSend 之后的一条指令是 mov x29, x29 , 那么肯定就是开启了这个优化。

 

所以,大家汇编调试的时候看到这样一行指令,不要觉得奇怪 mov x29,x29 不是啥都没做么?其实是用于这里的优化。

3. Crash根因

了解了 ObjC的 autorelease优化之后,再回到我们遇到的crash问题。有理由怀疑 [NSURL host] 这个方法在旧版本系统上不会走这个优化,因此返回值被放入了 AutoreleasePool 所以后面继续使用是正常的。但是iOS13 上走到了这个优化逻辑,实际上返回的 host 是没有加入 AutoreleasePool 的。 而这个时候恰好又没有 objc 对象接收,直接用 __bridge 转移到了 CF对象上。导致这个 host 直接释放了。

通过查看 对 [NSURL host] 的调用代码证明了这个猜想:

 

  1. +312 行调用 [NSURL host] 获取host.
  2. 因为 +316的指令是 mov x29, x29  所以如果[NSURL host]  里的实现是类似上述 funcB 则会走到autorelease优化。也就是返回的 host 没有加入autoreleasePool
  3. +320 行中,因为开启优化,也捕获做retain
  4. +328 行,直接release,  这个时候 host就释放了
  5. 后续继续对它进行访问,就Crash了。

还需要证明的就是 [NSURL host]本身的实现了。于是对比了iOS12 和 iOS13 上的实现:

iOS12 上内部通过调用了 [NSURL _cfurl] 获取,已经加入了autoreleasePool。

 

在iOS13上,就是正常的取值做autorelease, 因此会走到优化逻辑:

 

4. 小结

慎用 __bridge 来进行 OC对象和 CF对象直接的强转。 因为Autorelease优化的存在,这种用法可能让你的代码不安全,因此尽可能使用 CFBridgeRetain  __bridge_retained 来转换管理CF对象,避免因为作用域不一致的情况导致对象呗提前释放的问题。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/518146.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试官吐槽:“Python程序员就是不行!”网友:我能把你面哭!

最近几年,Python莫名火了起来,很多公司都想赶上这“莫名”的热潮,招聘到大牛人才。但是,最近一个HR在社交网站的吐槽又火了:那么问题来了,市面上为什么鲜有企业满意的优秀的Python程序员?企业到…

python3-pandas 数据结构 Series、DataFrame 基础

Pandas 应用 Pandas 的主要数据结构是 Series (一维数据)与 DataFrame(二维数据),这两种数据结构足以处理金融、统计、社会科学、工程等领域里的大多数典型用例。 数据结构 Series 是一种类似于一维数组的对象&#xf…

十年磨一剑 | 淘宝如何打造承载亿级流量的首页?

阿里妹导读:手机淘宝作为整个互联网领域旗舰 APP 之一,装机量和用户访问量都是名列前茅的。而首页作为打开手机淘宝的门面,是淘宝电商领域的主要流量入口和服务消费者的核心阵地,其业务的复杂性之高、系统的稳定性之重都有着极高的…

switchhosts 没有修改hosts的权限解决方案

使用swtichHost工具切换开发环境时候提示没有权限问题,如下图。。 解决方案有两点 1、进入 C:\Windows\System32\drivers\etc右键点击hosts的属性查看 属性的只读是否被勾选了,如果被勾选了将勾选勾去掉 上述完成后以管理员身份运行(管理员…

闲鱼亿级商品结构化背后的思考和演进

1. 缘起 闲鱼是一个典型的C2C场景的闲置交易平台。每个在闲鱼的用户都能享受到自由交易的乐趣。在这里,可能你只要简单的输入商品名,商品价格,库存等信息就能完成一个商品的发布。即便是发布以后,你也可以随时修改价格&#xff0…

QingStor NeonSAN跻身四强 新风口下的青云QingCloud正在厚积薄发

人类以日新月异的速度刷新着科技的成果,其中存储的发展历史尤其悠久,堪称万年进化史。自文明诞生以来,我们就一直在寻求能够更有效存储信息的方式,从4万年前的洞穴壁画、6000年前泥板上的楔形文字,到今天正在普及的SSD…

python3-pandas DataFrame 索引、bool索引、pandas 字符串方法

1、DataFrame 索引 1.1 普通索引取值 pandas 取行或者列的注意点: 方括号写数组,表示取行,对行进行操作方括号写字符串,表示取列,对列进行操作 import pandas as pd import numpy as np # pandas 取行或者列的注意…

MySQL8.0.17 - 初探 Clone Plugin

MySQL8.0.17推出了一个重量级的功能:clone plugin。允许用户可以将当前实例进行本地或者远程的clone。这在某些场景尤其想快速搭建复制备份或者在group replication里加入新成员时非常有用。本文主要试玩下该功能,并试图阐述下其实现的机制是什么。 我们…

javascript判断IPV6格式

随着ipv6的普及,在web上添加ip6地址的要求逐渐增多,下面这个函数是我按照ipv6的定义写的判断,可以用来判断正常的,缩写的ipv6格式,同时可以判断ipv6和ipv4混合的格式: //统计 10F: 或者:10B的个数 function cLength(s…

阿里毕玄:推荐给Java程序员的7本好书

我主要还是个Java程序员,所以进阶类型的书就推荐Java相关的。 《Java并发编程实战》 并发是高级语言里都需要掌握的稍微高级一些的技巧,这本书尽管是2012年的书了,但我觉得仍然是无比经典,绝对是必读书。《Netty实战》 Netty是Ja…

释放虚拟GPU力量 NVIDIA 加速企业远程办公生产力变革

现在众多的企业拥抱云和AI技术进行着数字化转型。随着企业在信息化建设的加剧,相关从业人员数量增多的同时,也对设备的计算性能要求越来越高。2020年开年的这场疫情,强制性地让企业进入远程办公模式,这个突发的状况也为企业的IT设…

python3-pandas 缺失数据的处理

1、缺失数据的处理 1.1、判断是否为nan isnull: 是nan为true notnull: 不是nan为true import pandas as pd import numpy as npt3 pd.DataFrame(np.arange(12).reshape(3,4), indexlist("abc"), columnslist("wxyz")) print(t3)…

Stream anyMatch查找案例

package com.gblfy.gxts;import lombok.AllArgsConstructor; import lombok.Data; import org.junit.Before; import org.junit.Test;import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;/*** 案例1:* 某班集中有…

记一次Cassandra Java堆外内存排查经历

背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测。压测时候比较容易触发OOM Killer,把cassandra进程干掉。问题是8G这个规格我配置的heap(Xmx)并不高(约6.5g)已经留出了足够的空间给系统。只有可能是Java堆…

程序员内功修炼系列:10 张图解谈 Linux 物理内存和虚拟内存

来源 | 后端技术学堂责编 | Carol封图 | CSDN 付费下载于视觉中国我们都知道,程序可没这么好骗,任你内存管理把虚拟地址空间玩出花来,到最后还是要给程序实实在在的物理内存,不然程序就要罢工了。所以物理内存这么重要的资源一定要…

阿里高级技术专家方法论:如何写复杂业务代码?

阿里妹导读:张建飞是阿里巴巴高级技术专家,一直在致力于应用架构和代码复杂度的治理。最近,他在看零售通商品域的代码。面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。结合实际的业务场景…

Android Studio 安装教程

注意安装之前请配置好java 和 Android SDK 1、下载 官网地址: https://developer.android.google.cn/studio/ 点击下载后,需要同意协议: 2、安装 1、双击程序 2、一路 next,如果想修改路径可自行修改。 3、安装完成点击Fi…

Stream filter过滤案例

package com.gblfy.gxts;import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.Data; import org.junit.Before; import org.junit.Test;import java.util.List;/*** 案例2:* 标签管理功能模块。允许用户批量添加标签&#xf…

Schedulerx2.0工作流支持数据传输

1. 前言 Schedulerx2.0是阿里中间件自研的基于akka架构的新一代分布式任务调度平台,提供定时、任务编排、分布式跑批等功能,具有高可靠、海量任务、秒级调度等能力。 Schedulerx2.0提供可视化的工作流进行任务编排,该文章将详细介绍如何使用…

应用实时监控 ARMS 上线用户行为回溯功能

随着前端技术日新月异迅猛发展,为了实现更好的前端性能,最大程度提高用户体验,支持单页应用的框架逐渐占领市场,如众所周知的React,Vue等等。但是在单页应用的趋势下,快速定位并解决JS错误却成为一大难题。…