现在的位置: 首页 > 综合 > 正文

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

2012年12月08日 ⁄ 综合 ⁄ 共 37590字 ⁄ 字号 评论关闭

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 参考资料

抱歉!评论已关闭.