首页 » 网站建设 » phpsegmentationfault技巧_Swift编译器CrashSegmentation fault解决筹划

phpsegmentationfault技巧_Swift编译器CrashSegmentation fault解决筹划

访客 2024-12-10 0

扫一扫用手机浏览

文章目录 [+]

由于属于编译器层抛出的 Crash,加之提示的缺点代码不固定且非必现,一时较为棘手。
网上类似缺点较多,但Segmentation fault属于访问了缺点内存的通用报错,参考意义较小。
和公司内外的团队互换过,也有碰着类似缺点,但缘故原由各不相同,难以借鉴。

虽然 Swift 库二进制化后,干系代码不会参与编译,本地涌现的概率大大减少,但在 CI/CD/仓库二进制化任务中依旧利用源码,涌现问题须要手动重试,影响效率且繁琐,故深入编译器寻求办理方案。

phpsegmentationfault技巧_Swift编译器CrashSegmentation fault解决筹划

Crash 堆栈

phpsegmentationfault技巧_Swift编译器CrashSegmentation fault解决筹划
(图片来自网络侵删)
结论

简而言之,是 Swift 代码中将在 OC 中声明为类属性的NSDictionary变量,当成 Swift 的Dictionary利用。
即一个 immutable 变量当作 mutable 变量利用了。
编译器在校验SILInstruction时出错,主动调用abort()结束进程或涌现EXC_BAD_ACCESS的 Crash。

准备事情编译 Swift

由于本地重现差错误,故拉取和本地同等的 swift-5.3.2-RELEASE 版本,同时推举利用 VSCode 进行调试,Ninja 进行构建。

Ninja 是专注于速率的小型构建系统。

把稳事变提前预留 50G 磁盘空间首次编译时长在一小时旁边,CPU 基本打满下载&编译源码

brew install cmake ninjamkdir swift-sourcecd swift-sourcegit clone git@github.com:apple/swift.gitcd swift/utils./update-checkout --tag swift-5.3.2-RELEASE --clone./build-script紧张目录

提取编译参数

笔者将干系代码抽离抖音工程, 本地复现编译报错问题后,从 Xcode 中提取编译参数:

VSCode 调试

选择得当的 LLDB 插件,以 CodeLLDB 为例配置如下的 launch.json。

个中args内容为获取前一步提取的编译参数,批量将个中每个参数用双引号包裹,再用逗号隔开所得。

{ "version": "0.2.0", "configurations": [ { "type": "lldb", "request": "launch", "name": "Debug", "program": "${workspaceFolder}/build/Ninja-DebugAssert/swift-macosx-x86_64/bin/swift", "args": ["-frontend","-c","-primary-file"/and other params/], "cwd": "${workspaceFolder}", } ]}SILLLVM

在深入 SIL 之前,先大略先容 LLVM,经典的 LLVM 三段式架构如下图所示,分为前端(Frontend),优化器(Optimizer)和后端(Backend)。
当须要支持新措辞时只需实现前端部分,须要支持新的架构只需实现后端部分,而前后真个连接枢纽便是 IR(Intermediate Representation),IR 独立于编程措辞和机器架构,故 IR 阶段的优化可以做到抽象而通用。

Frontend

前端经由词法剖析(Lexical Analysis),语法剖析(Syntactic Analysis)天生 AST,语义剖析(Semantic Analysis),中间代码天生(Intermediate Code Generation)等步骤,天生 IR。

IR

格式

IR 是 LLVM 前后真个桥接措辞,其紧张有三种格式:

可读的格式,以.ll 结尾Bitcode 格式,以.bc 结尾运行时在内存中的格式

这三种格式完备等价。

SSA

LLVM IR 和 SIL 都是 SSA(Static Single Assignment)形式,SSA 形式中的所有变量利用前必须声明且只能被赋值一次,如此实现的好处是能够进行更高效,更深入和更具定制化的优化。

如下图所示,代码改造为 SSA 形式后,变量只能被赋值一次,就能很随意马虎判断出 y1=1 是可被优化移除的赋值语句。

构造

根本构造由 Module 组成,每个 Module 大概相称于一个源文件。
Module 包含全局变量和 Function 等。
Function 对应着函数,包括方法的声实现,参数和返回值等。
Function 最主要的部分便是各种 Basic Block。

Basic Block(BB) 对应着函数的掌握流图,是 Instruction 的凑集,且一定以 Terminator Instructions 结尾,其代表着 Basic Block 实行结束,进行分支跳转或函数返回。

Instruction 对应着指令,是程序实行的基本单元。

Optimizer

IR 经由优化器进行优化,优化器会调用实行各种 Pass。
所谓 Pass,便是遍历一遍 IR,在进行针对性的处理的代码。
LLVM 内置了多少 Pass,开拓者也可自定义 Pass 实现特定功能,比如插桩统计函数运行耗时等。

Xcode Optimization Level

