共计 14091 个字符,预计需要花费 36 分钟才能阅读完成。
本篇文章为大家展示了如何理解 Linux 故障定位技术,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
主要是来了解并学习 linux 中故障定位技术的学习,故障定位技术分为在线故障定位和离线故障定位。
1、故障定位 (Debugging) 场景分类
为便于描述问题,将 Linux 上各种软件故障定位的情形分成两类
(1)在线故障故障定位
在线故障定位 (online-debugging) 就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过 console, ssh 等方式登录到操作系统上,在 shell 上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因
(2)离线故障定位
离线故障定位 (offline-debugging) 就是在故障发生时,故障所处的操作系统环境已经无法正常访问,但故障发生时系统的全部或部分状态已经被系统本身所固有或事先设定的方式收集起来,故障处理人员可通过对收集到的故障定位状态信息进行分析,定位出故障发生的原因
2、应用进程故障情形及处理
应用进程的故障一般不会影响操作系统运行环境的正常使用(如果应用代码的 bug 导致了内核的 crash 或 hang, 则属于内核存在漏洞),所以可采用在线故障定位的方法,灵活的进行分析. 应用代码故障的情形有如下几种:
(1)进程异常终止
很多用户认为进程异常终止情况无从分析,但实际上进程异常终止情况都是有迹可寻的. 所有的进程异常终止行为,都是通过内核发信号给特定进程或进程组实现的. 可分成几个类型进行描述:
– SIGKILL. SIGKILL 最特殊,因为该信号不可被捕获,同时 SIGKILL 不会导致被终止的进程产生 core 文件, 但如果真正的是由内核中发出的 SIGKILL, 则内核一定会在 dmesg 中记录下信息. 另外在内核中使用 SIGKILL 的地方 ***,如 oom_kill_process()中, 所以通过 dmesg 记录并且分析内核中使用 SIGKILL 的代码,并不难分析原因
– SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV. 这几个信号在保留情况下会终止进程并会产生 core 文件, 用户根据 core 中的 stack trace 信息,能直接定位出导致终止信号的代码位置. 另外, SIGQUIT,SIGABRT 一般是由用户代码自己使用的,好的代码一般会记录日志. SIGILL, SIGBUS, SIGFPE, SIGSEGV, 都是由内核中产生的,搜索内核源码,不难列出内核中使用这几个信号的地方, 如 SIGILL 是非法指令,可能是浮点运算产生的代码被 corrupted 或文本区域的物理内存 corruption; SIGBUS 多由 MCE 故障定位导致; SIGSEGV 多由应用代码的指针变量被 corrupted 导致. 对于应用的 heap 或 stack 的内存被 corrupted, 可用 valgrind 工具对应用进行 profile, 通常能直接发现导致 corruption 的代码
– SIGINT, SIGPIPE, SIGALRM, SIGTERM. 这几个信号在保留情况下终止进程但不会产生 core 文件. 对这几个信号,建议用户一定要定义一个 handler, 以记录产生问题的上下文. 比较容易忽略的是 SIGPIPE, 很多用户程序在使用 select()或 poll()时只监听 read/write 描述符,不监听 exception 描述符,在对方 TCP 已经关闭的情况下,仍然向 socket 中写入,导致 SIGPIPE.
– 对于恶意的代吗产生的进程终止行为,如合作的一些进程中,A 向 B 发 SIGKILL, 而没做日志记录,或者 B 直接判断某条件而调用 exit(), 也没有做日志记录. 在应用代码量很大的情况下,通过分析代码故障定位这种情形也许很难. SystemTap 提供了解决这个问题的一个比较好的方法,就是写用户层的 probes, 追踪进程对 signal(), exit() 等系统调用的使用
(2)进程阻塞,应用无法正常推进
这种情况,对于单个被阻塞的进程而言,属于正常状态,但对于包含多个进程的应用整体而言,属于异常. 应用无法推进,说明其中某一个进程推进的因素出现了问题,导致其他依赖于它的进程也要等待. 分析这种情形需要分析清楚进程或事件之间的依赖关系,及数据的处理流. 首先要用 gdb -p 的 back trace 功能查出各进程阻塞的执行路径, 以确定每个进程所处在的状态机的位置.
通常而言,如果只考虑各个进程的状态,则进程之间可能形成了一种互相依赖的环形关系,如(P1 发请求 = P2 处理 = P2 发反应 = P1 再请求 = P2 处理 = P2 再发反应), 但应用对 workload, 一般是按一个个的 transaction 或 session 的方式进行处理的, 每个 transaction 都有起点和终点, 我们需要用 strace, tcpdump 等工具以及应用的执行日志进行观察,分析出当前正被处理的 transaction 所被阻滞的位置,从而找出全部状态机被阻塞的原因. 导致这种状态机停止运转的原因有多个:如和应用通信的远端出现了问题,后端数据库 / 目录等出现了问题,应用的某个进程或线程处于非正常的 blocking 位置或直接终止,不再正常工作.
(3)用户进程形成死锁
用户进程形成死锁,如果没有内存上的故障定位,则完全是应用自身的逻辑问题. 死锁的进程或线程之间由于锁的互相占有形成了环路。这种情况发生时,用 gdb -p 的 back trace 的功能能直接确定死锁的进程全部阻塞在 futex()等和锁相关的系统调用上, 这些调用 futex()的路径可能是 mutex, semaphore, conditional variable 等锁函数. 通过分析 call trace 的代码,能直接确定各进程在执行到该位置时,可能已经持有的全部锁, 根据这个修改程序的代码,消除死锁环路,就可解决问题.
注意,内存故障也可导致假的死锁的,如物理内存故障可直接导致锁变量的值为 -1,所以使用该锁的进程都会阻塞. 如果是代码的 bug 导致的内存 corruption, 可用 valgrind 工具检查程序来发现. 但如果是物理内存的故障定位导致的 corruption, 则需要硬件的支持,对于高端的 PC, 如 MCE 功能的机器,当物理内存故障定位时能直接产生异常或报告, 但对于低端 PC 服务器,除了运行 memtest 工具进行检测外,没有其他方法
(4)进程长期处于 D (UnInterruptible)状态没法退出
这种多是由内核中的故障引起的. 内核在很多执行路径中会将进程至于 D 的状态,以确保关键的执行路径不被外部的信号中断, 导致不必要的内核中数据结构状态的不一致性. 但一般而言,进程处于 D 状态的时间不会太久, 因为状态结束的条件(如 timer 触发,
IO 操作完成等)很快会将进程唤醒. 当进程长期处于 D , 关键是要找出其阻塞的代码位置,用 sysrq 的 t 键功能可直接打印出系统中全部睡眠进程的内核执行堆栈,如 echo t /proc/sysrq-trigger, 其中包括出现 D 状态的进程的内核态堆栈. 找出代码位置后,一般可直接分析出 D 状态不能退出的原因, 如 IO read 操作因硬件或 nfs 故障而不能完成.
有可能导致 D 状态的原因比较复杂,如 lsquo;D rsquo; 的退出依赖于某变量的值,而该变量的值因某种原因被 ***corrupted 掉了.
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 的代码路径.
panic 一般是很直观的,panic 的堆栈信息能直接反映出导致 bug 的原因,如 MCE 故障,NMI 故障, 数据结构分配失败等. 但有时 panic 是因为内核主动发现了关键的数据结构不一致性,这种不一致性是什么时候,什么代码导致的,并不清楚,可能还需要多次测试用 SystemTap 这样的工具进行捕捉
(2)多处理机环境内核执行路径产生的死锁
内核死锁和 panic 不一样,产生死锁时,内核并不主动的使自己处于挂起状态. 但内核死锁发生时,两个以上的 CPU 的执行路径在内核态不能推进了,处于互相阻塞状态, 而且是 100% 的占用 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. 访问 http://ftp.redhat.com 可下载 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 某个进程在一端时间的函数调用情况
# echo “function” /sys/kernel/debug/tracing/current_tracer # echo “xxx” /sys/kernel/debug/tracing/set_ftrace_pid # echo 1 /sys/kernel/debug/tracing/tracing_enabled
除 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 一个进程或系统的执行时间分布,如
# perf top -f 1000 -p
还可以利用系统定义的“software counter”和各子系统的“trace points”对子系统进行分析, 如
# perf stat -a -e kmem:mm_page_alloc -e kmem:mm_page_free_direct -e kmem:mm_pagevec_free sleep 6
能统计 6 秒内 kmem 子系统的活动 (这一点实际是利用 ftrace 提供的 tracepoints 来实现)
我认为有了 perf, 用户就没必要使用 oprofile 了
5、用 kdump 工具内核故障定位实例
A) 部署 Kdump
部署 kdump 收集故障信息的步骤如下:
(1)设置好相关的内核启动参数
在 /boot/grub/menu.lst 中加入如下内容
crashkernel=128M@16M nmi_watchdog=1
其中 crashkernel 参数是用来为 kdump 的内核预留内存的; nmi_watchdog=1 是用来激活 NMI 中断的, 我们在未确定故障是否关闭了中断的情况下, 需要部署 NMI watchdog 才能确保触发 panic. 重启系统确保设置生效
(2)设置好相关的 sysctl 内核参数
在 /etc/sysctl.conf 中 *** 加入一行
kernel.softlookup_panic = 1
该设置确保 softlock 发生时会调用 panic, 从而触发 kdump 行为执行 # sysctl -p 确保设置生效
(3)配置 /etc/kdump.conf
在 /etc/kdump.conf 中加入如下几行内容
ext3 /dev/sdb1 core-collector makedumpfile -c ndash;message-level 7 -d 31 -i /mnt/vmcoreinfo path /var/crash default reboot
其中 /dev/sdb1 是用于放置 dumpfile 的文件系统, dumpfile 文件放置在 /var/crash 下,要事先在 /dev/sdb1 分区下创建 /var/crash 目录.“-d 31”指定对 dump 内容的过滤级别,这参数对于 dump 分区放不下全部内存内容或用户不想让 dumping 中断业务太长时间时很重要. vmcoreinfo 文件放置在 /dev/sdb1 分区的 / 目录下, 需要使用如下命令产生:
# makedumpfile -g //vmcoreinfo -x /usr/lib/debug/lib/modules/2.6.18-128.el5.x86_64/vmlinux
“vmlinux”文件是由 kernel-debuginfo 包提供的,在运行 makedumpfile 之前需要安装相应内核的 kernel-debuginfo 和 kernel-debuginfo-common 两个包,该两个包需从 http://ftp.redhat.com 下载.“default reboot”用来告诉 kdump, 收集完 dump 信息后重启系统
(4)激活 kdump
运行 # service kdump start 命令,你会看到,在成功完成的情况下会在 /boot/ 目录下生成一个 initrd-2.6.18-128.el5.x86_64kdump.img 文件,该文件就是 kdump 加载的内核的 initrd 文件,收集 dump 信息的工作就是在该 initrd 的启动环境下进行的. 查看 /etc/init.d/kdump 脚本的代码,你可看到其中会调用 mkdumprd 命令创建用于 dump 的 initrd 文件
1、测试 Kdump 部署的有效性
为了测试 kdump 部署的有效性,本人写了如下一个内核模块,通过 insmod 加载该内核模块,就能产生一个内核线程,在 10 秒左右后,占据 100% 的 CPU,在 20 秒左右后触发 kdump. 系统重启后,检查 /oracle 分区 /var/crash 目录下的内容,就能确认 vmcore 文件是否生成.
Zqfthread.c #include #include #include #include #include #include MODULE_AUTHOR(frzhang@redhat.com MODULE_DESCRIPTION( A module to test .... MODULE_LICENSE( GPL static struct task_struct *zqf_thread; static int zqfd_thread(void *data); static int zqfd_thread(void *data) { int i=0; while (!kthread_should_stop()) { i++; if ( i 10 ) { msleep_interruptible(1000); printk(%d seconds\n , i); } if ( i == 1000 ) // Running in the kernel i = 11 ; } return 0; } static int __init zqfinit(void) { struct task_struct *p; p = kthread_create(zqfd_thread, NULL, %s , zqfd if ( p ) { zqf_thread = p; wake_up_process(zqf_thread); // actually start it up return(0); } return(-1); } static void __exit zqffini(void) { kthread_stop(zqf_thread); } module_init(zqfinit); module_exit(zqffini) Makefile obj-m += zqfthread.o Making # make -C /usr/src/kernels/2.6.32-71.el6.x86_64/ M=`pwd` modules
2、用 crash 工具分析 vmcore 文件
用 crash 命令分析 vmcore 的命令行格式如下所示. 用 crash 打开 vmcore 后,主要是用 dmesg 及 bt 命令打印出问题的执行路径的 call trace, 用 dis 反汇编出代码,最终确认 call trace 对应的 C 源码中的位置,再进行逻辑分析.
# crash /usr/lib/debug/lib/modules/2.6.18-128.el5.x86_64/vmlinux /boot/System.map-2.6.18-128.el5.x86_64 ./vmcore
6、使用 kprobe 来观察内核函数的执行实例
kprobe 是 SystemTap 对内核函数进行 probing 的功能在内核中的实现,由于内核中提供了正式的 API 来使用 kprobe, 所以对很多内核程序员来说,也许直接使用 kprobe 比使用 SystemTap 更方便. 内核中提供了三种类型的 kprobe 处理函数,分别是 jprobe, kprobe, kretprobe, 下面的代码用这三个 probe 观察在 TCP/IP 的 arp_process 函数执行中对 ip_route_input()调用的返回结果. 这个代码还展示了在同一个函数 probe 的 Entry handler 和 Ret handler 之间共享参数的方法. 代码如下:
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) { struct net_device *dev = skb- struct in_device *in_dev; int no_addr, rpf; in_dev = in_dev_get(dev); no_addr = ( in_dev- ifa_list == NULL ); rpf = IN_DEV_RPFILTER(in_dev); in_dev_put(in_dev); printk(\narp_process() is called with interface device %s, in_dev(no_addr=%d,rpf=%d) \n , 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) { printk( Calling: %s()\n , ri- rp- kp.symbol_name); return(0); }; static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { int eax; eax = regs- ax 0xffff ; printk( Returning: %s() with a return value: 0x%lx(64bit) 0x%x(32bit)\n , ri- rp- kp.symbol_name, regs- ax, eax); return(0); }; static int fib_lookup_entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { struct fib_result *resp; resp = (struct fib_result *) regs- printk(Calling: %s()\n , ri- rp- kp.symbol_name); *((struct fib_result **)ri- data) = resp; return(0); }; static int fib_lookup_return_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { struct fib_result *resp; int eax; eax = regs- ax 0xffff ; resp = *((struct fib_result **) ri- data); printk(Returning: fib_lookup() with a return value: 0x%lx(64bit) 0x%x(32bit), result- type: %d\n , regs- ax, eax, resp- 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 Making # make -C /usr/src/kernels/2.6.32-71.el6.x86_64/ M=`pwd` modules
上述内容就是如何理解 Linux 故障定位技术,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注丸趣 TV 行业资讯频道。