iOS開發-使用 Hopper 調查 iOS 錯誤

在本文中,我將分享我們如何調查 UIKit 中的一個錯誤,自從 iOS 13.4.1 最近發佈以來,Apple 一直無法解決這個問題。

該問題於 2019 年 8 月首次在 iOS13 的第一個測試版中被發現。在 Badoo 和 Bumble 應用程序中,我們一直在努力改進界面。例如,我們正試圖最大限度地優化我們知道用戶覺得煩人和令人反感的註冊過程。簡化流程的一種方法是使用出現在鍵盤上方的系統性預測建議。這些是在用戶輸入數據時將寶貴的點擊次數降至最低的絕佳方式。在新的 iOS 中,我們驚訝地發現用戶電話號碼的預測建議已經消失了。


GM出來的時候我們看到問題還沒有解決。在我們看來,如此明顯的遺漏必然會被隨後的迴歸測試(Apple 必須有這些)發現,並認爲一切都會在第一個主要更新包中得到解決。這也是其他開發人員所希望的。然而,當 13.1 出現時,這些變化並沒有實現,所以我們別無選擇,只能打開雷達——這就是我們在 10 月初所做的。時間過去了:13.2 出來了,然後是 13.3 ......而這個錯誤仍未解決。

2 月份,我們的註冊團隊發現他們有一些額外的時間,並且沒有積壓的工作,因此決定更深入地調查這個問題。

當然,要開始“挖掘”,您需要知道在哪裏“挖掘”。由於預測建議仍然適用於某些類型的鍵盤,當我們開始尋找“挖掘”的位置時,我們的第一個想法是在不同版本的 iOS 中探索其視圖的層次結構。

iOS12 與 iOS13

很明顯,在 iOS13 中,Apple 對鍵盤實現進行了重構,並將預測建議分配給了一個單獨的控制器 ( UIPredictionViewController )。顯然,模塊化和分解的趨勢現在已經到達了蘋果公司。功能迴歸很可能是此過程的一部分。我們的搜索範圍開始縮小。

大多數 iOS 開發人員都知道私有系統類的接口很容易獲得並且很容易找到——它們只是一個搜索引擎查詢的距離。研究類接口,它的一個方法立即引起你的注意:

這就導致了一個假設,這將是很容易檢查出使用值得信賴的老工具,交叉混合,其中犯罪嫌疑人函數總是返回真值。

+(void)swizzleIsVisibleForInputDelegate {
    SEL targetSelector = sel_getUid("isVisibleForInputDelegate:inputViews:");
    Class targetClass = NSClassFromString(@"UIPredictionViewController");
    if (targetClass == nil) {
        return;
    }
    if (![targetClass instancesRespondToSelector:targetSelector]) {
        return;
    }
    Method method = class_getInstanceMethod(targetClass, targetSelector);
    if (method == NULL) {
        return;
    }
    IMP originalImplementation = method_getImplementation(method);
    IMP newImp = imp_implementationWithBlock(^BOOL(id me, id delegate, id views) {
        /// We call the original implementation to avoid any possible inconsistency vis-a-vis the state inside private classes.
        BOOL result = ((bool (*)(id,SEL,id,id))originalImplementation)(me, targetSelector, delegate, views);
        if ([delegate isKindOfClass:[UITextField class]] && [delegate keyboardType] == UIKeyboardTypePhonePad) {
            return YES;
        }
        return result;
    });
    method_setImplementation(method, newImp);
}

重新運行測試項目,我發現電話號碼預測建議又回到了 iOS13 中,並且沒有任何明顯問題。在這一點上,我們本可以終止調查,並可能非常謹慎地在發佈版本中使用這個有風險的解決方案(Apple 指南禁止),併爲某些用戶遠程打開或關閉它。但一直以來我都無法擺脫我的好奇心:使用電話式鍵盤時,導致此函數返回“false”的邏輯是什麼?

