分析红帽Linux上的故障定位技术
类别:机箱电源 来源:http://www.hnygpx.net 日期:2013-9-28 20:02:22
使用kprobe来观察内核函数的执行实例电源故障 linux安装 kprobe是SystemTap对内核函数进行probing的功能在内核中的实现,由于内核中提供了正式的API来使用kprobe,所以对很多内核程序员来说,也许直接使用kprobe比使用SystemTap更方便.内核中提供了三种类型的kprobe处理函数,分别是jprobe, kprobe, kretprobe,下面的代码用这三个probe观察在TCP/IP的arp_process函数执行中对ip_route_input()调用的返回结果.这个代码还展示了在同一个函数probe的Entry handler和Ret handler之间共享参数的方法.代码如下:windows7故障 arp_probe.c /* * arp_probe.c, by Qianfeng Zhang (frzhang@redhat.com) */ #include #include #include #include #include #include #include #include MODULE_AUTHOR("frzhang@redhat.com"); MODULE_DESCRIPTION("A module to track the call results of ip_route_input() inside arp_process using jprobe and kretprobe"); MODULE_LICENSE("GPL"); static int j_arp_process(struct sk_buff *skb) { dev; struct in_device *in_dev; int no_addr, rpf; in_dev = in_dev_get(dev); ifa_list == NULL ); rpf = IN_DEV_RPFILTER(in_dev); in_dev_put(in_dev); name, no_addr, rpf); jprobe_return(); return(0); }; static int j_fib_validate_source(__be32 src, __be32 dst, u8 tos, int oif, struct net_device *dev, __be32 *spec_dst, u32 *itag, u32 mark) { printk("fib_validate_source() is called with dst=0x%x, oif=%d \n", dst, oif); jprobe_return(); return(0); }; static struct jprobe my_jp1 = { .entry = j_arp_process, .kp.symbol_name = "arp_process" }; static struct jprobe my_jp2 = { .entry = j_fib_validate_source, .kp.symbol_name = "fib_validate_source" }; static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { rp->kp.symbol_name); return(0); }; static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { int eax; ax & 0xffff ; rp-> ax, eax); return(0); }; static int fib_lookup_entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { struct fib_result *resp; dx; rp->kp.symbol_name); data)= resp; return(0); }; static int fib_lookup_return_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { struct fib_result *resp; int eax; ax & 0xffff ; data); type); return(0); } static struct kretprobe my_rp1 = { .handler = return_handler, .entry_handler = entry_handler, .kp.symbol_name = "ip_route_input_slow" }; static struct kretprobe my_rp2 = { .handler = return_handler, .entry_handler = entry_handler, .kp.symbol_name = "fib_validate_source" }; static struct kretprobe my_rp3 = { .handler = fib_lookup_return_handler, .entry_handler = fib_lookup_entry_handler, .kp.symbol_name = "fib_lookup", .data_size = sizeof(struct fib_result *) }; static int __init init_myprobe(void) { int ret; printk("RTN_UNICAST is %d\n", RTN_UNICAST); if ((ret = register_jprobe(&my_jp1)) < 0) { printk("register_jprobe %s failed, returned %d\n", my_jp1.kp.symbol_name, ret); return(-1); } if ((ret = register_jprobe(&my_jp2)) < 0) { printk("register_jprobe %s failed, returned %d\n", my_jp2.kp.symbol_name, ret); return(-1); } if ((ret = register_kretprobe(&my_rp1)) < 0 ) { printk("register_kretprobe %s failed, returned %d\n", my_rp1.kp.symbol_name, ret); unregister_jprobe(&my_jp1); unregister_jprobe(&my_jp2); return(-1); } if ((ret = register_kretprobe(&my_rp2)) < 0 ) { printk("register_kretprobe %s failed, returned %d\n", my_rp2.kp.symbol_name, ret); unregister_jprobe(&my_jp1); unregister_jprobe(&my_jp2); unregister_kretprobe(&my_rp1); return(-1); } if ((ret = register_kretprobe(&my_rp3)) < 0 ) { printk("register_kretprobe %s failed, returned %d\n", my_rp3.kp.symbol_name, ret); unregister_jprobe(&my_jp1); unregister_jprobe(&my_jp2); unregister_kretprobe(&my_rp1); unregister_kretprobe(&my_rp2); return(-1); } return 0; } static void __exit rel_myprobe(void) { unregister_jprobe(&my_jp1); unregister_jprobe(&my_jp2); unregister_kretprobe(&my_rp1); unregister_kretprobe(&my_rp2); unregister_kretprobe(&my_rp3); } module_init(init_myprobe); module_exit(rel_myprobe); Makefile obj-m += arp_probe.o make -C /usr/src/kernels/2.6.32-71.el6.x86_64/ M=`pwd` modules 红帽Linux故障定位技术详解与实例 3、内核故障情形及处理 (1)内核panic panic是内核 直接的故障定位报告,发生panic时,内核已经认为故障定位已经导致操作系统不再具备正常运行的条件了.当发生panic时,Linux会将所有CPU的中断和进程调度功能都关掉,所以这时系统是没有任何反应的,如果用户启动的是图形界面,则在屏幕上也看不到任何关于panic的信息.电脑故障 我们通常遇到的,机器没反应,ping不通的情况,绝大部分都是panic. Panic发生时,内核直接在console上打印导致panic的代码位置的调用堆栈,传统的用户用串口连接到机器上来收集console上的打印信息,但串口这种方式,显然用起来不方便,现在的Linux,如RHEL5,RHEL6,都采用kdump的方法来收集panic时的信息.在配置好kdump的情况下,panic时系统会用kexec加载并切换到一个新的内核上(放置在预先分配的内存位置),并用磁盘或网络等将系统的全部或部分内存数据保存起来.电脑故障 用kdump收集到panic的数据后,用户用crash工具就能直接查看导致panic的代码路径.CPU安装设置 panic一般是很直观的,panic的堆栈信息能直接反映出导致bug的原因,如MCE故障,NMI故障,数据结构分配失败等.但有时panic是因为内核主动发现了关键的数据结构不一致性,这种不一致性是什么时候,什么代码导致的,并不清楚,可能还需要多次测试用SystemTap这样的工具进行捕捉 (2)多处理机环境内核执行路径产生的死锁 内核死锁和panic不一样,产生死锁时,内核并不主动的使自己处于挂起状态.但内核死锁发生时,两个以上的CPU的执行路径在内核态不能推进了,处于互相阻塞状态,而且是的占用CPU(用的spin-lock),直接或间接的导致全部CPU上的进程无法调度.内核死锁又分两种情况: -涉及到中断上下文的死锁.这种情况的死锁, 少一个CPU上的中断被屏蔽了.系统可能没法响应ping请求.由于有一个CPU已经没法响应中断,其上的local APIC定时中断没法工作,可以用NMI Watchdog的方法来检测出来(检查local APIC handler维护的计数器变量),NMI Watchdog可以在其处理程序中调用panic(),用户就可以用kdump收集内存信息,从而分析各死锁CPU上的调用堆栈,查处导致死锁的逻辑原因. -不涉及中断上下文的死锁.这种情况的死锁,各CPU上的中断都是正常的,系统能对ping请求作出反应,这时NMI Watchdog无法被触发.在 2.6.16之前的内核中,并没有一种很好的方法来处理这种情形.在RHEL5, RHEL6 内核中,每个CPU上提供了一个watchdog内核线程,在死锁出现的情况下,死锁CPU上的watchdog内核线程没法被调度(即使它是 高优先级的实时进程),它就没法update相应的counter变量,各CPU的NMI Watchdog中断会周期性的检查其CPU对应的counter,发现没有updated,会调用panic(),用户就可用kdump收集内存信息,分析各死锁CPU上的调用堆栈,查处导致死锁的逻辑原因. (3)内核的oops或warning oops 和warning和panic类似的地方是,他们都是因内核发现了不一致而主动报告的异常.但oops和warning导致的问题严重程度要比panic轻很多,以致于内核处理该问题时不需要使系统挂起.产生oops和warning,内核通常已经在dmesg中记录了相当的信息,特别是oops,至少会打印出现故障的地方的call trace. Oops也可转换成panic/kdump来进行offline-debugging,只要将/proc/sys/kernel下的panic_on_oops变量设置为1就行了. 产生oops和warning的直接原因有很多,如内核中的segment fault,或内核发现的某数据结构的counter值不对,而segment fault 和counter值的变化还有更深层次的原因,通常并不能从内核dmesg的信息中看出来,解决这种问题的是要用SystemTap进行probe,如发现某counter的值不对,就用SystemTap做一个probe来记录所有代码对该counter的访问,然后再进行分析. 定位oops和warning会比定位应用程序的内存访问故障定位困难很多,因为在内核并不能象用valgrind去trace应用程序一样跟踪数据结构的分配和使用情况. 2、其他(硬件相关)故障 机器自动重启是一种常见的故障情形,一般是由硬件如物理内存故障引起的,软件的故障只会导致死锁或panic,内核中几乎没有代码在发现问题的情况下去reboot机器.在/proc/sys/kernel目录下有个参数“panic”,其值如果设置为非0,则在panic发生若干秒后,内核会重启机器.现在高端的PC服务器,都在努力用软件来处理物理内存故障,如MCA的 “HWPoison”方法会将故障的物理页隔离起来,Kill掉故障页所在的进程就可以了,RHEL6现在已经支持 “HWPoison”.那些不具备MCA能力的机器,物理内存故障时,不会产生MCE异常,直接由硬件机制reboot机器 4、RHEL6 上的Debugging技术介绍 (1)Kdump故障定位收集和crash分析 kdump就是用来在内核panic的情况下收集系统内存信息的,用户也可在online情况下用sysrq的'c'键触发. Kdump 采用没有污染的内核来执行dump工作,所以其比以前的diskdump, lkcd方法更可靠.使用kdump,用户可选择将数据dump到本地盘或网络上,也可通过定义makedumpfile的参数过滤要收集的内存信息,已减少kdump所需要的停机时间 Crash就是对kdump的信息进行分析的工具.其实际就是gdb的一个wrapper.使用crash时, 好安装kernel-debuginfo,这样能解析kdump收集的内核数据的符号信息.用crash来定位问题的能力,完全取决于用户对内核代码的理解和分析能力 参考 “#>man kdump.conf”, “#>man crash”, “#>man makedumpfile”学习怎样使用kdump和crash.访问可下载debuginfo文件 (2)用systemTap定位bug systemtap 属于probe类的定位工具,它能对内核或用户代码的指定位置进行probe,当执行到指定位置或访问指定位置的数据时,用户定义的probe函数自动执行,可打印出该位置的调用堆栈,参数值,变量值等信息. systemtap选择进行probe的位置很灵活,这是systemtap的强大功能所在. Systemtap的probe点可括如下几个方面: -内核中全部系统调用,内核及模块中全部函数的入口或出口点 -自定义的定时器probe点 -内核中任意指定的代码或数据访问位置 -特定用户进程中任意制定的代码或数据访问位置 -各个功能子系统预先设置的若干probe点,如tcp,udp,nfs,signal各子系统都预先设置了很多探测点 systemTap的脚本用stap脚本语言来编写,脚本代码中调用stap提供的API进行统计,打印数据等工作,关于stap语言提供的API函数,参考 “#> man stapfuncs”.关于systemTap的功能和使用可参考 “#> man stap”, “#> man stapprobes” (3)ftrace ftrace 是linux内核中利用tracepoints基础设施实现的事件追踪机制,它的作用在于能比较清楚的给出在一定时间内系统或进程所执行的活动,如函数调用路径,进程切换流等. Ftrace可用于观察系统各部分的latency,以便进行实时应用的优化;它也可以通过记录一段时间内的内核活动来帮助故障定位.如用以下方法可trace某个进程在一端时间的函数调用情况 除tracing函数调用外,ftrace还可tracing系统的进程切换,唤醒,块设备访问,内核数据结构分配等活动.注意,tracing和profile是不同的,tracing记录的是一段时间内的全部活动,而不是统计信息,用户可以通过/sys/kernel/debug/tracing下的buffer_size_kb设置缓冲区的大小,以记录更长时间的数据. 关于ftrace的具体使用可参考内核源码Documenation/trace下的内容 (4)oprofile 和 perf oprofile和perf都是对系统进行profile(抽样,统计)的工具,它们主要用来解决系统和应用的性能问题. perf功能更强大,更全面,同时perf的用户空间工具和内核源码一起维护和发布,让用户能及时的享受perf内核新增加的特征. Perf 是在RHEL6中才有,RHEL5中没有Perf. Oprofile和perf 都使用现代CPU中具有的硬件计数器进行统计工作,但perf还可以使用内核中定义的 “software counter”及 “trace points”,所以能做更多的工作. Oprofile的抽样工作利用 CPU的NMI中断来进行,而perf既可以利用NMI中断也可利用硬件计数器提供的周期性中断.用户能很容易用perf来oprofile一个进程或系统的执行时间分布,如 还可以利用系统定义的 “software counter”和各子系统的 “trace points” 对子系统进行分析,如 能统计6秒内kmem子系统的活动(这一点实际是利用ftrace提供的tracepoints来实现) 我认为有了perf,用户就没必要使用oprofile了