一区二区三区电影_国产伦精品一区二区三区视频免费_亚洲欧美国产精品va在线观看_国产精品一二三四

聯(lián)系我們 - 廣告服務(wù) - 聯(lián)系電話:
您的當(dāng)前位置: > 關(guān)注 > > 正文

什么是Oops?linux之Oops原理及解析

來源:CSDN 時(shí)間:2023-03-10 15:05:19

前言

什么是Oops?從語(yǔ)言學(xué)的角度說,Oops應(yīng)該是一個(gè)擬聲詞。當(dāng)出了點(diǎn)小事故,或者做了比較尷尬的事之后,你可以說"Oops",翻譯成中國(guó)話就叫做“哎呦”。“哎呦,對(duì)不起,對(duì)不起,我真不是故意打碎您的杯子的”。看,Oops就是這個(gè)意思。


(資料圖片)

在Linux內(nèi)核開發(fā)中的Oops是什么呢?其實(shí),它和上面的解釋也沒什么本質(zhì)的差別,只不過說話的主角變成了Linux。當(dāng)某些比較致命的問題出現(xiàn)時(shí),我們的Linux內(nèi)核也會(huì)抱歉的對(duì)我們說:“哎呦(Oops),對(duì)不起,我把事情搞砸了”。Linux內(nèi)核在發(fā)生kernel panic時(shí)會(huì)打印出Oops信息,把目前的寄存器狀態(tài)、堆棧內(nèi)容、以及完整的Call trace都show給我們看,這樣就可以幫助我們定位錯(cuò)誤。

1. Oops的產(chǎn)生

挑選一位隨機(jī)幸運(yùn)內(nèi)核,insmod oops.ko產(chǎn)生如下標(biāo)準(zhǔn)打印,產(chǎn)生了一段如下圖打印: Oops 信息包含以下幾部分內(nèi)容:

