Webkit遠端除錯協議實戰

發表於2015-11-29

Webkit遠端除錯協議實戰

上一篇文章 介紹了 DevTools 和 Webkit Debug Protocol 這兩個 Web 開發利器的內部原理。本篇主要講解 iOS 的 Safari 遠端除錯。

iOS 的 Safari 遠端除錯,是 iOS7 引入的新功能。它允許開發者通過桌面端 Safari 的除錯工具遠端檢視移動端瀏覽器開啟的頁面。這套除錯工具,徹底解決了無線開發純靠 “alert” 的除錯困境。Safari 的遠端除錯中使用到的除錯協議,與 Google 開放的 Chrome Debug Protocol 有牽絲萬縷的關係,體現了這兩家網際網路巨頭相愛相殺的本質:

  1. 這兩套協議本質都是 Webkit Debug Protocol 的衍生產物。大部分的實際功能是一模一樣的,例如 DOM 檢視、網路請求監控、Console 等。
  2. 自從 Google 與 Webkit 專案撇清關係,分道揚鑣後生下親兒子 blink 後,自己的除錯功能越來越強大,並逐漸產生了一些和 Webkit 除錯協議不一樣的功能,故自成一套 Chrome Debug Protocol。
  3. Apple Safari 這個養在深閨裡小女子,雖然主導 Webkit,但是在實際產品使用中,卻並沒有直接使用 Webkit Debug Protocol,不僅僅使用了 binary plist 來作為序列化方法,還拋棄了 WebSocket 的通訊手段。沒有文件、沒有程式碼,我們姑且叫這個不捨得露面的東西叫做 Safari 除錯協議吧。

webinspectord 和 lockdown

iOS7 以後,每個 iOS 都有一個 webinspectord 守護程式,負責遠端除錯的通訊。這個程式暴露了一個服務介面,供外部應用(例如桌面端的 Safari 除錯工具)使用。

iOS 上所有的服務(檔案瀏覽、訊息推送、app 安裝等)都是通過一個 lockdown 服務管理連線上的。

自然的,除錯工具也需要透過 USB 介面,通過 lockdown 介面,連線到 webinspector 服務。

由於 iOS 的各種系統元件都極為神祕,沒有更多可以解釋的了。偉大的開源社群,通過各種手段實現了這些服務的介面,大家可以前去膜拜

Safari 遠端除錯服務概述

拋開 USB 通訊、lockdown 介面不談,Safari 遠端除錯服務所使用的協議本身其實就是 Webkit 除錯協議的二次包裝。也就是共享了 Webkit 除錯協議的大部分功能。

先分析這個協議裡面的主體:

  • iOS 裝置,iPad、iPhone 等物理裝置
    • UDID: 以 40 位 UDID 字串唯一識別一個裝置
  • Application:iOS 裝置上執行的開啟了 WebView 應用程式,裝置上可以同時執行多個 Application
    • Identifier:應用標示符
    • BundleIdentifier:應用的 main bundle 標示符
  • Page:每個 Application 可以開啟多個頁面
    • Identifier:頁面標示符
    • Title
    • URL

還有一些概念欄位:

  • ConnectionId, 標示當前連線到 webinspector 服務的連線
  • SenderId, 標示請求方(例如 DevTools )實體

大體可以看出,這個除錯服務的介面是有狀態的。裝置和 DevTools 建立連線後,擁有可以複用的連結作為後續通訊的通道。

假設我們需要傳送一條除錯指令到 iOS 上的某個 Webkit 核心,它的整個編碼流程應該是是這樣:

  1. Webkit 能識別的訊息物件進行 JSON 序列化為字串
  2. 構建 Safari 除錯協議中使用的 bplist 訊息體,來包裝之前得到的字串。這裡用到的 selector 就是 _rpc_forwardSocketData
  3. 將訊息題通過 Socket 傳輸到 iOS 上的除錯服務
  4. iOS 上除錯服務識別訊息,並解析 bplist,得倒 Webkit 能識別的訊息物件
  5. 將上一步得倒的訊息物件傳輸給 Webkit

所有的訊息,都是以 Apple 自己定義的 RPC 訊息提格式進行的。但它實際傳輸的有效資料,還是 Webkit
能夠識別的指令。另外,Safari 沒有如同 Chrome 那樣,使用了 WebSocket 作為暴露出去的應用層協議。它選擇了最基本的 Socket 通訊方式和 bplist 作為傳輸格式。

除錯訊息的大冒險

下面以一個具體的訊息作為例子,來說說這整個過程。

JSON 訊息和 Safari 的 RPC 協議

假設我們要開啟網路監控這個皮膚,需要傳送一個 JSON 指令。指令序列化之後我們的到:

就像之前說的,Safari 不直接使用 JSON 字串作為傳輸的序列化方案。Safari 遠端除錯協議有自己的 RPC 規範,所有的訊息都都有 __selector__arguments 兩個欄位。前者說明呼叫的方法,後者說明呼叫時的引數。