要了解真相,您需要該函數的源代碼。顯然,Apple 沒有公開 iOS 組件 left、right 和 center 的代碼;這不是你可以谷歌的東西。只有一種方法:逆向工程將二進制代碼反編譯爲源代碼。在此之前,我不止一次聽說過一種叫做Hopper的產品並閱讀了幾篇關於如何使用它在系統庫中閒逛的文章,但個人從未使用過它。令人驚喜的是,您甚至不必購買完整版本就可以使用它並研究它使用的工具。演示版包括無休止的 30 分鐘工作會議,可防止您保存索引或更改二進制文件。這一切使它成爲進行實驗的絕佳環境。

從同一個已發佈的私有接口中,您會發現UIPredictionViewController是框架的 UIKitCore 的一部分。剩下要做的就是找到它並將其加載到料斗中。框架的二進制文件深埋在 Xcode 中。例如,這裏是我們需要的 UIKitCore 的完整路徑:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore

我們將文件拖放到 Hopper 中,在系統對話框中確認操作並等待索引完成(對於像 UIKit 這樣的大型框架,這可能需要 4-6 分鐘)。該程序的界面非常簡單;以下是我的調查所需的關鍵要素。在界面的左側面板中是一個搜索字符串,它允許您導航代碼。如果您根據感興趣的類的名稱進行搜索,它將快速返回正在調查的函數,並在主窗口中打開其彙編代碼。

在上方的任務面板中有一個按鈕,用於切換顯示代碼的模式。從左到右:

● ASM 模式是彙編代碼。

● CFG 模式是一種流程圖(樹)形式的彙編代碼,其中代碼塊組合成框,並以分支的形式顯示轉換。

● 僞碼模式是生成的僞碼(詳見下文)。

● 十六進制模式是二進制文件的十六進制表示,也稱爲abracadabra,對我們的調查幾乎沒有用處。

現在剩下要做的就是解釋函數內部實際發生的事情。它的主體很長,因此,查看彙編代碼,需要真正的 asm 大師才能深入瞭解邏輯——這不是我可以聲稱的。這就是僞代碼模式有用的地方。在這裏,Hopper 儘可能簡化了彙編操作,在可能的情況下替換了函數的真實名稱並使用寄存器名稱作爲變量。這是它的樣子:

現在我們只需要遵循函數的邏輯,看看我們最終進入了哪些分支。爲此,我發現符號斷點非常有用。它還可以設置系統調用,將所有必要的變量和函數調用結果並行打印到 Xcode 控制檯。使用這種遠非狡猾的方法,我發現該函數因過早退出而中斷,因爲在作爲示例給出的代碼塊中,其中一個條件不起作用。讓我們一步一步地瞭解這裏發生了什麼。

這是一些上下文:在 rbx 寄存器中,在代碼中稍高的位置(爲了簡單起見,我將其向下移動),有一個指向實現UITextInputTraits_Private協議的對象的鏈接。這是UITextInputTraits公共協議的擴展版本。

好的,所以條件之一是驗證輸入字段配置沒有遮擋建議,並且在調試中您可以看到滿足條件:hidePrediction 屬性返回“false”。第二個條件驗證鍵盤未處於“拆分”模式(如果您不知道這一點,請在 iPad上向上滑動右下按鈕——只有 2-3% 的用戶知道這一點)。在我們的例子中,這個條件得到滿足。

讓我們繼續。在下一階段,keyboardType 開始發生變化,這表明我們離我們越來越近了。首先,它驗證當前的keyboardType 是否小於或等於0xb(或十進制中的11)。如果在 XCode 中打開UIKeyboardType聲明,您將看到總共有 13 種鍵盤類型,其中一種(UIKeyboardTypeAlphabet)已過時並作爲另一種鍵盤類型的別名。這意味着enum總共有12種類型:如果我們從0開始,那麼最後一個的值將等於11。也就是說,在代碼中以驗證溢出的形式進行值的驗證並且,這再次成功執行。

