來源 | HaaS技術社群不看這幾篇好文,就别說自己了解物聯網
真的存在問題分析的“銀彈”嗎?
寫過代碼的開發人員都明白,軟體bug是無法避免的。當代碼中出現bug時,可能導緻系統出現異常崩潰、記憶體洩漏等現象。是以軟體開發人員需要具備強大的診斷能力将這些問題抓出來,而掌握這些診斷能力便成為軟體開發人員成為高手的必經之路。
對于運作Linux系統的物聯網裝置而言,這個問題的答案簡單而美好——“銀彈”存在且有很多。諸如linux自身的coredump以及強大的valgrind等等,顯然linux的成熟與強大為開發者提供了足夠多的銀彈。但更多的物聯網裝置中,尤其輕量級裝置是無法運作linux的。本文便專注于讨論在這些裝置上如何分析遇到的各種問題。
通常這樣的輕量級物聯網裝置會運作在特定的RTOS上,部分裝置甚至直接落跑沒有OS。更有甚者,很多開發人員拿到的開發環境中唯一可以依賴的就是列印(沒有JTAG無法使用GDB工具)。本文針對這樣的艱苦環境,給出足夠多的“銀彈”,友善開發者分析各種問題。由于篇幅有限,是以每種“銀彈”都以技術思路闡述為主,不再具體到讨論代碼。