常見的一些方法(其實是 ObjC selector 的字串表達)如下:

  • _rpc_reportIdentifier::向 webinspector 服務註冊當前連結 (傳輸 connectionId )
  • _rpc_getConnectedApplications::要求獲取連線到 webinspector 的 iOS 應用列表
  • _rpc_forwardGetListing::獲取某個應用的頁面列表(傳輸 connectionId, appId )
  • _rpc_forwardSocketSetup::註冊當前會話 (傳輸 connectionId、senderId )
  • _rpc_forwardSocketData::利用某個會話傳輸資料(傳輸 connectionId、senderId、data )。Webkit 除錯協議所傳輸的 JSON 就是通過這個方法傳遞的,JSON 字串的二進位制表達被通過這個介面傳遞到 iOS 裝置上的除錯服務。

另一方面,iOS 端也會傳過來很多訊息,同樣遵循基本訊息提的格式,常見的 __selector 有:

  • _rpc_reportConnectedApplicationList::回報連線到 webinspector 的應用列表
  • _rpc_applicationSentListing::回報某個應用的頁面列表
  • _rpc_applicationConnected::某個 iOS 應用連線到了除錯服務
  • _rpc_applicationDisconnected::某個 iOS 應用從除錯服務斷開

Safari 不選擇 WebSocket 作為傳輸協議應該是從安全性、複雜性的角度去考慮。但選擇 bplist 作為傳輸格式,應該沒有太多理由,大概因為 Apple 體系內部都是用 bplist 的。

JSON 到 plist 的轉換

plist 和 bplist 都是 Apple 的通訊格式。其中 plist 非常常見。加入你做過 iOS 或者 Mac 開發,你一定寫過不少 plist。plist 就是一種擁有自有 DTD 的 XML 文件型別。說白了,它就是 XML 文件。

例如之前的 JSON 指令,轉換為 Safari 除錯協議能夠理解的 plist 文件:

可以看到 plist 擁有多種標籤來定義資料型別,例如 dict、string、data 等;同時節點的順序,都是遵循 key、value 的順序編寫。也就是說 JSON 是可以和 plist 互相轉換的。

這個轉換過程中,唯一麻煩的是 data 型別。這個標籤是用來儲存二進位制資料的,JSON 中沒有定義。但是在 Node.js 中,可以無縫轉換為一個 Buffer。

細心的你一定注意到上面 plist 中的兩個問題:

  1. 沒發現任何 JSON 字串的內容
  2. WIRSocketDataKey 裡面的竟然是 base64 編碼的字串

事實上,Safari 的除錯協議中,要求 JSON 字串是被當作 payload data 傳輸的。而 plist 標準中,data 資料型別,就是進行 base64 編碼的。

plist 到 bplist 的轉換

bplist 是 binary plist 的簡稱。它以二進位制編碼為基礎,可以用來儲存 plist 格式中同樣的內容。這在 Socket 通訊中十分有用。

要知道 Safari 除錯協議只接受 bplist 格式。具體客戶端的開發中,沒有規定一定要像本文中將一個指令先轉換為 plist,再轉換為 bplist。安排這樣的轉換,只是方便大家理解。你完全可以直接將一個 JSON 構造為 bplist。

前文的那段 plist,轉換為 binary plist 就是:

plist 和 bplist 都是有相關文件的,所以大家還是製作了不少第三方工具的:

最後一步

iOS 上的 webinspector 服務接受到這個 bplist 訊息,自然會進行一個逆向操作,得倒 JSON 的字串表達以及其他資訊(appId、pageId、senderId)。

最後,通過 Safair 除錯協議中的其他輔助資訊,將這個 JSON 指令傳輸給正確的 Webkit 例項。

Safari 除錯協議的會話流程

除錯協議的會話,本身是有一定流程的。只有一些初始操作完成後,DevTools 才能正確的傳送除錯指令。

為了方便閱讀,訊息全部以 plist 格式做演示,實際上我們傳輸的 bplist。

首先,當主機和 iOS 裝置的 webinspector 服務連線(PCUSBwebinspectord)創立的時候,會要求彙報這個連線的標示符。例如:

然後,我們要獲取到已經連線到除錯服務的 iOS 應用:

針對某個應用,獲取其內部的頁面列表:

拿到了 appId、pageId 之後,就可以開始一個除錯會話(註冊一個 senderId)了:

之後,就可以傳輸具體的除錯指令了。指令的編碼過程,就是之前演示的那樣。這裡不再贅述。

總結

本文分析了 Apple 在 iOS 裝置上開放的 Safari 除錯協議的原理以及具體通訊方式,目前還有若干缺陷需要進一步研究:

  • 除錯協議僅僅在 Development Provision Profile 簽名的應用以及 iOS 端 Safari 中開放
  • 受限於 Apple 的開發政策,還無法通過 Wifi 方式連線到除錯服務,進行真正的無線除錯
  • 在具體使用 DevTools 的時候,發現有一部分功能無法正常使用。原因可能在於 DevTools 前端應用的更新,也有可能在於 Safari 真的不支援全部的除錯指令

相關文章