接下來,我們發現了一個非常奇怪的條件if (!COND),很長一段時間我都無法理解它測試了什麼,因爲在上面的代碼中找不到變量COND。更重要的是,我的調試斷點表明,正是這種條件沒有滿足,導致過早退出函數。此時,除了返回 ASM 模式並在彙編程序視圖中研究相關代碼部分之外,別無他法。

爲了在 ASM 列表中找到這種情況,您可以使用“No code duplication”選項。這顯示了僞代碼,不是以帶有 if-else 條件的源代碼的形式,而是以獨特的代碼塊和 goto 形式的轉換的形式。在這種情況下,我們在二進制文件中看到這些塊的起始位置,並使用此指針在 ASM 模式下進行搜索。這就是我們如何發現我們感興趣的塊將在 fe79f4 中找到。

切換到 ASM 模式後,我們很容易找到這個塊:


我們現在接近最複雜的部分,即我們將要查看的三個彙編代碼字符串。

我從記憶中認出了第一個字符串,這要歸功於我學習的機構的那些彙編課程。這裏的一切都很簡單:ecx寄存器包含常量 0x930(二進制形式的 100100110000)。

在第二個字符串中,我們看到要在寄存器exceax上執行的指令bt。其中之一的價值是已知的。第二個的值可以在前面的截圖中看到:rax = [rbx keyboardType]。這是可以找到當前鍵盤類型的地方。(rax是整個 64 位寄存器;eax是它的 32 位部分)。

計算出數據後,現在我們需要了解命令邏輯。Google 爲我們提供了以下說明

在位偏移操作數(第二個操作數)指定的位位置處選擇位串(由第一個操作數指定,稱爲位基數)中的位,並將該位的值存儲在 CF 標誌中。

該指令在由第二個操作數(鍵盤類型)確定的位置從第一個操作數(常量 0x930)中提取一位,並將其放入 CF(進位標誌)。也就是說,作爲結果,CF 標誌中的值將根據鍵盤類型爲 0 或 1。

讓我們轉到最後一個操作jb,它具有以下描述:如果低於 (CF=1),則跳轉短。如果 CF 標誌中的值爲 1,不難猜測這就是函數執行的關鍵點(過早退出)。

這就是所有拼圖開始聚集在一起以產生連貫圖片的地方。我們有以下位掩碼:100100110000(這有 12 位,每個可用的鍵盤類型一個),正是這個決定了過早退出條件。現在,當我們按照 rawValue 升序檢查所有鍵盤類型的建議時,一切都應該如此。

在 iOS 12 中找不到此邏輯:在該操作系統中,預測建議適用於任何鍵盤類型。我懷疑在 iOS 13 中,他們決定關閉數字鍵盤的預測性建議,這基本上是有道理的。我想不出系統需要建議數字的任何場景。看起來UIKeyboardTypePhonePad最終被誤認爲是附帶損害,因爲它看起來非常像一個普通的數字鍵盤;而UIKeyboardTypeNamePhonePad,用於搜索電話聯繫人,結合了 qwerty 鍵盤和關閉的同類數字鍵盤,繼續提供預測建議。

我驚訝地發現與 Hopper 的合作是如此愉快和吸引人。我已經很久沒有這麼開心了。我在我的錯誤報告中與 Apple 工程師分享了我的發現,並在適當的時候將其狀態更改爲“已確定潛在修復 - 用於未來的操作系統更新”。我希望這個修復程序能在未來的更新中很快找到用戶。話雖如此,Hopper 不僅可以用於確定您或 Apple 存在的錯誤的原因,還可以爲第三方開發人員的程序查找 Apple 修復程序

如果你正在跳槽或者正準備跳槽不妨動動小手,添加一下咱們的交流羣1012951431來獲取一份詳細的大廠面試資料爲你的跳槽多添一份保障。

文末推薦:iOS熱門文集

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章