一段文本描述信息。 比如類似“Unable to handle kernel NULL pointer dereference at virtual address 00000000”的信息,它說明了發(fā)生的是哪類錯(cuò)誤Oops 信息的序號(hào) 比如是第 1 次、第 2 次等。這些信息與下面類似,中括號(hào)內(nèi)的數(shù)據(jù)表示序號(hào)。 Internal error: Oops: 817 [#1] PREEMPT SMP ARM內(nèi)核中加載的模塊名稱(也可能沒有),以下面字樣開頭 Modules linked in:xxx發(fā)生錯(cuò)誤的 CPU 的序號(hào),對(duì)于單處理器的系統(tǒng),序號(hào)為 0 CPU: 1 PID: 1412 Comm: insmod Tainted: P O 4.9.37 #1 下圖是關(guān)于Tainted(污染)后面字段具體含義(可以注意到,第3部分加載的模塊后面有些模塊帶有(PO)等字樣,實(shí)際上就是和這里是相同的含義),源碼路徑: \kernel\panic.c

/** *  print_tainted - return a string to represent the kernel taint state. * *  "P" - Proprietary module has been loaded.(沒有模塊MODULE_LICENSE或者帶有insmod認(rèn)為是與GPL不相容的的MODULE_LICENSE的模塊被認(rèn)定是專有的) *  "F" - Module has been forcibly loaded.(通過“insmod -f”被強(qiáng)制裝載的模塊) *  "S" - SMP with CPUs not designed for SMP.(oops發(fā)生在SMP內(nèi)核中,運(yùn)行于沒有證明安全運(yùn)行多處理器的硬件。 當(dāng)前這種情況僅限于幾種不支持SMP的處理器) *  "R" - User forced a module unload.(rmmod –f強(qiáng)制卸載) *  "M" - System experienced a machine check exception.(機(jī)器檢查異常) *  "B" - System has hit bad_page.(頁(yè)釋放函數(shù)發(fā)現(xiàn)了一個(gè)錯(cuò)誤的頁(yè)引用或者一些非預(yù)期的頁(yè)標(biāo)志) *  "U" - Userspace-defined naughtiness. *  "D" - Kernel has oopsed before.(內(nèi)核以前已經(jīng)OOPS過了) *  "A" - ACPI table overridden. *  "W" - Taint on warning. *  "C" - modules from drivers/staging are loaded. *  "I" - Working around severe firmware bug. *  "O" - Out-of-tree module has been loaded.(樹外模塊加載) *  "E" - Unsigned module has been loaded.(未簽名模塊加載) *  "L" - A soft lockup has previously occurred.(發(fā)生過軟鎖定) *  "K" - Kernel has been live patched. * *  The string is overwritten by the next call to print_tainted().*/

發(fā)生錯(cuò)誤時(shí) CPU 的各個(gè)寄存器值

當(dāng)前進(jìn)程的名字及進(jìn)程 ID Process insmod (pid: 1412, stack limit = 0x9eb8e210) 這并不是說發(fā)生錯(cuò)誤的是這個(gè)進(jìn)程,而是表示發(fā)生錯(cuò)誤時(shí),當(dāng)前進(jìn)程是它。錯(cuò)誤可能發(fā)生在內(nèi)核代碼、驅(qū)動(dòng)程序,也可能就是這個(gè)進(jìn)程的錯(cuò)誤

棧信息

棧回溯信息,可以從中看出函數(shù)調(diào)用關(guān)系

出錯(cuò)指令附近的指令的機(jī)器碼(出錯(cuò)指令在小括號(hào)里),也有可能沒有 關(guān)于錯(cuò)誤碼,如下為armv7架構(gòu)定義的FSR(錯(cuò)誤狀態(tài)寄存器,分為DFSR和IFSR,根據(jù)不同處理器使用不同的FSR)的錯(cuò)誤代碼,實(shí)際上源碼中,oops的錯(cuò)誤碼就是通過匯編獲取的寄存器值,如下為手冊(cè)中IFSR獲取錯(cuò)誤碼的方法(DFSR同樣) 如下為DFSR結(jié)構(gòu)(IFSR關(guān)于FS碼是相同的) 對(duì)于上面0x817的錯(cuò)誤碼解釋為:寫入內(nèi)存時(shí)報(bào)錯(cuò),錯(cuò)誤原因是:Translation fault 也就是頁(yè)表轉(zhuǎn)換出現(xiàn)問題

從上可以大致知道Oops 可以看成是內(nèi)核級(jí)的Segmentation Fault。應(yīng)用程序如果進(jìn)行了非法內(nèi)存訪問或執(zhí)行了非法指令,會(huì)得到Segfault信號(hào),一般的行為是coredump,應(yīng)用程序也可以自己截獲 Segfault信號(hào),自行處理。如果內(nèi)核自己犯了這樣的錯(cuò)誤,則會(huì)打出Oops信息,也就是說Oops一般是由于內(nèi)存原因?qū)е碌摹?/p>

2.源碼分析

2.1 溯源過程C部分

