在内核开发的过程中,经常会碰到内核崩溃,比如空指针异常,内存访问越界。通常我们只能靠崩溃之后打印出的异常调用栈信息来定位crash的位置和原因。总结下分析的方法和步骤。

通常oops发生之后,会在串口控制台或者dmesg日志输出看到如下的log,以某arm下linux内核的崩溃为例,

  1. <2>[515753.310000] kernel BUG at net/core/skbuff.c:1846!
  2. <1>[515753.310000] Unable to handle kernel NULL pointer dereference at virtual address 00000000
  3. <1>[515753.320000] pgd = c0004000
  4. <1>[515753.320000] [00000000] *pgd=00000000
  5. <0>[515753.330000] Internal error: Oops: 817 [#1] PREEMPT SMP
  6. <0>[515753.330000] last sysfs file: /sys/class/net/eth0.2/speed
  7. <4>[515753.330000] module: http_timeout bf098000 4142
  8. ...
  9. <4>[515753.330000] CPU: 0 Tainted: P (2.6.36 #2)
  10. <4>[515753.330000] PC is at __bug+0x20/0x28
  11. <4>[515753.330000] LR is at __bug+0x1c/0x28
  12. <4>[515753.330000] pc : [<c01472d0>] lr : [<c01472cc>] psr: 60000113
  13. <4>[515753.330000] sp : c0593e20 ip : c0593d70 fp : cf1b5ba0
  14. <4>[515753.330000] r10: 00000014 r9 : 4adec78d r8 : 00000006
  15. <4>[515753.330000] r7 : 00000000 r6 : 0000003a r5 : 0000003a r4 : 00000060
  16. <4>[515753.330000] r3 : 00000000 r2 : 00000204 r1 : 00000001 r0 : 0000003c
  17. <4>[515753.330000] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
  18. <4>[515753.330000] Control: 10c53c7d Table: 4fb5004a DAC: 00000017
  19. <0>[515753.330000] Process swapper (pid: 0, stack limit = 0xc0592270)
  20. <0>[515753.330000] Stack: (0xc0593e20 to 0xc0594000)
  21. <0>[515753.330000] 3e20: ce2ce900 c0543cf4 00000000 ceb4c400 000010cc c8f9b5d8 00000000 00000000
  22. <0>[515753.330000] 3e40: 00000001 cd469200 c8f9b5d8 00000000 ce2ce8bc 00000006 00000026 00000010
  23. ...
  24. <4>[515753.330000] [<c01472d0>] (PC is at __bug+0x20/0x28)
  25. <4>[515753.330000] [<c01472d0>] (__bug+0x20/0x28) from [<c0543cf4>] (skb_checksum+0x3f8/0x400)
  26. <4>[515753.330000] [<c0543cf4>] (skb_checksum+0x3f8/0x400) from [<bf11a8f8>] (et_isr+0x2b4/0x3dc [et])
  27. <4>[515753.330000] [<bf11a8f8>] (et_isr+0x2b4/0x3dc [et]) from [<bf11aa44>] (et_txq_work+0x24/0x54 [et])
  28. <4>[515753.330000] [<bf11aa44>] (et_txq_work+0x24/0x54 [et]) from [<bf11aa88>] (et_tx_tasklet+0x14/0x298 [et])
  29. <4>[515753.330000] [<bf11aa88>] (et_tx_tasklet+0x14/0x298 [et]) from [<c0171510>] (tasklet_action+0x12c/0x174)
  30. <4>[515753.330000] [<c0171510>] (tasklet_action+0x12c/0x174) from [<c05502b4>] (__do_softirq+0xfc/0x1a4)
  31. <4>[515753.330000] [<c05502b4>] (__do_softirq+0xfc/0x1a4) from [<c0171c98>] (irq_exit+0x60/0x64)
  32. <4>[515753.330000] [<c0171c98>] (irq_exit+0x60/0x64) from [<c01431fc>] (do_local_timer+0x60/0x74)
  33. <4>[515753.330000] [<c01431fc>] (do_local_timer+0x60/0x74) from [<c054f900>] (__irq_svc+0x60/0x10c)
  34. <4>[515753.330000] Exception stack(0xc0593f68 to 0xc0593fb0)

在这里,我们着重关注下面几点:

Oops信息 kernel BUG at net/core/skbuff.c:1846! Unable to handle kernel NULL pointer dereference at virtual address 00000000 , 这里能够简要的告诉是什么问题触发了oops,如果是由代码直接调用BUG()/BUG_ON()一类的,还能给出源代码中触发的行号。

寄存器PC/LR的值 PC is at __bug+0x20/0x28 LR is at __bug+0x1c/0x28 , 这里PC是发送oops的指令, 可以通过LR找到函数的调用者

CPU编号和CPU寄存器的值 sp ip fp r0~r10

oops时,应用层的Process Process swapper (pid: 0, stack limit = 0xc0592270) , 如果crash发生在内核调用上下文,这个可以用来定位对应的用户态进程

最重要的是调用栈,可以通过调用栈来分析错误位置

这里需要说明一点, skb_checksum+0x3f8/0x400 ,在反汇编后,可以通过找到skb_checksum函数入口地址偏移0x3f8来精确定位执行点

在需要精确定位出错位置的时候,我们就需要用到反汇编工具objdump了。下面就是一个示例,

  1. objdump -D -S xxx.o > xxx.txt

举个例子,比如我们需要寻找栈 (et_isr+0x2b4/0x3dc [et]) from [<bf11aa44>] (et_txq_work+0x24/0x54 [et]) ,这里我们可以知道这个函数是在 [et] 这个obj文件中,那么我们可以直接去找 et.o ,然后反汇编 objdump -D -S et.o > et.txt , 然后et.txt中就是反汇编后的指令。当然,单看汇编指令会非常让人头疼,我们需要反汇编指令和源码的一一对应才好分析问题。这就需要我们在编译compile的时候加上 -g 参数,把编译过程中的symbol和调试信息一并加入到最后obj文件中,这样objdump反汇编之后的文件中就包含嵌入的源码文件了。

对于内核编译来讲,就是需要在内核编译的根目录下,修改Makefile中 KBUILD_CFLAGS , 加上 -g 编译选项。

  1. KBUILD_CFLAGS := -g -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
  2. -fno-strict-aliasing -fno-common \
  3. -Werror-implicit-function-declaration \
  4. -Wno-format-security \
  5. -fno-delete-null-pointer-checks -Wno-implicit-function-declaration \
  6. -Wno-unused-but-set-variable \
  7. -Wno-unused-local-typedefs

下面是一份反编译完成后的文件的部分截取。我们可以看到,这里0x1f0是 <et_isr> 这个函数的入口entry,c的源代码是在前面,后面跟的汇编代码是对应的反汇编指令

  1. f0 <et_isr>:
  2. et_isr(int irq, void *dev_id)
  3. #else
  4. static irqreturn_t BCMFASTPATH
  5. et_isr(int irq, void *dev_id, struct pt_regs *ptregs)
  6. #endif
  7. {
  8. f0: e92d40f8 push {r3, r4, r5, r6, r7, lr}
  9. f4: e1a04001 mov r4, r1
  10. struct chops *chops;
  11. void *ch;
  12. uint events = 0;
  13. et = (et_info_t *)dev_id;
  14. chops = et->etc->chops;
  15. f8: e5913000 ldr r3, [r1]
  16. ch = et->etc->ch;
  17. /* guard against shared interrupts */
  18. if (!et->etc->up)
  19. fc: e5d32028 ldrb r2, [r3, #40] ; 0x28
  20. struct chops *chops;
  21. void *ch;
  22. uint events = 0;
  23. et = (et_info_t *)dev_id;
  24. chops = et->etc->chops;
  25. : e5936078 ldr r6, [r3, #120] ; 0x78
  26. ch = et->etc->ch;
  27. : e593507c ldr r5, [r3, #124] ; 0x7c
  28. /* guard against shared interrupts */
  29. if (!et->etc->up)
  30. : e3520000 cmp r2, #0
  31. c: 1a000001 bne 218 <et_isr+0x28>
  32. : e1a00002 mov r0, r2
  33. : e8bd80f8 pop {r3, r4, r5, r6, r7, pc}
  34. goto done;
  35. /* get interrupt condition bits */
  36. events = (*chops->getintrevents)(ch, TRUE);
  37. : e5963028 ldr r3, [r6, #40] ; 0x28
  38. c: e1a00005 mov r0, r5
  39. : e3a01001 mov r1, #1
  40. : e12fff33 blx r3
  41. : e1a07000 mov r7, r0
  42. /* not for us */
  43. if (!(events & INTR_NEW))
  44. c: e2100010 ands r0, r0, #16
  45. : 08bd80f8 popeq {r3, r4, r5, r6, r7, pc}
  46. ET_TRACE(("et%d: et_isr: events 0x%x\n", et->etc->unit, events));
  47. ET_LOG("et%d: et_isr: events 0x%x", et->etc->unit, events);
  48. /* disable interrupts */
  49. (*chops->intrsoff)(ch);
  50. : e5963038 ldr r3, [r6, #56] ; 0x38
  51. : e1a00005 mov r0, r5
  52. c: e12fff33 blx r3
  53. (*chops->intrson)(ch);
  54. }

在objdump反汇编出指令之后,我们可以根据调用栈上的入口偏移来找到对应的精确调用点。例如, (et_isr+0x2b4/0x3dc [et]) from [<bf11aa44>] (et_txq_work+0x24/0x54 [et]) , 我们可以知道调用点在 et_isr入口位置+0x2b4偏移 ,而刚才我们看到 et_isr的入口位置是0x1f0 ,那就是说在 0x1f0+0x2b4=0x4a4 偏移位置。我们来看看,如下指令 4a4: e585007c str r0, [r5, #124] ; 0x7c ,其对应的源代码就是上面那一段c代码, skb->csum = skb_checksum(skb, thoff, skb->len - thoff, 0); 。而我们也知道,下一个调用函数的确是 skb_checksum , 说明精确的调用指令是准确的。

  1. ASSERT((prot == IP_PROT_TCP) || (prot == IP_PROT_UDP));
  2. check = (uint16 *)(th + ((prot == IP_PROT_UDP) ?
  3. c: e3580011 cmp r8, #17
  4. : 13a0a010 movne sl, #16
  5. : 03a0a006 moveq sl, #6
  6. offsetof(struct udphdr, check) : offsetof(struct tcphdr, check)));
  7. *check = 0;
  8. : e18720ba strh r2, [r7, sl]
  9. thoff = (th - skb->data);
  10. if (eth_type == HTON16(ETHER_TYPE_IP)) {
  11. struct iphdr *ih = ip_hdr(skb);
  12. prot = ih->protocol;
  13. ASSERT((prot == IP_PROT_TCP) || (prot == IP_PROT_UDP));
  14. check = (uint16 *)(th + ((prot == IP_PROT_UDP) ?
  15. c: e087200a add r2, r7, sl
  16. : e58d2014 str r2, [sp, #20]
  17. offsetof(struct udphdr, check) : offsetof(struct tcphdr, check)));
  18. *check = 0;
  19. ET_TRACE(("et%d: skb_checksum: \n", et->etc->unit));
  20. skb->csum = skb_checksum(skb, thoff, skb->len - thoff, 0);
  21. : e5952070 ldr r2, [r5, #112] ; 0x70
  22. : e58dc008 str ip, [sp, #8]
  23. c: e0612002 rsb r2, r1, r2
  24. a0: ebfffffe bl 0 <skb_checksum>
  25. a4: e585007c str r0, [r5, #124] ; 0x7c
  26. *check = csum_tcpudp_magic(ih->saddr, ih->daddr,
  27. a8: e5953070 ldr r3, [r5, #112] ; 0x70
  28. static inline __wsum
  29. csum_tcpudp_nofold(__be32 saddr, __be32 daddr, unsigned short len,
  30. unsigned short proto, __wsum sum)
  31. {
  32. __asm__(
  33. ac: e59dc008 ldr ip, [sp, #8]

有几点比较geek的地方需要注意:

函数调用栈的调用不一定准确(不知道why?可能因为调用过程是通过LR来反推到的,LR在执行过程中有可能被修改?),但是有一点可以确认,调用的点是准确的,也就是说调用函数不一定准,但是调用函数+偏移是能够找到准确的调入指令

inline的函数以及被优化的函数可能不会出现在调用栈上,在编译的时候因为优化的需要,会就地展开代码,这样就不会在这里有调用栈帧(stack frame)存在了

REF

https://www.ibm.com/developerworks/cn/linux/l-cn-kdump4/index.html?ca=drs

Linux内核crash/Oops异常定位分析方法的更多相关文章

  1. LCD 显示异常定位分析方法

    第一种情况: 进入kernel或android 后,如果LCM图像示异常,可以通过如下步骤来判断问题出现在哪个层面. step1:通过DMMS截图,来判断上面刷到LCM的数据是否有问题. 若DMMS获 ...

  2. Linux内核中断和异常分析(中)

    在linux内核中,每一个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线.所有现在存在的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连,上次讲到单片机的时候,我就讲到了单片机 ...

  3. Linux内核OOM机制的详细分析(转)

    Linux 内核 有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了 防止内存耗尽而内核会把该进程杀掉.典 ...

  4. linux内核空间与用户空间信息交互方法

    linux内核空间与用户空间信息交互方法     本文作者: 康华:计算机硕士,主要从事Linux操作系统内核.Linux技术标准.计算机安全.软件测试等领域的研究与开发工作,现就职于信息产业部软件与 ...

  5. 【转】 Linux内核中读写文件数据的方法--不错

    原文网址:http://blog.csdn.net/tommy_wxie/article/details/8193954 Linux内核中读写文件数据的方法  有时候需要在Linuxkernel--大 ...

  6. Linux内核[CVE-2016-5195] (dirty COW)原理分析

    [原创]Linux内核[CVE-2016-5195] (dirty COW)原理分析-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com https://bbs.pediy.com/ ...

  7. Linux 内核调度器源码分析 - 初始化

    导语 上篇系列文 混部之殇-论云原生资源隔离技术之CPU隔离(一) 介绍了云原生混部场景中CPU资源隔离核心技术:内核调度器,本系列文章<Linux内核调度器源码分析>将从源码的角度剖析内 ...

  8. Linux内核Crash分析

    转载自:http://linux.cn/article-3475-1.html 在工作中经常会遇到一些内核crash的情况,本文就是根据内核出现crash后的打印信息,对其进行了分析,使用的内核版本为 ...

  9. Linux内核中断和异常分析(上)

    中断,通常被定义为一个事件.打个比方,你烧热水,水沸腾了,这时候你要去关掉烧热水的电磁炉,然后再去办之前手中停不下来的事情.那么热水沸腾就是打断你正常工作的一个信号机制.当然,还有其它的情况,我们以后 ...

随机推荐

  1. 编写App测试用例的关注点

    如何做到测试用例的百分百覆盖一直是测试用例编写过程中的难点,首先在测试时我们经常会遇见一些常见的bug,那么我们可以在编写测试用例时考虑到这些点.    一:关于业务逻辑               ...

  2. orcale 查询

    修改日期显示形式: alter session set nls_date_formate='DD-MON-RR'; alter session set nls_date_formate='yyyy-M ...

  3. 洛谷 P2023 BZOJ 1798 [AHOI2009]维护序列

    题目描述 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一 ...

  4. 0926mysql中MRR的用法

    转自 http://blog.itpub.net/22664653/viewspace-1673682  [MySQL]MySQL5.6新特性之Multi-Range Read 2015-05-27 ...

  5. php7 使用imagick 的坑

    imagick是一个PHP的扩展,用ImageMagick提供的API来进行图片的创建与修改,不过这些操作已经包装到扩展imagick中去了,最终调用的是ImageMagick提供的API. Imag ...

  6. 通过winrm使用powershell远程管理服务器

    原文地址 在Linux中,我们可以使用安全的SSH方便的进行远程管理.但在Windows下,除了不安全的Telnet以外,从Windows Server 2008开始提供了另外一种命令行原创管理方式, ...

  7. 介绍C++ STL常用模板使用方法的相关资料

    1.vector的几种初始化及赋值方式

  8. Oracle 使用sqlnet.ora/trigger限制/允许某IP或IP段访问指定用户

    Oracle 使用sqlnet.ora/trigger限制/允许某IP或IP段访问指定用户 学习了:http://blog.itpub.net/28602568/viewspace-2092858/ ...

  9. 新手git: ssh: connect to host localhost port 22: Connection refused

    由于gitlab上要git pull或者git clone,可是每次都出现这个问题.之前偶尔出现这个问题.可是仅仅是偶尔.这是为什么呢?然后就開始搜索网上的解决方式了. 这个问题搜索网上非常多答案.可 ...

  10. 【JavaScript】——JS入门

    结束XML之旅,開始JavaScript的学习,看视频.了解了她的前世今生,还是为她捏了把汗啊! 看了部分视 频了,简单的总结一下吧! JavaScript是什么? JavaScript是一种基于面向 ...