iOS 崩潰日誌在線符號化實踐

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"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":"導讀:本文將介紹什麼是符號化?如何做本地符號化?爲什麼做在線符號化?","attrs":{}}]},{"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":"italic","attrs":{}}],"text":"全文14328字,預計閱讀時間28分鐘。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一、 什麼是符號化?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在日常開發中,應用難免會發生崩潰。通常,我們直接從用戶導出來的崩潰日誌都是未符號化或者部分符號化的,都是一堆十六進制內存地址的集合,可讀性較差。未符號化或者部分符號化的崩潰日誌對閃退問題的解決幾乎毫無幫助,如下所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Last Exception Backtrace:\n0 CoreFoundation 0x1ca4cd27c 0x1ca3b5000 + 1147516\n1 libobjc.A.dylib 0x1c96a79f8 0x1c96a2000 + 23032\n2 CoreFoundation 0x1ca3ded94 0x1ca3b5000 + 171412\n3 TestBacktrace 0x102a47464 0x102a40000 + 29796\n4 UIKitCore 0x1f6c86e30 0x1f63d3000 + 9125424\n","attrs":{}}]},{"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":"只有符號化後的崩潰日誌才能顯示各個線程的函數調用,而不僅僅是毫無意義的虛擬內存地址。符號化後的崩潰日誌如下所示, 此時,我們就能夠直接從堆棧信息中知道應用TestBacktrace發生崩潰時的函數爲[AppDelegate Application:didFinishLaunchingWithOptions:],崩潰時函數所在文件爲 AppDelegate.m,行號爲23:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Last Exception Backtrace:\n0 CoreFoundation 0x1ca4cd27c __exceptionPreprocess + 228\n1 libobjc.A.dylib 0x1c96a79f8 objc_exception_throw + 55\n2 CoreFoundation 0x1ca3ded94 -[__NSSingleObjectArrayI objectAtIndex:] + 127\n3 TestBacktrace 0x102a47464 -[AppDelegate Application:didFinishLaunchingWithOptions:] + 29796 (AppDelegate.m:23)\n4 UIKitCore 0x1f6c86e30 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 411\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"二、符號化原理","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.1 什麼是dSYM文件?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"iOS 平臺中, dSYM 文件是指具有調試信息的目標文件,文件名通常爲:xxx.app.dSYM,其中 xxx 通常表示應用程序的二進制包名,如下圖所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bc/bcbad93feb3c3c3f5e5686bb90d35cec.png","alt":null,"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":"通常我們可以在 Xcode 打包出來的文件 xcarchive 裏面看到 dSYM 文件以及目錄架構:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6d/6de6c0fb7930fe21d160246648c9dcc2.png","alt":null,"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","attrs":{}}],"text":"dSYM 中存儲着文件名、方法名、行號","attrs":{}},{"type":"text","text":"等信息,是和可執行文件的16進制函數地址一一對應的,通過分析崩潰的崩潰文件可以準確知道具體的崩潰信息。","attrs":{}}]},{"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","attrs":{}}],"text":"DWARF(Debuging With Arbitrary Record Format) 是 ELF 和 Mach-O 等文件格式中用來存儲和處理調試信息的標準格式","attrs":{}},{"type":"text","text":"。DWARF 中的數據是高度壓縮的,可以通過 dwarfdump、otool 等命令提取其中的可讀信息。比如提取關鍵的調試信息 debug_info 、debug_line,可使用命令","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"dwarfdump --debug-line /Users/xxxx/Desktop/resource/TestBacktrace.app.dSYM > debug_line.txt\n","attrs":{}}]},{"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":"導出debug_line 的信息到文件 debug_line.txt 中,debug_info 也可以使用類似命令導出。","attrs":{}}]},{"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":"ELF、Mach-O 分別是 Linux 和 Mac OS 平臺用於存儲二進制文件、可執行文件、目標代碼和共享庫的文件名稱。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.2 如何生成dSYM文件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在編譯工程時, Debug 模式會默認選中不生成 dSYM 文件, 該配置可在 Build Setting|Build Option 中更改,Release 模式下 dSYM 是默認生成的。另外,如果開啓了 bitcode 優化的話,蘋果會做二次編譯優化,所以最終的 dSYM 就需要在 Apple Connect 手動下載了。每次編譯生成的 dSYM都會有所差別,通常 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"dSYM 中會有一個唯一標識","attrs":{}},{"type":"text","text":",稱作 UUID ,用以區分不同的 dSYM 文件。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/00409e9adb20740801180520f8eae3b2.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}}]},{"type":"heading","attrs":{"align":null,"level":3}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.3 如何通過崩潰日誌中應用的 UUID 找到匹配的 dSYM ?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"還原崩潰堆棧時,需要 dSYM 的 UUID 與崩潰時的應用 UUID 一致","attrs":{}},{"type":"text","text":"。通常,每一個 dSYM 文件都有一個 UUID,和 App 文件中的 UUID 對應,代表着是一個應用。而每一條崩潰信息都會記錄着應用的 UUID,用來和 dSYM 的 UUID 進行校對匹配。","attrs":{}}]},{"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":"1. 首先從崩潰日誌的 Binary Images 後找到應用的 UUID,如下可得到 TestBacktrace 的 UUID 爲:","attrs":{}}]},{"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":"6be881754f573769926b838490e39857。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Binary Images:\n0x102a40000 - 0x102a6bfff TestBacktrace arm64 <6be881754f573769926b838490e39857> /var/containers/Bundle/Application/B44844E6-AFF4-491E-8168-F4ED93D644C2/TestBacktrace.App/TestBacktrace\n0x102d14000 - 0x102d6bfff dyld arm64 <9c893b6aa3b13d9596326ef6952e7195> /usr/lib/dyld\n","attrs":{}}]},{"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":"2. 使用以下命令查看 dSYM 文件的 UUID,去掉 - 並且小寫之後,與第一步中的 UUID 是完全一致的,證明兩者是匹配的,否則是不匹配的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"xcrun dwarfdump --uuid < dSYM 文件>\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1d/1d72017bb219507562e2c840f329daaf.png","alt":null,"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":"3. 如果本地 dSYM 過多的話,一個個查看太麻煩,還可以使用mdfind命名根據 UUID 在本機查找 dSYM。以上面的 UUID 爲例,直接在終端輸入以下命令就可以了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"mdfind \"com_apple_xcode_dsym_uuids == 6BE88175-4F57-3769-926B-838490E39857\"\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.4 符號化流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將崩潰日誌中的 APP 二進制地址轉化爲函數流程如下所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f1/f171b47f1f0a886d5ddabe4816c4260a.png","alt":null,"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":"獲取到崩潰日誌 App 關鍵行信息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4f/4f3576e44c281cd0aaac716b07f44add.png","alt":null,"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":"從上圖中可以看到 APP 的關建行爲是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"3 TestBacktrace 0x102a47464 0x102a40000 + 29796\n","attrs":{}}]},{"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":"其中 TestBacktrace 爲我們的二進制包名名稱,其餘行都是系統堆棧。","attrs":{}}]},{"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":"獲取到偏移量、運行時堆棧地址、運行時APP起始地址","attrs":{}}]},{"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":"由關鍵行信息獲取到 TestBacktrace 相對於起始地址的偏移量爲 29796,運行時堆棧地址爲 0x102a47464,運行時APP起始地址爲 0x102a40000。","attrs":{}}]},{"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":"獲取 dSYM 起始地址","attrs":{}}]},{"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":"dSYM 文件中保存中符號表 TEXT 段的起始地址,起始地址可通過以下命令獲得:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"otool -l /Users/xxxxx/Desktop/TestBacktrace.app.dSYM/Contents/Resources/DWARF/TestBacktrace | grep __TEXT -C 5\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ad/adce336a04a32213a6627db658b34a72.png","alt":null,"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":"由上圖中可得到 dSYM 中代碼段起始地址爲 0x10000000。","attrs":{}}]},{"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":"計算崩潰地址對應 dSYM 符號表中的地址","attrs":{}}]},{"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":"因爲 iOS 加載 Mach-O 文件時爲了安全使用了 ASLR(Address Space Layout Randomization) 機制,導致二進制 Mach-O 文件每次加載到內存的首地址都會不一樣,但是偏移量,加載地址,起始地址的計算規則是一樣的;從上面我們可以得到 0x102a47464 (運行時地址) = 0x102a40000 (起始地址) + 29796(偏移量)這個公式。因此通過 dSYM 的起始地址和偏移量就可以計算出 0x102a47464 對應在 dSYM 中的地址爲 0x100007464 = 0x0000000100000000 + 29296。","attrs":{}}]},{"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":"獲取到具體的函數/行數/文件","attrs":{}}]},{"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":"獲取到運行堆棧地址在 dSYM 文件的對應地址 0x100007464 之後,在 dSYM 文件的 debug-info 中就可以查找到包含該地址的 DIE(Debug Information Entry) 單元,Mac OS 下可使用命令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"dwarfdump TestBacktrace.app.dSYM --lookup 0x100007464\n","attrs":{}}]},{"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":"獲取相應信息,如圖所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/04/043f49cd361a472d7415a4776410a16c.png","alt":null,"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":"text","text":"DW_TAG_Subprogram 表示這個DIE單元表示的是函數方法。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DW_AT_low_pc 表示這個方法起始地址爲 0x1000073b4 。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DW_AT_high_pc 表示這個方法結束地址爲 0x1000074c4 。這就表示崩潰日誌中 0x102a47464 轉化後的偏移地址 0x100007464 正好位於這 DW_AT_low_p 和 DW_AT_high_pc 之間。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DW_AT_name 表示我們的函數名爲 [AppDelegateApplication:didFinishLaunchingWithOptions:]。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DW_AT_decl_file表示函數所在文件路徑爲 AppDelegate.m。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DW_AT_decl_line 表示函數開始行數爲 19。","attrs":{}}]}]}],"attrs":{}},{"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":"組裝並格式化","attrs":{}}]},{"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":"最終經過格式優化,崩潰日誌中 0x102a47464 符號化出來對應的方法爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"3 TestBacktrace 0x102a47464 -[AppDelegate Application:didFinishLaunchingWithOptions:] + 29796 (AppDelegate.m:23\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"三、本地符號化","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.1 符號化方法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Xcode 符號化","attrs":{}}]},{"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":"將崩潰日誌、 dSYM 文件和可執行文件放在同一目錄下,然後將 崩潰日誌拖拽至 Devicelog中,右鍵 symbolicate Log 或者 Re-symbolicate Log 就能符號化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9c/9c85365468246fbe3d56ada6a634f52e.png","alt":null,"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","attrs":{}}],"text":"使用 symbolicatecrash 命令行符號化","attrs":{}}]},{"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":"text","marks":[{"type":"strong","attrs":{}}],"text":"定位 symbolicatecrash 腳本","attrs":{}}]}]}],"attrs":{}},{"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":"通常 symbolicatecrash 的路徑爲 /Applications/Xcode.App/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash","attrs":{}}]},{"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":"text","marks":[{"type":"strong","attrs":{}}],"text":"前置運行命令","attrs":{}}]}]}],"attrs":{}},{"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":"運行 symbolicatecrash 前一般需要先運行:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"export DEVELOPER_DIR=\"/Applications/XCode.App/Contents/Developer\"\n","attrs":{}}]},{"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":"text","marks":[{"type":"strong","attrs":{}}],"text":"運行symbolicatecrash命令","attrs":{}}]}]}],"attrs":{}},{"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":"首先將崩潰日誌、 dSYM 以及 symbolicatecrash 複製出來放到同一個文件夾,然後 cd 到當前文件夾,運行如下命令符號化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"./symbolicatecrash TestBacktrace-2021-07-30-135514.ips TestBacktrace.app.dSYM > symbol.log\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.2 系統日誌符號化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"值得注意的是,有些時候,崩潰日誌裏並不會有 App 的調用,而可能全都是系統庫的調用,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Thread 32 Crashed:\n0 libobjc.A.dylib 0x19aaf6c10 0x19aad3000 + 146448\n1 CFNetwork 0x187545a28 0x18737d000 + 1870376\n2 Foundation 0x18808db4c 0x187f6c000 + 1186636\n3 Foundation 0x187f8a908 0x187f6c000 + 125192\n4 Foundation 0x18808fde8 0x187f6c000 + 1195496\n5 Foundation 0x187f8a5c4 0x187f6c000 + 124356\n6 Foundation 0x1880907e0 0x187f6c000 + 1198048\n7 Foundation 0x1880902ac 0x187f6c000 + 1196716\n8 libdispatch.dylib 0x1869863e4 0x186976000 + 66532\n9 libdispatch.dylib 0x1869d7298 0x186976000 + 397976\n10 libdispatch.dylib 0x18697c028 0x186976000 + 24616\n11 libdispatch.dylib 0x18697b828 0x186976000 + 22568\n12 libdispatch.dylib 0x186988bb8 0x186976000 + 76728\n13 libdispatch.dylib 0x186989378 0x186976000 + 78712\n14 libsystem_pthread.dylib 0x1cf2c5580 0x1cf2ba000 + 46464\n","attrs":{}}]},{"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":"符號化後的日誌爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Thread 32 Crashed:0 libobjc.A.dylib _objc_release (in libobjc.A.dylib) 161 CFNetwork __CFNetworkHTTPConnectionCacheSetLimit (in CFNetwork) 1547282 Foundation ___NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ (in Foundation) 163 Foundation -[NSBlockOperation main] (in Foundation) 1004 Foundation ___NSOPERATION_IS_INVOKING_MAIN__ (in Foundation) 205 Foundation -[NSOperation start] (in Foundation) 7846 Foundation ___NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ (in Foundation) 207 Foundation ___NSOQSchedule_f (in Foundation) 1808 libdispatch.dylib __dispatch_block_async_invoke2 (in libdispatch.dylib) 1049 libdispatch.dylib __dispatch_client_callout (in libdispatch.dylib) 1610 libdispatch.dylib __dispatch_continuation_pop$VARIANT$mp (in libdispatch.dylib) 41211 libdispatch.dylib __dispatch_async_redirect_invoke (in libdispatch.dylib) 78412 libdispatch.dylib __dispatch_root_queue_drain (in libdispatch.dylib) 37613 libdispatch.dylib __dispatch_worker_thread2 (in libdispatch.dylib) 12014 libsystem_pthread.dylib __pthread_wqthread (in libsystem_pthread.dylib) 212\n","attrs":{}}]},{"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":"可以看出是 CFNetwork 網絡請求時發生野指針導致的問題,那麼我們就可以針對網絡相關的請求做進一步排查。","attrs":{}}]},{"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":"至此可以得出結論:符號化系統庫是很有必要的,特別是對一些 App 堆棧信息完全沒有的崩潰日誌。","attrs":{}}]},{"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","attrs":{}}],"text":"如何符號化系統庫符號","attrs":{}}]},{"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":"符號化自己 App 的方法名,需要編譯生成的 dSYM 文件。而要將系統庫的符號化爲完整的方法名,也需要 iOS 各系統庫的符號文件。","attrs":{}}]},{"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":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"匹配對應的符號文件版本","attrs":{}}]}]}]}],"attrs":{}},{"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":"用戶的崩潰日誌來自各種系統版本,需要對應版本的系統符號文件才能符號化。系統庫符號文件不是通用的,而是對應崩潰所在設備的系統版本和 CPU 型號的。","attrs":{}}]},{"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":"崩潰日誌中有這樣幾個信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Code Type: ARM-64\nOS Version: iOS 10.2 (14C82)\n.........\nBinary Images:\n0x102a40000 - 0x102a6bfff TestBacktrace arm64 <6be881754f573769926b838490e39857> /var/containers/Bundle/Application/B44844E6-AFF4-491E-8168-F4ED93D644C2/TestBacktrace.App/TestBacktrace\n0x102d14000 - 0x102d6bfff dyld arm64 <9c893b6aa3b13d9596326ef6952e7195> /usr/lib/dyld\n","attrs":{}}]},{"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":"Code Type 表示此設備的 CPU 架構爲 armv7、armv7s、arm64 還是 arm64e。","attrs":{}}]},{"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":"OS Version 表示此設備的系統版本號,括號中的字符串代表了此係統的 build 號。","attrs":{}}]},{"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":"Binary Images 中的<9c893b6aa3b13d9596326ef6952e7195> 裏面的字符表示對應的系統庫 dyld 的 UUID,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"只有 build + UUID 匹配的系統庫符號文件才能符號化系統符號","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"把符號文件放到指定位置","attrs":{}}]}]}]}],"attrs":{}},{"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":"把獲取到的對應版本的符號文件放到 Mac OS 的 ~/Library/Developer/Xcode/iOS DeviceSupport 目錄下,就可以使用Xcode自帶的符號化工具 symbolicatecrash 進行符號化了。這個工具會自動根據崩潰日誌中系統庫的 UUID 搜索本機系統庫的符號文件。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.3 獲取系統符號文件的2個方法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"從真機上獲取","attrs":{}}]},{"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":"大部分系統庫符號文件只能從真機上獲取,蘋果也沒有提供直接的下載地址。但是當你用 Xcode 第一次連接某臺設備進行真機調試時,會看到 Xcode 顯示 Processing symbol files ,這時候就是在拷貝真機上的符號文件到 Mac OS 系統的 /Users/xxx/Library/Developer/Xcode/iOS DeviceSupport 目錄下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bd/bd68cda635da003de277f8bf5dd94076.png","alt":null,"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":"目錄下的 14.7.1 (18G82) 這樣的文件夾就是對應的符號文件,通常都有 1-5GB 的大小。","attrs":{}}]},{"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","attrs":{}}],"text":"從固件中提取符號文件","attrs":{}}]},{"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":"從固件 (iPSW) 中可以通過一些方式提取到系統庫符號文件。固件解密分爲 下載並提取系統符號 和 系統庫符號 提取兩步。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"1. 下載並提取系統符號","attrs":{}}]},{"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","attrs":{}}],"text":"iOS9 以及 iOS9 之前","attrs":{}}]}]}],"attrs":{}},{"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":"a. 下載對應版本的 iPSW 固件,直接解壓,解壓后里面有幾個 dmg 格式的鏡像文件,最大的 dmg 文件就是系統鏡像。","attrs":{}}]},{"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":"b. 從 Firmware_Keys (見文末參考鏈接)找到對應固件的解密 key (頁面上 Root Filesystem 字段的 key )","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/50/501fcced734d5fc105addb2a086d420e.png","alt":null,"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":"c. 用 dmg 工具進行解密。cd 到解壓後的 iPSW 文件夾,執行 ./dmg extract xxx-xxxx-xxx.dmg dec.dmg -k  。extract 後面跟兩個參數,分別是系統鏡像 dmg 的名字和解密後的文件名,-k 後面填寫第2步獲取到的 key 。如果 key 不對,解密會失敗。解密成功後會生成一個 dec.dmg 文件,雙擊打開即可加載系統鏡像。","attrs":{}}]},{"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":"text","marks":[{"type":"strong","attrs":{}}],"text":"iOS10 以及 iOS10 之後","attrs":{}}]}]}],"attrs":{}},{"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":"下載對應版本的 iPSW 固件,直接解壓,解壓后里面有幾個 dmg 格式的鏡像文件,最大的dmg 文件就是系統鏡像。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/80c4703610f5e1dff6092431f05ef4a6.png","alt":null,"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":"2. 系統庫符號提取","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從 iPhone OS 3.1 開始,所有的系統庫都打包成一個文件:","attrs":{}}]},{"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":"dyld_shared_cache_xxx ,其中 xxx 表示具體的架構,此文件位於:/System/Library/Caches/com.Apple.dyld 目錄。","attrs":{}}]},{"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":"dyld_shared_cache_xxx 文件的解壓可以使用 dyld 中的 dsc_extractor.cpp 代碼,但做一定的改動。","attrs":{}}]},{"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":"a. 首先在 Apple 開源網站下載源碼dyld庫的源碼,注意,這裏需要下載 dyld-7 的源碼。","attrs":{}}]},{"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":"b. 下載之後,將文件 dsc_extractor.cpp,main 函數前後的代碼改爲如下代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"#if 1\n// test program\n#include \n#include \n#include \ntypedef int (*extractor_proc)(const char* shared_cache_file_path,const char* extraction_root_path,void (^progress)(unsigned current,unsigned total));\nint main(int argc, const char* argv[]){\nif ( argc != 4 ) {\nfprintf(stderr,\"usage: dsc_extractor \\n\");\nreturn 1;\n }\nvoid* handle = dlopen(argv[1],RTLD_LAZY);\nif ( handle == NULL ) {\nfprintf(stderr,\"dsc_extractor.bundle could not be loaded\\n\");\nreturn 1;\n }\n extractor_proc proc = (extractor_proc)dlsym(handle,\"dyld_shared_cache_extract_dylibs_progress\");\nif ( proc == NULL ) {\nfprintf(stderr,\"dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\\n\");\nreturn 1;\n }\nint result = (*proc)(argv[2],argv[3],^(unsigned c, unsigned total) { printf(\"%d/%d\\n\", c, total); } );\nfprintf(stderr, \"dyld_shared_cache_extract_dylibs_progress() => %d\\n\",r esult);\nreturn 0;\n}\n#endif\n","attrs":{}}]},{"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":"c. 在終端上 cd 到 dyld 源碼目錄 launch-cache 下,在終端命令行編譯並生成 dsc_extractor 工具。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"clang++ -o dsc_extractor dsc_extractor.cpp dsc_iterator.cpp\n","attrs":{}}]},{"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":"d. 從Xcode的包中 /Applications/Xcode.App/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib 中提取出dsc_extractor.bundle 文件。dsc_extractor.bundle 和要提取的 iOS 系統強關聯,比如 iOS14 的系統符號需要導出 Xcode12 裏的 dsc_extractor.bundle,而 iOS15 的需要 Xcode 13 Beta 裏的。如果不匹配的話,有可能不能提取出系統符號。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/4672e205faa4c065694a686c570a9301.png","alt":null,"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":"e. 調用如下命令提取出系統符號;如下,最終提取的系統庫在目錄 17C81 下,我們解析系統符號需要的文件基本爲 dylib 和 framework。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"dsc_extractor dsc_extractor.bundle /System/Library/Caches/com.Apple.dyld/dyld_shared_cache_arm64 17C81\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"四、在線符號化","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4.1 爲什麼要實現在線符號化","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打包時候符號文件是由持續集成打包機產生,本地獲取有成本。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"方便研發人員快速符號化崩潰日誌","attrs":{}},{"type":"text","text":"。很多時候,崩潰都是在非研發人員(產品,QA 等)使用應用的時候發生的;同步到研發人員之後,因爲本地環境的差異,在沒有打包環境的情況下,研發人員也需要能迅速符號化崩潰堆棧","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"線上用戶上傳的崩潰日誌規模大","attrs":{}},{"type":"text","text":"。大多數崩潰都是發版之後用戶使用過程中發生的,如果大量線上日誌未經符號化就同步到研發人員,就會增加研發人員的負擔,降低問題解決的效率。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用戶系統多,收集難度大","attrs":{}},{"type":"text","text":"。用戶的系統從 iOS9 到 iOS14 都有,千奇百怪,靠研發人員本地想要解析所有的系統符號純屬臆想。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4.2 在線 App /動態庫符號化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常情況下,我們只需要符號化極少部分崩潰日誌,這種情況下我們在本地就可以符號化了。但當我們的應用上線發版後,崩潰日誌日均收集量級可能超百萬以上,此時就不適合在 Mac OS 上使用腳本/工具符號化了( 在 Mac OS 上使用 symbolicatecrash 命令符號化單個日誌時,耗時基本 1 秒以上)。此時,就需要更通用,快速的符號化方式了。","attrs":{}}]},{"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":"爲了能夠在 Linux 服務器上極速符號化 iOS 崩潰日誌,我們深入調研了 iOS 本地符號化的原理,在和平臺方多次就技術方案進行了調研磋商之後,最終採取瞭如下方案:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc4b36263d669ff933832eb1fca41faa.jpeg","alt":null,"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","attrs":{}}],"text":"生成mapping文件","attrs":{}}]},{"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":"將 dSYM 文件通過腳本提取生成一個 mapping 文件,格式如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Format: Mach-O/64-BitArch: arm64Symbols: 5Tool Version:: 1.0.1File Version: 1.0.0UUID: e569d81abb2c372e89a2410edc3d368fBuilt Time: 2021-07-29 13:31:08Symbol table:6c64 6c78 -[ViewController viewDidLoad] (in TestBacktrace) (ViewController.m:17)6c78 6c84 -[ViewController viewDidLoad] (in TestBacktrace) (ViewController.m:0)\n","attrs":{}}]},{"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":"提取操作會涉及到 DWARF 中 debug_line 段數據的符號化,相關提取算法可以參考 DWARF 官方的資料。debug_line 段包含有詳細的代碼偏移量地址和文件名稱,按照 DWARF 的算法就可以解析出來,然後與 Symbol Table 的函數符號一一匹配,就能生成代碼地址偏移量與函數、文件、行數的映射關係。需要注意的是,蘋果的 Mach-O 現在大部分格式都是使用 DWARF2 和 DWARF4 版本,提取的時候需要重點關注這兩種格式的兼容和算法不同。最終,可以看到 Symbol table 每一行對應一個符號的偏移量。","attrs":{}}]},{"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":"可以發現 7464 剛好處於 7454 - 7478 之間,匹配出來的符號剛好是 -[AppDelegate Application:didFinishLaunchingWithOptions:] (in TestBacktrace) (AppDelegate.m:23) ,與 Mac OS 上使用 symbolicatecrash 腳本符號化的結果一致。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77ffd199912b56cb1b33ad7e9ef96dbd.png","alt":null,"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","attrs":{}}],"text":"根據 mapping 文件符號化","attrs":{}}]},{"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":"藉助於腳本工具提取的符號 mapping 文件,服務端就能夠脫離平臺限制,根據崩潰日誌中的 UUID 去匹配映射文件,在 Linux 上極速符號化崩潰日誌,提供高效實時的符號化服務。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4.3 在線符號化 iOS 系統庫符號","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Mac OS 平臺上,我們可以直接使用系統庫的符號直接使用腳本去符號化符號,但是一旦要符號化所有用戶上傳的崩潰日誌,這一套機制就難免被速度和平臺限制。並且iOS 系統從 2.0 開始,一直到現在 iOS 14 ,發出的版本幾百個,要手動提取出系統庫符號幾乎是不可能的事情。爲了解決這個問題,在借鑑了 dSYM 跨平臺符號化方案之後,我們做了一套系統符號自動化符號化的方案,最終實現了在 Linux 平臺上高效實時的符號化系統堆棧。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/66/661ca502750c0dc9ffe9637d3ce7bac8.jpeg","alt":null,"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":"1. 定時從 theiphonewiki 網站上導出各個系統以及最新發布系統的 iPSW 文件下載地址。","attrs":{}}]},{"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":"2. 解壓 iPSW 並加載系統鏡像 dmg 文件,找到 dyld_shared_cache_xxx 文件。","attrs":{}}]},{"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":"3. 使用工具 dsc_extractor 將系統庫符號文件導出,導出文件基本爲後綴爲 dylib 和 framework 的 Mach-O 類型文件。","attrs":{}}]},{"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":"4. 將所有的 dylib 和 framework 使用工具提取生成如下格式的 mapping 文件。這一步與 dSYM 提取操作會有一定差別,通常來說,系統庫只有符號表段,不需要對 debug_line 段做提取,相對比較簡單。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"20b8 20fa +[ZoomServicesUI enableZoomServices] (in AccessibilitySettingsLoader)20fa 2120 +[ZoomServicesUI disableZoomServices] (in AccessibilitySettingsLoader)2120 21b6 -[ZoomServicesUI init] (in AccessibilitySettingsLoader)21b6 2222 -[ZoomServicesUI dealloc] (in AccessibilitySettingsLoader)\n","attrs":{}}]},{"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":"5. 崩潰日誌上傳到符號化服務器之後,服務器根據崩潰日誌中系統庫的 UUID 和 mapping 文件中的 UUID 唯一確定 mapping 文件並符號化系統堆棧。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"五、效果","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 在線符號化系統上線之後,用戶的日誌經過崩潰組件自動上傳到性能之後,符號化解析系統直接將崩潰日誌符號化並聚類,最終符號化的崩潰日誌詳情如下。應用程序的地址最終表現爲 函數 + 文件 + 行數,系統堆棧會具體顯示出崩潰的函數,整個過程實時且高效。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Last Exception Backtrace:\n0 CoreFoundation 0x1ca4cd27c __exceptionPreprocess + 228\n1 libobjc.A.dylib 0x1c96a79f8 objc_exception_throw + 55\n2 CoreFoundation 0x1ca3ded94 -[__NSSingleObjectArrayI objectAtIndex:] + 127\n3 TestBacktrace 0x102a47464 -[AppDelegate Application:didFinishLaunchingWithOptions:] + 29796 (AppDelegate.m:23)\n4 UIKitCore 0x1f6c86e30 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 411\n5 UIKitCore 0x1f6c88594 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 3351\n6 UIKitCore 0x1f6c8dd20 -[UIApplication \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Thread 0 name: Dispatch queue: com.Apple.main-thread Thread 0 Crashed: 0 libsystem_kernel.dylib 0x00000001ca06a0dc __pthread_kill + 8 1 libsystem_pthread.dylib 0x00000001ca0e3094 pthread_kill$VARIANT$mp + 380 2 libsystem_c.dylib 0x00000001c9fc3ea8 abort + 140 3 libc++abi.dylib 0x00000001c9690788 __cxa_bad_cast + 0","attrs":{}}],"attrs":{}}]},{"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":"2. 部分來自研發和測試的崩潰日誌,平臺提供了在線符號化的入口,只需要手動上傳崩潰日誌到平臺,立刻就能把符號化後的崩潰日誌下載給相應人員。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"六、收益","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 線上問題定位速度獲得極大提升,從線上發生新增卡頓/崩潰問題到具體研發響應時間大大縮減,從發生崩潰到定位問題,基本都在 10 分鐘以內。","attrs":{}}]},{"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":"2. 目前,性能平臺日均在線符號化崩潰/卡頓日誌超百萬次,廠內接入產品線超 30+,符號化功能做到了上傳即解析,整個過程無需研發人員干預。真正做到了自動化、在線、實時的符號化崩潰、卡頓日誌,並實時根據符號化的問題代碼定位到具體開發人員,高效的響應並解決線上問題。","attrs":{}}]},{"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","attrs":{}}],"text":"參考資料:","attrs":{}}]},{"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":"[1] iOS Crash 分析必備:符號化系統庫方法https://zuikyo.github.io/2016/12/18/iOS%20Crash%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90%E5%BF%85%E5%A4%87%EF%BC%9A%E7%AC%A6%E5%8F%B7%E5%8C%96%E7%B3%BB%E7%BB%9F%E5%BA%93%E6%96%B9%E6%B3%95/[2] 聊聊從 iOS 固件提取系統庫符號 http://crash.163.com/#news/!newsId=31","attrs":{}}]},{"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":"[3] Xcode 中和 symbols 有關的幾個設置 https://www.jianshu.com/p/11710e7ab661[4] iOS_SDK https://en.wikipedia.org/wiki/IOS_SDK[5] IOS_version_history https://en.wikipedia.org/wiki/IOS_version_history#iOS_14","attrs":{}}]},{"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":"[6] dyld 源碼下載地址 https://opensource.apple.com/tarballs/dyld/[7] The DWARF Debugging Standard http://www.dwarfstd.org/[8] iOS9 之前的Firmware_Keys https://www.theiphonewiki.com/wiki/Firmware_Keys","attrs":{}}]},{"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":"[9] dmg 工具下載地址 https://github.com/Zuikyo/iOS-System-Symbols/blob/master/tools/dmg[10] 系統符號下載地址索引 wiki https://www.theiphonewiki.com/wiki/Firmware","attrs":{}}]},{"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","attrs":{}},{"type":"strong","attrs":{}},{"type":"strong","attrs":{}}],"text":"招聘信息","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"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","attrs":{}}],"text":"移動端高級/資深工程師","attrs":{}}]},{"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":"我們是百度APP移動技術平臺團隊,負責百度APP性能優化、網絡優化、架構升級、工程效能提升和新技術探索等。我們在線上線下研發工具,智能性能優化,網絡庫開發,網絡監控,編譯構建系統,動態化方案等方面深耕,技術深度上是移動端的燈塔。我們支持業務快速迭代的同時,保證超大規模團隊的研發效能,服務於MAU超過6億的用戶。","attrs":{}}]},{"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":"我們致力於打造行業內性能最佳的APPs,在高中低端機上有卓越的性能穩定性和流暢度。","attrs":{}}]},{"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":"我們致力於建設適合百度的移動技術平臺,賦能百度的移動端產品,智能音箱和車載設備。","attrs":{}}]},{"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","attrs":{}}],"text":"技術棧","attrs":{}},{"type":"text","text":":Java/OC/C/C++/Kotlin/Swift/Ruby/PHP等","attrs":{}}]},{"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","attrs":{}}],"text":"支撐業務","attrs":{}},{"type":"text","text":":百度APP的搜索,Feed,小程序,直播,視頻等;百度貼吧,好看視頻等其他移動端產品;小度音箱等IOT設備;智能交通,車聯網等車載設備。","attrs":{}}]},{"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","attrs":{}}],"text":"學歷要求","attrs":{}},{"type":"text","text":":統招本科畢業","attrs":{}}]},{"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":"如果你在某個方向有特長,有技術深度;如果你熱愛專研,挑戰疑難問題;如果你愛突破創新,使用端智能等新技術解決各種問題,這裏就是你發揮才能的舞臺!","attrs":{}}]},{"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","attrs":{}}],"text":"投遞郵箱:","attrs":{}}]},{"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":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"[email protected] (投遞備註崗位)","attrs":{}}]},{"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":"推薦閱讀:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=Mzg5MjU0NTI5OQ==&mid=2247503928&idx=1&sn=a595da431aad386e4210f886cbcddaca&chksm=c03ee044f7496952e70788ab0ba71d6acaaff6de4162d4fa3d3e872cfcbb4b34335b95f597cf&scene=21#wechat_redirect","title":"","type":null},"content":[{"type":"text","text":"|百度商業託管頁系統高可用建設方法和實踐","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=Mzg5MjU0NTI5OQ==&mid=2247503597&idx=1&sn=a9b60d17f310dc3b5ccf29d067d66111&chksm=c03efe91f74977872cc380f48edd6c97e7915bc2fd99c49ca9edfdbaf2f4d9484e40d05b5b86&scene=21#wechat_redirect","title":"","type":null},"content":[{"type":"text","text":"|AI 在視頻領域運用—彈幕穿人","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=Mzg5MjU0NTI5OQ==&mid=2247503500&idx=1&sn=f84fd6f9e62feeb7b91dfd6f1f0d145d&chksm=c03efef0f74977e66f26f287912dc37c1f3cb0124c57483205345ad7645ea59bc3518e1c82e2&scene=21#wechat_redirect","title":"","type":null},"content":[{"type":"text","text":"|iOS簽名校驗那些事兒","attrs":{}}]}]},{"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","attrs":{}}],"text":"---------- END ----------","attrs":{}}]},{"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":"百度 Geek 說","attrs":{}}]},{"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":"百度官方技術公衆號上線啦!","attrs":{}}]},{"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":"技術乾貨 · 行業資訊 · 線上沙龍 · 行業大會","attrs":{}}]},{"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":"招聘信息 · 內推信息 · 技術書籍 · 百度周邊","attrs":{}}]},{"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":"歡迎各位同學關注","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章