極力推薦Android 開發大總結文章:歡迎收藏 程序員Android 力薦 ,Android 開發者須要的必備技能 java
Kernel Exception 在工做中偶爾會遇到,發生此異常時候常常會致使手機死機或重啓。那麼如何解決這些問題,本篇文章將帶你從KE小白變小牛。linux
注: 本文部份內容參考MTK,若有侵權,請告知,立馬關閉此文,中止侵權。知識共享,感謝支持!android
本篇文章主要介紹 Android
開發中的部分知識點,經過閱讀本篇文章,您將收穫如下內容:程序員
基礎篇: 經過log分析KE 1.Kernel Exception 2.kernel空間佈局 3.瞭解printk 4.ram console 5.前期異常處理 6.die()流程 7.panic()流程 8.nested panic編程
##1.Kernel Exception數組
Android OS由3層組成,最底層是kernel,上面是native bin/lib,最上層是java層: 微信
任何軟件都有可能發生異常,好比野指針,跑飛、死鎖等等。框架
異常發生在kernel層,咱們就叫它爲KE(kernel exception),同理,發生在native就是NE,java層就是JE。這篇文章僅關注底層的KE。函數
kernel有2種崩潰類別工具
凡是程序就有bug。bug老是出如今預料以外的地方。聽說世界上第一個bug是繼電器式計算機中飛進一隻蛾子,倒黴的飛蛾夾在繼電器之間致使了計算機故障。因爲這個小蟲子,程序中的錯誤就被稱爲了bug。
有Bug就須要Debug,而調試是一種很個性化的工做,十我的可能有十種調試方法。但從手段上來說,大體可分爲兩類,在線調試 (Online Debug) 和離線調試 (Offline Debug).
####在線調試, Online debug, 指的是在程序的運行過程當中監視程序的行爲,分析是否符合預期。一般會藉助一些工具,如GDB和Trace32等。有時候也會藉助一些硬件設備的協助,如仿真器/JTAG,可是準備環境很是困難,並且用起來也很麻煩,除非一些runtime問題須要外不多使用。
####離線調試, Offline debug, 指的是在程序的運行中收集須要的信息,在Bug發生後根據收集到的信息來分析的一種手段。一般也分爲兩種方式,一種是Logging,一種是Memory Dump。
Logging, 日誌或者相關信息的收集,能夠比較清晰的看到代碼的執行過程,對於邏輯問題是一種有效的分析手段,因爲其簡單易操做,也是最爲重要的一種分析手法。
Memory Dump, 翻譯過來叫作內存轉儲,指的是在異常發生的時刻將內存信息所有轉儲到外部存儲器,即將異常現場信息備份下來以供過後分析。是針對CPU執行異常的一種很是有效的分析手段。在Windows平臺,程序異常發生以後能夠選擇啓動調試器來立刻調試。在Linux平臺,程序發生異常以後會轉儲core dump,而此coredump能夠用調試器GDB來進行調試。而內核的異常也能夠進行相似的轉儲。 下面咱們由淺入深剖析各類調試方法,先從logging開始吧。
##2.Kernel空間佈局
在分析KE前,你要了解kernel內存佈局,才知道哪些地址用來作什麼,可能會是什麼問題。
在內核空間中存在以下重要的段:
vmlinux代碼/數據段: 任何程序都有TEXT(可執行代碼),RW(數據段),ZI段(未初始化數據段),kernel也有,對應的是.text,.data,.bss
module區域: kernel能夠支持ko(模塊),所以須要一段空間用於存儲代碼和數據段。
vmalloc區域: kernel除了能夠申請連續物理地址的內存外,還能夠申請不連續的內存(虛擬地址是連續的),能夠避免內存碎片化而申請不到內存。
io map區域: 留給io寄存器映射的區域,有些版本沒有io map區域而是直接用vmalloc區域了。
memmap: kernel是經過page結構體描述內存的,每個頁框都有對應的page結構體,而memmap就是page結構體數組。
還有其餘段小的段沒有列出來,可能根據不一樣的版本而差異。
目前智能機已進入64bit,所以就存在32bit佈局和64bit佈局,下面一一講解。
ARM64可使用多達48bit物理、虛擬地址(擴充成64bit,高位全爲1或0)。對linux kernel來說,目前配置爲39bit的kernel空間。
因爲多達512GB的空間,所以徹底能夠將整個RAM映射進來,0xFFFFFFC000000000以後就是一一映射了,就無所謂high memory了。
vmalloc區域功能除了外設寄存器也直接映射到vmalloc了,就沒有32bit佈局裏的IO map space了。
不一樣版本的kernel,佈局稍有差異:
這是一張示意圖(有些地址可能會有差別)
內核代碼開始的地址是0xC0008000,前面放頁表(起始地址爲0xC0004000),若是支持模塊(*.ko)那麼地址在0xBF000000。
因爲kernel沒辦法將全部內存都映射進來,畢竟kernel本身只佔1G,若是RAM超過1G,就沒法所有映射。怎麼辦呢?只能先映射一部分了,這部分叫low memory。其餘的就按需映射,VMALLOC區域就是用於按需映射的。
ARM的外設寄存器和內存同樣,都統一地址編碼,所以0xF0000000以上的一段空間用於映射外設寄存器,便於操做硬件模塊。
0xFFFF0000是特殊地址,CPU用於存放異常向量表,kernel異常絕大部分都是CPU異常(MMU發出的abort/undef inst.等異常)。
以上是粗略的說明,還需查看代碼獲取完整的分析信息(內核在不停演進,有些部分可能還會變化)
##3.瞭解printk
最初學編程時,你們必定用過printf(),在kernel裏有對應的函數,叫printk()。
最簡單的調試方法就是用printk()印出你想知道的信息了,而前面章節講到oops/panic時,它們就經過printk()將寄存器信息/堆棧信息打印到kernel log buffer裏。
瞭解kernel log對問題的調試將很是重要,這裏有專門的課程介紹,請看:
MediaTek On-Line> Quick Start)> Deep in MTK Turnkey Solution Logging Tools> Kernel log。
能夠看到kernel log能夠經過串口輸出,也能夠在發生oops/panic後將buffer保存成文件打包到db裏,而後拿到串口log或db對kernel進行調試分析了。
一般手機會保留串口測試點,但要抓串口log通常都要拆機,比較麻煩。前面講到能夠將kernel log保存成文件打包在db裏,db是什麼東西?
db是叫AEE(Android Exception Engine,集成在Mediatek手機軟件裏)的模塊檢查到異常並收集異常信息生成的文件,裏面包含調試所需的log等關鍵信息。db有點像飛機的黑匣子。
對於KE來講,db裏包含了以下文件(db能夠經過GAT工具解開,請參考附錄裏的FAQ):
__exp_main.txt:異常類型,調用棧等關鍵信息。
_exp_detail.txt:詳細異常信息
SYS_ANDROID_LOG:android main log
SYS_KERNEL_LOG:kernel log
SYS_LAST_KMSG:上次重啓前的kernel log
SYS_MINI_RDUMP:相似coredump,能夠用gdb/trace32調試
SYS_REBOOT_REASON:重啓時的硬件記錄的信息。
SYS_VERSION_INFO:kernel版本,用於和vmlinux對比,只有匹配的vmlinux才能用於分析這個異常。
SYS_WDT_LOG:看門狗復位信息
以上這些文件通常足以調試KE了,除非一些特別的問題須要其餘信息,好比串口log等等。
##4.ram console 什麼是ram console?
請參考: MediaTek On-Line> Quick Start> Deep in MTK Turnkey Solution Logging Tools
ram console除了保持last kmsg外,還有重要的系統信息,這些很是有助於咱們調試。這些信息保存在ram console的頭部ram_console_buffer裏。
MediaTek On-Line> Quick Start> 深刻分析看門狗框架> 分析方法> HW reboot> HW reboot調試信息
##5.前期異常處理
CPU異常捕獲
對於野指針、跑飛之類的異常會被MMU攔截並報告給CPU,這一系列都是硬件行爲,具體請看:
這類問題比較難定位,也是佔KE比例的大頭,緣由一般是內存被踩壞、指針use atfer free等多種因素,在當時可能不會當即出現異常,而是到使用這塊內存纔有可能崩潰。
分析問題的手段也是多樣化,好比用watch point,MMU protect或加debug code等(請參考附錄FAQ)
軟件異常捕獲
在kernel代碼裏,通常會經過BUG(),BUG_ON(),panic()來攔截超出預期的行爲,這是軟件主動回報異常的功能。
這些問題分析一般有固定的套路,請參考後面的:《實例篇: 案例分析》
在內核調用能夠用來方便標記bug,提供斷言並輸出信息。最經常使用的兩個是BUG()和BUG_ON()。當被調用的時候,它們會引起oops,致使棧的回溯和錯誤信息的打印。使用方式以下
if (condition) BUG(); 或者 : BUG_ON(condition); //只是在BUG基礎上多層封存而已: #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0)
BUG() 的實現採用了埋入未定義指令(0xE7F001F2,記住這個值,log裏看到這個值,你就應該知道是調用了BUG()/BUG_ON()了)的方式
原生的kernel,BUG()是直接調用panic()的:
不過Mediatek修改了BUG()的實現,這樣有更多的調試信息輸出(die()有寄存器等信息輸出)
當你看到以下log時,就應該知道是BUG()/BUG_ON()引發的了!
[ 147.234926]<0>-(0)[122:kworker/u8:3]Unable to handle kernel paging request at virtual address 0000dead
##6.die()流程 通過前面的流程,走到了die()函數,該函數主要輸出便於調試的寄存器信息/堆棧信息等重要資料,咱們經過log分析KE就是分析這些資料,所以要知道整個流程。die() => panic()的大體流程以下:
在學習這些流程時,建議結合代碼和KE的log一塊兒看,你就知道log裏那些信息在代碼哪處打印出來的了。
先從die()入手,看下die()總流程:
若是這個異常是代碼裏調用BUG()/BUG_ON()引發,那麼有額外log說明
絕大部分的關鍵信息是由__die()函數輸出的,流程以下:
開始印出異常類型等信息,看一份kernel log有沒有oops,直接搜索關鍵字Internal error就能夠了:
接下來是module信息,不過咱們不建議使用module,這邊也不打算介紹了。
而後是重要的CPU寄存器信息(32bit的代碼,64bit類同):
有助於咱們分析問題的內存信息,問題極可能就出在裏面。
輸出的信息大體以下:
有時問題能夠直接從調用棧看出來,因而可知調用棧是多麼重要。
輸出的信息大體以下:
PC附近指令
能夠看到PC附近的指令:
輸出的信息大體以下:
分析log
到這裏die()函數就完成了它的使命,將重要信息輸出來了。接下來你要如何調試呢?這個就看我的的功力了,你能夠:
##7.panic()流程
流程走到panic()就裏死(異常重啓)不遠了,關鍵的信息已輸出到kernel log。那麼panic()作了什麼呢?
所以咱們也能夠經過搜索關鍵字Kernel panic查找是否有panic發生。
panic()會調用棧通知鏈上的回調函數同時感興趣的模塊,好比咱們的aee註冊了回調函數,用於保存kernel log/mini dump等關鍵信息,並將其保存到emmc的expdb分區,等等重啓後將其回讀並保存成KE db。
重啓過程DRAM會丟失,所以信息只能保存在flash上了,在分區表裏有一項就是expdb了:
##8.nested panic 有時die()/panic()流程不必定能正常走完,可能走到某一步又發生了異常,則就造成了嵌套,這種狀況,咱們通常不會關注後面的異常,而是關注最開始的那個異常。
爲了不異常嵌套,在發生第2次異常時,咱們就攔截下來,咱們在3個地方用於攔截nested panic:
不過nested panic能參考的信息不多,不像普通的KE那樣豐富。
至此,本篇已結束,若有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!
若有侵權,請聯繫小編,小編對此深感抱歉,屆時小編會刪除文章,當即中止侵權行爲,請您多多包涵。
既然都看到這裏,領兩個紅包在走吧! 如下兩個紅包天天均可以領取
1.支付寶搜索 522398497,或掃碼支付寶紅包海報。
2.微信紅包,微信掃一掃便可領取紅包