精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

內核oops錯誤原因及處理方法

Linux閱碼場 ? 來源:Linux閱碼場 ? 作者: lccz ? 2022-05-12 16:20 ? 次閱讀

個人簡介

lccz(龍城赤子),資深嵌入式開發者,愛好Linux內核相關技術。個人CSDN博客:wwwyue1985。

最近在調試設備時,遇到了一個偶發的開機死機問題。通過查看輸出日志,發現內核報告了oops錯誤,如下所示(中間省略了部分日志,以......代替)。

Unable to handle kernel NULL pointer dereference at virtual address 0000000cpgd = cdd90000[0000000c] *pgd=8df4d831, *pte=00000000, *ppte=00000000Internal error: Oops: 17 [#1] SMP ARMCPU: 0 PID: 206 Comm: mount Tainted: P           O   3.18.20 #4task: ced40e40 ti: cdf7c000 task.ti: cdf7c000PC is at exfat_fill_super+0xc8/0x4cc [exfat]LR is at exfat_fill_super+0x48/0x4cc [exfat]pc : []    lr : []    psr: a0080013sp : cdf7de48  ip : ffffffff  fp : c0744a30r10: 00000001  r9 : bf652dac  r8 : 00008000r7 : cdf80000  r6 : cf302000  r5 : cdf85000  r4 : cdf41000r3 : 00000000  r2 : cdf85104  r1 : 00000003  r0 : 000001b5Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment userControl: 10c5387d  Table: 8dd9006a  DAC: 00000015
SP: 0xcdf7ddc8:ddc8  cfa70880 fffffffc 0000000b cf17f800 cf4ea000 cf17f600 00000000 cfdee780dde8  bf64b670 a0080013 ffffffff cdf7de34 00008000 c0012e18 000001b5 00000003......Process mount (pid: 206, stack limit = 0xcdf7c238)Stack: (0xcdf7de48 to 0xcdf7e000)de40:                   00000001 cdf41000 cdf7deb0 cf17f60c 00000001 00008000de60: cdf41000 cdf7c038 c0744a30 c0264164 bf652db4 cdf7de84 3b9aca00 00000004de80: cf4ea6c0 00000083 cf4ea734 cf302000 cf4ea6c0 00000083 00008000 cdf41000......dfc0: 01197040 01197040 be9fff49 00000015 be9fff31 00008000 00000000 00000000dfe0: b6e3d2e0 be9ffaf8 0007ebec b6e3d2f0 60080010 be9fff49 00000000 00000000[] (exfat_fill_super [exfat]) from [] (mount_bdev+0x168/0x190)[] (mount_bdev) from [] (exfat_fs_mount+0x18/0x20 [exfat])[] (exfat_fs_mount [exfat]) from [] (mount_fs+0x14/0xcc)[] (mount_fs) from [] (vfs_kern_mount+0x4c/0x104)[] (vfs_kern_mount) from [] (do_mount+0x194/0xb54)[] (do_mount) from [] (SyS_mount+0x74/0xa0)[] (SyS_mount) from [] (ret_fast_syscall+0x0/0x38)Code: e5851108 e3a01003 e593300c e5933308 (e1d330bc)

從上述日志信息中,初步可以看出,在掛載exfat格式文件系統的存儲卡時,內核出現了空指針訪問問題,最終導致內核奔潰并輸出oops。因為之前沒有遇到過這個問題,且最近硬件更換了讀卡器,存儲卡也更新換代了,從之前的100MB/s換到了120MB/s,所以,最初懷疑問題可能是因為更換讀卡器或(和)存儲卡導致的。但是,硬件和卡的變更到底是如何影響并導致上述oops錯誤的,這其中的細節并不清楚。好在堆棧信息比較明確,異常時,PC指針指向了這個位置:exfat_fill_super+0xc8/0x4cc PC is at exfat_fill_super+0xc8/0x4cc [exfat])。那我們就順藤摸瓜,看看這個位置對應的代碼是什么。

首先,在工程中搜索exfat_fill_super這個函數,了解其位置和關聯模塊。一番操作下來,發現這個函數在第三方開源庫exfat中。這個庫提供了exfat文件系統掛載的支持,并被編譯為ko庫文件,在系統啟動時insmod到系統中。

