网站建设经济可行性报告,建设网站公司专业,wordpress ajax 评论,wordpress门户模板http://blog.chinaunix.net/uid-20788636-id-4377271.html
在工作中经常会遇到一些内核crash的情况#xff0c;本文就是根据内核出现crash后的打印信息#xff0c;对其进行了分析#xff0c;使用的内核版本为#xff1a;Linux2.6.32。 每一个进程的生命周期内#xff0c…http://blog.chinaunix.net/uid-20788636-id-4377271.html
在工作中经常会遇到一些内核crash的情况本文就是根据内核出现crash后的打印信息对其进行了分析使用的内核版本为Linux2.6.32。 每一个进程的生命周期内其生命周期的范围为几毫秒到几个月。一般都是和内核有交互例如用户空间程序使用系统调用进入内核空间。这时使用的不再是用户空间的栈空间使用对应的内核栈空间。对每一个进程来说Linux内核都会把两个不同的数据结构紧凑的存放在一个单独为进程分配的存储空间中一个是内核态的进程堆栈另一个是紧挨进程描述符的数据结构thread_info,叫线程描述符。内核的堆栈大小一般为8KB也就是8192个字节占用两个页。在Linux-2.6.32内核中thread_info.h文件中有对内核堆栈的定义
#define THREAD_SIZE 8192 在Linux内核中使用下面的联合结构体表示一个进程的线程描述符和内核栈在内核中文件include/linux/sched.h。
union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)];
}; 该结构是一个联合体我们在C语言书上看到过关于union的解释在在C Programming Language 一书中对于联合体是这么描述的
1) 联合体是一个结构
2) 它的所有成员相对于基地址的偏移量都为0
3) 此结构空间要大到足够容纳最宽的成员
4) 其对齐方式要适合其中所有的成员 通过上面的描述可知thread_union结构体的大小为8192个字节。也就是stack数组的大小类型是unsigned long类型。由于联合体中的成员变量都是占用同一块内存区域所以在平时写代码时总有一个概念对一个联合体的实例只能使用其中一个成员变量否则会把原先变量给覆盖掉这句话如果正确的话必须要有一个前提假设成员占用的字节数相同当成员所占的字节数不同时只会覆盖相应的字节。对于thread_union联合体我们是可以同时访问这两个成员只要能够正确获取到两个成员变量的地址。 在内核中的某一个进程使用了过多的栈空间时内核栈就会溢出到thread_info部分这将导致严重的问题系统重启例如递归调用的层次太深在函数内定义的数据结构太大。 图进程中thread_info task_struct和内核栈中的关系 下面我们看一下thread_info的结构体
struct thread_info { unsigned long flags; /* 底层标志*/ int preempt_count; /* 0 可抢占, 0 bug */ mm_segment_t addr_limit; /* 进程地址空间 */ struct task_struct *task; /*当前进程的task_struct指针 */ struct exec_domain *exec_domain; /*执行区间 */ __u32 cpu; /* 当前cpu */ __u32 cpu_domain; /* cpu domain */ struct cpu_context_save cpu_context; /* cpu context */ __u32 syscall; /* syscall number */ __u8 used_cp[16]; /* thread used copro */ unsigned long tp_value; struct crunch_state crunchstate; union fp_state fpstate __attribute__((aligned(8))); union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif struct restart_block restart_block; /*用于实现信号机制*/
};
PS1flag 用于保存各种特定的进程标志最重要的两个是TIF_SIGPENDING如果进程有待处理的信号就置位TIF_NEED_RESCHED表示进程应该需要调度器选择另一个进程替换本进程执行。 结合上面的知识看下当内核打印堆栈信息时都打印了上面信息。下面的打印信息是工作中遇到的一种情况打印了内核的堆栈信息PC指针在dev_get_by_flags中不能访问的内核虚地址为45685516内核中一般可访问的地址都是以0xCXXXXXXX开头的地址。
Unable to handle kernel paging request at virtual address 45685516
pgd c65a4000
[45685516] *pgd00000000
Internal error: Oops: 1 [#1]
last sysfs file: /sys/devices/form/tpm/cfg_l3/l3_rule_add
Modules linked in: splic mmp(P)
CPU: 0 Tainted: P (2.6.32.11 #42)
PC is at dev_get_by_flags0xfc/0x140
LR is at dev_get_by_flags0xe8/0x140
pc : [c06bee24] lr : [c06bee10] psr: 20000013
sp : c07e9c28 ip : 00000000 fp : c07e9c64
r10: c6bcc560 r9 : c646a220 r8 : c66a0000
r7 : c6a00000 r6 : c0204e56 r5 : 30687461 r4 : 45685516
r3 : 00000000 r2 : 00000010 r1 : c0204e56 r0 : ffffffff
Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: 0005397f Table: 065a4000 DAC: 00000017
Process swapper (pid: 0, stack limit 0xc07e8270)
Stack: (0xc07e9c28 to 0xc07ea000)
9c20: c0204e56 c6a00000 45685516 c69ffff0 c69ffff0 c69ffff0
9c40: c6a00000 30687461 c66a0000 c6a00000 00000007 c64b210c c07e9d24 c07e9c68
9c60: c071f764 c06bed38 c66a0000 c66a0000 c6a00000 c6a00000 c66a0000 c6a00000
9c80: c07e9cfc c07e9c90 c03350d4 c0334b2c 00000034 00000006 00000100 c64b2104
9ca0: 0000c4fb c0243ece c66a0000 c0beed04 c033436c c646a220 c07e9cf4 00000000
9cc0: c66a0000 00000003 c0bee8e8 c0beed04 c07e9d24 c07e9ce0 c06e4f5c 00004c68
9ce0: 00000000 faa9fea9 faa9fea9 00000000 00000000 c6bcc560 c0335138 c646a220
9d00: c66a0000 c64b2104 c085ffbc c66a0000 c0bee8e8 00000000 c07e9d54 c07e9d28
9d20: c071f9a0 c071ebc0 00000000 c071ebb0 80000000 00000007 c67fb460 c646a220
9d40: c0bee8c8 00000608 c07e9d94 c07e9d58 c002a100 c071f84c c0029bb8 80000000
9d60: c07e9d84 c0beee0c c0335138 c66a0000 c646a220 00000000 c4959800 c4959800
9d80: c67fb460 00000000 c07e9dc4 c07e9d98 c078f0f4 c0029bc8 00000000 c0029bb8
9da0: 80000000 c07e9dbc c6b8d340 c66a0520 00000000 c646a220 c07e9dec c07e9dc8
9dc0: c078f450 c078effc 00000000 c67fb460 c6b8d340 00000000 c67fb460 c64b20f2
9de0: c07e9e24 c07e9df0 c078fb60 c078f130 00000000 c078f120 80000000 c0029a94
9e00: 00000806 c6b8d340 c0bee818 00000001 00000000 c4959800 c07e9e64 c07e9e28
9e20: c002a030 c078f804 c64b2070 00000000 c64b2078 ffc45000 c64b20c2 c085c2dc
9e40: 00000000 c085c2c0 00000000 c0817398 00086c2e c085c2c4 c07e9e9c c07e9e68
9e60: c06c2684 c0029bc8 00000001 00000040 00000000 c085c2dc c085c2c0 00000001
9e80: 0000012c 00000040 c085c2d0 c0bee818 c07e9ed4 c07e9ea0 c00284e0 c06c2608
9ea0: bf00da5c 00086c30 00000000 00000001 c097e7d4 c07e8000 00000100 c08162d8
9ec0: 00000002 c097e7a0 c07e9f14 c07e9ed8 c00283d0 c0028478 56251311 00023c88
9ee0: c07e9f0c 00000003 c08187ac 00000018 00000000 01000000 c07ebc70 00023cbc
9f00: 56251311 00023c88 c07e9f24 c07e9f18 c03391e8 c0028348 c07e9f3c c07e9f28
9f20: c0028070 c03391b0 ffffffff 0000001f c07e9f94 c07e9f40 c002d4d0 c0028010
9f40: 00000000 00000001 c07e9f88 60000013 c07e8000 c07ebc78 c0868784 c07ebc70
9f60: 00023cbc 56251311 00023c88 c07e9f94 c07e9f98 c07e9f88 c025c3e4 c025c3f4
9f80: 60000013 ffffffff c07e9fb4 c07e9f98 c025c578 c025c3cc 00000000 c0981204
9fa0: c0025ca0 c0d01140 c07e9fc4 c07e9fb8 c0032094 c025c528 c07e9ff4 c07e9fc8
9fc0: c0008918 c0032048 c0008388 00000000 00000000 c0025ca0 00000000 00053975
9fe0: c0868834 c00260a4 00000000 c07e9ff8 00008034 c0008708 00000000 00000000
Backtrace:
[c06bed28] (dev_get_by_flags0x0/0x140) from [c071f764] (arp_process0xbb4/0xc74) r7:c64b210c r6:00000007 r5:c6a00000 r4:c66a0000 1首先看看这段堆栈信息是在内核中那个文件中打印出来的在fault.c文件中__do_kernel_fault函数在上面的打印中Unable to handle kernel paging request at virtual address 45685516该地址是内核空间不可访问的地址。
static void
__do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{ /* * Are we prepared to handle this kernel fault? */ if (fixup_exception(regs)) return; /* * No handler, well have to terminate things with extreme prejudice. */ bust_spinlocks(1); printk(KERN_ALERT Unable to handle kernel %s at virtual address %08lx\n, (addr PAGE_SIZE) ? NULL pointer dereference : paging request, addr); show_pte(mm, addr); die(Oops, regs, fsr); bust_spinlocks(0); do_exit(SIGKILL);
}
2 对于下面的两个信息在函数show_pte中进行了打印下面的打印涉及到了页全局目录页表的知识暂时先不分析后续补上。
pgd c65a4000
[45685516] *pgd00000000
void show_pte(struct mm_struct *mm, unsigned long addr)
{ pgd_t *pgd; if (!mm) mm init_mm; printk(KERN_ALERT pgd %p\n, mm-pgd); pgd pgd_offset(mm, addr); printk(KERN_ALERT [%08lx] *pgd%08lx, addr, pgd_val(*pgd));
……………………
}
(3) die函数中调用 在die函数中取得thread_info结构体的地址。 struct thread_info *thread current_thread_info();
static inline struct thread_info *current_thread_info(void)
{ register unsigned long sp asm (sp); return (struct thread_info *)(sp ~(THREAD_SIZE - 1));
}
Sp: 0xc07e9c28 通过current_thread_info得到 thread_info的地址
(0xc07e9c28 0xffffe000) 0xC07E8000thread_info的地址也就是栈底的地址
(4)下面的打印信息在__die函数中打印
Internal error: Oops: 1 [#1]
last sysfs file: /sys/devices/form/tpm/cfg_l2/l2_rule_add
Modules linked in: splic mmp(P)
CPU: 0 Tainted: P (2.6.32.11 #42)
PC is at dev_get_by_flags0xfc/0x140
LR is at dev_get_by_flags0xe8/0x140
pc : [c06bee24] lr : [c06bee10] psr: 20000013
sp : c07e9c28 ip : 00000000 fp : c07e9c64
r10: c6bcc560 r9 : c646a220 r8 : c66a0000
r7 : c6a00000 r6 : c0204e56 r5 : 30687461 r4 : 30687461
r3 : 00000000 r2 : 00000010 r1 : c0204e56 r0 : ffffffff
Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: 0005397f Table: 065a4000 DAC: 00000017
Process swapper (pid: 0, stack limit 0xc07e8270)
Stack: (0xc07e9c28 to 0xc07ea000) 函数的调用关系die(Oops, regs, fsr);---à __die(str, err, thread, regs);
下面是__die函数的定义
static void __die(const char *str, int err, struct thread_info *thread, struct pt_regs *regs)
{ struct task_struct *tsk thread-task; static int die_counter;
/*Internal error: Oops: 1 [#1]*/ printk(KERN_EMERG Internal error: %s: %x [#%d] S_PREEMPT S_SMP \n, str, err, die_counter);
/*last sysfs file: /sys/devices/form/tpm/cfg_l2/l2_rule_add*/ sysfs_printk_last_file();
/*内核中加载的模块信息Modules linked in: splic mmp(P) */ print_modules();
/*打印寄存器信息*/ __show_regs(regs);
/*Process swapper (pid: 0, stack limit 0xc07e8270) tsk-comm task_struct结构体中的comm表示的是除去路径后的可执行文件名称这里的swapper为idle进程进程号为0创建内核进程init其中stack limit 0xc07e8270 指向thread_info的结束地址。*/ printk(KERN_EMERG Process %.*s (pid: %d, stack limit 0x%p)\n, TASK_COMM_LEN, tsk-comm, task_pid_nr(tsk), thread 1);
/* dump_mem 函数打印从栈顶到当前sp之间的内容*/ if (!user_mode(regs) || in_interrupt()) { dump_mem(KERN_EMERG, Stack: , regs-ARM_sp, THREAD_SIZE (unsigned long)task_stack_page(tsk)); dump_backtrace(regs, tsk); dump_instr(KERN_EMERG, regs); }
} 在上面的函数中主要使用了thread_info,task_struct,sp之间的指向关系。task_struct结构体的成员stack是栈底也是对应thread_info结构体的地址。堆栈数据是从
栈底8K的地方开始向下存的。SP指向的是当前的栈顶。(unsigned long)task_stack_page(tsk)
#define task_stack_page(task) ((task)-stack) 该宏根据task_struct得到栈底也就是thread_info地址。
#define task_thread_info(task) ((struct thread_info *)(task)-stack)该宏根据task_struct得到thread_info指针。
5dump_backtrace函数 该函数用于打印函数的调用关系。Fp为帧指针用于追溯程序的方式方向跟踪调用函数。该函数主要是fp进行检查看看能否进行backtrace如果可以就调用汇编的c_backtrace在arch/arm/lib/backtrace.S函数中。
static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
{ unsigned int fp, mode; int ok 1; printk(Backtrace: ); if (!tsk) tsk current; if (regs) { fp regs-ARM_fp; mode processor_mode(regs); } else if (tsk ! current) { fp thread_saved_fp(tsk); mode 0x10; } else { asm(mov %0, fp : r (fp) : : cc); mode 0x10; } if (!fp) { printk(no frame pointer); ok 0; } else if (verify_stack(fp)) { printk(invalid frame pointer 0x%08x, fp); ok 0; } else if (fp (unsigned long)end_of_stack(tsk)) printk(frame pointer underflow); printk(\n); if (ok) c_backtrace(fp, mode);
}
6dump_instr
根据PC指针和指令mode, 打印出当前执行的指令码
Code: 0a000008 e5944000 e2545000 0a000005 (e4153010) 内核中函数的调用关系