kernel panic流程分析

我们在项目开发过程中,很多时候会出现由于某种原因经常会导致手机系统死机重启的情况(重启分android重启跟kernel重启,而我们这里只讨论kernel重启也就是 kernel panic 的情况),死机重启基本算是影响最严重的系统问题了,有稳定复现的,也有概率出现的,解题难度也千差万别,出现问题后,通常我们会拿到类似这样的kernel log信息(下面log仅以调用bug()为例,其它异常所致的死机log信息会有一些不同之处):
[ 2.052157] -(2)[1:swapper/0]------------[ cut here ]------------[ 2.052163] -(2)[1:swapper/0]kernel bug at c04289dc [verbose debug info unavailable][ 2.052169] -(2)[1:swapper/0]internal error: oops - bug: 0 [#1] preempt smp arm[ 2.052178] -(2)[1:swapper/0]disable aee kernel api[ 3.052192] -(2)[1:swapper/0]non-crashing cpus did not react to ipi[ 3.052204] -(2)[1:swapper/0]cpu: 2 pid: 1 comm: swapper/0 tainted: g w 3.18.35+ #3[ 3.052211] -(2)[1:swapper/0]task: df060000 ti: df04a000 task.ti: df04a000[ 3.052227] -(2)[1:swapper/0]pc is at ltr553_i2c_probe+0x94/0x9c[ 3.052233] -(2)[1:swapper/0]lr is at 0x0[ 3.052242] -(2)[1:swapper/0]pc : [] lr : [] psr: a0000113[ 3.052242] sp : df04bd30 ip : 00000000 fp : df04bd4c[ 3.052249] -(2)[1:swapper/0]r10: 00000003 r9 : de348fc0 r8 : c0428948[ 3.052255] -(2)[1:swapper/0]r7 : dea1bc00 r6 : dea1bc04 r5 : dea1bc20 r4 : c0b53358[ 3.052262] -(2)[1:swapper/0]r3 : c115ef4c r2 : 00000000 r1 : 00000000 r0 : de366a00[ 4.354655] -(2)[1:swapper/0] oops_end, 1, 11[ 4.354740] -(2)[1:swapper/0]kernel panic - not syncing: fatal exception这是linux 内核在死机之前输出的相关重要信息,包括pc指针、调用栈等在内的非常重要的便于debug的线索,比如我们可以借助gun tools(add2line)工具结合内核符号映射表vmlinux来定位当前pc指针所在的代码具体行数(定位到出错代码行并不意味着就找到了问题的根本原因跟修复异常,这个需要根据异常的复杂程度而论)。深入理解这些关键打印log信息的含义和机制非常有助于我们对于此类死机问题的定位和分析(对于内存被踩、硬件不稳定导致的一类问题分析有局限性),这也是我们需要深入学习内核异常流程的初衷。
这里我们必须弄清楚几个问题:
这些死机前留下的关键register信息是怎么来的,有什么用,具体含义是什么?如何利用这些遗留的线索找到出问题代码具体在哪支文件,在哪一行?内核发生致命异常到死机的总流程是怎样的,类似死机问题应该如何着手分析?为此,本文就从最常见的主动触发bug()为例解析上面的疑问及分析整个kernel panic流程。
什么是bug() ?有过驱动调试经验的人肯定都知道这个东西,这里的bug跟我们一般认为的“软件缺陷”可不是一回事,这里说的bug()其实是linux kernel中用于拦截内核程序超出预期的行为,属于软件主动汇报异常的一种机制。这里有个疑问,就是什么时候会用到呢?一般来说有两种用到的情况,一是软件开发过程中,若发现代码逻辑出现致命fault后就可以调用bug()让kernel死掉(类似于assert),这样方便于定位问题,从而修正代码执行逻辑;另外一种情况就是,由于某种特殊原因(通常是为了debug而需抓ramdump),我们需要系统进入kernel panic的情况下使用。
bug()跟bug_on(1)其实本质是一回事,后者只是在前者的基础上做了简单的封装而已,bug()的实现 本质是埋入一条未定义指令:0xe7f001f2,触发arm发起undefined instruction异常(ps:arm有分10种异常类型,详细可以复习arm异常模型章节)。
#define bug_instr_value 0xe7f001f2#define bug_instr(__value) __inst_arm(__value)#define bug() _bug(__file__, __line__, bug_instr_value)#define _bug(file, line, value) __bug(file, line, value)#define __bug(__file, __line, __value) \\do { \\ asm volatile(bug_instr(__value) \\n); \\ unreachable(); \\} while (0)bug() 流程分析bug()到系统重启的总流程图:
调用bug()会向cpu下发一条未定义指令而触发arm发起未定义指令异常,随后进入kernel异常处理流程历经 oops,die(),__die()等流程输出用于调试分析的关键线索,最后进入panic()结束自己再获得重生的过程,这个就是整个过程的基本流程,下面先来看die()具体做了什么呢?
die() 流程源码:
void die(const char *str, struct pt_regs *regs, int err){ enum bug_trap_type bug_type = bug_trap_type_none; unsigned long flags = oops_begin(); int sig = sigsegv; if (!user_mode(regs)) bug_type = report_bug(regs->arm_pc, regs); if (bug_type != bug_trap_type_none) str = oops - bug; if (__die(str, err, regs)) sig = 0; oops_end(flags, regs, sig);}总流程大致如下:
通常来说,代码分析过程结合kernel log一起看会理解来得更加深刻,如果是bug()/bug_on(1)导致的异常,那么走到report_bug 就可以看到下面标志性 log:
enum bug_trap_type report_bug(unsigned long bugaddr, struct pt_regs *regs) {... if (!is_valid_bugaddr(bugaddr)) return bug_trap_type_none;... printk(kern_default ------------[ cut here ]------------\\n); if (file) pr_crit(kernel bug at %s:%u!\\n, file, line); else pr_crit(kernel bug at %p [verbose debug info unavailable]\\n, (void *)bugaddr); ===>[ 2.052157] -(2)[1:swapper/0]------------[ cut here ]------------[ 2.052163] -(2)[1:swapper/0]kernel bug at c04289dc [verbose debug info unavailable][ 2.052169] -(2)[1:swapper/0]internal error: oops - bug: 0 [#1] preempt smp arm所以如果在log中看到了这个 “[ cut here ]” 的信息就推断是软件发生致命fault而主动call了bug()所致的系统重启了,就可以根据相关信息尝试定位分析修复异常了.这里要注意的是还有另外一种__warn()的情况也会打印出 “[ cut here ]” 的标志性log但是内核并不会挂掉,容易造成误导:
#define __warn() warn_slowpath_null(__file__, __line__)#define __warn_printf(arg...) warn_slowpath_fmt(__file__, __line__, arg)void warn_slowpath_fmt(const char *file, int line, const char *fmt, ...){... warn_slowpath_common(file, line, __builtin_return_address(0), taint_warn, &args);static void warn_slowpath_common(const char *file, int line, void *caller, unsigned taint, struct slowpath_args *args){... pr_warn(------------[ cut here ]------------\\n); pr_warn(warning: cpu: %d pid: %d at %s:%d %ps()\\n, raw_smp_processor_id(), current->pid, file, line, caller); ===>[ 1.106219] -(2)[1:swapper/0]------------[ cut here ]------------[ 1.107018] -(2)[1:swapper/0]warning: cpu: 2 pid: 1 at /home/android/work/prj/n-6580/kernel-3.18/kernel/irq/manage.c:454 __enable_irq+0x50/0x8c()所以其实从显现上很好区分两种情况,如果是bug()/bug_on(1)那么内核一定会挂掉重启,而__warn()只会dump_stack()而不会死机, 从源码跟log信息也可以容易区分两种情况,如果是bug()/bug_on(1)的话一定有类似下面的log输出,只要搜索关键字:“internal error: oops” 即可。
[ 2.052163] -(2)[1:swapper/0]kernel bug at c04289dc [verbose debug info unavailable][ 2.052169] -(2)[1:swapper/0]internal error: oops - bug: 0 [#1] preempt smp arm__die() 流程分析从上面输出的log信息还不足以定位具体出问题的代码位置,包括定位异常所需要的最关键的 pc指针、调用栈等这些对于调试来说至关重要的线索信息都是在__die()中输出。
流程图:
打印出标志性log信息:
static int __die(const char *str, int err, struct pt_regs *regs){... printk(kern_emerg internal error: %s: %x [#%d] s_preempt s_smp s_isa \\n, str, err, ++die_counter);===>[ 2.052169] -(2)[1:swapper/0]internal error: oops - bug: 0 [#1] preempt smp armlog 显示异常str是oops - bug,error-code 为0,die计数器次数:1
oops 的本意为 “哎呀” 的一个俚语,这里意形象的意指kernel出现了一件意外而不知道该如何处理的事件。
notify_die() 会通知对oops感兴趣的模块执行相关回调,比如mtk的aee异常引擎模块就是通过注册到die_chain通知链上的。
int notrace notify_die(enum die_val val, const char *str, struct pt_regs *regs, long err, int trap, int sig){ struct die_args args = { .regs = regs, .str = str, .err = err, .trapnr = trap, .signr = sig, }; return atomic_notifier_call_chain(&die_chain, val, &args);}mtk的aee异常引擎在kernel初始化的时候会去注册到die_chain通知链,而且我们可以看到其实还注册了panic通知链。
int __init aee_ipanic_init(void){ spin_lock_init(&ipanic_lock); mrdump_init(); atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); register_die_notifier(&die_blk); register_ipanic_ops(&ipanic_oops_ops); ipanic_log_temp_init(); ipanic_msdc_init(); logi(ipanic: startup, partition assgined %s\\n, aee_ipanic_plabel); return 0;}而对我们调试追踪有用的关键信息是在 __show_regs() 里面打印的:
void __show_regs(struct pt_regs *regs){ unsigned long flags; char buf[64]; show_regs_print_info(kern_default); print_symbol(pc is at %s\\n, instruction_pointer(regs)); print_symbol(lr is at %s\\n, regs->arm_lr); printk(pc : [] lr : [] psr: %08lx\\n sp : %08lx ip : %08lx fp : %08lx\\n, regs->arm_pc, regs->arm_lr, regs->arm_cpsr, regs->arm_sp, regs->arm_ip, regs->arm_fp); printk(r10: %08lx r9 : %08lx r8 : %08lx\\n, regs->arm_r10, regs->arm_r9, regs->arm_r8); printk(r7 : %08lx r6 : %08lx r5 : %08lx r4 : %08lx\\n, regs->arm_r7, regs->arm_r6, regs->arm_r5, regs->arm_r4); printk(r3 : %08lx r2 : %08lx r1 : %08lx r0 : %08lx\\n, regs->arm_r3, regs->arm_r2, regs->arm_r1, regs->arm_r0); flags = regs->arm_cpsr; buf[0] = flags & psr_n_bit ? 'n' : 'n'; buf[1] = flags & psr_z_bit ? 'z' : 'z'; buf[2] = flags & psr_c_bit ? 'c' : 'c'; buf[3] = flags & psr_v_bit ? 'v' : 'v'; buf[4] = '\\0'; printk(flags: %s irqs o%s fiqs o%s mode %s isa %s segment %s\\n, buf, interrupts_enabled(regs) ? n : ff, fast_interrupts_enabled(regs) ? n : ff, processor_modes[processor_mode(regs)], isa_modes[isa_mode(regs)], get_fs() == get_ds() ? kernel : user); show_extra_register_data(regs, 128);}void dump_stack_print_info(const char *log_lvl){ printk(%scpu: %d pid: %d comm: %.20s %s %s %.*s\\n, log_lvl, raw_smp_processor_id(), current->pid, current->comm, print_tainted(), init_utsname()->release, (int)strcspn(init_utsname()->version, ), init_utsname()->version);...这里打印出了重要的pc停下的位置、相关寄存器信息,发生的是user还是kernel的异常、发生异常的cpu、进程pid等信息。
接下来 dump_mem() 用于dump出当前线程的内存信息:
dump_mem(kern_emerg, stack: , regs->arm_sp, thread_size + (unsigned long)task_stack_page(tsk));使用 dump_backtrace(regs, tsk) 打印出调试最直观的调用栈信息:
[ 3.056363] -(2)[1:swapper/0]backtrace: -(2)[1:swapper/0][ 3.056386] -(2)[1:swapper/0][] (dump_backtrace) from [] (show_stack+0x18/0x1c) [ 3.056393] -(2)[1:swapper/0] r6:c103d790-(2)[1:swapper/0] r5:ffffffff-(2)[1:swapper/0] r4:00000000-(2)[1:swapper/0] r3:00000000-(2)[1:swapper/0] [ 3.056426] -(2)[1:swapper/0][] (show_stack) from [] (dump_stack+0x90/0xa4) [ 3.056439] -(2)[1:swapper/0][] (dump_stack) from [] (ipan6503] -(2)[1:swapper/0][] (notifier_call_chain) from [] (atomic_notifier_call_chain+0x3c/0x50) [ 3.056509] -(2)[1:swapper/0] r10:c10efec4-(2)[1:swapper/0] r9:df060000-(2)[1:swapper/0] r8:df04a020-(2)[1:swapper/0] r7:c0caaaf0-(2)[1:swapper/0] r6:c10f0c88-(2)[1:swapper/0] r5:00000001 [ 3.056539] -(2)[1:swapper/0] r4:df04bb74-(2)[1:swapper/0] [ 3.056554] -(2)[1:swapper/0][] (atomic_notifier_call_chain) from [] (notify_die+0x44/0x4c) [ 3.056560] -(2)[1:swapper/0] r6:df04bce8-(2)[1:swapper/0] r5:00000000-(2)[1:swapper/0] r4:00000001-(2)[1:swapper/0] [ 3.056585] -(2)[1:swapper/0][] (notify_die) from [] (die+0x114/0x41c) [ 3.056590] -(2)[1:swapper/0] r4:c102826c-(2)[1:swapper/0] [ 3.056607] -(2)[1:swapper/0][] (die) from [] (arm_notify_die+0x24/0x5c) [ 3.056612] -(2)[1:swapper/0] r10:df04a000-(2)[1:swapper/0] r9:00000000-(2)[1:swapper/0] r8:df04bce8-(2)[1:swapper/0] r7:e7f001f2-(2)[1:swapper/0] r6:df04a000-(2)[1:swapper/0] r5:c04289dc [ 3.056642] -(2)[1:swapper/0] r4:00000004-(2)[1:swapper/0] [ 3.056658] -(2)[1:swapper/0][] (arm_notify_die) from [] (do_undefinstr+0x1a4/0x1ec) [ 3.056670] -(2)[1:swapper/0][] (do_undefinstr) from [] (__und_svc_finish+0x0/0x34) [ 3.056676] -(2)[1:swapper/0]exception stack(0xdf04bce8 to 0xdf04bd30) [ 3.056687] -(2)[1:swapper/0]bce0: de366a00 00000000 00000000 c115ef4c c0b53358 dea1bc20 [ 3.056698] -(2)[1:swapper/0]bd00: dea1bc04 dea1bc00 c0428948 de348fc0 00000003 df04bd4c 00000000 df04bd30 [ 3.056706] -(2)[1:swapper/0]bd20: 00000000 c04289dc a0000113 ffffffff [ 3.056711] -(2)[1:swapper/0] r9:c010c98c-(2)[1:swapper/0] r8:e7100000-(2)[1:swapper/0] r7:00000000-(2)[1:swapper/0] r6:c010cd98-(2)[1:swapper/0] r5:00000000-(2)[1:swapper/0] r4:c04289e0 [ 3.056750] -(2)[1:swapper/0][] (ltr553_i2c_probe) from [] (i2c_device_probe+0xd0/0x12c) [ 3.056756] -(2)[1:swapper/0] r5:dea1bc20-(2)[1:swapper/0] r4:c0b53358-(2)[1:swapper/0] [ 3.056778] -(2)[1:swapper/0][] (i2c_device_probe) from [] (driver_probe_device+0x160/0x43c)通过上面的调用栈信息结合gun tools(add2line)基本就可以定位发生异常的具体代码位置了。
最后会通过dump_instr(kern_emerg, regs) 打印出pc指针和前4条指令:
static void dump_instr(const char *lvl, struct pt_regs *regs){... for (i = -4; i [ 3.226706] -(2)[1:swapper/0][] (/0]code: e89da830 e30e3f4c e34c3115 e5830000 (e7f001f2)看到这个 e7f001f2 了吧,是不是很眼熟?这个就是bug()中埋入的未定义指令!
到这一步,大部分关键信息都已经输出了,可以通过add2line工具定位出具体死在的代码行号,大致看看发生了什么,如果是bug()导致的异常,那么就可以考虑分析和修复异常了,因为bug()属于主动汇报异常,一般来说debug难度会相对其它的被动上报方式容易得多.
例如:
从上面log知pc死在的地址,通过add2line工具结合内核符号映射表 vmlinux 就可以定位出具体代码所在文件行号:
arm-linux-androideabi-addr2line -e out/target/product/$project/obj/kernel_obj/vmlinux -f -c c04289dcltr553_i2c_probe/aosp/kernel-3.18/drivers/misc/mediatek/alsps/ltr553/ltr553.c:3278定位到了具体代码行号就可以进一步分析代码log找出问题原因修复异常了(一般来说bug()导致的异常比较好解,其它的情况难度就是天差地别了..)。 那么接下来kernel要干什么呢?重要信息都输出完了接下来就直接走 kernel panic 流程了.
panic 流程panic 本意是“恐慌”的意思,这里意旨kernel发生了致命错误导致无法继续运行下去的情况。
流程图:
最后附上总时序图:

VR展厅全景线上虚拟展厅的功能及应用的介绍
三星率先支持5G超级SIM卡,三星S20系列抢占市场先机
PCB板材有铅喷锡和无铅喷锡谁更好
谷歌又推新硬件:“白板”也可以很高科技
Freescale MCF5441x系列产品的主要功能特性及应用电路
kernel panic流程分析
野外作战时既能保护眼睛又可实时获取信息的重要装备——AR战术眼镜
Golang函数式编程简述
传感器巨头,终止激光雷达开发计划!
华为的救赎从史上最强大的Mate手机开始
小型功率放大器电路仿真模型的搭建
旧家电回收能否撬动家电换新,助力家电行业市场发展
什么是COP?能为开发者带来哪些优势?如何选择最佳COP器件?
【分享】什么是串口通信?串口常见通信问题如何解决?
中国移动推动“网络+云+资源”深度融合,满足不同客户按单点菜的需求
机器人浮动主轴打磨去毛刺工具及应用案例
腾讯云区块链服务TBaaS架构介绍
云南联通混改模式是否有望在全国范围内进一步推广?
加速度计与MEMS明日之星
C语言函数参数介绍