其次,我們看看問題日志中,PC指針指向的代碼具體是哪一行?因為日志中只提示在exfat_fill_super這個函數的0xc8偏移處,為了準確找到這個位置,我們需要借助gdb,如下所示:

(gdb) l exfat_fill_super                    sb->s_d_op = &exfat_dentry_ops;    }   #endif    static int exfat_fill_super(struct super_block *sb, void *data, int silent)    {            struct inode *root_inode = NULL;            struct exfat_sb_info *sbi;            int debug, ret;           long error;(gdb) l *exfat_fill_super+0xc8 0x9670 is at ./exfat-nofuse-master/exfat_super.c:2301.            int option;            char *iocharset;
            opts->fs_uid = current_uid();            opts->fs_gid = current_gid();            opts->fs_fmask = opts->fs_dmask = current->fs->umask;            opts->allow_utime = (unsigned short) -1;opts->codepage=exfat_default_codepage;            opts->iocharset = exfat_default_iocharset;            opts->casesensitive = 0;

可以看到,gdb告訴我們,0xc8偏移在2301這一行(也告訴我們對應的匯編0x9670處,后面會用到):

2301opts->fs_fmask=opts->fs_dmask=current->fs->umask;

但是,比較煩人的是,這行代碼是連續賦值,并且都使用到了指針,所以并不能一下就確定問題到底在那一個賦值上產生。不過,不著急,我們先看看這行代碼做了什么。按照C語言的規則,連續賦值是從右到左執行,所以先執行的應該是:

opts->fs_dmask = current->fs->umask;

執行這行代碼時,需要先確定current->fs,再確定fs->umask,最后,將結果給opts->fs_dmask。所以,就這一處賦值而言,就有三個可能的疑點。

先看第一個current->fs。這里current是一個宏,用于獲取當前線程的任務結構體(這里又隱藏一個指針)。

#define get_current() (current_thread_info()->task)#define current get_current()

對于當前arm平臺,線程信息是通過堆棧寄存器獲取的。

static inline struct thread_info *current_thread_info(void){   register unsigned long sp asm ("sp");   return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));}

從上面代碼,進一步的得知,線程信息是堆棧寄存器通過位運算獲得的。這里的THREAD_SIZE定義如下:

#define THREAD_SIZE_ORDER   1#define THREAD_SIZE       (PAGE_SIZE << THREAD_SIZE_ORDER)

這是一個跟頁面大小相關的量。在當前系統中,PAGE_SIZE4KB大小,所以THREAD_SIZE8KB大小,也即0x2000,一共14位。減去1,就是1FFFF,取反就是0b0000(第一個01bit,其余為4bit),然后參與“與”運算。這一連串的運算,總結為一句話,就是將給定的棧指針地址的低13位與0進行與運算,即將棧指針低13位清零。

這就是說內核線程結構體是在當前棧8KB對齊的低地址處。這是內核在設計時故意安排的,可以提高查找效率。我們來看這個指針獲取是否存在空指針訪問的問題:

current_thread_info()->task

回到最開始的日志中,部分信息如下

task: ced40e40 ti: cdf7c000 task.ti: cdf7c000PC is at exfat_fill_super+0xc8/0x4cc [exfat]LR is at exfat_fill_super+0x48/0x4cc [exfat]pc : []    lr : []    psr: a0080013sp : cdf7de48  ip : ffffffff  fp : c0744a30

其中,sp在cdf7de48,所以thread_info的位置應該是cdf7c000,從上面的日志中也可以看到ticdf7c000,所以這個位置不會是空指針的位置。

這里的task是thread_info結構體的一個子域,如下

