<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Kernel Exception 問題分析詳解

          共 6584字,需瀏覽 14分鐘

           ·

          2021-01-05 11:53

          和你一起終身學(xué)習(xí),這里是程序員Android

          經(jīng)典好文推薦,通過閱讀本文,您將收獲以下知識(shí)點(diǎn):

          一、Kernel Exception概述
          二、Kernel空間布局
          三、printk 概述
          四、AEE db log機(jī)制
          五、前期異常處理
          六、die()流程
          七、panic()流程
          八、nested panic

          一、Kernel Exception(KE)概述

          Android OS由3層組成,最底層是Kernel,上面是Native bin/lib,最上層是Java層:


          Android OS 3層結(jié)構(gòu)

          任何軟件都有可能發(fā)生異常,比如野指針,跑飛、死鎖等等。
          當(dāng)異常發(fā)生在kernel層,我們就叫它為KE(kernel exception),同理,發(fā)生在Native就是NE,Java層就是JE。這篇文章僅關(guān)注底層的KE。

          1. KE類別

          kernel有以下2種(oops、panic)崩潰類別

          • 1.oops (類似assert,有機(jī)會(huì)恢復(fù))

          oops是美國(guó)人比較常有的口語。就是有點(diǎn)意外,吃驚,或突然的意思。內(nèi)核行為表現(xiàn)為通知感興趣模塊,打印各種信息,如寄存器值,堆棧信息…
          當(dāng)出現(xiàn)oops時(shí),我們就可以根據(jù)寄存器等信息調(diào)試并解決問題。
          /proc/sys/kernel/panic_on_oops為1時(shí)導(dǎo)致panic。我們默認(rèn)設(shè)置為1,即oops會(huì)發(fā)生panic。

          • 2.Panic – 困惑,恐慌,它表示Linux kernel遇到了一個(gè)不知道該怎么繼續(xù)的情況。內(nèi)核行為表現(xiàn)為通知感興趣模塊,死機(jī)或者重啟。
            在kernel代碼里,有些代碼加了錯(cuò)誤檢查,發(fā)現(xiàn)錯(cuò)誤可能直接調(diào)用了panic(),并輸出信息提供調(diào)試

          2. KE常用調(diào)試方法

          凡是程序就有bug。bug總是出現(xiàn)在預(yù)料之外的地方。據(jù)說世界上第一個(gè)bug是繼電器式計(jì)算機(jī)中飛進(jìn)一只蛾子,倒霉的飛蛾夾在繼電器之間導(dǎo)致了計(jì)算機(jī)故障。由于這個(gè)小蟲子,程序中的錯(cuò)誤就被稱為了bug。

          有Bug就需要Debug,而調(diào)試是一種很個(gè)性化的工作,十個(gè)人可能有十種調(diào)試方法。但從手段上來講,大致可分為兩類,在線調(diào)試 (Online Debug) 和離線調(diào)試 (Offline Debug).

          3.在線調(diào)試

          Online debug, 指的是在程序的運(yùn)行過程中監(jiān)視程序的行為,分析是否符合預(yù)期。通常會(huì)借助一些工具,如GDB和Trace32等。有時(shí)候也會(huì)借助一些硬件設(shè)備的協(xié)助,如仿真器/JTAG,但是準(zhǔn)備環(huán)境非常困難,而且用起來也很麻煩,除非一些runtime問題需要外很少使用。

          4.離線調(diào)試,

          Offline debug, 指的是在程序的運(yùn)行中收集需要的信息,在Bug發(fā)生后根據(jù)收集到的信息來分析的一種手段。通常也分為兩種方式,一種是Logging,一種是Memory Dump。

          Logging
          日志或者相關(guān)信息的收集,可以比較清晰的看到代碼的執(zhí)行過程,對(duì)于邏輯問題是一種有效的分析手段,由于其簡(jiǎn)單易操作,也是最為重要的一種分析手法。

          Memory Dump
          翻譯過來叫做內(nèi)存轉(zhuǎn)儲(chǔ),指的是在異常發(fā)生的時(shí)刻將內(nèi)存信息全部轉(zhuǎn)儲(chǔ)到外部存儲(chǔ)器,即將異?,F(xiàn)場(chǎng)信息備份下來以供事后分析。是針對(duì)CPU執(zhí)行異常的一種非常有效的分析手段。在Windows平臺(tái),程序異常發(fā)生之后可以選擇啟動(dòng)調(diào)試器來馬上調(diào)試。在Linux平臺(tái),程序發(fā)生異常之后會(huì)轉(zhuǎn)儲(chǔ)core dump,而此coredump可以用調(diào)試器GDB來進(jìn)行調(diào)試。而內(nèi)核的異常也可以進(jìn)行類似的轉(zhuǎn)儲(chǔ)。

          二、Kernel空間布局

          在分析KE前,你要了解kernel內(nèi)存布局,才知道哪些地址用來做什么,可能會(huì)是什么問題。

          在內(nèi)核空間中存在如下重要的段:

          1. vmlinux代碼/數(shù)據(jù)段:

          任何程序都有TEXT(可執(zhí)行代碼),RW(數(shù)據(jù)段),ZI段(未初始化數(shù)據(jù)段),kernel也有,對(duì)應(yīng)的是.text,.data,.bss

          2.module區(qū)域:

          kernel可以支持ko(模塊),因此需要一段空間用于存儲(chǔ)代碼和數(shù)據(jù)段。

          3. vmalloc區(qū)域:

          kernel除了可以申請(qǐng)連續(xù)物理地址的內(nèi)存外,還可以申請(qǐng)不連續(xù)的內(nèi)存(虛擬地址是連續(xù)的),可以避免內(nèi)存碎片化而申請(qǐng)不到內(nèi)存。

          4. io map區(qū)域:

          留給io寄存器映射的區(qū)域,有些版本沒有io map區(qū)域而是直接用vmalloc區(qū)域了。

          5.memmap:

          kernel是通過page結(jié)構(gòu)體描述內(nèi)存的,每一個(gè)頁(yè)框都有對(duì)應(yīng)的page結(jié)構(gòu)體,而memmap就是page結(jié)構(gòu)體數(shù)組。

          還有其他段小的段沒有列出來,可能根據(jù)不同的版本而差別。

          6. ARM64bit kernel布局

          目前智能機(jī)已進(jìn)入64bit,因此就存在32bit布局和64bit布局,下面一一講解。

          ARM64可以使用多達(dá)48bit物理、虛擬地址(擴(kuò)充成64bit,高位全為1或0)。對(duì)linux kernel來講,目前配置為39bit的kernel空間。

          由于多達(dá)512GB的空間,因此完全可以將整個(gè)RAM映射進(jìn)來,0xFFFFFFC000000000之后就是一一映射了,就無所謂high memory了。

          vmalloc區(qū)域功能除了外設(shè)寄存器也直接映射到vmalloc了,就沒有32bit布局里的IO map space了。

          不同版本的kernel,布局稍有差別:

          • kernel-3.10

          kernel-3.10

          >= kernel-3.18 && < kernel-4.6

          • = kernel-4.6/N0.MP8 kernel-4.4(patch back)

          >= kernel-4.6/N0.MP8 kernel-4.4(patch back)

          7. ARM32bit kernel布局

          這是一張示意圖(有些地址可能會(huì)有差異)


          ARM32bit kernel布局


          整個(gè)地址空間是4G,kernel被配置為1G,程序占3G。

          內(nèi)核代碼開始的地址是0xC0008000,前面放頁(yè)表(起始地址為0xC0004000),如果支持模塊(*.ko)那么地址在0xBF000000。

          由于kernel沒辦法將所有內(nèi)存都映射進(jìn)來,畢竟kernel自己只占1G,如果RAM超過1G,就無法全部映射。怎么辦呢?只能先映射一部分了,這部分叫l(wèi)ow memory。其他的就按需映射,VMALLOC區(qū)域就是用于按需映射的。

          ARM的外設(shè)寄存器和內(nèi)存一樣,都統(tǒng)一地址編碼,因此0xF0000000以上的一段空間用于映射外設(shè)寄存器,便于操作硬件模塊。

          0xFFFF0000是特殊地址,CPU用于存放異常向量表,kernel異常絕大部分都是CPU異常(MMU發(fā)出的abort/undef inst.等異常)。

          以上是粗略的說明,還需查看代碼獲取完整的分析信息(內(nèi)核在不停演進(jìn),有些部分可能還會(huì)變化)

          三、printk 概述

          1. kernel log

          最初學(xué)編程時(shí),大家一定用過printf(),在kernel里有對(duì)應(yīng)的函數(shù),叫printk()。

          最簡(jiǎn)單的調(diào)試方法就是用printk()印出你想知道的信息了,而前面章節(jié)講到oops/panic時(shí),它們就通過printk()將寄存器信息/堆棧信息打印到kernel log buffer里。

          可以看到kernel log可以通過串口輸出,也可以在發(fā)生oops/panic后將buffer保存成文件打包到db里,然后拿到串口log或db對(duì)kernel進(jìn)行調(diào)試分析了。

          通常手機(jī)會(huì)保留串口測(cè)試點(diǎn),但要抓串口log一般都要拆機(jī),比較麻煩。前面講到可以將kernel log保存成文件打包在db里,db是什么東西?

          四、AEE db log機(jī)制

          db是叫AEE(Android Exception Engine,集成在Mediatek手機(jī)軟件里)的模塊檢查到異常并收集異常信息生成的文件,里面包含調(diào)試所需的log等關(guān)鍵信息。db有點(diǎn)像飛機(jī)的黑匣子。

          對(duì)于KE來說,db里包含了如下文件(db可以通過GAT工具解開,請(qǐng)參考附錄里的FAQ):

          • __exp_main.txt:異常類型,調(diào)用棧等關(guān)鍵信息。

          • _exp_detail.txt:詳細(xì)異常信息

          • SYS_ANDROID_LOG:android main log

          • SYS_KERNEL_LOG:kernel log

          • SYS_LAST_KMSG:上次重啟前的kernel log

          • SYS_MINI_RDUMP:類似coredump,可以用gdb/trace32調(diào)試

          • SYS_REBOOT_REASON:重啟時(shí)的硬件記錄的信息。

          • SYS_VERSION_INFO:kernel版本,用于和vmlinux對(duì)比,只有匹配的vmlinux才能用于分析這個(gè)異常。

          • SYS_WDT_LOG:看門狗復(fù)位信息

          以上這些文件一般足以調(diào)試KE了,除非一些特別的問題需要其他信息,比如串口log等等。

          1. 系統(tǒng)重啟時(shí)關(guān)鍵信息

          ram console除了保持last kmsg外,還有重要的系統(tǒng)信息,這些非常有助于我們調(diào)試。這些信息保存在ram console的頭部ram_console_buffer里。

          ram console


          這個(gè)結(jié)構(gòu)體里的off_linux指向了struct last_reboot_reason,里面保存了重要的信息:


          ram console


          以上重要的信息在重啟后將被打包到db里的SYS_REBOOT_REASON文件里。對(duì)這只文件的各個(gè)欄位解讀請(qǐng)查看:

          五、前期異常處理

          1.CPU異常捕獲

          對(duì)于野指針、跑飛之類的異常會(huì)被MMU攔截并報(bào)告給CPU,這一系列都是硬件行為。

          這類問題比較難定位,也是占KE比例的大頭,原因通常是內(nèi)存被踩壞、指針use atfer free等多種因素,在當(dāng)時(shí)可能不會(huì)立即出現(xiàn)異常,而是到使用這塊內(nèi)存才有可能崩潰。

          2.軟件異常捕獲

          在kernel代碼里,一般會(huì)通過BUG(),BUG_ON(),panic()來攔截超出預(yù)期的行為,這是軟件主動(dòng)回報(bào)異常的功能。

          在內(nèi)核調(diào)用可以用來方便標(biāo)記bug,提供斷言并輸出信息。最常用的兩個(gè)是BUG()和BUG_ON()。當(dāng)被調(diào)用的時(shí)候,它們會(huì)引發(fā)oops,導(dǎo)致棧的回溯和錯(cuò)誤信息的打印。使用方式如下

          if (condition)
          BUG();
          或者 :
          BUG_ON(condition); //只是在BUG基礎(chǔ)上多層封存而已:
          ` #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0)`

          3. 32bit kernel:

          BUG() 的實(shí)現(xiàn)采用了埋入未定義指令(0xE7F001F2,記住這個(gè)值,log里看到這個(gè)值,你就應(yīng)該知道是調(diào)用了BUG()/BUG_ON()了)的方式


          64bit kernel:

          原生的kernel,BUG()是直接調(diào)用panic()的:

          不過Mediatek修改了BUG()的實(shí)現(xiàn),這樣有更多的調(diào)試信息輸出(die()有寄存器等信息輸出)

          MTK 修改

          當(dāng)你看到如下log時(shí),就應(yīng)該知道是BUG()/BUG_ON()引起的了!

          [ 147.234926]<0>-(0)[122:kworker/u8:3]Unable to handle kernel paging request at virtual address 0000dead

          六、die()流程

          經(jīng)過前面的流程,走到了die()函數(shù),該函數(shù)主要輸出便于調(diào)試的寄存器信息/堆棧信息等重要資料,我們通過log分析KE就是分析這些資料,因此要知道整個(gè)流程。die() => panic()的大致流程如下:


          die()流程圖

          在學(xué)習(xí)這些流程時(shí),建議結(jié)合代碼和KE的log一起看,你就知道log里那些信息在代碼哪處打印出來的了。

          1.die()總流程

          先從die()入手,看下die()總流程:


          die()總流程


          走到debug_locks_off()就有l(wèi)og輸出了,如下:


          debug_locks_off() log輸出

          如果這個(gè)異常是代碼里調(diào)用BUG()/BUG_ON()引起,那么有額外log說明



          輸出的log大致如下:


          log

          2. __die()流程

          絕大部分的關(guān)鍵信息是由__die()函數(shù)輸出的,流程如下:


          __die()流程


          異常類型信息

          開始印出異常類型等信息,看一份kernel log有沒有oops,直接搜索關(guān)鍵字Internal error就可以了:


          輸出的信息大致如下:


          log

          3. module信息

          接下來是module信息,不過我們不建議使用module,這邊也不打算介紹了。

          4.CPU寄存器信息

          然后是重要的CPU寄存器信息(32bit的代碼,64bit類同):


          CPU信息


          輸出的信息大致如下:

          log信息

          5.寄存器附近的內(nèi)存

          有助于我們分析問題的內(nèi)存信息,問題很可能就出在里面。

          輸出的信息大致如下:

          6. 調(diào)用棧

          有時(shí)問題可以直接從調(diào)用??闯鰜?,由此可見調(diào)用棧是多么重要。

          輸出的信息大致如下:

          7.PC附近指令

          可以看到PC附近的指令:

          輸出的信息大致如下:

          8.分析log

          到這里die()函數(shù)就完成了它的使命,將重要信息輸出來了。接下來你要如何調(diào)試呢?這個(gè)就看個(gè)人的功力了,你可以:

          • 通過PC指向的函數(shù),用addr2line(后面的GNU tools有介紹)定位到哪只文件的哪一行,大致可以知道發(fā)生了什么,如果無法一下子定位,也可以通過結(jié)合printk()多次觀察KE時(shí)的log排查。如果是由BUG()/BUG_ON()引起的KE,則就可以著手修復(fù)問題了。

          • 查看調(diào)用棧,有些時(shí)候調(diào)用??梢哉f明流程,看看代碼是否有按預(yù)期跑,如果沒有,可以結(jié)合printk()定位問題。

          • 如果你想看函數(shù)參數(shù)或全局變量信息,那么你需要用《進(jìn)階篇: ramdump分析》的知識(shí)調(diào)試了。

          七、panic()流程

          流程走到panic()就里死(異常重啟)不遠(yuǎn)了,關(guān)鍵的信息已輸出到kernel log。那么panic()做了什么呢?

          1. panic()流程

          panic()流程


          panic()有標(biāo)志性的log輸出,大致如下:


          kernel panic 異常

          因此我們也可以通過搜索關(guān)鍵字Kernel panic查找是否有panic發(fā)生。

          2. panic通知鏈

          panic()會(huì)調(diào)用棧通知鏈上的回調(diào)函數(shù)同時(shí)感興趣的模塊,比如我們的aee注冊(cè)了回調(diào)函數(shù),用于保存kernel log/mini dump等關(guān)鍵信息,并將其保存到emmc的expdb分區(qū),等等重啟后將其回讀并保存成KE db。

          3. expdb

          重啟過程DRAM會(huì)丟失,因此信息只能保存在flash上了,在分區(qū)表里有一項(xiàng)就是expdb了:



          流程大致如下(版本不停演進(jìn),可能有很大變化,僅供參考):



          重啟后,aee將回讀aeedb分區(qū)資料并轉(zhuǎn)化為KE db。

          八、nested panic

          有時(shí)die()/panic()流程不一定能正常走完,可能走到某一步又發(fā)生了異常,則就形成了嵌套,這種情況,我們一般不會(huì)關(guān)注后面的異常,而是關(guān)注最開始的那個(gè)異常。

          為了避免異常嵌套,在發(fā)生第2次異常時(shí),我們就攔截下來,我們?cè)?個(gè)地方用于攔截nested panic:

          • do_PrefetchAbort()

          • do_DataAbort()

          • do_undefinstr()


          攔截后不走die()/panic()流程,因?yàn)檫@些流程可能會(huì)再次發(fā)生異常,走我們寫的函數(shù)aee_stop_nested_panic()函數(shù):



          在里面盡量少用kernel模塊,很有可能也會(huì)發(fā)生異常,僅僅將寄存器等重要信息輸出到ram console就等死(死循環(huán)等等看門狗復(fù)位?。_@時(shí)你抓回來的db里的SYS_LAST_KMSG就可以看到這些資料,大致如下(不同版本稍有區(qū)別):



          里面包含了寄存器信息、堆棧信息和調(diào)用棧,我們就可以通過工具(addr2line)還原當(dāng)時(shí)異常的位置。

          不過nested panic能參考的信息很少,不像普通的KE那樣豐富。

          至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章,小編覺得很優(yōu)秀,歡迎點(diǎn)擊閱讀原文,支持原創(chuàng)作者,如有侵權(quán),懇請(qǐng)聯(lián)系小編刪除。同時(shí)感謝您的閱讀,期待您的關(guān)注。

          點(diǎn)個(gè)在看,方便您使用時(shí)快速查找!

          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久精品美女 | 人人操人人超碰 | 亚洲中文字幕不卡 | 蜜桃视频操B网 | 伊人一区二区三区 |