在 Xcode - Build Setting - Apple Clang - Code Generation - Optimization Level 中,可以选定优化级别,-O0 表示无优化,即不调用任何优化 Pass。
其他优化级别则调用实行对应的 Pass。

Backend

后端将 IR 转成天生相应 CPU 架构的机器码。

Swiftc

不同于 OC 利用 clang 作为编译器前端,Swift 自定义了编译器前端 swiftc,如下图所示。

这里就表示出来 LLVM 三段式的好处了,支持新措辞只需实现编译器前端即可。

比拟 clang,Swift 新增了对 SIL(Swift Intermediate Language)的处理过程。
SIL 是 Swift 引入的新的高等中间措辞,用以实现更高等别的优化。

Swift 编译流程

Swift 源码经由词法剖析,语法剖析和语义剖析天生 AST。
SILGen 获取 AST 后天生 SIL,此时的 SIL 称为 Raw SIL。
在经由剖析和优化,天生 Canonical SIL。
末了,IRGen 再将 Canonical SIL 转化为 LLVM IR 交给优化器和后端处理。

SIL 指令

SIL 假设虚拟寄存器数量无上限,以%+数字命名,如%0,%1 等一贯往上递增 以下先容几个后续会用到的指令:

alloc_stack : 分配栈内存apply : 传参调用函数Load : 从内存中加载指定地址的值function_ref : 创建对 SIL 函数的引用

SIL 详细的指令解析可参考官方文档。

Identifier

LLVM IR 标识符有 2 种基本类型:

全局标识符:包含方法和全局变量等,以@开头局部标识符:包含寄存器名和类型等,以%开头,个中%+数字代表不决名变量变量

在 SIL 中,标识符以@开头

SIL function 名都以@+字母/数字命名,且常日都经由 mangleSIL value 同样以%+字母/数字命名,表示其引用着 instruction 或 Basic block 的参数@convention(swift)利用 Swift 函数的调用约定(Calling Convention),默认利用@convention(c)和@convention(objc_method)分别表示利用 C 和 OC 的调用约定@convention(method)表示 Swift 实例方法的实现@convention(witness_method)表示 Swift protocol 方法的实现SIL 构造

SIL 实现了一整套和 IR 类似的构造,定制化实现了SILModule SILFunction SILBasicBlock SILInstruction。

调试过程复现 Crash

根据前文的准备事情设置好编译参数后,启动编译,复现 Crash,两种 Crash 都有复现,场景如下图所示。
abort()和EXC_BAD_ACCESS会导致上文涌现的Illegal instruction: 4和Segmentation fault: 11缺点。
由于二者的上层堆栈同等,以下以前者为例进行剖析。

堆栈剖析

通过堆栈溯源可看出是在天生SILFunction后,实行postEmitFunction校验SILFunction的合法性时,利用SILVerifier层层遍历并校验 BasicBlock(visitSILBasicBlock)。
对 BasicBlock 内部的SILInstruction进行遍历校验(visitSILInstruction)。

在获取SILInstruction的类型时调用getKind()返回非常,触发 Crash。

非常 SIL由于此时SILInstruction非常,比较难定位是在校验哪段指令时非常,故在遍历SILInstruction时打印上一段指令的内容。
swift 源代码根目录实行以下命令,增量编译

cd build/Ninja-DebugAssert/swift-macosx-x86_64ninja

复现后打印内容如下图所示:

调试小 tips:LLVM 中很多类都实现了 dump()函数用以打印内容,方便调试。

// function_ref Dictionary.subscript.setter%32 = function_ref @$sSDyq_Sgxcis : $@convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@in Optional<τ_0_1>, @in τ_0_0, @inout Dictionary<τ_0_0, τ_0_1>) -> () // user: %33%33 = apply %32<AnyHashable, Any>(%13, %11, %24) : $@convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@in Optional<τ_0_1>, @in τ_0_0, @inout Dictionary<τ_0_0, τ_0_1>) -> ()%34 = load [take] %24 : $Dictionary<AnyHashable, Any> // users: %43, %37正常 SIL

命令行利用swiftc -emit-silgen能天生 Raw SIL,由于该类引用到了 OC 文件,故加上桥接文件的编译参数,完全命令如下:

swiftc -emit-silgen /Users/cs/code/ThirdParty/Swift_MVP/Swift_MVP/SwiftCrash.swift -o test.sil -import-objc-header /Users/cs/code/ThirdParty/Swift_MVP/Swift_MVP/Swift_MVP-Bridging-Header.h

截取部分 SIL 如下

%24 = alloc_stack $Dictionary<AnyHashable, Any> // users: %44, %34, %33, %31%25 = metatype $@objc_metatype TestObject.Type // users: %40, %39, %27, %26%34 = load [take] %24 : $Dictionary<AnyHashable, Any> // users: %42, %36%35 = function_ref @$sSD10FoundationE19_bridgeToObjectiveCSo12NSDictionaryCyF : $@convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@guaranteed Dictionary<τ_0_0, τ_0_1>) -> @owned NSDictionary // user: %37%36 = begin_borrow %34 : $Dictionary<AnyHashable, Any> // users: %38, %37%37 = apply %35<AnyHashable, Any>(%36) : $@convention(method) <τ_0_0, τ_0_1 where τ_0_0 : Hashable> (@guaranteed Dictionary<τ_0_0, τ_0_1>) -> @owned NSDictionary // users: %41, %40SIL 剖析

