Swift编译器Crash—Segmentation fault解决方案

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"抖音上线 Swift 后,编译时偶现"},{"type":"codeinline","content":[{"type":"text","text":"Segmentation fault: 11"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"Illegal instruction: 4"}]},{"type":"text","text":"的错误,CI\/CD 和本地均有出现,且重新编译后均可恢复正常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于属于编译器层抛出的 Crash,加之提示的错误代码不固定且非必现,一时较为棘手。网上类似错误较多,但"},{"type":"codeinline","content":[{"type":"text","text":"Segmentation fault"}]},{"type":"text","text":"属于访问了错误内存的通用报错,参考意义较小。和公司内外的团队交流过,也有遇到类似错误,但原因各不相同,难以借鉴。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然 Swift 库二进制化后,相关代码不会参与编译,本地出现的概率大大减少,但在 CI\/CD\/仓库二进制化任务中依旧使用源码,出现问题需要手动重试,影响效率且繁琐,故深入编译器寻求解决方案。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Crash 堆栈"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/94\/948287c6eaa3752af0f3912e385a50dc.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"结论"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"简而言之,是 Swift 代码中将在 OC 中声明为类属性的"},{"type":"codeinline","content":[{"type":"text","text":"NSDictionary"}]},{"type":"text","text":"变量,当成 Swift 的"},{"type":"codeinline","content":[{"type":"text","text":"Dictionary"}]},{"type":"text","text":"使用。即一个 immutable 变量当作 mutable 变量使用了。编译器在校验"},{"type":"codeinline","content":[{"type":"text","text":"SILInstruction"}]},{"type":"text","text":"时出错,主动调用"},{"type":"codeinline","content":[{"type":"text","text":"abort()"}]},{"type":"text","text":"结束进程或出现"},{"type":"codeinline","content":[{"type":"text","text":"EXC_BAD_ACCESS"}]},{"type":"text","text":"的 Crash。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"准备工作"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"编译 Swift"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于本地重现过错误,故拉取和本地一致的 swift-5.3.2-RELEASE 版本,同时推荐使用 VSCode 进行调试,Ninja 进行构建。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Ninja 是专注于速度的小型构建系统。"}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"注意事项"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提前预留 50G 磁盘空间"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首次编译时长在一小时左右,CPU 基本打满"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"下载&编译源码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nbrew install cmake ninja\nmkdir swift-source\ncd swift-source\ngit clone [email protected]:apple\/swift.git\ncd swift\/utils\n.\/update-checkout --tag swift-5.3.2-RELEASE --clone\n.\/build-script"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"主要目录"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/2f\/2f1177a47fd39cd65744196fd8473455.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"提取编译参数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"笔者将相关代码抽离抖音工程, 本地复现编译报错问题后,从 Xcode 中提取编译参数:"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/92\/924bc017ca7def530db5b3a9aa013b51.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"VSCode 调试"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"选择合适的 LLDB 插件,以 CodeLLDB 为例配置如下的 launch.json。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中"},{"type":"codeinline","content":[{"type":"text","text":"args"}]},{"type":"text","text":"内容为获取前一步提取的编译参数,批量将其中每个参数用双引号包裹,再用逗号隔开所得。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"type\":  \"lldb\",\n            \"request\": \"launch\",\n            \"name\": \"Debug\",\n            \"program\": \"${workspaceFolder}\/build\/Ninja-DebugAssert\/swift-macosx-x86_64\/bin\/swift\",\n            \"args\": [\"-frontend\",\"-c\",\"-primary-file\"\/*and other params*\/],\n            \"cwd\": \"${workspaceFolder}\",\n        }\n    ]\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"SIL"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"LLVM"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在深入 SIL 之前,先简单介绍 LLVM,经典的 LLVM 三段式架构如下图所示,分为前端(Frontend),优化器(Optimizer)和后端(Backend)。当需要支持新语言时只需实现前端部分,需要支持新的架构只需实现后端部分,而前后端的连接枢纽就是 IR(Intermediate Representation),IR 独立于编程语言和机器架构,故 IR 阶段的优化可以做到抽象而通用。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/29\/296dc9b8fc4b7fa7b3b1ae7b8a7b59ce.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Frontend"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端经过词法分析(Lexical Analysis),语法分析(Syntactic Analysis)生成 AST,语义分析(Semantic Analysis),中间代码生成(Intermediate Code Generation)等步骤,生成 IR。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"IR"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"格式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IR 是 LLVM 前后端的桥接语言,其主要有三种格式:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可读的格式,以.ll 结尾"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Bitcode 格式,以.bc 结尾"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行时在内存中的格式"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这三种格式完全等价。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"SSA"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LLVM IR 和 SIL 都是 SSA(Static Single Assignment)形式,SSA 形式中的所有变量使用前必须声明且只能被赋值一次,如此实现的好处是能够进行更高效,更深入和更具定制化的优化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下图所示,代码改造为 SSA 形式后,变量只能被赋值一次,就能很容易判断出 y1=1 是可被优化移除的赋值语句。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/80\/809402d63f3e70207e160d81adf0dab9.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"结构"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基础结构由 Module 组成,每个 Module 大概相当于一个源文件。Module 包含全局变量和 Function 等。Function 对应着函数,包括方法的声实现,参数和返回值等。Function 最重要的部分就是各类 Basic Block。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Basic Block(BB) 对应着函数的控制流图,是 Instruction 的集合,且一定以 Terminator Instructions 结尾,其代表着 Basic Block 执行结束,进行分支跳转或函数返回。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Instruction 对应着指令,是程序执行的基本单元。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/66\/667155baaed0c17c54b6576d3c5c9a11.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Optimizer"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IR 经过优化器进行优化,优化器会调用执行各类 Pass。所谓 Pass,就是遍历一遍 IR,在进行针对性的处理的代码。LLVM 内置了若干 Pass,开发者也可自定义 Pass 实现特定功能,比如插桩统计函数运行耗时等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Xcode Optimization Level"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Xcode - Build Setting - Apple Clang - Code Generation - Optimization Level 中,可以选定优化级别,-O0 表示无优化,即不调用任何优化 Pass。其他优化级别则调用执行对应的 Pass。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/d8\/d89862fe92b8e84fbe0db8796a993b37.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Backend"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后端将 IR 转成生成相应 CPU 架构的机器码。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Swiftc"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同于 OC 使用 clang 作为编译器前端,Swift 自定义了编译器前端 swiftc,如下图所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/c8\/c8d3f941f16f45275c18c224b40a305c.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里就体现出来 LLVM 三段式的好处了,支持新语言只需实现编译器前端即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对比 clang,Swift 新增了对 SIL(Swift Intermediate Language)的处理过程。SIL 是 Swift 引入的新的高级中间语言,用以实现更高级别的优化。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Swift 编译流程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Swift 源码经过词法分析,语法分析和语义分析生成 AST。SILGen 获取 AST 后生成 SIL,此时的 SIL 称为 Raw SIL。在经过分析和优化,生成 Canonical SIL。最后,IRGen 再将 Canonical SIL 转化为 LLVM IR 交给优化器和后端处理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/67\/673f2c3f4848e8f769ec51a1a0f75d45.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"SIL 指令"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SIL 假设虚拟寄存器数量无上限,以%+数字命名,如%0,%1 等一直往上递增 以下介绍几个后续会用到的指令:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"alloc_stack"}]},{"type":"text","text":" `: 分配栈内存"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"apply"}]},{"type":"text","text":" : 传参调用函数"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Load"}]},{"type":"text","text":" : 从内存中加载指定地址的值"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"function_ref"}]},{"type":"text","text":" : 创建对 SIL 函数的引用"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SIL 详细的指令解析可参考官方文档。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Identifier"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LLVM IR 标识符有 2 种基本类型:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"全局标识符"},{"type":"text","text":":包含方法和全局变量等,以@开头"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"局部标识符"},{"type":"text","text":":包含寄存器名和类型等,以%开头,其中%+数字代表未命名变量变量"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 SIL 中,标识符以@开头"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SIL function 名都以@+字母\/数字命名,且通常都经过 mangle"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SIL value 同样以%+字母\/数字命名,表示其引用着 instruction 或 Basic block 的参数"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"@convention(swift)"}]},{"type":"text","text":"使用 Swift 函数的调用约定(Calling Convention),默认使用"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"@convention(c)"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"@convention(objc_method)"}]},{"type":"text","text":"分别表示使用 C 和 OC 的调用约定"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"@convention(method)"}]},{"type":"text","text":"表示 Swift 实例方法的实现"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"@convention(witness_method)"}]},{"type":"text","text":"表示 Swift protocol 方法的实现"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"SIL 结构"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SIL 实现了一整套和 IR 类似的结构,定制化实现了"},{"type":"codeinline","content":[{"type":"text","text":"SILModule SILFunction SILBasicBlock SILInstruction"}]},{"type":"text","text":"。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/b0\/b031589197f72f6ecf9fcb5623af9dd8.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"调试过程"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"复现 Crash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据前文的准备工作设置好编译参数后,启动编译,复现 Crash,两种 Crash 都有复现,场景如下图所示。"},{"type":"codeinline","content":[{"type":"text","text":"abort()"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"EXC_BAD_ACCESS"}]},{"type":"text","text":"会导致上文出现的"},{"type":"codeinline","content":[{"type":"text","text":"Illegal instruction: 4"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"Segmentation fault: 11"}]},{"type":"text","text":"错误。由于二者的上层堆栈一致,以下以前者为例进行分析。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/36\/3636f23915ce6691653448f5e0278d59.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/6f\/6fc0171435438298a4f6a94ae5c45421.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"堆栈分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过堆栈溯源可看出是在生成"},{"type":"codeinline","content":[{"type":"text","text":"SILFunction"}]},{"type":"text","text":"后,执行"},{"type":"codeinline","content":[{"type":"text","text":"postEmitFunction"}]},{"type":"text","text":"校验"},{"type":"codeinline","content":[{"type":"text","text":"SILFunction"}]},{"type":"text","text":"的合法性时,使用"},{"type":"codeinline","content":[{"type":"text","text":"SILVerifier"}]},{"type":"text","text":"层层遍历并校验 BasicBlock("},{"type":"codeinline","content":[{"type":"text","text":"visitSILBasicBlock"}]},{"type":"text","text":")。对 BasicBlock 内部的"},{"type":"codeinline","content":[{"type":"text","text":"SILInstruction"}]},{"type":"text","text":"进行遍历校验("},{"type":"codeinline","content":[{"type":"text","text":"visitSILInstruction"}]},{"type":"text","text":")。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在获取"},{"type":"codeinline","content":[{"type":"text","text":"SILInstruction"}]},{"type":"text","text":"的类型时调用"},{"type":"codeinline","content":[{"type":"text","text":"getKind()"}]},{"type":"text","text":"返回异常,触发 Crash。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/df\/df9d6e6bf870794e1250fef5fceaa2a6.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"异常 SIL"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于此时"},{"type":"codeinline","content":[{"type":"text","text":"SILInstruction"}]},{"type":"text","text":"异常,比较难定位是在校验哪段指令时异常,故在遍历"},{"type":"codeinline","content":[{"type":"text","text":"SILInstruction"}]},{"type":"text","text":"时打印上一段指令的内容。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"swift 源代码根目录执行以下命令,增量编译"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"\ncd build\/Ninja-DebugAssert\/swift-macosx-x86_64\nninja\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"复现后打印内容如下图所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"调试小 tips:LLVM 中很多类都实现了 dump()函数用以打印内容,方便调试。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e2\/e2570a93b40328e0554950f078940922.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"\/\/ function_ref Dictionary.subscript.setter\n%32 = function_ref @$sSDyq_Sgxcis : $@convention(method)  (@in Optional, @in τ_0_0, @inout Dictionary) -> () \/\/ user: %33\n%33 = apply %32(%13, %11, %24) : $@convention(method)  (@in Optional, @in τ_0_0, @inout Dictionary) -> ()\n%34 = load [take] %24 : $*Dictionary \/\/ users: %43, %37\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"正常 SIL"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"命令行使用"},{"type":"codeinline","content":[{"type":"text","text":"swiftc -emit-silgen"}]},{"type":"text","text":"能生成 Raw SIL,由于该类引用到了 OC 文件,故加上桥接文件的编译参数,完整命令如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"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\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"截取部分 SIL 如下"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"%24 = alloc_stack $Dictionary \/\/ users: %44, %34, %33, %31\n%25 = metatype $@objc_metatype TestObject.Type  \/\/ users: %40, %39, %27, %26\n%34 = load [take] %24 : $*Dictionary \/\/ users: %42, %36\n%35 = function_ref @$sSD10FoundationE19_bridgeToObjectiveCSo12NSDictionaryCyF : $@convention(method)  (@guaranteed Dictionary) -> @owned NSDictionary \/\/ user: %37\n%36 = begin_borrow %34 : $Dictionary \/\/ users: %38, %37\n%37 = apply %35(%36) : $@convention(method)  (@guaranteed Dictionary) -> @owned NSDictionary \/\/ users: %41, %40\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"SIL 分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对正常 SIL 逐条指令分析"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在栈中分配类型为"},{"type":"codeinline","content":[{"type":"text","text":"Dictionary"}]},{"type":"text","text":"的内存,将其地址存到寄存器%24,该寄存器的使用者是%44, %34, %33, %31"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"%25 表示类型"},{"type":"codeinline","content":[{"type":"text","text":"TestObject.Type"}]},{"type":"text","text":",即"},{"type":"codeinline","content":[{"type":"text","text":"TestObject"}]},{"type":"text","text":"的类型 metaType"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"加载%24 寄存器的值到%34 中,同时销毁%24 的值"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"创建对函数"},{"type":"codeinline","content":[{"type":"text","text":"_bridgeToObjectiveC()-> NSDictionary"}]},{"type":"text","text":"的引用,存到%35 中"}]}]}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于函数名被 mangle,先将函数名 demangle,如下图所示,得到函数"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/de\/dee07e8ded2f75635a2271d7724a41d5.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/0c\/0c415e1e973dec698cab47cae8824705.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"@convention(method)"}]},{"type":"text","text":"表明是 Swift 实例方法,有 2 个泛型参数,其中第一个参数"},{"type":"codeinline","content":[{"type":"text","text":"τ_0_0"}]},{"type":"text","text":"实现了 Hashable 协议"}]}]}]},{"type":"numberedlist","attrs":{"start":"5","normalizeStart":"5"},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"生成一个和%34 相同类型的值,存入%36,%36 结束使用之前,%34 一直存在"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"执行%35 中存储的函数,传入参数%36,返回"},{"type":"codeinline","content":[{"type":"text","text":"NSDictionary"}]},{"type":"text","text":"类型,结果存在%37。其作用就是将"},{"type":"codeinline","content":[{"type":"text","text":"Dictionary"}]},{"type":"text","text":"转成了"},{"type":"codeinline","content":[{"type":"text","text":"NSDictionary"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"曙光初现"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对比异常 SIL,可以看出是在执行桥接方法"},{"type":"codeinline","content":[{"type":"text","text":"_bridgeToObjectiveC()"}]},{"type":"text","text":"时失败,遂查看源码,发现是一个 OC 的"},{"type":"codeinline","content":[{"type":"text","text":"NSDictionary"}]},{"type":"text","text":"不可变类型桥接到 Swift 的"},{"type":"codeinline","content":[{"type":"text","text":"Dictionary"}]},{"type":"text","text":"成为一个可变类型时,对其内容进行修改。虽然这种写法存在可能导致逻辑异常,但并不致编译器 Crash,属于编译器代码 bug。更有意思的是,只有在 OC 中将该属性声明为类属性(class)时,才会导致编译器 Crash。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class SwiftCrash: NSObject {\n  func execute() {\n    \/\/compiler crash\n    TestObject.cachedData[\"\"] = \"\"\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@interface TestObject : NSObject\n@property (strong, nonatomic, class) NSDictionary *cachedData;\n@end\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"解决方案"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"源码修改"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"找到错误根源就好处理了,将问题代码中的 NSDictionary 改成 NSMutableDictionary 即可解决。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重新运行 Swift 编译器编译源码,无报错。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"修改抖音源码后,也再没出现编译器 Crash 的问题,问题修复。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"静态分析"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"潜在问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然"},{"type":"codeinline","content":[{"type":"text","text":"NSDictionary"}]},{"type":"text","text":"正常情况下可以桥接成 Swift 的"},{"type":"codeinline","content":[{"type":"text","text":"Dictionary"}]},{"type":"text","text":"正常使用,但当在 Swift 中对 immutable 对象进行修改后,会重新生成新的对象,对原有对象无影响,测试代码和输出结果如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出变量"},{"type":"codeinline","content":[{"type":"text","text":"temp"}]},{"type":"text","text":"内容无变化,Swift 代码修改无效。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"TestObject *t = [TestObject new];\nt.cachedData = [@{@\"oc\":@\"oc\"} mutableCopy];\nNSDictionary *temp = t.cachedData;\nNSLog(@\"before execution : temp %p: %@\",temp,temp);\nNSLog(@\"before execution : cachedData %p: %@\",t.cachedData,t.cachedData);\n[[[SwiftDataMgr alloc] init] executeWithT:t];\nNSLog(@\"after execution : temp %p: %@\",temp,temp);\nNSLog(@\"after execution : cachedData %p: %@\",t.cachedData,t.cachedData);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"class SwiftDataMgr: NSObject {\n  @objc\n  func execute(t : TestObject) {\n    t.cachedData[\"swift\"] = \"swift\"\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/71\/7112c480799d4f103e7a8d13481ebffa.png","alt":"图片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"新增规则"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新增对抖音源码的静态检测规则,检测所有 OC immutable 类是否在 Swift 中被修改。防止编译器 crash 和导致潜在的逻辑错误。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有需检测的类如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"NSDictionary\/NSSet\/NSData\/NSArray\/NSString\/NSOrderedSet\/NSURLRequest\/\nNSIndexSet\/NSCharacterSet\/NSParagraphStyle\/NSAttributedString\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"后记"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"行文至此,该编译器 Crash 问题已经解决。同时近期在升级 Xcode 至 12.5 版本时又遇到另一种编译器 Crash 且未提示具体报错文件,笔者如法炮制找出错误后并修复。待深入分析生成"},{"type":"codeinline","content":[{"type":"text","text":"SILInstruction"}]},{"type":"text","text":"异常的根本原因后,另起文章总结。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外笔者为 Swift 编译器提交了 bug 报告并附上最小可复现 demo, 有需要的同学可以在此链接下载:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/bugs.swift.org\/browse\/SR-14417"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文转载自:字节跳动技术团队(ID:toutiaotechblog)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文链接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/FSlJKnC0y51nsLDp1B3tXg","title":"xxx","type":null},"content":[{"type":"text","text":"Swift编译器Crash—Segmentation fault解决方案"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章