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解決方案"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章