直接通過打印找到產(chǎn)生的對(duì)應(yīng)代碼,oops的打印為__die函數(shù)(\arch\arm\kernel\traps.c)。 第265行打印就是oops信息,后邊三個(gè)字符串來自三個(gè)編譯開關(guān),分別表示允許搶占,支持對(duì)稱多處理器,采用ARM指令。 第269行忽略,備注也寫的陷阱和錯(cuò)誤數(shù)在ARM上幾乎沒有意義 第273行,打印加載的模組信息 第274行,打印寄存器信息(CPU號(hào),任務(wù)名,污染原因,PC,LR(鏈接寄存器,保存函數(shù)返回的地址),SP(棧指針),IP,FP(棧頂指針),R10-R0等寄存器值,CPU的Flags (Flags后邊大寫字母表示相應(yīng)的位為1,小寫表示為0)【指NZCV這幾個(gè)狀態(tài)寄存器】) 第275行,打印了當(dāng)前出錯(cuò)的進(jìn)程名,pid值,和堆棧限制,在ARM平臺(tái)棧的增長(zhǎng)方向是從高地址向低地址,sp指針是當(dāng)前的棧頂,stack limit打印的是棧的限制,表示最小地址是多少,如果SP比這個(gè)值小,那么表示棧溢出了。 第279行之后打印堆棧和函數(shù)的調(diào)用回溯。 如下為pt_reg的定義 進(jìn)一步溯源,深入探究oops源碼,調(diào)用__die的函數(shù)為die(\arch\arm\kernel\traps.c): 第344行:調(diào)用oops_begin,在這個(gè)地方關(guān)閉本CPU中斷,獲取CPUID, 對(duì)oops上鎖.如果同一個(gè)CPU已經(jīng)在處理die了,那么就是嵌套die,不需要再獲取鎖了 第347行獲取cpu是不是處于用戶模式,如果不是用戶模式并且report_bug的返回值如果不等于BUG_TRAP_TYPE_NONE打印會(huì)變?yōu)椤監(jiān)ops - BUG”,如果是這種情況,就比較嚴(yán)重,一般會(huì)打印如下 第355行:die的最后是調(diào)用oops_end,這里邊的操作很多是和oops_begin相對(duì)應(yīng)的,然后調(diào)用oops_exit,該函數(shù)會(huì)打印trace結(jié)束標(biāo)志,調(diào)用kmsg_dump(KMSG_DUMP_OOPS)。但是如果oops產(chǎn)生在中斷過程中,oops_end函數(shù)會(huì)直接產(chǎn)生panic或者如果配置宏CONFIG_PANIC_ON_OOPS_VALUE的值為1(panic_on_oops),則也會(huì)直接panic 通常情況由于空指針或者錯(cuò)誤的虛擬地址導(dǎo)致的oops,函數(shù)為:__do_kernel_fault。源碼位于\arch\arm\mm\fault.c 第138行:嘗試進(jìn)行異常修復(fù),這里有一套很復(fù)雜的內(nèi)存異常回復(fù)處理,不深入,失敗后會(huì)繼續(xù)向下執(zhí)行 第152行:執(zhí)行完前面的die操作后,直接干掉出問題的進(jìn)程 繼續(xù)溯源,可以找到在\arch\arm\mm\fault.c中發(fā)現(xiàn)兩個(gè)函數(shù)都有調(diào)用__do_kernel_fault。分別是do_bad_area和do_page_fault,這里先不具體分析其源碼,繼續(xù)溯源

調(diào)用do_bad_area函數(shù)的有如下函數(shù):do_alignment,do_translation_fault,do_sect_fault 調(diào)用do_page_fault函數(shù)的有:do_translation_fault 而最終匯總成如下該數(shù)組 最終由函數(shù)do_DataAbort調(diào)用

2.2溯源過程匯編部分

以下部分為匯編過程,并且涉及到部分內(nèi)存申請(qǐng)流程。 當(dāng)內(nèi)核申請(qǐng)內(nèi)存時(shí),虛擬內(nèi)存映射到實(shí)際物理內(nèi)存,系統(tǒng)自動(dòng)觸發(fā)缺頁(yè)中斷,缺頁(yè)中斷機(jī)制根據(jù)所訪問頁(yè)面的狀態(tài)來分配物理頁(yè)面并建立映射關(guān)系。觸發(fā)缺頁(yè)中斷的情況有兩種 , 第一,程序訪問了非法地址(我們主要分析的);第二,訪問的地址是合法的,但是該地址還未分配物理頁(yè)框。

當(dāng)程序訪問的虛擬頁(yè)面沒有進(jìn)行過物理頁(yè)面的映射時(shí),會(huì)通過發(fā)生缺頁(yè)中斷來分配和映射物理頁(yè)面。發(fā)生缺頁(yè)中斷時(shí),處理器會(huì)跳轉(zhuǎn)到異常向量表 Data abort 向量中開始執(zhí)行缺頁(yè)中斷的匯編階段,這個(gè)階段與處理器架構(gòu)緊密聯(lián)系,例如對(duì)于ARMv7-A架構(gòu),匯編處理流程為:__vectors_start -> vector_dabt -> __dabt_usr/__dabt_svc -> dabt_helper -> v7_early_abort 如下為中斷向量表,源碼位于:arch\arm\kernel\entry-armv.S

以svc為例,會(huì)調(diào)用dabt_helper 最后dabt_helper會(huì)bl到CPU_DABORT_HANDLER這個(gè)函數(shù)中,根據(jù)使用的架構(gòu)不同,該函數(shù)使用的可能會(huì)不相同 如下使用的v7架構(gòu),使用函數(shù)為v7_early_abort v7_early_abort源碼位于:\arch\arm\mm\abort-ev7.S 這個(gè)函數(shù)實(shí)際上就是實(shí)現(xiàn)了從arm中獲取FSR(錯(cuò)誤狀態(tài)寄存器)和FAR(錯(cuò)誤地址寄存器,也就是要映射的地址),r0=地址,ri=錯(cuò)誤碼,r2=pt_regs(在對(duì)應(yīng)的__dabt_svc中已經(jīng)獲取)

