Linux内核Crash分析
转载自:http://linux.cn/article-3475-1.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[]; /* thread used copro */
unsigned long tp_value; struct crunch_state crunchstate; union fp_state fpstate __attribute__((aligned()));
union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
struct restart_block restart_block; /*用于实现信号机制*/
};
PS:(1)flag 用于保存各种特定的进程标志,最重要的两个是:TIF_SIGPENDING,如果进程有待处理的信号就置位,TIF_NEED_RESCHED表示进程应该需要调度器选择另一个进程替换本进程执行。
结合上面的知识,看下当内核打印堆栈信息时,都打印了上面信息。下面的打印信息是工作中遇到的一种情况,打印了内核的堆栈信息,PC指针在 dev_get_by_flags中,不能访问的内核虚地址为45685516,内核中一般可访问的地址都是以0xCXXXXXXX开头的地址。
Unable to handle kernel paging request at virtual address
pgd = c65a4000
[] *pgd=
Internal error: Oops: [#]
last sysfs file: /sys/devices/form/tpm/cfg_l3/l3_rule_add
Modules linked in: splic mmp(P)
CPU: Tainted: P (2.6.32.11 #)
PC is at dev_get_by_flags+0xfc/0x140
LR is at dev_get_by_flags+0xe8/0x140
pc : [<c06bee24>] lr : [<c06bee10>] psr:
sp : c07e9c28 ip : fp : c07e9c64
r10: c6bcc560 r9 : c646a220 r8 : c66a0000
r7 : c6a00000 r6 : c0204e56 r5 : r4 :
r3 : r2 : r1 : c0204e56 r0 : ffffffff
Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: 0005397f Table: 065a4000 DAC:
Process swapper (pid: , stack limit = 0xc07e8270)
Stack: (0xc07e9c28 to 0xc07ea000)
9c20: c0204e56 c6a00000 c69ffff0 c69ffff0 c69ffff0
9c40: c6a00000 c66a0000 c6a00000 c64b210c c07e9d24 c07e9c68
9c60: c071f764 c06bed38 c66a0000 c66a0000 c6a00000 c6a00000 c66a0000 c6a00000
9c80: c07e9cfc c07e9c90 c03350d4 c0334b2c c64b2104
9ca0: 0000c4fb c0243ece c66a0000 c0beed04 c033436c c646a220 c07e9cf4
9cc0: c66a0000 c0bee8e8 c0beed04 c07e9d24 c07e9ce0 c06e4f5c 00004c68
9ce0: faa9fea9 faa9fea9 c6bcc560 c0335138 c646a220
9d00: c66a0000 c64b2104 c085ffbc c66a0000 c0bee8e8 c07e9d54 c07e9d28
9d20: c071f9a0 c071ebc0 c071ebb0 c67fb460 c646a220
9d40: c0bee8c8 c07e9d94 c07e9d58 c002a100 c071f84c c0029bb8
9d60: c07e9d84 c0beee0c c0335138 c66a0000 c646a220 c4959800 c4959800
9d80: c67fb460 c07e9dc4 c07e9d98 c078f0f4 c0029bc8 c0029bb8
9da0: c07e9dbc c6b8d340 c66a0520 c646a220 c07e9dec c07e9dc8
9dc0: c078f450 c078effc c67fb460 c6b8d340 c67fb460 c64b20f2
9de0: c07e9e24 c07e9df0 c078fb60 c078f130 c078f120 c0029a94
9e00: c6b8d340 c0bee818 c4959800 c07e9e64 c07e9e28
9e20: c002a030 c078f804 c64b2070 c64b2078 ffc45000 c64b20c2 c085c2dc
9e40: c085c2c0 c0817398 00086c2e c085c2c4 c07e9e9c c07e9e68
9e60: c06c2684 c0029bc8 c085c2dc c085c2c0
9e80: 0000012c c085c2d0 c0bee818 c07e9ed4 c07e9ea0 c00284e0 c06c2608
9ea0: bf00da5c 00086c30 c097e7d4 c07e8000 c08162d8
9ec0: c097e7a0 c07e9f14 c07e9ed8 c00283d0 c0028478 00023c88
9ee0: c07e9f0c c08187ac c07ebc70 00023cbc
9f00: 00023c88 c07e9f24 c07e9f18 c03391e8 c0028348 c07e9f3c c07e9f28
9f20: c0028070 c03391b0 ffffffff 0000001f c07e9f94 c07e9f40 c002d4d0 c0028010
9f40: c07e9f88 c07e8000 c07ebc78 c0868784 c07ebc70
9f60: 00023cbc 00023c88 c07e9f94 c07e9f98 c07e9f88 c025c3e4 c025c3f4
9f80: ffffffff c07e9fb4 c07e9f98 c025c578 c025c3cc c0981204
9fa0: c0025ca0 c0d01140 c07e9fc4 c07e9fb8 c0032094 c025c528 c07e9ff4 c07e9fc8
9fc0: c0008918 c0032048 c0008388 c0025ca0
9fe0: c0868834 c00260a4 c07e9ff8 c0008708
Backtrace:
[<c06bed28>] (dev_get_by_flags+0x0/0x140) from [<c071f764>] (arp_process+0xbb4/0xc74)
r7:c64b210c r6: 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, we'll have to terminate things with extreme prejudice.
*/
bust_spinlocks();
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();
do_exit(SIGKILL);
}
(2) 对于下面的两个信息,在函数show_pte中进行了打印,下面的打印涉及到了页全局目录,页表的知识,暂时先不分析,后续补上。
pgd = c65a4000
[] *pgd= 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 - ));
}
Sp: 0xc07e9c28 通过current_thread_info得到 thread_info的地址
(0xc07e9c28 & 0xffffe000) = 0xC07E8000(thread_info的地址,也就是栈底的地址)
(4)下面的打印信息在__die函数中打印
Internal error: Oops: [#]
last sysfs file: /sys/devices/form/tpm/cfg_l2/l2_rule_add
Modules linked in: splic mmp(P)
CPU: Tainted: P (2.6.32.11 #)
PC is at dev_get_by_flags+0xfc/0x140
LR is at dev_get_by_flags+0xe8/0x140
pc : [<c06bee24>] lr : [<c06bee10>] psr:
sp : c07e9c28 ip : fp : c07e9c64
r10: c6bcc560 r9 : c646a220 r8 : c66a0000
r7 : c6a00000 r6 : c0204e56 r5 : r4 :
r3 : r2 : r1 : c0204e56 r0 : ffffffff
Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: 0005397f Table: 065a4000 DAC:
Process swapper (pid: , 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 + );
/* 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指针。
(5)dump_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 = ;
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 = ;
} else if (verify_stack(fp)) {
printk("invalid frame pointer 0x%08x", fp);
ok = ;
} else if (fp < (unsigned long)end_of_stack(tsk))
printk("frame pointer underflow");
printk("\n");
if (ok)
c_backtrace(fp, mode);
}
(6)dump_instr
根据PC指针和指令mode, 打印出当前执行的指令码
Code: 0a000008 e5944000 e2545000 0a000005 (e4153010)
内核中函数的调用关系
Linux内核Crash分析的更多相关文章
- Linux内核源代码分析方法
Linux内核源代码分析方法 一.内核源代码之我见 Linux内核代码的庞大令不少人"望而生畏",也正由于如此,使得人们对Linux的了解仅处于泛泛的层次.假设想透析Linux ...
- 2019-2020-1 20199303 《Linux内核原理分析》 第一周作业
2019-2020-1 20199303 <Linux内核原理分析> 第一周作业 1. 环境准备 在众多的Linux发行版中,Ubuntu,小红帽还有类Unix系统的BSD系统,我选择了目 ...
- Linux内核crash/Oops异常定位分析方法
在内核开发的过程中,经常会碰到内核崩溃,比如空指针异常,内存访问越界.通常我们只能靠崩溃之后打印出的异常调用栈信息来定位crash的位置和原因.总结下分析的方法和步骤. 通常oops发生之后,会在串口 ...
- Linux内核启动分析过程-《Linux内核分析》week3作业
环境搭建 环境的搭建参考课件,主要就是编译内核源码和生成镜像 start_kernel 从start_kernel开始,才真正进入了Linux内核的启动过程.我们可以把start_kernel看做平时 ...
- 从linux内核代码分析操作系统启动过程
朱宇轲 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 在本次的实验中, ...
- Linux内核启动分析
张超<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我的代码可见https://www.shiyanlo ...
- Linux内核及分析 第八周 进程的切换和系统的一般执行过程
学习笔记: 一.进程调度与进程调度的时机分析 1.不同类型的进程有不同需求的调度需求: 第一种分类: —I/O-bound:频繁的进行I/O,通常会花费很多时间等待I/O操作的完成 —CPU-boun ...
- Linux内核及分析 第七周 可执行程序的装载
实验步骤 1. 更新menu,用test.c覆盖test_exec.c 2. 把init 和 hello 放到了rootfs.img目录下,执行exec命令的时候自动加载了hello程序 3. 执行e ...
- Linux内核及分析 第六周 分析Linux内核创建一个新进程的过程
实验过程 1.github上克隆相应的mengning/menu.git 2.测试menuOS,测试fork直接执行结果 3.配置调试系统,进入gdb调试,利用file linux-3.18.6/vm ...
随机推荐
- leetcode 【 Search for a Range 】python 实现
题目: Given a sorted array of integers, find the starting and ending position of a given target value. ...
- 引用其他头文件时出现这种错误,莫名其妙,error C2065: “ColorMatrix”: 未声明的标识符
今天做项目时,直接拷贝了另一个工程里的头文件和源文件,然后运行时就出现这种问题,莫名其妙,在原程序里运行一点问题就没有,但是在新工程里就是error. >e:\c++\button_fly2\b ...
- Selenium - WebDriver: Locating Elements
Selenium provides the following methods to locate elements in a page: find_element_by_id find_elemen ...
- 【转载】zookeeper使用和原理探究(一)
最近开始看到一些公司在使用zookeeper,本身对此了解的很少,这里看到一篇非常好的文章,因此转载 原贴地址:http://www.blogjava.net/BucketLi/archive/201 ...
- easyui可封装的各种方法
组件:datagrid 获取选中行(单行) var row = $('#tt').datagrid('getSelected'); if (row){ alert('Item ID ...
- tomcat源码分析一
废话少说,拉代码,导入eclipse开干,具体步骤可以参考http://hi.baidu.com/hateeyes/blog/item/7f44942a20ad8f9d023bf66d.html 下面 ...
- 体验devstack安装openstack
由于公司制度,工作环境是不能直接上网的,所以在工作时间从没有体验过devstack或者其他联网方式安装openstack. 因自己购置了一台不错的主机,因而决定尝试安装一番,经过一段为期不短的内心极度 ...
- PHP共享内存的应用shmop系列
简单的说明 可能很少情况会使用PHP来操控共享内存,一方面在内存的控制上,MC已经提供了一套很好的方式,另一方面,自己来操控内存的难度较大,内存的读写与转存,包括后面可能会用到的存储策略,要是没有一定 ...
- elasticsearch备份与恢复4_使用ES-Hadoop将ES中的索引数据写入HDFS中
背景知识见链接:elasticsearch备份与恢复3_使用ES-Hadoop将HDFS数据写入Elasticsearch中 项目参考<Elasticsearch集成Hadoop最佳实践> ...
- iOS-app发布证书和调试证书配置
iOS-app发布证书.真机调试证书.测试证书.推送证书详细过程 更重要的是让你彻底明白为什么要这样配置证书 说句废话:凡事当你弄清楚为什么时,就揭开了它复杂和神秘的面纱 正文开始 一:发布证书 遵旨 ...