您的位置:首页 > 运维架构 > 网站架构

【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Oops在Linux 2.6内核+PowerPC架构下的前世今生

2010-01-31 01:33 1061 查看
Oops在Linux 2.6内核+PowerPC架构下的前世今生
Sailor_forever sailing_9806#163.com

(本原创文章发表于Sailor_forever 的个人blog,未经本人许可,不得用于商业用途。任何个人、媒体、其他网站不得私自抄袭;网络媒体转载请注明出处,增加原文链接,否则属于侵权行为。如有任何问题,请留言或者发邮件给sailing_9806#163.com)
http://blog.csdn.net/sailor_8318/archive/2010/01/31/5273890.aspx

【摘要】本文详细分析了2.6内核下Oops的来龙去脉,重点介绍了Oops的转储机制。首先介绍了Oops的基本概念、内核的异常级别及PowerPC的异常类型,接着介绍了Oops的处理流程。基于这个流程和嵌入式系统的文件系统及存储介质的特点,详细介绍了用户态和内核态中Oops的转储机制。最后介绍了PowerPC的EABI规范及如何根据此规则分析Oops log信息。

【关键字】Oops 转储,panic,backtrace,syslogd,klogd,PowerPC, EABI

目录

1 何谓OOPS 2
2 内核的异常级别 3
2.1 Bug 3
2.2 Oops 3
2.3 Panic 4
3 PowerPC的异常类型 4
4 OOPS的处理流程 6
4.1 异常入口 6
4.2 Die 10
4.3 Panic 15
5 OOPS的记录及转储 17
5.1 Printk 17
5.2 符号化记录backtrace 17
5.3 Klogd 18
5.4 Syslogd 18
5.5 用户态OOPS转储 18
5.6 内核态OOPS转储 19
5.6.1 进程上下文 19
5.6.2 中断上下文 25
6 PowerPC的EABI规范 27
6.1 寄存器的使用规则 27
6.2 堆栈的结构 28
6.2.1 栈的增减规则 28
6.2.2 栈的结构 29
6.3 从反汇编来看EABI的实现 30
6.4 从show_stack来看函数是如何调用的 34
7 如何分析OOPS 35
7.1 如何分析backtrace 35
7.2 如何分析栈 35
8 Oops典型实例 35
9 参考资料 35

1 何谓OOPS
Oops是美国人比较常有的口语。就是有点意外,吃惊,或突然的意思。“Oops”并不是很严重,正如在Britney Spears的 “Oops I Did It Again”那首歌的歌词中,也是一种轻描淡写,有时含有抱歉的意思。

http://v.youku.com/v_show/id_XMTM0ODgxMDYw.html

对于Linux内核来说,Oops就意外着内核出了异常,此时会将产生异常时CPU的状态,出错的指令地址、数据地址及其他寄存器,函数调用的顺序甚至是栈里面的内容都打印出来,然后根据异常的严重程度来决定下一步的操作:杀死导致异常的进程或者挂起系统。

最典型的异常是在内核态引用了一个非法地址,通常是未初始化的野指针Null,这将导致页表异常,最终引发Oops。

Linux系统足够健壮,能够正常的反应各种异常。异常通常导致当前进程的死亡,而系统依然能够继续运转,但是这种运转都处在一种不稳定的状态,随时可能出问题。对于中断上下文的异常及系统关键资源的破坏,通常会导致内核挂起,不再响应任何事件。

2 内核的异常级别
2.1 Bug
Bug是指那些不符合内核的正常设计,但内核能够检测出来并且对系统运行不会产生影响的问题,比如在原子上下文中休眠。如:
BUG: scheduling while atomic: insmod/826/0x00000002
Call Trace:
[ef12f700] [c00081e0] show_stack+0x3c/0x194 (unreliable)
[ef12f730] [c0019b2c] __schedule_bug+0x64/0x78
[ef12f750] [c0350f50] schedule+0x324/0x34c
[ef12f7a0] [c03515c0] schedule_timeout+0x68/0xe4
[ef12f7e0] [c027938c] fsl_elbc_run_command+0x138/0x1c0
[ef12f820] [c0275820] nand_do_read_ops+0x130/0x3dc
[ef12f880] [c0275ebc] nand_read+0xac/0xe0
[ef12f8b0] [c0262d98] part_read+0x5c/0xe4
[ef12f8c0] [c017bcac] jffs2_flash_read+0x68/0x254
[ef12f8f0] [c0170550] jffs2_read_dnode+0x60/0x304
[ef12f940] [c017088c] jffs2_read_inode_range+0x98/0x180
[ef12f970] [c016e610] jffs2_do_readpage_nolock+0x94/0x1ac
[ef12f990] [c016ee04] jffs2_write_begin+0x2b0/0x330
[ef12fa10] [c005144c] generic_file_buffered_write+0x11c/0x8d0
[ef12fab0] [c0051e48] __generic_file_aio_write_nolock+0x248/0x500
[ef12fb20] [c0052168] generic_file_aio_write+0x68/0x10c
[ef12fb50] [c007ca80] do_sync_write+0xc4/0x138
[ef12fc10] [f107c0dc] oops_log+0xdc/0x1e8 [oopslog]
[ef12fe70] [f3087058] oops_log_init+0x58/0xa0 [oopslog]
[ef12fe80] [c00477bc] sys_init_module+0x130/0x17dc
[ef12ff40] [c00104b0] ret_from_syscall+0x0/0x38
--- Exception: c01 at 0xff29658
LR = 0x10031300

2.2 Oops
程序在内核态时,进入一种异常情况,比如引用非法指针导致的数据异常,数组越界导致的取指异常,此时异常处理机制能够捕获此异常,并将系统关键信息打印到串口上,正常情况下Oops消息会被记录到系统日志中去。

Oops发生时,进程处在内核态,很可能正在访问系统关键资源,并且获取了一些锁,当进程由于Oops异常退出时,无法释放已经获取的资源,导致其他需要获取此资源的进程挂起,对系统的正常运行造成影响。通常这种情况,系统处在不稳定的状态,很可能崩溃。

2.3 Panic
当Oops发生在中断上下文中或者在进程0和1中,系统将彻底挂起,因为中断服务程序异常后,将无法恢复,这种情况即称为内核panic。另外当系统设置了panic标志时,无论Oops发生在中断上下文还是进程上下文,都将导致内核Panic。由于在中断复位程序中panic后,系统将不再进行调度,Syslogd将不会再运行,因此这种情况下,Oops的消息仅仅打印到串口上,不会被记录在系统日志中。

3 PowerPC的异常类型
32位处理器通常有各种异常机制来响应系统运行过程中的异常。以MPC8378为例,其异常类型如下:

其中以200、300、400、1000、1100所代表的machine check,取指,取数及TLB等异常为主。大部分是非法地址及非法指令造成的。TLB miss经过简单的处理后会最终转向300和400异常处理。

4 OOPS的处理流程
4.1 异常入口
异常处理在内核中的相关代码为:
http://lxr.linux.no/#linux+v2.6.25/arch/powerpc/kernel/head_32.S#L384
汇编级的异常入口。
329/* Machine check */
330/*
331 * On CHRP, this is complicated by the fact that we could get a
332 * machine check inside RTAS, and we have no guarantee that certain
333 * critical registers will have the values we expect. The set of
334 * registers that might have bad values includes all the GPRs
335 * and all the BATs. We indicate that we are in RTAS by putting
336 * a non-zero value, the address of the exception frame to use,
337 * in SPRG2. The machine check handler checks SPRG2 and uses its
338 * value if it is non-zero. If we ever needed to free up SPRG2,
339 * we could use a field in the thread_info or thread_struct instead.
340 * (Other exception handlers assume that r1 is a valid kernel stack
341 * pointer when we take an exception from supervisor mode.)
342 * -- paulus.
343 */
344 . = 0x200
345 mtspr SPRN_SPRG0,r10
346 mtspr SPRN_SPRG1,r11
347 mfcr r10
348#ifdef CONFIG_PPC_CHRP
349 mfspr r11,SPRN_SPRG2
350 cmpwi 0,r11,0
351 bne 7f
352#endif /* CONFIG_PPC_CHRP */
353 EXCEPTION_PROLOG_1
3547: EXCEPTION_PROLOG_2
355 addi r3,r1,STACK_FRAME_OVERHEAD
356#ifdef CONFIG_PPC_CHRP
357 mfspr r4,SPRN_SPRG2
358 cmpwi cr1,r4,0
359 bne cr1,1f
360#endif
361 EXC_XFER_STD(0x200, machine_check_exception)
362#ifdef CONFIG_PPC_CHRP
3631: b machine_check_in_rtas
364#endif
365
366/* Data access exception. */
367 . = 0x300
368DataAccess:
369 EXCEPTION_PROLOG
370 mfspr r10,SPRN_DSISR
371 andis. r0,r10,0xa470 /* weird error? */
372 bne 1f /* if not, try to put a PTE */
373 mfspr r4,SPRN_DAR /* into the hash table */
374 rlwinm r3,r10,32-15,21,21 /* DSISR_STORE -> _PAGE_RW */
375 bl hash_page
3761: stw r10,_DSISR(r11)
377 mr r5,r10
378 mfspr r4,SPRN_DAR
379 EXC_XFER_EE_LITE(0x300, handle_page_fault)
380
381
382/* Instruction access exception. */
383 . = 0x400
384InstructionAccess:
385 EXCEPTION_PROLOG
386 andis. r0,r9,0x4000 /* no pte found? */
387 beq 1f /* if so, try to put a PTE */
388 li r3,0 /* into the hash table */
389 mr r4,r12 /* SRR0 is fault address */
390 bl hash_page
3911: mr r4,r12
392 mr r5,r9
393 EXC_XFER_EE_LITE(0x400, handle_page_fault)

handle_page_fault在
http://lxr.linux.no/#linux+v2.6.25/arch/powerpc/kernel/entry_32.S#L466
/*
464 * Top-level page fault handling.
465 * This is in assembler because if do_page_fault tells us that
466 * it is a bad kernel page fault, we want to save the non-volatile
467 * registers before calling bad_page_fault.
468 */
469 .globl handle_page_fault
470handle_page_fault:
471 stw r4,_DAR(r1)
472 addi r3,r1,STACK_FRAME_OVERHEAD
473 bl do_page_fault
474 cmpwi r3,0
475 beq+ ret_from_except
476 SAVE_NVGPRS(r1)
477 lwz r0,_TRAP(r1)
478 clrrwi r0,r0,1
479 stw r0,_TRAP(r1)
480 mr r5,r3
481 addi r3,r1,STACK_FRAME_OVERHEAD
482 lwz r4,_DAR(r1)
483 bl bad_page_fault
484 b ret_from_except_full

并最终调用bad_page_fault或者do_page_fault
http://lxr.linux.no/#linux+v2.6.25/arch/powerpc/mm/fault.c#L404
399/*
400 * bad_page_fault is called when we have a bad access from the kernel.
401 * It is called from the DSI and ISI handlers in head.S and from some
402 * of the procedures in traps.c.
403 */
404void bad_page_fault(struct pt_regs *regs, unsigned long address, int sig)
405{
406 const struct exception_table_entry *entry;
407
408 /* Are we prepared to handle this fault? */
409 if ((entry = search_exception_tables(regs->nip)) != NULL) {
410 regs->nip = entry->fixup;
411 return;
412 }
413
414 /* kernel has accessed a bad area */
415
416 switch (regs->trap) {
417 case 0x300:
418 case 0x380:
419 printk(KERN_ALERT "Unable to handle kernel paging request for "
420 "data at address 0x%08lx/n", regs->dar);
421 break;
422 case 0x400:
423 case 0x480:
424 printk(KERN_ALERT "Unable to handle kernel paging request for "
425 "instruction fetch/n");
426 break;
427 default:
428 printk(KERN_ALERT "Unable to handle kernel paging request for "
429 "unknown fault/n");
430 break;
431 }
432 printk(KERN_ALERT "Faulting instruction address: 0x%08lx/n",
433 regs->nip);
434
435 die("Kernel access of bad area", regs, sig);
436}

大多数Oops的提示信息都是Unable to handle kernel paging request。

另外一种常见错误是machine check
485void machine_check_exception(struct pt_regs *regs)
486{
487 int recover = 0;
488
489 /* See if any machine dependent calls. In theory, we would want
490 * to call the CPU first, and call the ppc_md. one if the CPU
491 * one returns a positive number. However there is existing code
492 * that assumes the board gets a first chance, so let's keep it
493 * that way for now and fix things later. --BenH.
494 */
495 if (ppc_md.machine_check_exception)
496 recover = ppc_md.machine_check_exception(regs);
497 else if (cur_cpu_spec->machine_check)
498 recover = cur_cpu_spec->machine_check(regs);
499
500 if (recover > 0)
501 return;
502
503 if (user_mode(regs)) {
504 regs->msr |= MSR_RI;
505 _exception(SIGBUS, regs, BUS_ADRERR, regs->nip);
506 return;
507 }
508
509#if defined(CONFIG_8xx) && defined(CONFIG_PCI)
510 /* the qspan pci read routines can cause machine checks -- Cort
511 *
512 * yuck !!! that totally needs to go away ! There are better ways
513 * to deal with that than having a wart in the mcheck handler.
514 * -- BenH
515 */
516 bad_page_fault(regs, regs->dar, SIGBUS);
517 return;
518#endif
519
520 if (debugger_fault_handler(regs)) {
521 regs->msr |= MSR_RI;
522 return;
523 }
524
525 if (check_io_access(regs))
526 return;
527
528 if (debugger_fault_handler(regs))
529 return;
530 die("Machine check", regs, SIGBUS);
531
532 /* Must die if the interrupt is not recoverable */
533 if (!(regs->msr & MSR_RI))
534 panic("Unrecoverable Machine check");
535}

4.2 Die
无论何种原因导致的异常,最终都将调用die统一处理。
http://lxr.linux.no/#linux+v2.6.25/arch/powerpc/kernel/traps.c#L97
97int die(const char *str, struct pt_regs *regs, long err)
98{
99 static struct {
100 spinlock_t lock;
101 u32 lock_owner;
102 int lock_owner_depth;
103 } die = {
104 .lock = __SPIN_LOCK_UNLOCKED(die.lock),
105 .lock_owner = -1,
106 .lock_owner_depth = 0
107 };
108 static int die_counter;
109 unsigned long flags;
110
111 if (debugger(regs))
112 return 1;
113
114 oops_enter();
115
116 if (die.lock_owner != raw_smp_processor_id()) {
117 console_verbose();
118 spin_lock_irqsave(&die.lock, flags);
119 die.lock_owner = smp_processor_id();
120 die.lock_owner_depth = 0;
121 bust_spinlocks(1);
122 if (machine_is(powermac))
123 pmac_backlight_unblank();
124 } else {
125 local_save_flags(flags);
126 }
127
128 if (++die.lock_owner_depth < 3) {
129 printk("Oops: %s, sig: %ld [#%d]/n", str, err, ++die_counter);
130#ifdef CONFIG_PREEMPT
131 printk("PREEMPT ");
132#endif
133#ifdef CONFIG_SMP
134 printk("SMP NR_CPUS=%d ", NR_CPUS);
135#endif
136#ifdef CONFIG_DEBUG_PAGEALLOC
137 printk("DEBUG_PAGEALLOC ");
138#endif
139#ifdef CONFIG_NUMA
140 printk("NUMA ");
141#endif
142 printk("%s/n", ppc_md.name ? ppc_md.name : "");
143
144 print_modules();
145 show_regs(regs);
146 } else {
147 printk("Recursive die() failure, output suppressed/n");
148 }
149
150 bust_spinlocks(0);
151 die.lock_owner = -1;
152 add_taint(TAINT_DIE);
153 spin_unlock_irqrestore(&die.lock, flags);
154
155 if (kexec_should_crash(current) ||
156 kexec_sr_activated(smp_processor_id()))
157 crash_kexec(regs);
158 crash_kexec_secondary(regs);
159
160 if (in_interrupt())
161 panic("Fatal exception in interrupt");
162
163 if (panic_on_oops)
164 panic("Fatal exception");
165
166 oops_exit();
167 do_exit(err);
168
169 return 0;
170}

129:打印当前异常原因及Oops的次数
125: show regs,打印系统关键寄存器将调用栈
160:如果异常发生时,系统处于中断上下文,则panic
163:如果进程上下文已经设置了panic_on_oops,则panic
167:若非panic,则说明异常发生时处于进程上下文,则杀死异常进程,重新调度。

http://lxr.linux.no/#linux+v2.6.25/arch/powerpc/kernel/traps.c#L97
449void show_regs(struct pt_regs * regs)
450{
451 int i, trap;
452
453 printk("NIP: "REG" LR: "REG" CTR: "REG"/n",
454 regs->nip, regs->link, regs->ctr);
455 printk("REGS: %p TRAP: %04lx %s (%s)/n",
456 regs, regs->trap, print_tainted(), init_utsname()->release);
457 printk("MSR: "REG" ", regs->msr);
458 printbits(regs->msr, msr_bits);
459 printk(" CR: %08lx XER: %08lx/n", regs->ccr, regs->xer);
460 trap = TRAP(regs);
461 if (trap == 0x300 || trap == 0x600)
462#if defined(CONFIG_4xx) || defined(CONFIG_BOOKE)
463 printk("DEAR: "REG", ESR: "REG"/n", regs->dar, regs->dsisr);
464#else
465 printk("DAR: "REG", DSISR: "REG"/n", regs->dar, regs->dsisr);
466#endif
467 printk("TASK = %p[%d] '%s' THREAD: %p",
468 current, task_pid_nr(current), current->comm, task_thread_info(current));
469
470#ifdef CONFIG_SMP
471 printk(" CPU: %d", raw_smp_processor_id());
472#endif /* CONFIG_SMP */
473
474 for (i = 0; i < 32; i++) {
475 if ((i % REGS_PER_LINE) == 0)
476 printk("/n" KERN_INFO "GPR%02d: ", i);
477 printk(REG " ", regs->gpr[i]);
478 if (i == LAST_VOLATILE && !FULL_REGS(regs))
479 break;
480 }
481 printk("/n");
482#ifdef CONFIG_KALLSYMS
483 /*
484 * Lookup NIP late so we have the best change of getting the
485 * above info out without failing
486 */
487 printk("NIP ["REG"] ", regs->nip);
488 print_symbol("%s/n", regs->nip);
489 printk("LR ["REG"] ", regs->link);
490 print_symbol("%s/n", regs->link);
491#endif
492 show_stack(current, (unsigned long *) regs->gpr[1]);
493 if (!user_mode(regs))
494 show_instructions(regs);
495}

453:产生错误的指令地址,及子程序待返回的地址。
467:导致异常的进程地址、进程号及进程的名称。
482-491:当定义了CONFIG_KALLSYMS时,将查找内核符号表,解析指令地址NIP和LR。
492:打印调用栈。
493:如果是内核态异常,则打印异常指令。

http://lxr.linux.no/#linux+v2.6.25/arch/powerpc/kernel/process.c#L965
965void show_stack(struct task_struct *tsk, unsigned long *stack)
966{
967 unsigned long sp, ip, lr, newsp;
968 int count = 0;
969 int firstframe = 1;
970
971 sp = (unsigned long) stack;
972 if (tsk == NULL)
973 tsk = current;
974 if (sp == 0) {
975 if (tsk == current)
976 asm("mr %0,1" : "=r" (sp));
977 else
978 sp = tsk->thread.ksp;
979 }
980
981 lr = 0;
982 printk("Call Trace:/n");
983 do {
984 if (!validate_sp(sp, tsk, MIN_STACK_FRAME))
985 return;
986
987 stack = (unsigned long *) sp;
988 newsp = stack[0];
989 ip = stack[FRAME_LR_SAVE];
990 if (!firstframe || ip != lr) {
991 printk("["REG"] ["REG"] ", sp, ip);
992 print_symbol("%s", ip);
993 if (firstframe)
994 printk(" (unreliable)");
995 printk("/n");
996 }
997 firstframe = 0;
998
999 /*
1000 * See if this is an exception frame.
1001 * We look for the "regshere" marker in the current frame.
1002 */
1003 if (validate_sp(sp, tsk, INT_FRAME_SIZE)
1004 && stack[FRAME_MARKER] == REGS_MARKER) {
1005 struct pt_regs *regs = (struct pt_regs *)
1006 (sp + STACK_FRAME_OVERHEAD);
1007 printk("--- Exception: %lx", regs->trap);
1008 print_symbol(" at %s/n", regs->nip);
1009 lr = regs->link;
1010 print_symbol(" LR = %s/n", lr);
1011 firstframe = 1;
1012 }
1013
1014 sp = newsp;
1015 } while (count++ < kstack_depth_to_print);
1016}
1017
1018void dump_stack(void)
1019{
1020 show_stack(current, NULL);
1021}
1022EXPORT_SYMBOL(dump_stack);

Call trace的原理:根据保存的SP指针回退,而每个栈里又在固定的位置了保存了调用过程中函数的一系列返回地址,因此得到了调用顺序,由异常时的最后一个指令往回退。

4.3 Panic
在中断上下文及设置了panic_on_oops时发生Oops将触发panic。
panic_on_oops为内核的控制参数,可以在用户态更改。
panic_on_oops的缺省设置是"0",即在Oops发生时不会进行panic()操作。
echo “kernel.panic_on_oops = 1″ >>/etc/sysctl.conf
sysctl -p
或者sysctl -w kernel.panic_on_oops=1

Panic将导致系统重启,重启等待的时间也可以控制。
echo “kernel.panic = 5″ >>/etc/sysctl.conf
sysctl –p
或者echo 5 >/proc/sys/kernel/panic
或者sysctl -w kernel.panic =5
或者在命令行参数里静态指定panic=5,但此值可被用户再次更改

http://lxr.linux.no/#linux+v2.6.25/kernel/panic.c#L62
62NORET_TYPE void panic(const char * fmt, ...)
63{
64 long i;
65 static char buf[1024];
66 va_list args;
67#if defined(CONFIG_S390)
68 unsigned long caller = (unsigned long) __builtin_return_address(0);
69#endif
70
71 /*
72 * It's possible to come here directly from a panic-assertion and not
73 * have preempt disabled. Some functions called from here want
74 * preempt to be disabled. No point enabling it later though...
75 */
76 preempt_disable();
77
78 bust_spinlocks(1);
79 va_start(args, fmt);
80 vsnprintf(buf, sizeof(buf), fmt, args);
81 va_end(args);
82 printk(KERN_EMERG "Kernel panic - not syncing: %s/n",buf);
83 bust_spinlocks(0);
84
85 /*
86 * If we have crashed and we have a crash kernel loaded let it handle
87 * everything else.
88 * Do we want to call this before we try to display a message?
89 */
90 crash_kexec(NULL);
91
92#ifdef CONFIG_SMP
93 /*
94 * Note smp_send_stop is the usual smp shutdown function, which
95 * unfortunately means it may not be hardened to work in a panic
96 * situation.
97 */
98 smp_send_stop();
99#endif
100
101 atomic_notifier_call_chain(&panic_notifier_list, 0, buf);
102
103 if (!panic_blink)
104 panic_blink = no_blink;
105
106 if (panic_timeout > 0) {
107 /*
108 * Delay timeout seconds before rebooting the machine.
109 * We can't use the "normal" timers since we just panicked..
110 */
111 printk(KERN_EMERG "Rebooting in %d seconds..",panic_timeout);
112 for (i = 0; i < panic_timeout*1000; ) {
113 touch_nmi_watchdog();
114 i += panic_blink(i);
115 mdelay(1);
116 i++;
117 }
118 /* This will not be a clean reboot, with everything
119 * shutting down. But if there is a chance of
120 * rebooting the system it will be rebooted.
121 */
122 emergency_restart();
123 }
124#ifdef __sparc__
125 {
126 extern int stop_a_enabled;
127 /* Make sure the user can actually press Stop-A (L1-A) */
128 stop_a_enabled = 1;
129 printk(KERN_EMERG "Press Stop-A (L1-A) to return to the boot prom/n");
130 }
131#endif
132#if defined(CONFIG_S390)
133 disabled_wait(caller);
134#endif
135 local_irq_enable();
136 for (i = 0;;) {
137 touch_softlockup_watchdog();
138 i += panic_blink(i);
139 mdelay(1);
140 i++;
141 }
142}
143
144EXPORT_SYMBOL(panic);

当panic为0时,则不重启,打开中断,陷入死循环。否则延时panic时间后,reboot

5 OOPS的记录及转储
5.1 Printk
Printk利用一个长度为 LOG_BUF_LEN循环缓冲区__log_buf记录打印信息。可以在任何地方调用printk,甚至在中断处理函数里也可以调用,而且对数据量的大小没有限制。Printk首先查询console的sem,当可用时,会根据当前系统的log级别选择将相关信息发送到真正的物理串口,然后唤醒任何正在等待消息的进程,即那些睡眠在 syslog 系统调用上的进程,或者读取 /proc/kmsg的进程。这两个访问日志引擎的接口几乎是等价的,不过请注意,对 /proc/kmsg 进行读操作时,日志缓冲区中被读取的数据就不再保留,而syslog 系统调用却能随意地返回日志数据,并保留这些数据以便其它进程也能使用。Console不可用时,仅仅将log记录到循环缓冲区,然后返回。当串口再次可用时,会自动将log刷新到终端上。

如果循环缓冲区填满了,printk就绕回缓冲区的开始处填写新数据,覆盖最陈旧的数据,于是记录进程就会丢失最早的数据。但与使用循环缓冲区所带来的好处
相比,这个问题可以忽略不计。例如,循环缓冲区可以使系统在没有记录进程的情况下照样运行,同时覆盖那些不再会有人去读的旧数据,从而使内存的浪费减到最
少。

5.2 符号化记录backtrace
在显示调用栈信息时,若定义了CONFIG_KALLSYMS宏,则会将LR等信息解码出来。
http://lxr.linux.no/#linux+v2.6.25/kernel/kallsyms.c#L322
321/* Look up a kernel symbol and print it to the kernel messages. */
322void __print_symbol(const char *fmt, unsigned long address)
323{
324 char buffer[KSYM_SYMBOL_LEN];
325
326 sprint_symbol(buffer, address);
327
328 printk(fmt, buffer);
329}

从2.6内核才开始有CONFIG_KALLSYMS,这样可以提供更丰富的调试信息。这个选项(在"Generl setup/Standard features"下)使得内核符号信息内建在内核中。对于2.4内核,无法提供符号化的调用栈,因此只能借用ksymoops工具来解析。

5.3 Klogd
Printk会唤醒等待获取内核打印信息的进程Klogd。Klogd默认读取/proc/kmsg,并将它们分发到 Syslogd。如果没有运行klogd,数据将保留在循环缓冲区中,直到某个进程读取或缓冲区溢出为止。 因此此时的Oops信息不会自动传递到内核空间,这种情况下,就只好手动查看 /proc/kmsg 了。

手工读取内核消息时,在停止klogd之后,可以发现 /proc 文件很象一个FIFO,读进程会阻塞在里面以等待更多的数据。显然,如果已经有 klogd 或其它的进程正在读取相同的数据,就不能采用这种方法进行消息读取,因为会与这些进程发生竞争。

如果想避免因为来自驱动程序的大量监视信息而扰乱系统日志,则可以为 klogd 指定 -f (file) 选项,指示 klogd将消息保存到某个特定的文件。

5.4 Syslogd
Syslogd通过本地UNIX套接字接口接收用户空间其他进程发送过来的日志信息,随后查看/etc/syslog.conf,找出处理这些数据的方法。Syslogd 根据设施和优先级对消息进行区分。内核消息由 LOG_KERN 设施记录,并以 printk 中使用的优先级记录(例如,printk 中使用的KERN_ERR对应于Syslogd 中的 LOG_ERR)。

一般情况下syslog的日志文件存储路径为/var/log/messages。

5.5 用户态OOPS转储
Oops消息通常都会通过串口打印出来,但是对于嵌入式设备,在系统正常运行过程中,一般不接串口设备,即使有串口设备,这种Oops日志信息也不便保存,不便于远程分析调试。因此需要将Oops信息记录下来,保存成文件。

对于非panic的Oops,正常情况下,当系统同时运行了Klogd和Syslogd时,Oops信息会自动保存到日志文件/var/log/messages中。但内核所有的调试信息及用户的相关日志都混杂在一起,不便于分析Oops,因此需要将oops信息单独提取出来。

PC平台上一般自动运行了Klogd和Syslogd,嵌入式平台上,这两个程序需要定制,否则将无法记录Oops信息。

尽管如此,仅仅靠Oops信息有时候还难以找到系统崩溃的原因,需要在发生Oops后将当时系统的一些关键信息都保存起来,这就需要Oops的自动转储。

可以模仿Klogd,开发后台监控程序,定期查询/proc/kmsg,当发现“Oops:”这种系统Oops的字样时,自动保存相关信息到指定文件中。在收集到指定长度的log时或者接收内核kmsg消息超时时停止查询,然后调用相关脚本自动采集系统相关信息,并将这些信息和Oops信息打包保存。继续查询/proc/kmsg,等待下一次Oops。这样就可以将不同的Oops和其他无关信息分离,分别打包。这种Oops的转储方式,非常利于嵌入式设备上log的远程采集记录及后续分析。

Oops通常意外着系统出现严重错误,很可能伴随系统重启。Oops的转储要求系统重启或者断电后,log文件不能丢失。PC机采用的是硬盘,自然是不用考虑掉电丢失的问题。但是嵌入式系统的文件系统都是基于RAM或者Flash的,而基于RAM的Ramdisk文件系统中的信息在掉电后不能保存,因此保存log文件的文件系统必须是基于Flash的文件系统,如JFFS2或者YAFFS。关于嵌入式系统的文件系统的选择及组合可以参考相关文章。

对于系统panic的情况,内核即挂起,因此用户态的klogd及Syslogd无法获得调度机会,也就无法保存相关log文件。因此有必要开发在内核态直接读写文件的记录机制。

5.6 内核态OOPS转储
另一种方式就是在内核中直接读写文件,保存Oops信息。但是这需要重新开发相关内核模块。
5.6.1 进程上下文
以下为内核态读写文件的代码:
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>

#include<linux/types.h>

#include<linux/fs.h>
#include<linux/string.h>
#include<asm/uaccess.h> /* get_fs(),set_fs(),get_ds() */

#define OOPS_LOG_FILE "/root/oops_log_test.log"
#define CFG_OOPS_LOCK_SUPPORT 0
#define CFG_OOPS_BUF_LEN 512

#define CFG_OOPS_RD_TEST 1

static char tmp[100];

static struct file *filp = NULL;

static spinlock_t oops_access_lock; /* For exclusive access to logfile */

int oops_log(const char *fmt, ...)
{
char buf[CFG_OOPS_BUF_LEN];
long num_written;
va_list args;

mm_segment_t old_fs;
ssize_t ret = 0;

#if CFG_OOPS_LOCK_SUPPORT
unsigned long flags;
#endif

va_start(args, fmt);

num_written = vsnprintf(buf, sizeof(buf), fmt, args);
if (num_written < 0)
{
printk("oops_log copy string err!/n");
}
else if (num_written >= sizeof(buf))
{
buf[CFG_OOPS_BUF_LEN - 2] = '/n';
buf[CFG_OOPS_BUF_LEN - 1] = '/0';
}
else
{
; /* OK */
}

va_end(args);

/* eruidin cancel disable irq and spin lock 20091221 */
#if CFG_OOPS_LOCK_SUPPORT
spin_lock_irqsave (&oops_access_lock, flags); /* Lock out IRQ handler: */
#endif

if(IS_ERR(filp))
{
printk("log file is not opened!/n");
ret = -1;
}
else
{
old_fs = get_fs();
set_fs(get_ds());

filp->f_op->write(filp, buf, strlen(buf), &filp->f_pos);

#if CFG_OOPS_RD_TEST
filp->f_op->llseek(filp,0,0);
ret = filp->f_op->read(filp, tmp, strlen(buf), &filp->f_pos);

if(ret > 0)
{
printk("oops_log: %s/n", tmp);
}
else if(ret == 0)
{
printk("oops_log: read nothing/n");
}
else
{
printk("oops_log: read error/n");
ret = -1;
}
#endif

set_fs(old_fs);

}

#if CFG_OOPS_LOCK_SUPPORT
spin_unlock_irqrestore (&oops_access_lock, flags); /* Lock in IRQ handler: */
#endif

return ret;

}

static int __init oops_log_init(void)
{
filp = filp_open(OOPS_LOG_FILE, O_RDWR | O_CREAT, 0644);

if(IS_ERR(filp))
{
printk("oops_log module loaded failed, open log fiel err!/n");
}
else
{
printk("oops_log module loaded successfully!/n");
}

#if CFG_OOPS_LOCK_SUPPORT
spin_lock_init(&oops_access_lock); /* Apply the spinlock macro. */
#endif

oops_log("oops_log test!!!!!!!!!!/n");

return 0;
}

static void __exit oops_log_exit(void)
{
if(filp)
{
filp_close(filp,NULL);
}

printk("oops_log unloaded!/n");
}

EXPORT_SYMBOL(oops_log);

module_init(oops_log_init);
module_exit(oops_log_exit);

MODULE_LICENSE("GPL");

此log以模块形式加载,oops_log为记录log的函数接口。在oops_log_init中调用oops_log进行简单的测试。
CFG_OOPS_RD_TEST宏控制是否进行读测试,以验证内核态log是否记录成功
-sh-3.1# insmod /usr/local/esw/drivers/oopslog.ko
Using fallback suid method
oops_log module loaded successfully!
oops_log: oops_log test!!!!!!!!!!

由log信息可知,内核态读写文件正常。

CFG_OOPS_LOCK_SUPPORT控制内核态读写log文件时是否进行保护。当打开
此开关时,测试结果如下:
-sh-3.1# insmod /usr/local/esw/drivers/oopslog.ko
Using fallback suid method
oops_log module loaded successfully!
BUG: scheduling while atomic: insmod/826/0x00000002
Call Trace:
[ef12f700] [c00081e0] show_stack+0x3c/0x194 (unreliable)
[ef12f730] [c0019b2c] __schedule_bug+0x64/0x78
[ef12f750] [c0350f50] schedule+0x324/0x34c
[ef12f7a0] [c03515c0] schedule_timeout+0x68/0xe4
[ef12f7e0] [c027938c] fsl_elbc_run_command+0x138/0x1c0
[ef12f820] [c0275820] nand_do_read_ops+0x130/0x3dc
[ef12f880] [c0275ebc] nand_read+0xac/0xe0
[ef12f8b0] [c0262d98] part_read+0x5c/0xe4
[ef12f8c0] [c017bcac] jffs2_flash_read+0x68/0x254
[ef12f8f0] [c0170550] jffs2_read_dnode+0x60/0x304
[ef12f940] [c017088c] jffs2_read_inode_range+0x98/0x180
[ef12f970] [c016e610] jffs2_do_readpage_nolock+0x94/0x1ac
[ef12f990] [c016ee04] jffs2_write_begin+0x2b0/0x330
[ef12fa10] [c005144c] generic_file_buffered_write+0x11c/0x8d0
[ef12fab0] [c0051e48] __generic_file_aio_write_nolock+0x248/0x500
[ef12fb20] [c0052168] generic_file_aio_write+0x68/0x10c
[ef12fb50] [c007ca80] do_sync_write+0xc4/0x138
[ef12fc10] [f107c0dc] oops_log+0xdc/0x1e8 [oopslog]
[ef12fe70] [f3087058] oops_log_init+0x58/0xa0 [oopslog]
[ef12fe80] [c00477bc] sys_init_module+0x130/0x17dc
[ef12ff40] [c00104b0] ret_from_syscall+0x0/0x38
--- Exception: c01 at 0xff29658
LR = 0x10031300
BUG: 提示信息表示在拥有自旋锁的时候,进程休眠。原子上下文是不允许休眠的。fsl_elbc_run_command+0x138/0x1c0的实现是执行了写操作后,需要等待中断中发送的同步信号量。
http://lxr.linux.no/#linux+v2.6.25/drivers/mtd/nand/fsl_elbc_nand.c#L361
385 /* wait for FCM complete flag or timeout */
386 ctrl->irq_status = 0;
387 wait_event_timeout(ctrl->irq_wait, ctrl->irq_status,
388 FCM_TIMEOUT_MSECS * HZ/1000);

1122/* NOTE: This interrupt is also used to report other localbus events,
1123 * such as transaction errors on other chipselects. If we want to
1124 * capture those, we'll need to move the IRQ code into a shared
1125 * LBC driver.
1126 */
1127
1128static irqreturn_t fsl_elbc_ctrl_irq(int irqno, void *data)
1129{
1130 struct fsl_elbc_ctrl *ctrl = data;
1131 struct elbc_regs __iomem *lbc = ctrl->regs;
1132 __be32 status = in_be32(&lbc->ltesr) & LTESR_NAND_MASK;
1133
1134 if (status) {
1135 out_be32(&lbc->ltesr, status);
1136 out_be32(&lbc->lteatr, 0);
1137
1138 ctrl->irq_status = status;
1139 smp_wmb();
1140 wake_up(&ctrl->irq_wait);
1141
1142 return IRQ_HANDLED;
1143 }
1144
1145 return IRQ_NONE;
1146}

由于nand Flash读写的设计原因,导致不能在原子上下文中读写Flash中的文件。

改用NFS网络文件系统,此时log文件在服务器上。
-sh-3.1# insmod /usr/local/esw/drivers/oopslog.ko
Using fallback suid method
oops_log module loaded successfully!
BUG: scheduling while atomic: insmod/818/0x00000002
Call Trace:
[efbc7890] [c00081e0] show_stack+0x3c/0x194 (unreliable)
[efbc78c0] [c0019b2c] __schedule_bug+0x64/0x78
[efbc78e0] [c0350f50] schedule+0x324/0x34c
[efbc7930] [c010e8f8] nfs_wait_bit_killable+0x24/0x4c
[efbc7940] [c0351864] __wait_on_bit+0x98/0xec
[efbc7960] [c0351918] out_of_line_wait_on_bit+0x60/0x74
[efbc79a0] [c010e8bc] nfs_wait_on_request+0x34/0x4c
[efbc79b0] [c0113e08] nfs_sync_mapping_wait+0xfc/0x3a4
[efbc7a10] [c0114190] nfs_wb_page+0xe0/0x120
[efbc7a60] [c0111a04] nfs_readpage+0xa4/0x424
[efbc7aa0] [c0052a68] generic_file_aio_read+0x16c/0x654
[efbc7b20] [c0107444] nfs_file_read+0xd4/0x11c
[efbc7b50] [c007cbb8] do_sync_read+0xc4/0x138
[efbc7c10] [f106e134] oops_log+0x134/0x1e8 [oopslog]
[efbc7e70] [f107c058] oops_log_init+0x58/0xa0 [oopslog]
[efbc7e80] [c00477bc] sys_init_module+0x130/0x17dc
[efbc7f40] [c00104b0] ret_from_syscall+0x0/0x38
--- Exception: c01 at 0xff29658
LR = 0x10031300
oops_log test!!!!!!!!!!

现象一样,在拥有自旋锁的时候不能读写网络文件系统中的文件。

5.6.2 中断上下文
5.6.2.1 读写文件
在中断服务程序中调用oops_log接口,在内核态进行文件读写
-sh-3.1# KERNEL INFO: FISH: enter fpga_interrupt_card0_handler
------------[ cut here ]------------
Kernel BUG at c016e8c4 [verbose debug info unavailable]
Oops: Exception in kernel mode, sig: 5 [#1]
PREEMPT MPC837x RDB
Modules linked in: fish oopslog gpio_driver
NIP: c016e8c4 LR: c016e8b4 CTR: 00000000
REGS: c0451900 TRAP: 0700 Not tainted (2.6.25)
MSR: 00029032 <EE,ME,IR,DR> CR: 22002084 XER: 00000000
TASK = c0431570[0] 'swapper' THREAD: c0450000
GPR00: 00010000 c04519b0 c0431570 00000000 00000000 00000001 00020000 00000000
GPR08: ef85828c c0450000 00000003 efb94370 48002088 01000000 0fffa000 00000000
GPR16: 00000000 00000018 00000000 00000000 c0451a00 ef83d2c0 c0440000 00000000
GPR24: 00000018 00000000 00000045 00000018 00000018 ef41d330 ef0b04f8 c0feaf20
NIP [c016e8c4] jffs2_write_end+0x12c/0x328
LR [c016e8b4] jffs2_write_end+0x11c/0x328
Call Trace:
[c04519b0] [c016e8b4] jffs2_write_end+0x11c/0x328 (unreliable)
[c04519f0] [c00514c8] generic_file_buffered_write+0x198/0x8d0
[c0451a90] [c0051e48] __generic_file_aio_write_nolock+0x248/0x500
[c0451b00] [c0052168] generic_file_aio_write+0x68/0x10c
[c0451b30] [c007ca80] do_sync_write+0xc4/0x138
[c0451bf0] [f107c0dc] oops_log+0xdc/0x1e8 [oopslog]
[c0451e50] [f308e45c] fpga_interrupt_card0_handler+0x34/0xc4 [fish]
[c0451e70] [c004b6a8] handle_IRQ_event+0x5c/0xb0
[c0451e90] [c004d8d8] handle_level_irq+0xbc/0x180
[c0451eb0] [c0006494] do_IRQ+0xa0/0xc4
[c0451ec0] [c0010b48] ret_from_except+0x0/0x14
--- Exception: 501 at cpu_idle+0xa0/0x100
LR = cpu_idle+0xa0/0x100
[c0451f80] [c00092a0] cpu_idle+0xe4/0x100 (unreliable)
[c0451fa0] [c0353074] 0xc0353074
[c0451fc0] [c04079b4] start_kernel+0x258/0x2dc
[c0451ff0] [00003438] 0x3438
Instruction dump:
389dffd8 7cc3da14 7fc5f378 54e76026 7f23cb78 7cfb3a14 7d1bd050 4800599d
54290024 8009000c 7c791b78 5400012e <0f000000> 813f0000 3956c474 3d6072cf
Kernel panic - not syncing: Fatal exception in interrupt
Rebooting in 180 seconds..

因为在中断上下文,无论是否持有锁都不能休眠,休眠将导致一个bug,进而引发一个Oops,最终导致内核Panic。

因为在中断上下文(包括硬件中断及软件中断)中无法进行文件读写,也就意味着当在中断处理程序中发生异常导致Oops时,无法在异常处理中将Oops信息保存到文件中。

本质上是因为内核态进行文件读写时有休眠的情况,这将导致系统异常。因此一个备选方案是开发新的不休眠的设备驱动程序,将Oops 信息保存到其他的存储区域中。但因为此信息不是基于文件系统的,因此用户无法直接读取该log信息,需要用户开发相关解析log的工具。

5.6.2.2 直接读写非易失性存储器
非易失性介质包括Flash、NVRAM、EEPROM,EEPROM因为价格原因一般大小有限,而NVRAM一般较贵,还需要额外的电池供电,在嵌入式系统中非特殊原因一般都不会配置,而Flash是最常见的嵌入式存储介质。但内核的Flash驱动,为了系统的整体性能,在写入数据后需要等待中断信号,因此会休眠。

修改内核的Flash驱动会产生较大的影响,因此最好的办法是重写一套简单的Flash驱动,写入后,采用查询的办法,等待写入完毕或者超时,若此时间较长则会对系统的响应速度产生影响,对于一定的实时应用会产生影响,因此需要评估,应该保证利用Flash来存储oops log不会影响到系统的正常运转。

从实现来说,利用NVRAM来存储log,驱动最简单,最重要的是其写入速度很快,无需额外延时等待,对系统的性能影响最小。

所有的oops都将以int die(const char *str, struct pt_regs *regs, long err)为入口,其中oops_enter表示oops处理开始,oops_exit标识oops处理结束。在此期间,有很多地方打印oops输出信息,若在每个地方都添加额外的代码将输出信息保存到Flash中,改动的地方太多,且可移植性不好。因为最终的输出信息都由printk输出,因此最好的办法是在printk中根据oops开始结束标志来决定是否保存一份额外的输出信息至Flash中。Oops处理完毕后,清除oops标识,则以后printk的信息不会额外保存到Flash中。

由于在内核中以裸数据的形式将oops log保存在非易失性介质中,用户态需要工具来解析这些数据,并将其保存成log文件。

用户空间mmap即可将Flash或者NVRAM映射给用户直接读取。定时查询是否有oops信息,若有则将log从Flash中读取出来,保存成log文件存在Flash文件系统中。同时调用相关脚本获取系统其他信息,和oops log文件一起作为一个完整的log信息。正常情况下,从flash中读取log信息后,将log从Flash中删除,以备下次再记录log信息,但是Flash擦写需要一定的时间,在系统正常运行过程中,此擦写会对系统产生一定的影响。因此后台检测程序至负责动态读取log文件,而log信息的擦除会在在系统业务尚未运行前进行,这样就不会对系统产生影响。

6 PowerPC的EABI规范
ABI或EABI(Extend ABI)通常是处理器体系结构的一部分,它与平台是紧密相连的。可以把ABI理解为一套规则,这套规则一般包括定义了以下内容:
1、如何使用机器的寄存器。比如用那个通用寄存器来作stack pointer和frame pointer。
2、堆栈的结构。
3、参数传递规则。

特定于那个平台的编译器和链接器实现都要遵循这些约定。

6.1 寄存器的使用规则
GPR0:随意使用,无需保存
GPR1:该寄存器保存堆栈的栈顶指针,也就是SP指针。
GPR2:专用
GPR3-GPR4:使用这两个寄存器保存程序的返回值。
GPR3-GPR10:用于传递函数参数。当参数多于8个时,使用存储器的栈传送。
GPR11-GPR12: 随意使用,无需保存
GPR13:专用,该寄存器用于保存sdata段的基地址指针。
GPR14-GPR31:程序可以自由使用这几个寄存器。

共分为三类
Volatile:随意使用,函数调用或者中断时不用保存
Nonvolatile:非易失性的,使用前需要保存,用后恢复
Dedicated:专用的,如R1作为SP,不能用于其他用途,因为中断可能随时来临

6.2 堆栈的结构
PowerPC架构没有专门的push、pop指令来实现堆栈结构,因此需要有一套规范来支持参数传递、Nonvolatile寄存器的保存以及局部变量等。将这些数据以统一的格式存放在栈中。

6.2.1 栈的增减规则
在EABI规范中,SP总是以双字对齐的方式指向当前stack frame的底部,新的SP递减增长。但是对于Linux平台,SP的对齐单位为16字节。

6.2.2 栈的结构
进行函数调用时,堆栈将向下增长,并按照特定的格式保存现场,以防止中断后无法返回到调用处。

Back Chain:当前栈顶指针寄存器SP保存上一个栈桢的Back Chain的地址。当函数返回时,SP指回上一个栈桢。
LR Save Word:保存LR寄存器的值,用于函数返回,即调用函数处的下一条指令地址。此值为程序当前栈的上一个栈的1字偏移处。
Padding:自动填充补齐,将Linux的栈对齐在16字节边界上。
Parameter Save Area:用于存放函数参数。当参数多于R3-R10 8个时才用此区域。
Local Variable Area:用于存放局部变量。首先选择GPR0/GPR11/ GPR12保存局部变量,同时R3-R10中未用作函数参数的寄存器也可以保存局部变量,只有当寄存器不够用时才用此区域。
CR Save Area:若函数可能更改CR,则保存CR寄存器。
32-bit General Register Save Area:保存函数用到的32位寄存器。若使用了GPR14- GPR31之间的寄存器,则需要将之上至GPR31的所有寄存器入栈保存,如使用了GPR16,则应保存GPR16- GPR31。

注意,根据具体的函数不一样,栈桢的内容是不一样的,最小的栈桢只有Back Chain和LR Save Word,共8字节,栈始终对齐在8字节边界,否则填充补齐。对于PowerPC Linux,此值为16字节。

Back Chain和LR Save Word紧紧相邻,且Back Chain是递减的,这些特征便于定位哪个是函数调用的边界。

6.3 从反汇编来看EABI的实现
Gcc工具链中的objdump可以将efl格式的印象反汇编,格式如下:
powerpc-linux-objdump -d vmlinux > vmlinux-1.6.25.S
powerpc-linux-objdump -S vmlinux > vmlinux-mix-1.6.25.S
-S尽可能的将c和反汇编代码对应起来,便于分析,当未打开-g选项时,-S和-d效果一样,无法获得源文件。

下面我们以arch/powerpc/kernel/trap.c中的die来分析EABI的实现。
cat vmlinux-1.6.25.S | grep die
。。。
c000e2dc <die>:
。。
可知到die函数的实现在c000e2dc

c000e2dc <die>:
c000e2dc: 94 21 ff e0 stwu r1,-32(r1)
// 栈空间递减32个字节,Linux中栈对齐在16字节上,而EABI规范只要求8字节对齐即可,同时u表示将递减前的r1放在新的栈的0字节偏移处Back Chain Word,这样程序返回时即可恢复原有的栈
c000e2e0: 7c 08 02 a6 mflr r0
将LR链接寄存器即程序的返回地址暂存中r0中
c000e2e4: bf 41 00 08 stmw r26,8(r1)
将r26-r31共6个寄存器24个字节入栈,偏移量为8字节,32个字节的栈空间使用完毕
c000e2e8: 3f a0 c0 43 lis r29,-16317
c000e2ec: 7c 7c 1b 78 mr r28,r3
c000e2f0: 90 01 00 24 stw r0,36(r1)
将r0中的程序返回地址保存在上一个栈的36-32=4字节偏移的地方,这正是LR Save Word的偏移量,因为0字节偏移处存放的是上一个栈的位置Back Chain Word
c000e2f4: 7c 9b 23 78 mr r27,r4
c000e2f8: 7c ba 2b 78 mr r26,r5
r3, r4, r5为函数的参数,这和X86不一样,因为嵌入式平台上通用寄存器较多,当参数较少时将会使用寄存器传参,多余的参数才放在栈上,这样效率更高些
c000e2fc: 48 01 0b a5 bl c001eea0 <oops_enter>
c000e300: 80 1d 23 c0 lwz r0,9152(r29)
c000e304: 2f 80 00 00 cmpwi cr7,r0,0
c000e308: 41 9e 01 28 beq- cr7,c000e430 <die+0x154>
。。。。。。。。。
c000e448: 3d 20 c0 3b lis r9,-16325
c000e44c: 38 89 e7 1c addi r4,r9,-6372
c000e450: 4b ff ff 5c b c000e3ac <die+0xd0>
c000e454: 3c 60 c0 3a lis r3,-16326
c000e458: 38 63 4f 08 addi r3,r3,20232
c000e45c: 48 01 0b cd bl c001f028 <panic>
c000e460: 48 34 2c fd bl c035115c <preempt_schedule>
c000e464: 4b ff ff 94 b c000e3f8 <die+0x11c>
c000e468: 48 01 09 f9 bl c001ee60 <oops_exit>
c000e46c: 7f 43 d3 78 mr r3,r26
c000e470: 48 01 4a 65 bl c0022ed4 <do_exit>
调用do_exit后就完了,die函数怎么没有消除工作呢?是因为do_exit后,进程就消亡了,其栈空间会统一全部释放而无需一层层释放呢?

int die(const char *str, struct pt_regs *regs, long err)
{
static struct {
spinlock_t lock;
u32 lock_owner;
int lock_owner_depth;
} die = {
.lock = __SPIN_LOCK_UNLOCKED(die.lock),
.lock_owner = -1,
.lock_owner_depth = 0
};
static int die_counter;
unsigned long flags;

if (debugger(regs))
return 1;

oops_enter();

if (die.lock_owner != raw_smp_processor_id()) {
console_verbose();
spin_lock_irqsave(&die.lock, flags);
die.lock_owner = smp_processor_id();
die.lock_owner_depth = 0;
bust_spinlocks(1);
if (machine_is(powermac))
pmac_backlight_unblank();
} else {
local_save_flags(flags);
}

if (++die.lock_owner_depth < 3) {
printk("Oops: %s, sig: %ld [#%d]/n", str, err, ++die_counter);
#ifdef CONFIG_PREEMPT
printk("PREEMPT ");
#endif
#ifdef CONFIG_SMP
printk("SMP NR_CPUS=%d ", NR_CPUS);
#endif
#ifdef CONFIG_DEBUG_PAGEALLOC
printk("DEBUG_PAGEALLOC ");
#endif
#ifdef CONFIG_NUMA
printk("NUMA ");
#endif
printk("%s/n", ppc_md.name ? ppc_md.name : "");

print_modules();
show_regs(regs);
} else {
printk("Recursive die() failure, output suppressed/n");
}

bust_spinlocks(0);
die.lock_owner = -1;
add_taint(TAINT_DIE);
spin_unlock_irqrestore(&die.lock, flags);

if (kexec_should_crash(current) ||
kexec_sr_activated(smp_processor_id()))
crash_kexec(regs);
crash_kexec_secondary(regs);

if (in_interrupt())
panic("Fatal exception in interrupt");

if (panic_on_oops)
panic("Fatal exception");

oops_exit();
do_exit(err);

return 0;
}

c001ee60 <oops_exit>:
c001ee60: 94 21 ff f0 stwu r1,-16(r1)
c001ee64: 7c 08 02 a6 mflr r0
c001ee68: 90 01 00 14 stw r0,20(r1)
c001ee6c: 4b ff fe c1 bl c001ed2c <do_oops_enter_exit>
c001ee70: 4b ff fe 39 bl c001eca8 <init_oops_id>
c001ee74: 3d 20 c0 45 lis r9,-16315
c001ee78: 3c 60 c0 3a lis r3,-16326
c001ee7c: 39 29 52 88 addi r9,r9,21128
c001ee80: 38 63 69 20 addi r3,r3,26912
c001ee84: 80 a9 00 00 lwz r5,0(r9)
c001ee88: 80 c9 00 04 lwz r6,4(r9)
c001ee8c: 48 00 13 55 bl c00201e0 <printk>
c001ee90: 80 01 00 14 lwz r0,20(r1)
c001ee94: 38 21 00 10 addi r1,r1,16
c001ee98: 7c 08 03 a6 mtlr r0
c001ee9c: 4e 80 00 20 blr
函数入口,保存SP,函数返回时从栈上取出程序返回的地址,并恢复SP,blr即跳转到返回地址处。

c001eea0 <oops_enter>:
c001eea0: 94 21 ff f0 stwu r1,-16(r1)
c001eea4: 7c 08 02 a6 mflr r0
c001eea8: 93 e1 00 0c stw r31,12(r1)
c001eeac: 90 01 00 14 stw r0,20(r1)
c001eeb0: 48 1e e2 cd bl c020d17c <debug_locks_off>
c001eeb4: 80 01 00 14 lwz r0,20(r1)
c001eeb8: 83 e1 00 0c lwz r31,12(r1)
c001eebc: 38 21 00 10 addi r1,r1,16
c001eec0: 7c 08 03 a6 mtlr r0
c001eec4: 4b ff fe 68 b c001ed2c <do_oops_enter_exit>
栈递减16个字节,将r31保存在12字节偏移处,0和4字节偏移处分别为上一个栈的地址及程序的返回地址。因此8字节偏移处没有使用,只是为了16字节对齐而用。

在调用b c001ed2c <do_oops_enter_exit>之前,已经将栈恢复了,因此到程序跳转到do_oops_enter_exit后,因为栈在oops_enter这一级并没有记录,因此Backtrace中没有oops_enter的调用轨迹。-fnoomit-frame-pointer编译选项可以防止这种栈丢失的现象。

6.4 从show_stack来看函数是如何调用的

void show_stack(struct task_struct *tsk, unsigned long *stack)
{
unsigned long sp, ip, lr, newsp;
int count = 0;
int firstframe = 1;

sp = (unsigned long) stack;
if (tsk == NULL)
tsk = current;
if (sp == 0) {
if (tsk == current)
asm("mr %0,1" : "=r" (sp));
else
sp = tsk->thread.ksp;
}

lr = 0;
printk("Call Trace:/n");
do {
if (!validate_sp(sp, tsk, MIN_STACK_FRAME))
return;

stack = (unsigned long *) sp;
// 获得当前栈
newsp = stack[0];
// 获得上一个栈的地址
ip = stack[FRAME_LR_SAVE];
// 获得程序返回地址
if (!firstframe || ip != lr) {
printk("["REG"] ["REG"] ", sp, ip);
print_symbol("%s", ip);
if (firstframe)
printk(" (unreliable)");
printk("/n");
}
firstframe = 0;

/*
* See if this is an exception frame.
* We look for the "regshere" marker in the current frame.
*/
if (validate_sp(sp, tsk, INT_FRAME_SIZE)
&& stack[FRAME_MARKER] == REGS_MARKER) {
struct pt_regs *regs = (struct pt_regs *)
(sp + STACK_FRAME_OVERHEAD);
printk("--- Exception: %lx", regs->trap);
print_symbol(" at %s/n", regs->nip);
lr = regs->link;
print_symbol(" LR = %s/n", lr);
firstframe = 1;
}

sp = newsp;
} while (count++ < kstack_depth_to_print);
}

根据当前栈底即可回朔每一个每一层栈及对应的函数调用过程,但没有打印栈里面的内容,这对于分析数据的变化过程带来不便。因此可以完善PowerPC Linux内核此处的处理过程,便于更精准的定位问题。

7 如何分析OOPS
7.1 如何分析backtrace
7.2 如何分析栈
8 Oops典型实例
9 参考资料
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