高通平台Linux kernel死机解题心得

1、前言

1.1 目的

能够结合知识背景,借助相关调试工具,使用一般分析手段分析、定位解决项目过程中遇到的死机类系统稳定性问题,提升工作效率

持续积累,拓宽知识深度和广度

1.2 死机?

指系统发生致命性异常导致主动或者被动进入系统完全不可用的状态,导致系统死机的问题原因有很多,排除硬件问题,还有这些大模块:Android、Linux kenrel、modem、TZ 等等,各个子系统都有可能导致系统死机重启,我们这里主要介绍最常见的Linux kernel panic的一般调试分析手法。

1.3 调试策略

稳定复现的问题,可以借助飞线抓uart log的方式来获取异常现场,kernel在发生panic之前会把很多重要寄存器信息、以及重要的call stack(调用栈)信息打印出来(可参考http://blog.csdn.net/forever_2015/article/details/53235716),通常可以借助相关GNU工具(addr2Line) 解析出异常地址所在的文件、函数、行号来定位问题。这是最常用的分析手法,对于很多简单的死机问题通常可以比较清晰的定位解决。

概率性复现的问题,上面飞线抓uart log的方式就显得有些无力了,由于出现异常的时机存在不确定性,所以必须要一直连着串口线操作才可以,还有些问题几天或者更长时间才出现一次,此种情况下,普通的抓uart log的方式就显得很苍白无力了。这个时候就需要借助另外的分析手段抓ramdump分析了。ramdump是什么?其实就是指内存转储,简单来说就是整个DRAM的运行时内容数据,当系统发生崩溃性异常时候,通过一种机制实现将DRAM中的数据保存起来的一种方式,保留了异常现场,待离线分析用。ramdump中保留了所以异常时候的DRAM中的信息,包括各种全局变量、局部变量、进程状态等待供调试的信息,通过Crash、gdb、Trace32等工具就可以完成这些信息的提前,非常有助于复杂问题的分析。

2、案例描述

2.1 问题现象:

扫描二维码关注公众号,回复: 3385349 查看本文章

高通8940平台系统灭屏下,快速重复用错误指纹触摸指纹模组(或亮屏在指纹列表目录下),系统死机,持续测试30min出现2-4次,概率很高。

2.2 初步判断

我们知道Android层(用户空间进程)如果主线程(tid == 0)被堵塞了会触发Watchdog time out导致system_server进程被杀 =》zygote被杀 =》开机动画响起,出现Android crash的情况:

 
  1. services/core/java/com/android/server/Watchdog.java :533

  2. Slog.w(TAG, "*** GOODBYE!");

  3. Process.killProcess(Process.myPid());

  4. System.exit(10);

通常这种情况出现异常后不会马上死机重启,会有一个触发WDT的等待时间(各种组件前台后台进程触发时间设置策略不同),此种类型异常给用户的感觉就是:指纹失效了,然后等个10s左右手机自动重启。

而目前的情况不同,与测试mm沟通确认发现,出问题后手机没有任何等待,直接黑屏死机,没有重启,直接进入了ramdump模式,所以可以初步判断为底层发现异常,而跟指纹相关的最大可能就是发生了kernel panic(TA crash异常最终会以Android wdt方式表现出来)。

3、ramdump的抓取

高通平台首先保证机器是已使能ramdump抓取机制的,默认设置开关:

 
  1. kernel/drivers/power/reset/msm-poweroff.c

  2. static int download_mode = 1;

如果开启了secboot 支持的项目还需要更改BOOT:

 
  1. BOOT.BF.3.3/boot_images/core/securemsm/secdbgplcy/oem/oem_debug_policy.c

  2. -//#define SKIP_SERIAL_NUMBER_CHECK 1

  3. +#define SKIP_SERIAL_NUMBER_CHECK 1

高通平台可以使用PC端的QPST工具抓取全部的dump信息,步骤如下:

安装QPST工具(需要安装QBUK驱动),打开程序主界面,选址Ports分页:

插入usb,自动识别抓取ramdump,完全傻瓜式操作:

大概5min左右完成,选择 Help =》Open Log File Directory ,拿到抓取的ramdump数据:

PS: 若仅仅是为了测试调试,可以这样主动触发ramdump

adb root

adb shell

echo c > /proc/sysrq-trigger

其本质就是让内核访问空指针内存,被MMU拦截而触发data abort异常.

4、 ramdump解析

4.1 高通QCAP网站

https://cap.qti.qualcomm.com/default.aspx

登录高通账户后进入如下界面:(此处建议使用google浏览器,个别浏览器有不支持的情况)

点击 START NEW ANALYSIS

将对应模块(KERNEL/TZ/RPM/MODEM/ADSP等)的符号文件(elf、vmlinux)路径加到对应的选项里面选择开始即可,解析完成后会产生一个报告文件:QCAPReport.html ,里面会有各个模块的相关异常信息描述,包括dmesg等等。QCAP的优点是使用简单,工具安装简单,缺点是解析出来的信息很有限,跟抓log差不多的意思,无法分析定位复杂问题。(QCAP的使用可以参考高通文档:80-nr964-54_j_qcap start-up guide.pdf)

4.2 ramdump-parse

(此脚本只能解析kernel的异常,不同子系统需要配合不同的解析脚步)

ramdump-parse脚本配置:http://blog.csdn.net/forever_2015/article/details/70185313

解析前需要确保vimlinux跟ramdump的一致性,可以按下面的方法确认:

 
  1. $ strings vmlinux |grep "Linux version"

  2. Linux version 3.18.31 (android@ubuntu) (gcc version 4.9 20150123 (prerelease) (GCC) ) #1 SMP PREEMPT Wed Aug 9 23:23:27 CST 2017

  3.  
  4. $ strings DDRCS0.BIN |grep "Linux version"

  5. Linux version 3.18.31 (android@ubuntu) (gcc version 4.9 20150123 (prerelease) (GCC) ) #1 SMP PREEMPT Wed Aug 9 23:23:27 CST 2017

如果不匹配,无法继续分析,若确认匹配后就可以执行解析:

 
  1. $ ramdump-parser.sh

  2. Start ramdump parser..

  3. cd /home/android/tools/ramdump/tools/linux-ramdump-parser-v2

  4. python ramparse.py -v /home/android/share/fp-quick-wakeup/crash/Port_COM112-6901-die/vmlinux -g /home/android/tools/gnu-tools/aarch64-linux-android-4.9/bin/aarch64-linux-android-gdb -n /home/android/tools/gnu-tools/aarch64-linux-android-4.9/bin/aarch64-linux-android-nm -j /home/android/tools/gnu-tools/aarch64-linux-android-4.9/bin/aarch64-linux-android-objdump -a /home/android/share/fp-quick-wakeup/crash/Port_COM112-6901-die/ -o /home/android/share/fp-quick-wakeup/crash/Port_COM112-6901-die/out -x

  5.  
  6. [1/32] --clock-dump ... 0.848617s

  7. [2/32] --cpr3-info ... 0.157973s

  8. [3/32] --cpr-info ... 1.026817s

  9. [4/32] --cpu-state ... 0.104325s

  10. [5/32] --ddr-compare ... 5.864537s

  11. [6/32] --check-for-watchdog ... 0.011254s

  12. [7/32] --parse-debug-image ... 10.632866s

  13. [8/32] --dmesg ... 0.519516s

  14. ...

  15. out: /home/android/share/fp-quick-wakeup/crash/Port_COM112-6901-die/out

  16. ls out/

  17. ClockDumps.txt DDRCacheCompare.txt MSM_DUMP_DATA_L1_DATA_CACHE_0x2 roareadiff.txt t32_startup_script.cmm thermal_info.txt

  18. core0_regs.cmm dmesg_TZ.txt MSM_DUMP_DATA_L1_DATA_CACHE_0x3 secure_world_core0_regs.cmm tasks_sched_stats0.txt timerlist.txt

  19. core1_regs.cmm kconfig.txt MSM_DUMP_DATA_L1_DATA_CACHE_0x6 secure_world_core1_regs.cmm tasks_sched_stats1.txt tmc-etf.bin

  20. core2_regs.cmm launch_t32.sh MSM_DUMP_DATA_L1_DATA_CACHE_0x7 secure_world_core2_regs.cmm tasks_sched_stats2.txt tmc-etr.bin

  21. core3_regs.cmm lpm.txt msm_iommu_domain_00.txt secure_world_core3_regs.cmm tasks_sched_stats3.txt tmc_etr.txt

  22. core4_regs.cmm mdpinfo_out.txt msm_iommu_domain_01.txt secure_world_core4_regs.cmm tasks_sched_stats4.txt vmalloc.txt

  23. core6_regs.cmm memory.txt msm_iommu_domain_02.txt secure_world_core6_regs.cmm tasks_sched_stats5.txt

  24. core7_regs.cmm mem_stat.txt msm_iommu_domain_03.txt secure_world_core7_regs.cmm tasks_sched_stats6.txt

  25. cpr3_info.txt MSM_DUMP_DATA_L1_DATA_CACHE_0x0 New Folder spm.txt tasks_sched_stats7.txt

  26. cprinfo.txt MSM_DUMP_DATA_L1_DATA_CACHE_0x1 page_tables.txt t32_config.t32 tasks.txt

5、解题手段

5.1 分析dmesg

上面是解析完成后的文件,有我们需要的kernel log,也就是文件 dmesg_TZ.txt,打开 dmesg_TZ.txt 看下大致发生了什么事情?

搜索关键字发现发生了kernel panic!

...

 
  1. [ 423.400073] Unable to handle kernel NULL pointer dereference at virtual address 00000008

  2. [ 423.400075] [silead finger_interrupt_handler 505]:S IRQ 19 , GPIO 12 state is 0

  3. [ 423.400083] [silead finger_interrupt_handler 506]:state is 0

  4. [ 423.400096] pgd = ffffffc0017eb000

  5. [ 423.400103] [00000008] *pgd=000000008ea0a003, *pud=000000008ea0a003, *pmd=000000008ea0b003, *pte=006000000b000707

  6. [ 423.400124] Internal error: Oops: 96000046 [#1] PREEMPT SMP

  7. [ 423.400132] Modules linked in: wlan(O)

  8. [ 423.400148] CPU: 4 PID: 0 Comm: swapper/4 Tainted: G W O 3.18.31-perf #1

  9. [ 423.400155] Hardware name: Qualcomm Technologies, Inc. MSM8940-PMI8950 MTP (DT)

  10. [ 423.400164] task: ffffffc04eae4980 ti: ffffffc0b28bc000 task.ti: ffffffc0b28bc000

  11. [ 423.400182] PC is at run_timer_softirq+0x4ac/0x5ec

  12. [ 423.400192] LR is at run_timer_softirq+0x324/0x5ec

  13. [ 423.400199] pc : [<ffffffc0000feb98>] lr : [<ffffffc0000fea10>] pstate: 600001c5

  14. [ 423.400204] sp : ffffffc0b28bfb60

  15. ...

  16. [ 423.401490] Process swapper/4 (pid: 0, stack limit = 0xffffffc0b28bc058)

  17. [ 423.401496] Call trace:

  18. [ 423.401510] [<ffffffc0000feb98>] run_timer_softirq+0x4ac/0x5ec

  19. [ 423.401523] [<ffffffc0000a6864>] __do_softirq+0x178/0x350

  20. [ 423.401532] [<ffffffc0000a6c8c>] irq_exit+0x74/0xb0

  21. [ 423.401543] [<ffffffc0000edf18>] __handle_domain_irq+0xb4/0xec

  22. [ 423.401553] [<ffffffc00008254c>] gic_handle_irq+0x54/0x84

  23. [ 423.401560] Exception stack(0xffffffc0b28bfd40 to 0xffffffc0b28bfe60)

  24. ...

  25. [ 423.401671] [<ffffffc000085da8>] el1_irq+0x68/0xd4

  26. [ 423.401685] [<ffffffc000851480>] cpuidle_enter_state+0xd0/0x224

  27. [ 423.401695] [<ffffffc0008516ac>] cpuidle_enter+0x18/0x20

  28. [ 423.401706] [<ffffffc0000e1cc0>] cpu_startup_entry+0x288/0x384

  29. [ 423.401717] [<ffffffc000091d5c>] secondary_start_kernel+0x108/0x114

  30. [ 423.401728] Code: b90052a0 34000840 f9400321 f9400720 (f9000420)

  31. [ 423.401736] ---[ end trace d0daa1892c14378b ]---

  32. [ 423.401753] Kernel panic - not syncing: Fatal exception in interrupt

  33. [ 423.401774] CPU7: stopping

...

看到异常调用栈,一眼看不出问题所在,那么我们需要搞清楚CPU发生了什么事情?CPU停下的原因是什么?

先来看当前64位CPU的状态寄存器组PSTATE:《ARMv8-A Architecture reference manual-DDI0487A_g_armv8_arm.pdf》

pstate:600001c5

0x600001c5 ==》1610613189 ==》1100000000000000000000111000101

解析后代表的意思就是禁止debug异常,禁止IRQ,切换异常等级到EL1(Exception Level 1),使用SP_EL1为堆栈指针,EL1说明异常确实是发生在kernel层,禁止IRQ是为了不让被中断干扰现场.

继续看Oops:

[423.400124] Internal error: Oops:96000046[#1] PREEMPT SMP

这里Oops后面接的96000046是ARM发生异常后上报的错误码,分析kernel panic流程代码(可参考:http://blog.csdn.net/forever_2015/article/details/53235716)可知,这个错误码就是ESR(异常综合寄存器)寄存器的值,

根据上面PSTATE的解析,ESR寄存器也将是使用ESR_EL1,ESR_EL1寄存器的描述如下:

An ESR_ELx holds the syndrome information for an exception that is taken to AArch64 state

所以,ESR寄存器中的EC值保持了异常的类型等信息,我们可以解析这个值:

0x96000046 ==》2516582470 ==》10010110000000000000000001000110

EC ==》100101

对应如下:

表面kernel层发生数据终止异常,并且没有改变Exception level,也就是没有路由到EL3或者sec-EL1.

==》死机的原因就是因为CPU发生了 Data abort异常!那么什么时候会发生Data abort异常呢?简单来说就是访问了不可访问的虚拟内存地址,被内存管理单元(MMU)拦截到的异常,比如最常见的空指针异常,内核线程访问非内核虚拟地址空间等(内核虚拟地址空间:

0xFFFF_0000_0000_0000 =>0xFFFF_FFFF_FFFF_FFFF )

现在死机的原因是知道了,那么接下来我们最想知道的就是导致死机的代码是在哪里,我们可以借助add2Line工具解析出调用栈所在函数,文件行号:

 
  1. $ aarch64-linux-android-addr2line -e vmlinux -f -C ffffffc0000feb98

  2. __list_del

  3. /home/android/project/6901-7.1/LA.UM.5.6/LINUX/android/kernel/msm-3.18/include/linux/list.h:89

  4.  
  5.  
  6. $ aarch64-linux-android-addr2line -e vmlinux -f -C ffffffc0000a6864

  7. static_key_count

  8. /home/android/project/6901-7.1/LA.UM.5.6/LINUX/android/kernel/msm-3.18/include/linux/jump_label.h:88

  9.  
  10. $ aarch64-linux-android-addr2line -e vmlinux -f -C ffffffc0000a6c8c

  11. tick_irq_exit

  12. /home/android/project/6901-7.1/LA.UM.5.6/LINUX/android/kernel/msm-3.18/kernel/softirq.c:363

如此便可以还原调用栈的代码,最终是下面的代码导致的异常:

 
  1. 87 static inline void __list_del(struct list_head * prev, struct list_head * next)

  2. 88 {

  3. 89 next->prev = prev; ==》猜想那会不会是next->prev指针为NULL所致么?

  4. 90 prev->next = next;

  5. 91 }

要确认是不是NULL指针所致,我们需要来看下对应的汇编指令,打开Trace32,切到汇编源码混合显示(也可以通过objdump -S vmlinux > vmlinux.S 查看):

为方便查看,复制贴出来:

 
  1. static inline void __list_del(struct list_head * prev, struct list_head * next)

  2. |{

  3. 89| next->prev = prev;

  4. NSX:FFFFFFC0000FE958|F9000420 str x0,[x1,#0x8]; x0,[x1,#8]

  5. 90| prev->next = next;

  6. NSX:FFFFFFC0000FE95C|F9000001 str x1,[x0]

  7. 769| entry->prev = LIST_POISON2;

  8. NSX:FFFFFFC0000FE960|D2804000 mov x0,#0x200 ; x0,#512

  9. NSX:FFFFFFC0000FE964|F2FBD5A0 movk x0,#0xDEAD,lsl #0x30 ; x0,#57005,lsl #48

  10. NSX:FFFFFFC0000FE968|F9000760 str x0,[x27,#0x8] ; x0,[x27,#8]

  11. |...

简单解释下,

FFFFFFC0000FE968 ==》当前汇编指令的虚拟地址

F9000760 ===》汇编机器码,ARM/ARM64的指令机器码都是32位固定长度

str x0,[x27,#0x8]==》 汇编指令,;后的是注释

根据AAPCS(ARM二进制过程调用标准)参数传递规则,ARM64的 v0 - v7 参数直接由 x0 - x7 传递,其他参数由压栈传递,子程序返回结果保存到x0,

那么这里可推导如下:

x0 == prev

x1 == next

指令:strx0,[x1,#0x8]

x1+0x8 其实就是next+8个字节的偏移,看下struct list_head 的结构:

 
  1. struct list_head {

  2. struct list_head *next, *prev;

  3. };

ARM体系结构中,ARM64一个指针占8个字节内存,也就是: [x1+0x8] == prev

所以这个str指令就是对应上面的next->prev = prev ,那么我们来看下具体的寄存器x0,x1值是多少?

从dmesg log中找到当前发生异常的调用栈(注意,一定要找对应的异常栈的寄存器值),如下:

 
  1. [ 423.400182] PC is at run_timer_softirq+0x4ac/0x5ec

  2. [ 423.400192] LR is at run_timer_softirq+0x324/0x5ec

  3. [ 423.400199] pc : [<ffffffc0000feb98>] lr : [<ffffffc0000fea10>] pstate: 600001c5

  4. [ 423.400204] sp : ffffffc0b28bfb60

  5. [ 423.400210] x29: ffffffc0b28bfb60 x28: ffffffc0b2619038

  6. [ 423.400219] x27: ffffffc000c9a000 x26: 0000000000000000

  7. [ 423.400228] x25: ffffffc001741120 x24: ffffffc0006e277c

  8. [ 423.400237] x23: ffffffc0b2619000 x22: ffffffc0b28bfbf8

  9. [ 423.400246] x21: ffffffc0b28bc000 x20: ffffffc0013d2000

  10. [ 423.400254] x19: ffffffc0b2618000 x18: 0000007f9588e080

  11. [ 423.400263] x17: 0000007f9a36d4b4 x16: ffffffc0001e4f6c

  12. [ 423.400272] x15: 003b9aca00000000 x14: 0000000000000001

  13. [ 423.400280] x13: 0000000000000000 x12: 0000000000000001

  14. [ 423.400289] x11: 000000000000000f x10: ffffffc000c9c3f4

  15. [ 423.400297] x9 : 0000000000000000 x8 : 0000000000000005

  16. [ 423.400305] x7 : 0000000000000000 x6 : 000000000001451c

  17. [ 423.400314] x5 : ffffffc0b2ae8000 x4 : 00135f1aa15bb200

  18. [ 423.400323] x3 : 0000000000000018 x2 : 0000000000000000

  19. [ 423.400331] x1 : 0000000000000000 x0 : ffffffc0b28bfbf8

上面可以看到,x1+0x8 ==0x0000000000000000+0x8==0x0000000000000008

上面我们有讲过,ARM64内核的虚拟地址空间范围是0xFFFF_0000_0000_0000 =>0xFFFF_FFFF_FFFF_FFFF

很明显这个值0x0000000000000008不在范围内,它属于用户空间的虚拟地址空间,肯定会被MMU拦截掉上报data abort异常,所以此题的真正原因是程序跑飞访问非法地址所致.

CPU发生异常的原因已经明确了,但仔细一看,好像也看不出来具体是哪里源码导致的死机,而且这些都是内核本身的代码,没人改动过的,为什么会报异常呢?

目前看来从kernel log上的信息无法直接分析出导致问题的具体源代码,从dmesg的这些信息我们已经知道出问题的是这个prev指针,但是比较难直接抓到导致异常的真凶源码位置。

5.2 Trace32分析

安装:http://blog.csdn.net/forever_2015/article/details/70185527

5.2.1 启动模拟器

前面使用ramdump-parse脚本解析完成后,out下有生成这几只文件:

launch_t32.sh t32_config.t32 t32_startup_script.cmm

但是需要做一些简单修改才可以使用trace32工具加载(参考http://blog.csdn.net/forever_2015/article/details/70185638

./launch_t32.sh

输入v.f 调出 当前的调用栈关系

为便于分析传参分析,需要将Locals的框框打钩:

可以看到,异常时候的各种参数都显示出来了,这样就非常有利于我们debug了,这也是单纯从dmesg无法得到的重要信息!注意inline类型的函数会被编译器默认优化掉,所以inline类型的函数的参数不可见,需要通过读汇编代码,分析寄存器传参推导。

输入d.list 查看PC停止的位置,如下高亮:

5.2.2 分析Call Stack:

为方便查看,把调用栈信息复制出来,如下:

 
  1. 1. ...

  2. -007|die(

  3. | ?,

  4. | regs = 0xFFFFFFC0B28BFA40 -> (

  5. | user_regs = (regs = (0xFFFFFFC0B28BFBF8, 0x0, 0x0, 0x18, 0x00135F1AA15BB200, 0xFFFFFFC0B2AE800

  6. | regs = (0xFFFFFFC0B28BFBF8, 0x0, 0x0, 0x18, 0x00135F1AA15BB200, 0xFFFFFFC0B2AE8000, 0x0001451C

  7. | sp = 0xFFFFFFC0B28BFB60,

  8. | pc = 0xFFFFFFC0000FEB98,

  9. | pstate = 0x600001C5,

  10. | orig_x0 = 0xFFFFFFC0B2618000,

  11. | syscallno = 0xFFFFFFC0000FE7D0),

  12. | err = 0x96000046)

  13. | flags = 0x01C0

  14. | ret = 0x1

  15. | tsk = 0xFFFFFFC04EAE4980

  16. | die_counter = 0x1

  17. -008|__do_kernel_fault.part.5(

  18. | mm = 0x0,

  19. | addr = 0x8,

  20. | esr = 0x96000046,

  21. | regs = 0xFFFFFFC0B28BFA40)

  22. -009|do_page_fault(

  23. | addr = 0x8,

  24. | esr = 0x96000046,

  25. | regs = 0xFFFFFFC0B28BFA40)

  26. | tsk = 0xFFFFFFC04EAE4980

  27. | mm = 0x0

  28. | vm_flags = 0xFFFFFFC000C9A000

  29. | vma = 0xFFFFFFC0B28BFA40

  30. -010|do_translation_fault(

  31. | addr = 0x0A44,

  32. | esr = 0x0124F800,

  33. | ?)

  34. -011|do_mem_abort(

  35. | addr = 0x8,

  36. | esr = 0x96000046,

  37. | regs = 0xFFFFFFC0B28BFA40)

  38. | inf = 0xFFFFFFC0013DC790 -> (

  39. | fn = 0xFFFFFFC000099A74,

  40. | sig = 0x0B,

  41. | code = 0x00030001,

  42. | name = 0xFFFFFFC0010DF250 -> 0x6C)

  43. | info = (

  44. | si_signo = 0x0032D110,

  45. | si_errno = 0xFFFFFFC0,

  46. | si_code = 0x01C0,

  47. | _sifields = (_pad = (0x7, 0x0, 0xB28BF9E0, 0xFFFFFFC0, 0x000A6D78, 0xFFFFFFC0, 0xB28BF9F0, 0xFFF

  48. -012|el1_da(asm)

  49. -->|exception

  50. -013|__list_del(inline)

  51. -013|detach_timer(inline)

  52. -013|detach_expired_timer(inline)

  53. -013|__run_timers(inline)

  54. -013|run_timer_softirq(

  55. | ?)

  56. | base = 0xFFFFFFC0B2618000 -> (

  57. | lock = (rlock = (raw_lock = (owner = 0x6FCD, next = 0x6FCE))),

  58. | running_timer = 0xFFFFFFC001741120 -> (

  59. | entry = (next = 0xFFFFFFC0B27BC9B8, prev = 0xFFFFFFC0B27BC9B8),

  60. | expires = 0x0000000100003098,

  61. | base = 0xFFFFFFC0B27BC000,

  62. | function = 0xFFFFFFC0006E277C -> ,

  63. | data = 0x0,

  64. | slack = 0xFFFFFFFF,

  65. | start_pid = 0xFFFFFFFF,

  66. | start_site = 0x0,

  67. | start_comm = (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0))

  68. | timer_jiffies = 0x0000000100003035,

  69. | next_timer = 0x0000000100003034,

  70. | active_timers = 0x7,

  71. | all_timers = 0x7,

  72. | cpu_=_0x4,

  73. | tv1 = (vec = ((next = 0xFFFFFFC0B2618038, prev = 0xFFFFFFC0B2618038), (next = 0xFFFFFFC0B2618048

  74. | tv2 = (vec = ((next = 0xFFFFFFC0B2619038, prev = 0xFFFFFFC0B2619038), (next = 0xFFFFFFC0B2619048

  75. | tv3 = (vec = ((next = 0xFFFFFFC0B2619438, prev = 0xFFFFFFC0B2619438), (next = 0xFFFFFFC0B2619448

  76. | tv4 = (vec = ((next = 0xFFFFFFC0B2619838, prev = 0xFFFFFFC0B2619838), (next = 0xFFFFFFC0B2619848

  77. | tv5 = (vec = ((next = 0xFFFFFFC0B2619C38, prev = 0xFFFFFFC0B2619C38), (next = 0xFFFFFFC0B2619C48

  78. | fn = 0xFFFFFFC0006E277C ->

  79. | data = 0x0

  80. | it_func_ptr = 0x0

  81. ...

看到这里,我们可以猜想是不是run_timer_softirq的参数出现了问题导致后面发生的一系列异常?可以从这个方向开始思考,我们先来看下这个函数的实现:

 
  1. static void run_timer_softirq(struct softirq_action *h)

  2. {

  3. struct tvec_base *base = __this_cpu_read(tvec_bases);

  4. hrtimer_run_pending();

  5. __run_deferrable_timers();

  6. if (time_after_eq(jiffies, base->timer_jiffies))

  7. __run_timers(base);

  8. }

我们看到这个函数最重要的参数变量就是这个base,传入的*h没有使用,继续来看下*base的结构tvec_base :

 
  1. struct tvec_base {

  2. spinlock_t lock;

  3. struct timer_list *running_timer;

  4. unsigned long timer_jiffies;

  5. unsigned long next_timer;

  6. unsigned long active_timers;

  7. unsigned long all_timers;

  8. int cpu; // 跟踪所在的CPU是哪个核,这里是CPU 4

  9. struct tvec_root tv1;

  10. struct tvec tv2;

  11. struct tvec tv3;

  12. struct tvec tv4;

  13. struct tvec tv5;

  14. } ____cacheline_aligned;

这里就看到,*base的结构里面有个 struct timer_list * 的结构,我们继续看它的结构是怎么样的:

 
  1. struct timer_list {

  2. /*

  3. * All fields that change during normal runtime grouped to the

  4. * same cacheline

  5. */

  6. struct list_head entry;

  7. unsigned long expires;

  8. struct tvec_base *base;

  9. void (*function)(unsigned long);

  10. unsigned long data;

  11. int slack;

  12. ...

这个就是内核里面实现定时器标准的数据结构了,其中function这个指针就是time out后执行的callback,而且异常发生在CPU 4,

了解这些数据结构背景后就可以借助T32工具来分析了,既然这个定时器发生了异常,那么我们最想知道的是这是哪一个定时器?

它在源码中的定义是在哪个文件哪个行号?这些都可以借助Trace32工具来获取.

首先我们查看*running_timer的数据内容:

工具栏 调出:view -> Watch,输入:(struct timer_list *)0xffffffc001741120

复制出来就是这样:

 
  1. (struct timer_list *)0xffffffc001741120 = 0xFFFFFFC001741120 -> (

  2. entry = (next = 0xFFFFFFC0B27BC9B8, prev = 0xFFFFFFC0B27BC9B8),

  3. expires = 4294979736,

  4. base = 0xFFFFFFC0B27BC000,

  5. function = 0xFFFFFFC0006E277C,

  6. data = 0,

  7. slack = -1,

  8. start_pid = -1,

  9. start_site = 0x0,

  10. start_comm = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))

这个就是发生异常的那个timer的数据结构实例,我们最希望的就是希望可以通过这里的数据信息找到它在源码的位置,然后进一步分析它,使用Trace32的dump分析功能就可以做到这点。

菜单栏调出:view -》dump

输入地址 0xffffffc001741120 然后点OK

右击高亮,选择view info:

同理,还可以看function的位置(0xFFFFFFC0006E277C):

上面所示,出异常的timer实例就是:fp_drv/m_timer, callback = timer_out_handle,

源码位置也给出来了,那么就可以着手修复问题了。

6、 同源不同表现

6.1 dmesg分析

导致Kernel panic的源码改动是同一处,但是死机后的表现却可能大不一样,像这个问题当时出现的时候有抓到两份ramdump,解开后却发现表现不一样(很可能每次出现死机都在不一样的地方),逆向推导过程总是需要耐心细心分析,加上一定的运气成分,那么就可以比较顺利定位问题点,简单介绍下:

解析ramdump后查看的dmesg是这样的:

 
  1. [ 256.595276] list_add corruption. prev->next should be next (ffffffc0ab1d4178), but was ffffffc0aba4c628. (prev=ffffffc001b77da8).

  2. [ 256.595282] Modules linked in: wlan(O)

  3. [ 256.595299] CPU: 5 PID: 6210 Comm: mdss_fb0 Tainted: G W O 3.18.31 #1

  4. [ 256.595305] Hardware name: Qualcomm Technologies, Inc. MSM8940-PMI8950 MTP (DT)

  5. [ 256.595311] Call trace:

  6. [ 256.595324] [<ffffffc000089b14>] dump_backtrace+0x0/0x270

  7. [ 256.595334] [<ffffffc000089d98>] show_stack+0x14/0x1c

  8. [ 256.595345] [<ffffffc000dbd254>] dump_stack+0x80/0xa4

  9. [ 256.595356] [<ffffffc0000a3240>] warn_slowpath_common+0x8c/0xb0

  10. [ 256.595366] [<ffffffc0000a32c4>] warn_slowpath_fmt+0x60/0x80

  11. [ 256.595374] [<ffffffc000345dd4>] __list_add+0x74/0xf0

  12. [ 256.595384] [<ffffffc00010356c>] __internal_add_timer+0xb4/0xbc

  13. [ 256.595392] [<ffffffc000103b30>] internal_add_timer+0x34/0x90

  14. [ 256.595404] [<ffffffc000dca78c>] schedule_timeout+0x1f0/0x278

  15. [ 256.595414] [<ffffffc0003e95f0>] mdss_mdp_cmd_wait4pingpong+0x12c/0x534

  16. [ 256.595424] [<ffffffc0003c1494>] mdss_mdp_display_wait4pingpong+0xd8/0x404

  17. [ 256.595432] [<ffffffc0003c2110>] mdss_mdp_display_commit+0x890/0x1128

  18. [ 256.595443] [<ffffffc0003f8c30>] mdss_mdp_overlay_kickoff+0x9ec/0x15f4

  19. [ 256.595453] [<ffffffc00043e098>] __mdss_fb_display_thread+0x278/0x4c4

  20. [ 256.595462] [<ffffffc0000beccc>] kthread+0xf0/0xf8

  21. [ 256.595468] ---[ end trace 02fd337171f1bd82 ]---

  22. [ 256.595498] ------------[ cut here ]------------

  23. [ 256.595506] kernel BUG at /home/android/work/prj/6901-7.1/LA.UM.5.6/LINUX/android/kernel/msm-3.18/lib/list_debug.c:40!

  24. [ 256.595513] Internal error: Oops - BUG: 0 [#1] PREEMPT SMP

  25. [ 256.595519] Modules linked in: wlan(O)

  26. [ 256.595533] CPU: 5 PID: 6210 Comm: mdss_fb0 Tainted: G W O 3.18.31 #1

  27. [ 256.595538] Hardware name: Qualcomm Technologies, Inc. MSM8940-PMI8950 MTP (DT)

  28. [ 256.595545] task: ffffffc02f5e6e00 ti: ffffffc002bdc000 task.ti: ffffffc002bdc000

  29. [ 256.595553] PC is at __list_add+0xcc/0xf0

  30. [ 256.595560] LR is at __list_add+0x74/0xf0

  31. [ 256.595567] pc : [<ffffffc000345e2c>] lr : [<ffffffc000345dd4>] pstate: 200001c5

看到红色部分的kernel BUG 我们就知道,这个属于内核主动上报异常的行为,我们看看list_debug.c:40 这里有什么?

如下双链表add实现的红色部分代码,明显就是触发了BUG_ON(x)的条件导致!

 
  1. void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next)

  2. {

  3. WARN(next->prev != prev,

  4. "list_add corruption. next->prev should be "

  5. "prev (%p), but was %p. (next=%p).\n",

  6. prev, next->prev, next);

  7. WARN(prev->next != next,

  8. "list_add corruption. prev->next should be "

  9. "next (%p), but was %p. (prev=%p).\n",

  10. next, prev->next, prev);

  11. WARN(new == prev || new == next,

  12. "list_add double add: new=%p, prev=%p, next=%p.\n",

  13. new, prev, next);

  14.  
  15. BUG_ON((prev->next != next || next->prev != prev ||

  16. new == prev || new == next) && PANIC_CORRUPTION);

  17.  
  18. next->prev = new;

  19. new->next = next;

  20. new->prev = prev;

  21. prev->next = new;

  22. }

这个内核双链表标准的插入实现,这个BUG_ON条件满足被触发了Panic所致,表示参数传的有错误。继续看log发现:

[  256.595299] CPU: 5 PID: 6210 Comm: mdss_fb0 Tainted: G        W  O   3.18.31 #1

异常发生在CPU5这个核,进程是mdss_fb0,这个是属于显示相关的进程,一般我们是动不到的,初步判断是别的地方改动埋的雷导致mdss_fb0整个进程在执行的时候炸了的过程。

那么我们要做的就是如何从这个爆炸现场到推出埋雷的地方。

6.2 Trace32分析

打开Trace32,输入 v.f 回车,将call frame内容贴出来:

 
  1. -000|ipi_cpu_stop(inline)

  2. -000|handle_IPI(ipinr = 3, regs = 0xFFFFFFC0A3BF36E0)

  3. -001|gic_handle_irq(regs = 0xFFFFFFC0A3BF36E0)

  4. -002|el1_irq(asm)

  5. -->|exception

  6. -003|debug_check_no_obj_freed(address = 0xFFFFFFC028805C00, size = 128)

  7. -004|current_thread_info(inline)

  8. -004|preempt_count(inline)

  9. -004|should_resched(inline)

  10. -004|slab_free(inline)

  11. -004|kfree(x = 0xFFFFFFC028805C00)

  12. -005|ext4_ext_map_blocks(handle = 0x0, inode = 0xFFFFFFC0A3FA8D50, map = 0xFFFFFFC0A3BF3A80, ?)

  13. -006|ext4_map_blocks(handle = 0x0, inode = 0x80, map = 0xFFFFFFC0A3BF3A80, flags = 12)

  14. -007|ext4_get_block(inode = 0xFFFFFFC0A3FA8D50, ?, bh = 0xFFFFFFC0A3BF3AD0, flags = 0)

  15. -008|ext4_get_block(?, ?, ?, ?)

  16. -009|generic_block_bmap(?, ?, ?)

  17. -010|ext4_bmap(mapping = 0xFFFFFFC0A3FA8ED0, block = 12712)

  18. -011|bmap(?, block = 128)

  19. -012|jbd2_journal_bmap(journal = 0xFFFFFFC0A41F3900, ?, retp = 0xFFFFFFC0A3BF3CC0)

  20. -013|jbd2_journal_next_log_block(journal = 0xFFFFFFC0A41F3900, retp = 0xFFFFFFC0A3BF3CC0)

  21. -014|jbd2_journal_commit_transaction(journal = 0xFFFFFFC0A41F3900)

  22. -015|kjournald2(arg = 0xFFFFFFC0A41F3900)

  23. -016|kthread(_create = 0xFFFFFFC0A41E4400)

  24. -017|ret_from_fork(asm)

  25. ---|end of frame

这个call frame看上去还挺正常的,属于正常的系统异常切换操作?怎么发现跟dmesg里面看到的call statck不对???

我们来看下加载脚本:t32_startup_script.cmm

 
  1. title "/home/android/share/fp-quick-wakeup/crash/Port_COM29-0813/out"

  2. sys.cpu CORTEXA53

  3. sys.up

  4. data.load.binary /home/android/share/fp-quick-wakeup/crash/Port_COM29-0813/OCIMEM.BIN 0x8600000

  5. data.load.binary /home/android/share/fp-quick-wakeup/crash/Port_COM29-0813/DDRCS0.BIN 0x40000000

  6. data.load.binary /home/android/share/fp-quick-wakeup/crash/Port_COM29-0813/DDRCS1.BIN 0x80000000

  7. Register.Set NS 1

  8. Data.Set SPR:0x30201 %Quad 0x41c22000

  9. Data.Set SPR:0x30202 %Quad 0x00000032B5193519

  10. Data.Set SPR:0x30A20 %Quad 0x000000FF440C0400

  11. Data.Set SPR:0x30A30 %Quad 0x0000000000000000

  12. Data.Set SPR:0x30100 %Quad 0x0000000004C5D93D

  13. Register.Set CPSR 0x3C5

  14. MMU.Delete

  15. MMU.SCAN PT 0xFFFFFF8000000000--0xFFFFFFFFFFFFFFFF

  16. mmu.on

  17. mmu.pt.list 0xffffff8000000000

  18. data.load.elf /home/android/share/fp-quick-wakeup/crash/Port_COM29-0813/vmlinux /nocode

  19. task.config /opt/t32/demo/arm64/kernel/linux/linux-3.x/linux3.t32

  20. menu.reprogram /opt/t32/demo/arm64/kernel/linux/linux-3.x/linux.men

  21. task.dtask

  22. v.v %ASCII %STRING linux_banner

  23. do /home/android/share/fp-quick-wakeup/crash/Port_COM29-0813/out/core0_regs.cmm

上面可以看到脚本里面配置trace32默认加载的是CPU 0的上下文,而我们从dmesg看到的panic异常是发生在CPU 5,需要手动切换到CPU 5的上下文:

执行:do core1_regs.cmm

(这里有必要需要说明一下,按一般理解应该执行do core5_xx, 但是我发现这样还不对,只能一个个试验了,发现刚好是core1的这个,暂时不知道具体原因)

 
  1. -000|arch_counter_get_cntvct_cp15()

  2. -001|arch_counter_get_cntvct()

  3. -002|__delay(cycles = 19200)

  4. -003|__const_udelay(?)

  5. -004|msm_trigger_wdog_bite()

  6. -005|do_msm_restart(?, cmd = 0x0)

  7. -006|machine_restart(?)

  8. -007|emergency_restart()

  9. -008|panic(?)

  10. -009|oops_end(inline)

  11. -009|die(?, regs = 0xFFFFFFC002BDF870, err = 0)

  12. -010|arm64_notify_die(?, ?, ?, ?)

  13. -011|do_undefinstr(regs = 0xFFFFFFC002BDF870) --- 发生未定义指令异常 BUG()

  14. -012|__list_add(new = 0xFFFFFFC002BDF990, prev = 0xFFFFFFC001B77DA8, next = 0xFFFFFFC0AB1D4178)

  15. -013|__internal_add_timer(?, ?)

  16. -014|tbase_get_deferrable(inline)

  17. -014|internal_add_timer(base = 0xFFFFFFC0AB1D4000, timer = 0x0124F800)

  18. -015|spin_unlock_irqrestore(inline)

  19. -015|__mod_timer(inline)

  20. -015|schedule_timeout(?)

  21. -016|mdss_mdp_cmd_wait4pingpong(ctl = 0xFFFFFFC0A791A318, ?)

  22. -017|mdss_mdp_display_wait4pingpong(ctl = 0xFFFFFFC0A791A318, use_lock = FALSE)

  23. -018|mdss_mdp_display_commit(ctl = 0xFFFFFFC0A791A318, ?, commit_cb = 0xFFFFFFC002BDFD38)

  24. -019|mdss_mdp_overlay_kickoff(mfd = 0xFFFFFFC0A7B08360, ?)

  25. -020|__mdss_fb_perform_commit(inline)

  26. -020|__mdss_fb_display_thread(data = 0xFFFFFFC0A7B08360)

  27. -021|kthread(_create = 0xFFFFFFC05ABCE200)

  28. -022|ret_from_fork(asm)

  29. ---|end of frame

勾选Local、Caller 分析:

双链表的结构有前驱next指针后继prev指针,所以prev->next == next && next->prev == prev,这个条件是必现要成立的,我们来看这里的情况:

 
  1. -012|__list_add(

  2. | new = 0xFFFFFFC002BDF990 -> (

  3. | next = 0xFFFFFFC002BDF9C0,

  4. | prev = 0xFFFFFFC00010356C),

  5. | prev = 0xFFFFFFC001B77DA8 -> (

  6. | next = 0xFFFFFFC0ABA4C628,

  7. | prev = 0xFFFFFFC0ABA4C628),

  8. | next = 0xFFFFFFC0AB1D4178 -> (

  9. | next = 0xFFFFFFC001B77DA8,

  10. | prev = 0xFFFFFFC001B77DA8))

很显然,prev == next->prev, 但是 prev->netxt != next, 所以导致了上面的BUG_ON(1)异常!.

所以出问题的就是这个prev,我们来看看这个prev具体是什么内容?

前面看过timer的结构知道,这个prev其实就是指向一个struct timer_list *的指针,我们看下这个结构的内容是什么,

调出 view -> Watch 窗口:

 
  1. (struct timer_list *)0xFFFFFFC001B77DA8 = 0xFFFFFFC001B77DA8 -> (

  2. entry = (next = 0xFFFFFFC0ABA4C628, prev = 0xFFFFFFC0ABA4C628),

  3. expires = 4294963038,

  4. base = 0xFFFFFFC0ABA4C000,

  5. function_=_0xFFFFFFC00077D4A8,

  6. data = 0,

  7. slack = -1,

  8. start_pid = -1,

  9. start_site = 0x0,

  10. start_comm = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))

同上:view -> dump :0xFFFFFFC00077D4A8

 
  1. address

  2. NSD:FFFFFFC00077D4A8

  3. address_info

  4. attr: AARCH64

  5. frame:RET:X30 CFA:SP+0x0

  6. line

  7. \\vmlinux\fp_drv \277--278 /home/android/work/prj/6901-7.1/LA.UM.5.6/LINUX/android/vendor/ti

  8. ----------------------------------------------------------------------------------------------------

  9. P:FFFFFFC00077D4A8--FFFFFFC00077D4AB

  10. function

  11. \\vmlinux\fp_drv\timer_out_handle

  12. ...

如此,很清晰的显示了出问题的源码位置,到这里,异常定位分析就已经基本完成了,完成了“追根溯源”,

剩下的就是去分析代码出解决方案了。

猜你喜欢

转载自blog.csdn.net/sdkdlwk/article/details/82733956