对正常 SIL 逐条指令剖析

在栈等分配类型为Dictionary<AnyHashable, Any>的内存,将其地址存到寄存器%24,该寄存器的利用者是%44, %34, %33, %31%25 表示类型TestObject.Type,即TestObject的类型 metaType加载%24 寄存器的值到%34 中,同时销毁%24 的值创建对函数_bridgeToObjectiveC()-> NSDictionary的引用,存到%35 中由于函数名被 mangle,先将函数名 demangle,如下图所示,得到函数

@convention(method)表明是 Swift 实例方法,有 2 个泛型参数,个中第一个参数τ_0_0实现了 Hashable 协议天生一个和%34 相同类型的值,存入%36,%36 结束利用之前,%34 一贯存在实行%35 中存储的函数,传入参数%36,返回NSDictionary类型,结果存在%37。
其浸染便是将Dictionary转成了NSDictionary曙光初现

比拟非常 SIL,可以看出是在实行桥接手法_bridgeToObjectiveC()时失落败,遂查看源码,创造是一个 OC 的NSDictionary不可变类型桥接到 Swift 的Dictionary成为一个可变类型时,对其内容进行修正。
虽然这种写法存在可能导致逻辑非常,但并不致编译器 Crash,属于编译器代码 bug。
更故意思的是,只有在 OC 中将该属性声明为类属性(class)时,才会导致编译器 Crash。

class SwiftCrash: NSObject { func execute() { //compiler crash TestObject.cachedData[""] = "" }}

@interface TestObject : NSObject@property (strong, nonatomic, class) NSDictionary cachedData;@end办理方案源码修正

找到缺点根源就好处理了,将问题代码中的 NSDictionary 改成 NSMutableDictionary 即可办理。

重新运行 Swift 编译器编译源码,无报错。

修正抖音源码后,也再没涌现编译器 Crash 的问题,问题修复。

静态剖析潜在问题

虽然NSDictionary正常情形下可以桥接成 Swift 的Dictionary正常利用,但当在 Swift 中对 immutable 工具进行修正后,会重新天生新的工具,对原有工具无影响,测试代码和输出结果如下:

可以看出变量temp内容无变革,Swift 代码修正无效。

TestObject t = [TestObject new];t.cachedData = [@{@"oc":@"oc"} mutableCopy];NSDictionary temp = t.cachedData;NSLog(@"before execution : temp %p: %@",temp,temp);NSLog(@"before execution : cachedData %p: %@",t.cachedData,t.cachedData);[[[SwiftDataMgr alloc] init] executeWithT:t];NSLog(@"after execution : temp %p: %@",temp,temp);NSLog(@"after execution : cachedData %p: %@",t.cachedData,t.cachedData);

class SwiftDataMgr: NSObject { @objc func execute(t : TestObject) { t.cachedData["swift"] = "swift" }}

新增规则

新增对抖音源码的静态检测规则,检测所有 OC immutable 类是否在 Swift 中被修正。
防止编译器 crash 和导致潜在的逻辑缺点。

所有需检测的类如下:

NSDictionary/NSSet/NSData/NSArray/NSString/NSOrderedSet/NSURLRequest/NSIndexSet/NSCharacterSet/NSParagraphStyle/NSAttributedString后记

行文至此,该编译器 Crash 问题已经办理。
同时近期在升级 Xcode 至 12.5 版本时又碰着另一种编译器 Crash 且未提示详细报错文件,笔者如法炮制找出错误后并修复。
待深入剖析天生SILInstruction非常的根本缘故原由后,另起文章总结。

此外笔者为 Swift 编译器提交了 bug 报告并附上最小可复现 demo, 有须要的同学可以在此链接下载:

bugs.swift.org/browse/SR-1…

加入我们

我们是卖力抖音客户端根本能力研发和新技能探索的团队。
我们在工程/业务架构,研发工具,编译系统等方向深耕,支撑业务快速迭代的同时,担保超大规模团队的研发效能和工程质量。
在性能/稳定性等方面不断探索,努力为环球数亿用户供应最极致的根本体验。

如果你对技能充满激情亲切,欢迎加入抖音根本技能团队,让我们共建亿级环球化 App。
目前我们在深圳、北京、上海和杭州均有招聘需求。

内推可以联系邮箱:chenshan.cc@bytedance.com,邮件标题:姓名-事情年限-抖音-根本技能-iOS/Android。

相关文章

phpcomposermac技巧_mac安装composer

1. 创建一个存放composer命令的目录打开Terminal,在家目录创建一个bin目录(有就不用创建了 :sudo mkdi...

网站建设 2024-12-12 阅读0 评论0