2.3 do_DataAbort的函數(shù)注冊(cè)

從2.1和2.2分別對(duì)C部分和匯編部分進(jìn)行簡(jiǎn)單的分析,下面來看一下do_DataAbort是如何識(shí)別不同的頁(yè)面分配的 如下函數(shù)為對(duì)fsr_info數(shù)組的注冊(cè)函數(shù),因?yàn)閐o_DataAbort實(shí)際上就是根據(jù)fsr_info這個(gè)數(shù)組進(jìn)行函數(shù)調(diào)用的 全局搜索hook_fault_code可以發(fā)現(xiàn)如下:實(shí)際上也就是對(duì)fsr_info數(shù)組補(bǔ)齊了段錯(cuò)誤的函數(shù)回調(diào) 也就是說,接下來只要對(duì)著fsr_info數(shù)組這個(gè)數(shù)組進(jìn)行分析,就能知道oops的全部產(chǎn)生原因了

2.4 總流程圖

3.oops產(chǎn)生原因分析

如下表,為匯總的frs_info,包括對(duì)齊,頁(yè)表轉(zhuǎn)換,頁(yè),段權(quán)限 我們繼續(xù)對(duì)源碼進(jìn)行分析。如下圖為do_DataAbort函數(shù)中對(duì)fsr寄存器讀取數(shù)據(jù)的處理。也就是對(duì)fsr寄存器取fs,因?yàn)閒s分布為fs[3:0]位于bit3:0,fs[4]位于bit10,所以處理后對(duì)fsr_info進(jìn)行直接查表即可 第547行如上分析 第550行執(zhí)行表中對(duì)應(yīng)函數(shù),只有do_bad會(huì)返回1,其余函數(shù)皆返回0. 第561行,執(zhí)行由于do_bad對(duì)應(yīng)的fsr導(dǎo)致的錯(cuò)誤,arm_notify_die中判斷當(dāng)前CPU是否處于用戶態(tài),如果不是則執(zhí)行die

3.1 do_translation_fault