感受“銀彈”的魅力
“銀彈”的魅力在于,一個bug出現後開發人員依靠它可以不用連仿真器、不用打開gdb單步調試的情況下能快速找到bug原因。我們先看看一些例子:
-
代碼中通路了非法記憶體(比如:在不可寫的位址處寫了資料)導緻系統奔潰。
當機診斷“銀彈”,可以記錄通路非法記憶體時的pc值,告訴使用者挂在了那個函數的哪一行;
-
代碼跑飛了(pc=0),直接觸發異常。
棧回溯“銀彈”+ 當機診斷“銀彈”,可以将整個棧回溯列印出來,找到A->B->C的函數調用過程;
-
版本過大導緻記憶體或者flash消耗太大。
靜态記憶體統計“銀彈”,可以友善的分析整個版本的靜态記憶體消耗,找出ram或flash消耗的罪魁禍首;
-
記憶體申請時malloc 失敗,出現記憶體不足。
動态記憶體統計“銀彈”,可以記錄使用者的曆史過往資料,根據使用者是在哪個任務或子產品中申請的記憶體、從系統啟動開始記憶體的申請情況等資訊,幫助使用者檢視是否有元件申請了過大記憶體但沒有釋放等記憶體洩漏的情況;
-
裝置長時間運作後出現記憶體不足。
記憶體洩漏分析“銀彈”,可以對比兩個時間點的動态記憶體變化,找到已申請未釋放的記憶體,實作記憶體洩露問題的定位
-
已經有明顯的踩記憶體現象,但是無法具體定位踩記憶體根因。
記憶體踩踏檢查“銀彈”,可以設定指定的記憶體區域屬性為不可通路,進而制造memory通路異常,結合異常現場列印,快速定位踩記憶體的元兇;
-
系統挂死、序列槽沒有列印,問題分析無從下手。
主動異常觸發“銀彈”,通過序列槽指令等手段主動觸發系統異常,在異常進行中追溯初被打斷之前的程式現場,實作問題的定位。
如何使用這些“銀彈”?
RTOS領域大部分系統的診斷能力都十分薄弱,需要開發者自己學會并掌握大量的診斷能力。本章節給出這些診斷能力即“銀彈”的實作思路,供讀者參考。
棧回溯
棧回溯的目的是根據函數棧的内容,分析初函數調用關系。以嵌入式最常見的ARM架構為例,每一級函數調用都會把函數傳回位址進行壓棧(LP寄存器),開發者要做的就是在出現問題時,通過PC寄存器找到函數,通過SP寄存器找到棧的位置,然後一層層向上找到棧中儲存的傳回位址。思路如下圖:
linux等其他桌面級OS也具備棧回溯能力,但其是通過-mapcs-frame或-funwind-tables編譯選項實作的。這些選項對性能和代碼體積都有影響,且嵌入式很多bsp代碼并不開源,無法加入編譯選項重編。是以這裡讨論的是不依賴編譯選項的棧回溯能力。
這裡面給出一些啟發性的關鍵技術點:
- 開發者可以手動分析或者寫程式自動完成棧回溯;
- 分析時的輸入,棧内容與反彙編代碼;
- 開發者需要熟悉CPU架構中使用的壓棧指令,對照反彙編尋找這些指令;
- 尋找時從PC開始向上尋找,找到函數入口處的壓棧指令,從棧中找到LR,如此往複向上尋找;
當機診斷
類似中斷處理,當系統出現異常時會進入特定的向量處理流程。
對于開發者來說,如果使用OS本身已經接管了異常,并在異常發生時進行了必要的列印,那麼最重要的就是如何了解這些異常資訊了。以ARM的Cortex-M 3/4/7為例,可以參考這篇文檔,分析異常原因。
Using Cortex-M3/M4/M7 Fault Exceptions這裡面最核心的思路時,通過異常時的PC找到出問題的指令,通過上述文檔提及的下列關鍵寄存器的值,找到異常的原因。
還有,通常PC位址不足以分析出問題。比如異常出現在free函數中,那是誰調用了free導緻問題就變得非常重要。此時可以結合上一節的“棧回溯”實作向上追溯,完成問題分析。
如果開發者使用的OS碰巧異常時列印的資訊太少,那就需要自行補充異常處理代碼了。從相關vector開始走讀代碼,并再合适的位置加上列印,這裡篇幅有限不再詳細展開。
靜态記憶體統計
對于嵌入式物聯網裝置,記憶體占用總是非常敏感的。當出現問題時,RAM或者Flash已經不足,那麼開發者應該怎麼入手分析問題呢?本小節先從靜态記憶體統計入手,分析編譯時已經确定的Flash與RAM消耗,找到耗記憶體大戶。
靜态記憶體分析方法的核心是利用好連結時産生的map檔案,map檔案通過連接配接選項‘-Map=xxx.map’可以産生。如果使用gcc進行連結,需要使用‘-Wl,-Map=output.map’編譯選項,将其傳遞給連結器。
有了Map檔案如何檢視?map檔案中羅列了每個函數、變量、隻讀資料的大小。對于小型嵌入式裝置,函數與隻讀資料(比如字元串、初始化的data資料)是放入Flash中的,變量則是放入RAM中的。
可以使用python寫一個分析map的腳本,實作類似這樣的效果:
該腳本從map檔案中提取各個函數、變量與隻讀資料的資訊,并按照從大到小進行排序,最終輸出到excle中。如上圖所示,開發者就可以很友善檢視這些超大的資料從何而來,進而實作靜态記憶體優化的目的。
筆者會将該python腳本配套專門的文章發出,請持續關注本社群。
動态記憶體統計
記憶體統計的核心是替換原有平台提供的malloc與free接口,增加輔助資訊進行統計。這裡給出一個簡單的思路:
- 實作new_malloc/new_free。原先外部調用malloc/free的地方替換成new_malloc/new_free。
- new_malloc中将傳入的size加16,然後調用malloc申請記憶體。得到ptr後預留16的offset傳回個上一層。最後進行記憶體統計。
- 這保留的16位元組用來記錄函數傳回位址、目前任務ID、記憶體塊大小等資訊,友善記憶體塊追查。
- new_free中對應進行16位元組的偏移,再調用free接口。最後進行記憶體統計。
- 可選:可以維護一個連結清單把已申請記憶體串起來。
由此便可以實作:
- 任意時刻可以得知動态記憶體的占用情況,得知高記憶體占用的任務;
- 甚至對于大的記憶體塊,可以擷取到調用者的資訊;
- 當出現記憶體不足時,可以根據調用者資訊進行分類,找到大量申請記憶體的代碼。
另一個思路,便是重新實作OS的malloc與free算法,這裡篇幅有限不再詳細展開。
記憶體洩漏分析
想做到記憶體洩漏分析,首先需要對申請到的記憶體進行追蹤,增加如下的TraceInfo資訊。
然後,找到系統記憶體穩定的時間點。比如一個物聯網裝置啟動之後,做起點A,然後觸發一個完整業務,等業務停止後,記作結尾B。接下來對比A與B之間的記憶體差異,類似如下流程。
上圖橙色部分就是需要開發者為記憶體洩漏分析能力而增加的軟體功能。最終找到 LEAK_CHECK Start(即例子中的A)與LEAK_CHECK End(即例子中的B)兩個時間點之間,所有已申請未釋放的記憶體。
開發者根據這些記憶體的棧回溯資訊,便能找到代碼中記憶體洩漏的點了。
記憶體踩踏檢查
記憶體踩踏問題的第一步是找到穩定複現的場景,然後增加診斷資訊複現問題,最終捕獲踩踏記憶體的元兇。
這裡各處一些大緻思路:
- 首先,需要找到一種記憶體被踩就系統停止的方法。以ARM Cortex-M為例有兩種方式,硬體斷點或MPU記憶體保護單元;
- 硬體斷點适合小記憶體範圍的監控,MPU适合大範圍的記憶體監控,具體實作方式需要參考ARM文檔;
- 利用這樣的硬體特性,代碼就可以實作watchpoint功能,通過相關API在指定位址增加監控;
- 以棧越界為例,通過下圖(左邊是原問題版本,右邊是增加監控的版本)方式便可以抓出來是那個函數出現了棧越界;
主動異常觸發
當系統出現意外的代碼死循環時,可能導緻序列槽沒有任何列印,這種問題非常難以定位。即使對于有指令行互動的OS,也可能因為中斷被屏蔽,無法執行指令,進而毫無定位問題的手段。問題如下圖所示
這裡筆者給出一種主動異常觸發的機制,通過特殊的序列槽指令(或者其他中斷)觸發CPU跑到特殊的中斷向量中,該向量主動執行非法指令進而觸發系統異常。最終系統在異常進行中列印出完整的系統體檢報告。
該技術的一些關鍵點:
- 需要找到一個中斷承接打斷系統的工作;
- 将該中斷優先級設定成最高;
- 重新實作系統的開關中斷操作,将“關閉所有中斷”替換成“關閉特殊中斷外的所有中斷”;
- 特殊中斷處理流程直接執行非法指令,比如ARM的udf指令,觸發異常;
- 異常進行中擷取系統資訊,恢複出特殊中斷之前的系統現場,分析卡死原因。
有沒有更簡單的方案?
與其讓開發人員一步步自己實作這些銀彈,能否提供更簡便直接使用這些銀彈的方法?
答案是有,隻要使用阿裡雲旗下的物聯網作業系統AliOS Things,配合配套SmartTrace工具。便直接可以用上上述各種銀彈法寶了。
AliOS Things開源在這裡:
https://gitee.com/alios-things/AliOS-Things後續本社群會陸續更新一些SmartTrace相關的診斷維測系列文章,教會開發者使用AliOS Things時如何快速定位各種疑難雜正問題,成為bugfix高手。擁有“銀彈”,享受開發的樂趣~~