歡迎您光臨本站 註冊首頁

紅帽Linux上的故障定位技術

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  

1. 故障定位(Debugging)場景分類

為便於描述問題,將Linux上各種軟體故障定位的情形分成兩類

A) 在線故障故障定位

在線故障定位(online-debugging)就是在故障發生時, 故障所處的操作系統環境仍然可以訪問,故障處理人員可通過console, ssh等方式登錄到操作系統上,在shell上執行各種操作命令或測試程序的方式對故障環境進行觀察,分析,測試,以定位出故障發生的原因

B) 離線故障定位

離線故障定位(offline-debugging)就是在故障發生時,故障所處的操作系統環境已經無法正常訪問,但故障發生時系統的全部或部分狀態已經被系統本身所固有或事先設定的方式收集起來,故障處理人員可通過對收集到的故障狀態信息進行分析,定位出故障發生的原因

2. 應用進程故障情形及處理

應用進程的故障一般不會影響操作系統運行環境的正常使用(如果應用代碼的bug導致了內核的crash或hang,則屬於內核存在漏洞),所以可採用在線故障定位的方法,靈活的進行分析. 應用代碼故障的情形有如下幾種:

A) 進程異常終止

很多用戶認為進程異常終止情況無從分析,但實際上進程異常終止情況都是有跡可尋的. 所有的進程異常終止行為,都是通過內核發信號給特定進程或進程組實現的. 可分成幾個類型進行描述:

- 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() 等系統調用的使用

B) 進程阻塞,應用無法正常推進

這種情況,對於單個被阻塞的進程而言,屬於正常狀態, 但對於包含多個進程的應用整體而言,屬於異常. 應用無法推進,說明其中某一個進程推進的因素出現了問題,導致其他依賴於它的進程也要等待. 分析這種情形需要分析清楚進程或事件之間的依賴關係,及數據的處理流. 首先要用gdb -p <pid> 的back trace功能查出各進程阻塞的執行路徑, 以確定每個進程所處在的狀態機的位置. 通常而言,如果只考慮各個進程的狀態,則進程之間可能形成了一種互相依賴的環形關係,如(P1發請求=>P2處理=>P2發反應=>P1再請求=>P2處理=>P2再發反應), 但應用對workload, 一般是按一個個的transaction 或 session的方式進行處理的,每個transaction都有起點和終點, 我們需要用strace, tcpdump 等工具以及應用的執行日誌進行觀察,分析出當前正被處理的transaction所被阻滯的位置,從而找出全部狀態機被阻塞的原因. 導致這種狀態機停止運轉的原因有多個:如和應用通信的遠端出現了問題,後端資料庫/目錄等出現了問題,應用的某個進程或線程處於非正常的blocking位置或直接終止,不再正常工作.

C) 用戶進程形成死鎖

用戶進程形成死鎖,如果沒有內存上的故障,則完全是應用自身的邏輯問題. 死鎖的進程或線程之間由於鎖的互相佔有形成了環路。 這種情況發生時,用gdb -p <pid> 的back trace的功能能直接確定死鎖的進程全部阻塞在futex()等和鎖相關的系統調用上, 這些調用futex()的路徑可能是mutex, semaphore, conditional variable 等鎖函數. 通過分析call trace 的代碼,能直接確定各進程在執行到該位置時,可能已經持有的全部鎖, 根據這個修改程序的代碼,消除死鎖環路,就可解決問題.

注意,內存故障也可導致假的死鎖的,如物理內存故障可直接導致鎖變數的值為-1, 所以使用該鎖的進程都會阻塞. 如果是代碼的bug導致的內存corruption,可用valgrind工具檢查程序來發現. 但如果是物理內存的故障導致的corruption, 則需要硬體的支持,對於高端的PC, 如MCE功能的機器,當物理內存故障時能直接產生異常或報告, 但對於低端PC伺服器,除了運行memtest工具進行檢測外,沒有其他方法

D) 進程長期處於 'D' (UnInterruptible)狀態沒法退出

這種多是由內核中的故障引起的. 內核在很多執行路徑中會將進程至於'D'的狀態,以確保關鍵的執行路徑不被外部的信號中斷, 導致不必要的內核中數據結構狀態的不一致性. 但一般而言,進程處於 'D' 狀態的時間不會太久, 因為狀態結束的條件(如timer觸發,IO操作完成等)很快會將進程喚醒. 當進程長期處於 'D',關鍵是要找出其阻塞的代碼位置, 用 sysrq 的t鍵功能可直接列印出系統中全部睡眠進程的內核執行堆棧,如 echo 't' > /proc/sysrq-trigger, 其中包括出現 'D'狀態的進程的內核態堆棧. 找出代碼位置后,一般可直接分析出 'D' 狀態不能退出的原因, 如IO read操作因硬體或nfs故障而不能完成.

有可能導致 'D' 狀態的原因比較複雜,如‘D’的退出依賴於某變數的值,而該變數的值因某種原因被永久corrupted掉了.

3. 內核故障情形及處理

A) 內核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這樣的工具進行捕捉

B) 多處理機環境內核執行路徑產生的死鎖

內核死鎖和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上的調用堆棧,查處導致死鎖的邏輯原因.

C) 內核的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技術介紹

A) 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文件

B) 用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”

C) 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下的內容

D) 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 <my process>

還可以利用系統定義的 “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 –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文件

B) 測試Kdump部署的有效性

為了測試kdump部署的有效性,本人寫了如下一個內核模塊,通過insmod 載入該內核模塊, 就能產生一個內核線程,在10秒左右後,佔據100%的CPU,在20秒左右後觸發kdump. 系統重啟后,檢查/oracle分區/var/crash 目錄下的內容,就能確認vmcore文件是否生成.

Zqfthread.c #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/completion.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>

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

C) 用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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <net/ip_fib.h>
#include <linux/rtnetlink.h>
 

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->dev;
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->dx;
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
 

6. 參考文檔

A) #>man kdump.conf, #>man makedumpfile, #>man crash
B) #>apropos systemtap
C) http://en.wikipedia.org/wiki/SystemTap
D) http://lwn.net/Articles/322666/
E) kernel-source/Documentation/trace/tracepoints.txt
F) kernel-source/tools/perf/Documentation
G) www.linux-kongress.org/2010/slides/lk2010-perf-acme.pdf
H) kernel-source/Documentation/trace/
I) kernel-source/Documentation/krpobe.txt

 



[火星人 ] 紅帽Linux上的故障定位技術已經有1746次圍觀

http://coctec.com/docs/linux/show-post-68664.html