static int __kprobesdo_translation_fault(unsigned long addr, unsigned int fsr,             struct pt_regs *regs){/* …… */#define TASK_SIZE       (UL(CONFIG_PAGE_OFFSET) - UL(SZ_16M))/* 如果是用戶空間的地址,用do_page_fault處理 */    if (addr < TASK_SIZE)         return do_page_fault(addr, fsr, regs);/* 至此的地址都是內(nèi)核空間,如果regs顯式為用戶空間。說明兩者沖突,進(jìn)入bad_area */    if (user_mode(regs))          goto bad_area;    /* 中間略過一部分代碼 */    return 0;bad_area:    do_bad_area(addr, fsr, regs);    return 0;}

如下圖為do_bad_area源碼 第195判斷是否處于用戶模式,如果不是就Oops

3.2 do_page_fault

直接看下圖流程即可,不進(jìn)行具體分析,總之在處于非用戶模式下缺頁(yè)且處理出現(xiàn)錯(cuò)誤,會(huì)執(zhí)行__do_kernel_fault。題外:do_page_fault完成了真正的物理頁(yè)面分配工作,另外棧擴(kuò)展、mmap的支持等也都在這里。對(duì)于物理頁(yè)面的分配,會(huì)調(diào)用到do_anonymous_page->。。。-> __rmqueue,__rmqueue中實(shí)現(xiàn)了物理頁(yè)面分配的伙伴算法

3.3 do_sect_fault

源碼如下,一旦出錯(cuò),直接do_bad_area

3.4 bad_mode

bad_mode(中斷異常)也可以導(dǎo)致die并且最終直接panic,源碼如下 引用流程:xx中斷異常 -> xx_invalid -> common_invalid -> bad_mode

3.5總結(jié)

4. Oops的解決思路

1.先看是否由BUG/BUG_ON引起,如果是BUG引起的,則直接看產(chǎn)生條件,這種具體情況具體分析 2.如果不是,則可以根據(jù)Oops現(xiàn)場(chǎng)打印進(jìn)一步分析。我們繼續(xù)看在第一章中產(chǎn)生的Oops信息。如下圖。首先直接看到了錯(cuò)誤原因,空指針引起的,然后看到錯(cuò)誤碼0x817:即寫內(nèi)存時(shí),缺頁(yè),映射失敗。接下來直接看PC指針就行了 3.PC is at myoops_test_init +0xc/0x14 確定了出問題的函數(shù)位置,然后看一下出錯(cuò)進(jìn)程是insmod 也就是說就是在insmod oops.ko驅(qū)動(dòng)時(shí)后出的問題。接下來需要對(duì)該進(jìn)程增加調(diào)試信息,以供我們能夠找到出錯(cuò)位置 4.增加 –g編譯選項(xiàng),見下圖。如果file帶有stripped,說明makefile或者腳本中存在選項(xiàng),將其暫時(shí)屏蔽即可。 5.對(duì)于內(nèi)核增加調(diào)試信息,直接搜索debug_info,將其打開即可 6.使用對(duì)應(yīng)工具鏈的gbd定位問題源碼所在位置 查看代碼(默認(rèn)顯示10行) l/list 例:l *(函數(shù)名+偏移) 然后根據(jù)定位到的對(duì)應(yīng)源碼上下文繼續(xù)查找問題即可 7.使用addr2line定位內(nèi)核中問題源碼。如下圖為之前出現(xiàn)問題的一串oops打印。可以發(fā)現(xiàn)問題出現(xiàn)在dwc2_queue_transaction。我們直接找到內(nèi)核對(duì)應(yīng)的符號(hào)表,找到該函數(shù)對(duì)應(yīng)內(nèi)核的所在位置 確定其偏移為 0x8054ced8+0xf8=0x8054CFD0 使用命令 xxx(工具鏈)-addr2line -C -f -e vmlinux 8054CFD0,確定到了問題所在行數(shù)2805 附addr2line參數(shù)說明: (1).-a:在函數(shù)名、文件名和行號(hào)信息之前,以十六進(jìn)制形式顯示地址。 (2).-b:指定目標(biāo)文件的格式為bfdname。 (3).-C:將低級(jí)別的符號(hào)名解碼為用戶級(jí)別的名字。 (4).-e:指定需要轉(zhuǎn)換地址的可執(zhí)行文件名,默認(rèn)文件是a.out。 (5).-f:在顯示文件名、行號(hào)信息的同時(shí)顯示函數(shù)名。 (6).-s:僅顯示每個(gè)文件名(the base of each file name)去除目錄名。 (7).-i:如果需要轉(zhuǎn)換的地址是一個(gè)內(nèi)聯(lián)函數(shù),則還將打印返回第一個(gè)非內(nèi)聯(lián)函數(shù)的信息。 (8).-j:讀取指定section的偏移而不是絕對(duì)地址。 (9).-p:使打印更加人性化:每個(gè)地址(location)的信息都打印在一行上。 (10).-r:啟用或禁用遞歸量限制。 (11).–help:打印幫助信息。 (12).–version:打印版本號(hào)。

進(jìn)階:反匯編方案,適合高手 使用命令:arm-seev100-linux-gnueabihf-objdump -d oops.ko > test.s 然后直接生擼匯編,從PC可以看出出錯(cuò)在0xc。此時(shí)r3=0,r2=1。Str即將r2中數(shù)據(jù)給到r3指向的內(nèi)存即0。而0這個(gè)內(nèi)存地址很明顯是非法的

責(zé)任編輯:

標(biāo)簽:

相關(guān)推薦:

精彩放送:

新聞聚焦
Top 主站蜘蛛池模板: SHOW| 金坛市| 恩平市| 安顺市| 屏东市| 镇赉县| 开封市| 色达县| 松滋市| 南充市| 福清市| 莱州市| 全椒县| 乌海市| 浮山县| 钦州市| 休宁县| 南部县| 安多县| 青冈县| 边坝县| 山东| 崇明县| 青浦区| 永仁县| 包头市| 满洲里市| 修文县| 建水县| 石渠县| 太和县| 天镇县| 浦东新区| 中卫市| 琼结县| 民县| 都兰县| 延安市| 涟水县| 永新县| 新宾|