struct thread_info {   unsigned long     flags;    /* low level flags */   int          preempt_count; /* 0 => preemptable, <0 => bug */   mm_segment_t      addr_limit;    /* address limit */   struct task_struct *task;    /* main task structure */   struct exec_domain *exec_domain;  /* execution domain */

那么,task有沒有可能是一個空指針呢?上面oosp的日志也給出了,

task: ced40e40,所以,task也不為空。

這樣,current就指代了這里的task,一個不為空的地址。所以我們再看

current->fs

這里的fstask_struct結構體的一個子域struct fs_struct *fs;(部分字段省略)

struct task_struct {   volatile long state;   /* -1 unrunnable, 0 runnable, >0 stopped */   void *stack;   atomic_t usage;   unsigned int flags;    /* per process flags, defined below */   unsigned int ptrace;   ....../* CPU-specific state of this task */   struct thread_struct thread;/* filesystem information */   struct fs_struct *fs;/* open file information */   struct files_struct *files;/* namespaces */   struct nsproxy *nsproxy;   ......#ifdef CONFIG_PERF_EVENTS   struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];   struct mutex perf_event_mutex;   struct list_head perf_event_list;#endif#ifdef CONFIG_DEBUG_PREEMPT   unsigned long preempt_disable_ip;#endif   ......};

從上面的定義,可以看到,它是跟文件系統相關的一個結構體。分析到這里時,考慮到問題所在函數為exfat_fill_super,看名字似乎是填充文件系統超級快的操作,加之測試部門反饋,問題出現后,格式化存儲卡就會恢復,所以我懷疑,會不會是因為更換讀卡器和存儲卡,導致讀取超級塊信息有誤,才使得文件系統相關訪問出現空指針,并報告oops

為了驗證這一想法,我將上述連續賦值的這行代碼(即前述問題所在的2301行代碼)進行拆分,分為多條語句,然后在每一個指針使用點添加日志,以便在問題出現時,輸出問題到底在哪個指針上。另外,為了盡可能保留環境,在問題出現后,采取軟重啟設備,并通過重新配置uboot參數,讓內核通過nfs掛載根文件系統,這樣就可以替換之前的ko庫文件來測試了。

奇怪的是,每次替換后,問題就不出現了。這一現象似乎打破了之前的猜測,感覺問題又偏向軟件一側了。在這種取巧的打印方案沒有取得效果后,我決定直接分析匯編代碼,看看問題出現時,空指針到底落在了哪里。反匯編目標文件,結合gdb報告的位置(前面已提到)和oops中報告的指令內容

Code: e5851108 e3a01003 e593300c e5933308 (e1d330bc)

確定問題就在下面匯編中9670這一行:

9660:  e5851108   str    r1, [r5, #264] ; 0x1089664:  e3a01003   mov    r1, #39668:  e593300c   ldr    r3, [r3, #12]966c:  e5933308   ldr    r3, [r3, #776] ; 0x308
9670:  e1d330bc   ldrh   r3, [r3, #12]
9674:  e1c2c0bc   strh   ip, [r2, #12]9678:  e1c200be   strh   r0, [r2, #14]967c:  e1c230ba   strh   r3, [r2, #10]9680:  e1c230b8   strh   r3, [r2, #8]

這是一條加載指令,即將r3寄存器指示的內存地址,偏移12位置后的兩個字節,加載到r3寄存器中。這里r3指示的內存地址是什么呢?根據oops中給出的信息,是00000000,加上12,就是地址0000000C,所以oops報告

Unable to handle kernel NULL pointer dereference at virtual address 0000000c

結合C代碼及問題點前后的匯編代碼,直觀感覺,這里的12應該是一個結構體中某一個子域的偏移,找到這個偏移對應的域,那么就可以確定是在哪一個賦值上出現了空指針。

回到C代碼,問題代碼行前后有好幾個結構體使用,為了快速確定偏移,我選擇參考內核container_of宏,定義一個找偏移的宏

#define my_offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)

通過這個宏,快速找到每一個元素在結構體中的偏移。當然,也可以通過看代碼來確定,但是沒有這種方法來得快。就是通過這個操作,引出了問題的最終原因。我們繼續。

添加獲取偏移的日志后,得到的相關偏移信息如下:

task_offset=12, fs_offset=904, umask_offset=12, fs_fmask=8, fs_dmask=10 

這里的12、90412、、810似乎跟匯編有隱隱的對應關系。但是這里的904776沒有什么關系。我決定再看看添加日志后目標文件的反匯編代碼,如下:

97b8:  e3a0b000   mov    fp, #097bc:  e3a0207b   mov    r2, #123   ; 0x7b97c0:  e3000000   movw   r0, #097c4:  e300a000   movw   sl, #097c8:  e5933388   ldr    r3, [r3, #904] ; 0x38897cc:  e3400000   movt   r0, #097d0:  e340a000   movt   sl, #097d4:  e1d330bc   ldrh   r3, [r3, #12]97d8:  e1c930ba   strh   r3, [r9, #10]97dc:  e1c930b8   strh   r3, [r9, #8]97e0:  e5cb2000   strb   r2, [fp]97e4:  e595300c   ldr    r3, [r5, #12]

因為此時代碼被修改,所以只能大概判斷之前問題所在的匯編范圍。從上面可以看出,這一次匯編里的數值跟打印出來的偏移對應上了。根據這次的偏移,結合匯編,基本可以確定,之前出問題的匯編對應的就是C代碼中的fs->umask這個語句

因為fs為空,所以再去獲取umask,就會報空指針異常。那問題來了,為啥fs會變空呢?有經驗的讀者,此時可能已經猜出問題的原因了。

我們看到,之前代碼反匯編后,fs的偏移是776,添加日志重新編譯后,反匯編成了904。雖然添加日志,導致代碼被修改,但是并不影響這個偏移,所以,這里的fs偏移可能就是問題所在。對于偏移變化,我考慮了三個因素,分別進行了驗證:

1 是ko庫文件因為flash壞塊或其他原因,導致二進制文件部分bit翻轉。實際驗證后,排除了這個原因。

2 是ko庫針對不同平臺編譯的,放置錯誤導致。實際驗證后,這個原因也排除了。

3 是當前添加日志后所編譯ko庫,其依賴的內核配置跟之前編譯ko庫依賴內核配置相比有更新,也就是內核配置發生了變化(內核版本本身是一致的)。這種情況最常見的就是對內核進行了menuconfig操作。檢查fs所在的task_struct結構體,發現其中有很多ifdef不過都不曾配置過,倒是有一個perf相關的CONFIG_PERF_EVENTS,由于調測性能所需,是后來新配置的。但是這個配置選項在fs結構體后面(見前面task_struct結構體),按理說是不影響fs在整個結構體中偏移的。考慮到task_struct結構體里面包含了很多子結構體,不排除上述perf配置影響了fs前面的某些子結構體而導致fs自己的偏移發生變化。

說了這么多,那么到底是不是呢,驗證一下就知道了。關閉上述選項,重新編譯內核,之后再編譯exfat,查看匯編,發現偏移回到了776Yes,問題就是這里了。最終原因就是內核更新了,但是ko沒有更新,導致二者不匹配(舊的ko庫從776偏移找fs,但是在新內核中,fs的偏移已經成了904),產生了潛在的問題。

問題原因最終是找到了,但是問題產生的過程,其實更值得引起注意:ko庫因為也是在內核空間運行,所以需要跟kernel版本匹配起來,做版本一致管理。進一步的,不僅僅是嵌入式領域,桌面端也同樣的,如果系統中加載了ko庫,當更新kernel時,就需要考慮對ko庫的影響。二者需要統一起來看待和管理。

原文標題:一個內核oops問題的分析及解決

文章出處:【微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 嵌入式
    +關注

    關注

    5072

    文章

    19026

    瀏覽量

    303523
  • 內核
    +關注

    關注

    3

    文章

    1366

    瀏覽量

    40236
  • Oops
    +關注

    關注

    0

    文章

    4

    瀏覽量

    3305

原文標題:一個內核oops問題的分析及解決

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    WIFI驅動引起內核錯誤oops

    Ad hoc網絡中再加一個節點進來,一開始ping,那個新加入開發板內核的就崩了;或者驅動運行在PC上,幾乎是一ping就崩(當然驅動是分平臺編譯好的);自己也看了一下查找oops錯誤方法
    發表于 01-27 22:43

    Cortex-M3內核HardFault錯誤調試定位方法有哪幾種

    STM32程序進入HardFault_Handler故障的原因有哪些?Cortex-M3內核HardFault錯誤調試定位方法有哪幾種?
    發表于 01-27 07:03

    內核oops的根本原因是什么?我們如何調試內核oops

    Broadcom 交換機設備連接到我們主板上帶有 PCIe 的 T1042。開關工作在 EP 模式。linux內核版本是4.14。在檢測到 pcie 錯誤的現場卡中引發內核 oops
    發表于 04-20 06:19

    你了解Linux內核中的常見符號?

    一些內核調用可以用來方便標記bug,提供斷言并輸出信息。最常用的兩個是BUG()和BUG_ON()。當被調用的時候,它們會引發oops,導致棧的回溯和錯誤信息的打印。
    發表于 05-15 15:47 ?573次閱讀
    你了解Linux<b class='flag-5'>內核</b>中的常見符號?

    linux內核中的Oops

    MODULE_LICENSE("GPL"); 很明顯,錯誤的地方就是第8行。接下來,我們把這個模塊編譯出來,再用insmod來插入到內核空間,正如我們預期的那樣,Oops出現了
    發表于 04-02 14:31 ?585次閱讀

    Linux環境下段錯誤的產生原因及調試方法小結

    dmesg可以在應用程序crash掉時,顯示內核中保存的相關信息。如下所示,通過dmesg命令可以查看發生段錯誤的程序名稱、引起段錯誤發生的內存地址、指令指針地址、堆棧指針地址、錯誤
    的頭像 發表于 04-30 15:23 ?2515次閱讀

    cortex內核hardfault錯誤的定位方法實戰

    單片機一般是cortex-m3之類的內核,其實其他內核也是一個道理。hardfault錯誤一般是操作了不該操作的內存,或者執行了不該執行的動作,例如一個非法的函數指針,你非要去調用。調試這個
    發表于 12-01 13:36 ?10次下載
    cortex<b class='flag-5'>內核</b>hardfault<b class='flag-5'>錯誤</b>的定位<b class='flag-5'>方法</b>實戰

    變壓器漏油的原因處理方法

    【跑冒滴漏】變壓器漏油的原因處理方法
    發表于 06-29 14:36 ?0次下載

    如何解讀內核oops

    96000045表示錯誤碼。后面[]內的數值是與頁面有關的oops信息被顯示的次數。之后顯示內核的重要特性SMP和PREEMPT被顯示的配置情況。這條信息所在的內核啟用了SMP支持,所
    的頭像 發表于 10-21 12:39 ?1387次閱讀

    EPS-B2伺服驅動器報警原因處理方法

    東菱伺服驅動器故障碼查詢,EPS-B2伺服驅動器報警原因處理方法:   E.03   報警名稱:參數錯誤。   故障原因:參
    的頭像 發表于 02-15 16:50 ?4775次閱讀

    怎么解讀內核oops

    Oops錯誤代碼根據錯誤原因會有不同的定義,如果發現自己遇到的Oops和下面無法對應的話,最好去內核
    的頭像 發表于 02-17 16:08 ?933次閱讀

    AN028 Cortex-M3內核HardFault錯誤調試定位方法

    AN028 Cortex-M3內核HardFault錯誤調試定位方法
    發表于 02-27 18:32 ?0次下載
    AN028 Cortex-M3<b class='flag-5'>內核</b>HardFault<b class='flag-5'>錯誤</b>調試定位<b class='flag-5'>方法</b>

    深入分析內核panic的內核錯誤處理方案

    die函數主要執行oops相關流程,且若異常為中斷流程中觸發或設置了panic_on_oops選項,則進一步通過panic將系統掛起。
    發表于 04-14 15:18 ?3681次閱讀

    RS232通信時怎么處理錯誤?RS232通信中的錯誤處理方法

    RS232通信時怎么處理錯誤?RS232通信中的錯誤處理方法? RS232通信是一種電氣標準,它定義了計算機和串行通信設備之間的通信協議。盡管RS232通信很穩定,但仍然可能會出現
    的頭像 發表于 10-17 16:33 ?2869次閱讀

    服務器錯誤是怎么回事?常見錯誤原因及解決方法匯總

    服務器錯誤是怎么回事?最常見的原因分有六個,分別是:硬件問題、軟件問題、網絡問題、資源耗盡、數據庫、文件權限問題。可以根據以下具體錯誤原因進行辨別,并選擇適合的解決
    的頭像 發表于 08-12 10:11 ?1347次閱讀