內核開發者一直在試圖尋找一種快捷高效的內核調試手段,用于內核開發之中。高效的調試技術有利于提高內核開發效率,縮短內核開發周期。 本文研究了一種新型的內核調試技術― Kprobes , Kprobes 是一個輕量級的內核調試工具,利用 Kprobes 技術可以在運行的內核中動態的插入探測點,在探測點處執行用戶預定義的操作。本文首先根據 Kprobes 在 Linux 內核中的源碼實現,針對 Linux CPU異常技術,single-step技術,Loadable Kernel Module技術以及RCU同步技術在 Kprobes 中的應用進行了研究。其次,針對 Kprobes 目前所支持的kprobe,jprobe,kretprobe等三種調試手段的實現進行了詳細的分析研究。
一、 Kprobes 調試技術
Kprobes
調試技術概述
一直以來,內核開發者一直在試圖尋找一種快捷高效的內核調試手段,用于內核開發之中。從 2.6版本的
Linux
開始,一種新的內核調試技術出現了,這就是
Kprobes
技術。
Kprobes
最早是源于IBM的Dprobe項目發展起來的,Dprobe是一個IBM公司開發的內核調試工具。從2.6.9
Linux
內核開始,
Kprobes
被加入內核源碼,并處于不斷完善之中,越來越多的功能被添加到
Kprobes
內核調試技術中來。
Kprobes
目前已經能在 i386,x86_64, ppc64, ia64,sparc64等CPU平臺上正常工作。
目前,大多發行版本都包含一個內核調試工具 SystemTap。SystemTap可以通過編寫腳本調試內核,該工具正是依托
Kprobes
來實現的。
Kprobes
是一個輕量級的內核調試工具,也就是說,
Kprobes
的運行基本不會影響到正常內核執行的流程。
利用
Kprobes
技術可以在運行的內核中動態的插入探測點,當內核運行到該探測點后可以執行用戶預定義的回調函數。當執行完用戶函數后又會回到正常的內核執行流程,開始新一輪的調試工作。
Kprobes
支持三種探測方式:第一種是最基本的探測方式稱為 kprobe,該探測方式支持在內核的任意位置放置探測點(除了與
Kprobes
實 現相關的代碼)。第二種調試方式稱之為 jprobe,該探測方式主要用于調試函數傳入的參數。第三種調試方式稱之為 kretprobe,該調試方式是在函數返回時執行用戶回調函數,利用該方式可以調試內核函數返回值。以上討論的后兩種調試方式都是基于第一種 kprobe調試方式實現的。
Kprobes
還支持三種回調函數類型:第一種叫做 pre_handler,該回調函數用于在執行被探測指令前執行。第二種叫 post_handler,該回調函數用于在執行完被探測指令后執行。第三種叫fault_handler,此函數用于在出現內存訪問錯誤時進行處理。
目前
Kprobes
內核調試技術已經被很多內核開發者所采用,并使用在內核開發過程的各個階段。
Kprobes
的開發還在不斷的進行之中,目前的SystemTap社區負責對該調試技術的維護以及新功能的開發,主要由IBM,Intel,Redhat等公司維護。
Kprobes
配置說明
使用
Kprobes
進行內核調試前,先要對被調試內核進行相關配置。以
Linux
2.6.20.3 版本
Linux
內核為例:
首先,需要把
Kprobes
相關代碼編譯進內核,進入內核目錄運行 make menuconfig 命令,在Instrumentation Support項目中選擇
Kprobes
。
其次,選擇 Configure standard kernel features 中的 Include all symbols in kallsyms項,該項用于啟用kallsyms_lookup_name()函數,這個函數用于檢索內核函數的地址。
在最新版本的
Kprobes
實現中已經支持直接利用函數名來進行注冊。
第三,選擇Loadable module support中的Enable Loadable module support項,該項用于啟用內核的可插入模塊功能。因為
Kprobes
的調試是通過模塊插入實現的,調試者需要編寫調試模塊并插入內核方式進行內核調試,因此必須選擇該選項。
Kprobes
中的關鍵技術
Kprobes
不只是純軟件的實現方案,該技術與具體硬件緊密相連,用到了一些硬件的特性。因此
Kprobes
的實現框架分為兩個部分實現:第一部分是
Kprobes
的 管理,這部分是與體系結構無關的代碼。第二部分是與具體CPU體系結構相關的實現代碼,比如準備單步執行環境等。這些代碼與具體 CPU體系結構緊密相連,因此在不同 CPU 上各有不同的實現方式。本文以下所有的討論都是基于 Intel IA32 CPU架構,2.6.20.3內核。下文討論用于支持
Kprobes
實現的四種關鍵技術。
Linux
CPU異常處理技術以及在
Kprobes
中的應用
CPU異常是在CPU運行期間,由于外圍硬件發出中斷信號或是執行CPU異常指令等情況所引發的。
CPU異常可分為硬件異常和軟件異常。
硬件異常也稱為硬件中斷,一般是有外圍硬件設備發出中斷信號引起。當外圍設備發出一個中斷信號,該信號被發往中斷處理器仲裁,比較有名的是 8259中斷處理芯片,目前Intel的CPU一般采用APIC(Advanced Programmable Interrupt Controllers)來實現。中斷處理器仲裁后發往 CPU的中斷引腳,這時CPU會開始一次中斷處理流程。
軟件異常不是由外圍設備發出的,而是由程序員寫入程序里的一些 CPU 異常指令引起的,比如int,int3這類指令就會引起一次軟件異常。在早期的 CPU上
Linux
系統調用的實現就是利用 int指令。當CPU執行到這些異常指令時,同樣會開始一次異常處理流程。
操作系統CPU異常處理的實現是和特定CPU體系結構緊密相關的。Intel IA32體系的CPU的每個中斷或是異常都有一個向量號,這些向量號從 0到255。
Linux
內核中,每一個中斷向量號都對應中斷描述符表(IDT)中的一項,因此中斷描述符表一共有 256項。IDT每一項包含8個字節,這8個字節的其中一部分是中斷處理函數的地址。表項的其他字段的含義這里不再介紹,可以從 Intel IA32程序員手冊中查到。
Linux
內核在初始化階段用set_trap_gate()宏初始中斷描述符表。
Linux
內核中,異常處理是通過兩級跳轉實現的。比如,如果當CPU運行過程中接收到一次異常信號,此時CPU會到根據中斷向量號到中斷描述符表中找到對應項,再跳轉到表項中所指的地址去執行。
這里跳轉到的地址一般情況下對應源文件
linux
/arch/i386/kernel/entry.S 中的某個匯編函數入口。匯編函數經過一定的初始化工作后再跳轉到C函數中去執行。這里的C函數才是真正的異常處理函數,當從C函數返回到原來的匯編函數 后,匯編函數還會做一部分后續工作。最后匯編函數執行 iret指令從中斷上下文中返回到被中斷的代碼中繼續執行。這是
Linux
內核處理異常一般過程,因此稱
Linux
的異常處理過程是兩次跳轉實現的。
在
Kprobes
的實現中同樣也用到了CPU異常,當插入一個探測點的時候,
Kprobes
處理例程會把插入點處的指令保存起來,然后用int3指令代替。當CPU運行到插入的int3指令時,
Linux
內核就進入了異常處理流程,之后再運行調試者預定義的回調函數。利用 CPU異常來實現探測點的觸發并處理是
Kprobes
實現的關鍵。
single-step技術及在 Kprobes 中應用
調試器的單獨執行功能是非常有用的,程序員可以通過單步執行代碼來確定程序執行的流程,隨時掌握變量變化情況,從而精確定位程序錯誤發生的位置。可以說單步執行功能是一個調試器必須具備的功能。single-step技術就是為了調試器的單步執行而設計的。
single-step技術的主要思想是,當程序執行到某條想要單獨執行 CPU指令時,在執行之前產生一次CPU異常,此時把異常返回時的CPU的EFLAGS寄存器的TF(調試位)位置為1,把IF(中斷屏蔽位)標志位置為 0,然后把EIP指向單步執行的指令。當單步指令執行完成后,CPU會自動產生一次調試異常(由于TF被置位)。此時,調試器一般都會把控制權又交回調試 器,回到交互模式。
在
Kprobes
實現中同樣也用到了single-step技術,但目的不是為了回到交互模式,而是把控制權交回
Kprobes
控制流程。當
Kprobes
完成pre_handler()處理后,就會利用single-step技術執行被調試指令。此時,
Kprobes
會利用debug異常,執行post_handler()。這是single-step
技術在
Kprobes
的主要應用。
oadable kernel module技術及在 Kprobes 中的應用
loadable kernel module(LKM)技術是
Linux
內核的又一強大特性。
在LKM技術出現以前,添加內核代碼只能通過修改內核源碼,然后重新編譯內核再重啟后才能生效。
當LKM技術出現后,內核編程變的簡單許多,內核開發者只需要把代碼寫成內核模塊形式,再插入內核就可以作為內核的一部分運行。確切來講,LKM技術為內 核開發者提供了在內核運行過程中動態插入和卸載內核模塊的功能。LKM技術在不用重新編譯內核的情況下,可以擴展內核的功能。目前,大量的設備驅動程序利 用LKM技術實現。驅動開發者把驅動程序寫成模塊的形式,并在內核啟動時自動加載入內核,當然這也可以通過手動加載方式實現。
LKM技術的出現帶來了兩個好處:第一,LKM技術使得驅動程序的開發和調試變得異常簡單,很大程度上提高了驅動開發的效率。第二,LKM技術的出現使得 內核鏡像不至于過于龐大,因為大部分的內核代碼可以寫成模塊形式,使用時才加載。這樣不會使得內核在系統內存中占用太多空間而影響系統性能。
在利用
Kprobes
進行內核調試時同樣用到了LKM技術。調試者需要把調試代碼寫成模塊形式并插入內核。當調試模塊被插入內核后就進入調試階段,而當調試模塊從內核中卸載時,也就意味著調試過程的結束。可以說LKM技術是
Kprobes
技術進行內核調試基礎。
RCU技術及在 Kprobes 中的應用
Linux
內 核有時會訪問到一些全局的數據結構,如果此時內核被搶占并且數據被修改,又或者在SMP系統(即多處理器系統)上運行,可能會有多個 CPU同時訪問同一塊內存的情況,此時內核數據就可能產生不一致性。為了避免這種情況,內核使用了一些同步機制,比如利用信號量同步,利用自旋鎖同步等方 法。在2.6版本的內核中又引入了一種新的內核同步機制RCU,這是Read Copy Update的縮寫。RCU的主要思想是分為兩個部分,第一部分是防止其他訪問者對被保護對象寫入,第二部分是真正展開寫入行為。讀者可以隨時訪問被 RCU保護的對象而不用獲得任何鎖。寫者先對對象的副本修改,在所有讀者都退出時再執行寫入行為,不同的寫者之間需要同步。RCU同步機制適用于存在大量 讀操作而很少寫操作的情況。因為這種情況下,讀操作不用獲得任何鎖就可以對共享對象進行讀操作,極大提高了效率。
在
Kprobes
對探測點數據結構的操作中也是大量存在讀操作而只有很少部分寫操作。為了提高效率,
Kprobes
的 實現中也引入了RCU機制。當發生探測點注冊或是注銷時,或是在執行探測點的回調函數時,探測點數據結構struct kprobe就會被RCU機制保護起來。根據RCU的原理,寫者的操作是被延遲的,而如果讀者發生了阻塞,那寫者的操作直到讀者被喚醒后才進行,這會大大 降低 RCU的效率。因此在執行
Kprobes
回調函數時內核的搶占以及CPU中斷都被禁用了。對于
Kprobes
來說,用RCU機制實現回調函數訪問也極大的提高了SMP系統上多探測點探測的效率,因為可以并行的執行回調函數。但在這樣的機制下,回調函數必須被設計成可重入的函數。
Kprobes 調試技術體系結構分析
根據 Kprobes 的源碼實現,從邏輯上基本可以把 Kprobes 分為3個部分:第一部分是注冊探測點部分,第二部分是調試處理部分,第三部分是注銷探測點部分。第一部分主要功能是進行一些安全性檢查,并根據要求安裝探測點等工作。第二部分是 Kprobes 實現的關鍵,該部分根據探測類型執行預定的操作。這里的探測類型主要有三種:kprobe,jprobe和kretprobe。這部分還會完成被探測指令單步執行等操作。第三部分是當調試者撤銷探測要求時,對探測點的注銷操作,主要是恢復被探測指令等工作。
下面分別介紹kprobe,jprobe和kretprobe的實現機制。
3.1 kprobe的實現
3.1.1 相關數據結構與函數分析
1) struct kprobe結構
該數據結構是整個
Kprobes
體系的基礎,所有
Kprobes
的行為都是圍繞該結構展開。以下是
struct kprobe結構的主要成員:
2) struct notifier_block 結構
該結構用于注冊異常發生時調用的回調函數。 int (*notifier_call)(struct notifier_block *, unsigned long, void *)成員是回調函數指針,int priority成員用于設置調用優先級。
Kprobes
中定義的優先級為最高優先級,確保注冊的回調函數被首先調用到。
3) register_kprobe()函數
該函數用于完成struct kprobe結構的注冊,插入探測點等操作。
4) kprobe_handler()函數
該函數用于處理由kprobe引發的int3異常。
5) post_kprobe_handler ()函數
該函數用于處理由kprobe引發的debug異常。
kprobe處理流程分析
kprobe是 Kprobes 實現體系中最底層也是最基本的一種探測方式,jprobe和kretprobe探測方式都是通過kprobe來實現的。kprobe的主要處理流程如下圖所示:
1)kprobe的注冊過程
當調試者向內核插入一個 kprobe 模塊時,首先會執行注冊探測點操作。該操作主要由register_kprobe()函數完成,在下文中都稱該函數為注冊器。注冊器的參數中包含一個 struct kprobe結構,該結構由調試者在調試模塊中創建。
首先,注冊器會進行一些正確性檢查工作,判斷傳入的 struct kprobe結構中symbol_name和addr 是否同時存在,如果是則返回錯誤。之后還會判斷探測地址是否在內核代碼段中并且不在
Kprobes
實現相關的代碼中。這樣的檢查是很必要的,如果探測地址出現在
Kprobes
實現相關代碼中就會造成遞歸現象。如果被探測的地址已經被注冊過,則會在kprobe_table中以鏈表形式組織。
其次,注冊器會保存被探測地址的指令碼到struct kprobe結構的ainsn.ainsn中去,以便以后進行single-step操作。對kprobe初始化完成后,注冊器會把傳入的struct kprobe結構指針插入哈希表中。最后,注冊器把被探測的指令的第一個字節替換成 int3指令。到這里,kprobe的注冊工作就完成了。
2)kprobe int3異常處理過程
完成注冊后kprobe的準備工作就完成了,一旦內核執行到被探測的指令,也就是注冊時被替換成的int3指令時,就會引發一次軟件異常。CPU會根據中斷描述符表執行中斷處理函數,int3的中斷處理函數在/
linux
/arch/i386/kernel/entry.S中實現,KPROBE_ENTRY(int3)就是該中斷處理函數的入口。匯編中斷處理函數會調用 do_int3()函數,作為 int3 中斷處理的 C 語言處理函數。
do_int3()函數一開始就會去調用notify_die()函數,該函數的主要作用是調用內核代碼注冊的異常的 回 調 函 數 。 在
Kprobes
的初始化代碼(init_Kprobes()函數)中調用了register_die_notifier() 用于注冊異常回調函數 。
Kprobes
注冊的異常回調函數為probe_exceptions_notify()。
此時執行權交到
Kprobes
之 中,kprobe_exception_notify()函數開始執行。該函數的參數中有一個參數val,該參數可以用于判斷當前回調函數由有什么異常產 生的。這里異常由 int3指令產生,因此接收到的參數應該為“DIE_INT3”。此時,又會調用 kprobe_handler()函數,該函數是
Kprobes
處理int3異常的主要實現函數。該函數首先會把發生異常的地址記錄下來,因為該地址就是注冊探測點的地址。為了防止內核被搶占,該函數禁止內核搶占功能。在 i386 CPU上,進入int3中斷處理時已經關閉CPU中斷,目前
Kprobes
的實現中只有i386體系上會關閉CPU中斷,在其他體系上的實現都沒有這樣做。
接著,開始檢查此次 int3 異常是否是由前一次
Kprobes
處理流程引發的,如果是由前一次
Kprobes
處理流程引發,則有兩種可能性。第一是該次
Kprobes
處理由于前一次回調函數執行了被探測代碼造成的,第二種可能性是由于 jprobe造成的,這部分將在 jprobe的實現一節中詳細討論。如果int3異常不是由前一次
Kprobes
處理流程引發的,根據先前記錄下來的探測點地址到哈希表中找到已注冊的struct kprobe結構。如果該結構中包含了pre_handler函數指針,則執行該預定的函數。
執行完用戶定義的 pre_handler函數時,已經完成了一部分的調試工作。接下來,就開始準備single-step步驟,該步驟用 prepare_singlestep()函數完成。這個函數與體系結構相關,下面是prepare_singlestep()函數在i386體系CPU 上的主要實現代碼:
程序1 prepare_singlestep()函數部分代碼
01 regs->eflags |= TF_MASK;
02 regs->eflags &= ~IF_MASK;
03 regs->eip = (unsigned long)p->ainsn.insn;
上面的代碼中設置了EFLAGS中的TF位并清空IF位,同時把異常返回的指令寄存器地址改為保存起來的原探測指令處,當異常返回時這些設置就會生效。 single-step技術已經在上文中討論過,這里不再贅述。執行完被探測的指令后,由于 CPU的標志寄存器被置位,此時又會引發一次CPU異常,該異
常在
Linux
內核中被稱為DEBUG異常。
3)Kprobe DEBUG異常處理
Linux
內核中對DEBUG異常的處理方式與處理int3異常很類似。DEGUG異常的中斷處理函數也是在/
linux
/arch/i386/kernel/entry.S 中實現,KPROBE_ENTRY(debug)就是該異常的中斷處理函數的入口。該函數會調用do_debug()函數進一步處理DEBUG異常,同樣 的notify_die()函數被調用。與int3異常不同的是此時傳入notify_die()函數的第一個參數是“DIE_DEBUG”。
最終,notify_die() 函數會調用
Kprobes
初始化時注冊的回調函數kprobe_exceptions_notify() 。此時,控制權又一次交回
Kprobes
。
kprobe_exceptions_notify() 判斷傳入的類型為DIE_DEBUG,這時會去調用post_kprobe_handler ()函數。post_kprobe_handler ()首先判斷用戶定義的post_handler回調函數是否存在,如果存在則執行之。
之后,會調用 resume_execution()函數做一些會做恢復工作,該函數會把 EFLAGES寄存器的TF 為清空,并根據被探測指令類型的不同,做不同的處理。在 resume_execution()返回后,post_kprobe_handler ()函數就會啟用在 int3 異常處理中被禁止的內核搶占功能。到這里,
Kprobes
對DEBUG異常的處理基本完成了,又把控制權交回內核。
以上是kprobe執行的主要流程,可以看出kprobe利用了兩次CPU異常的方式執行了用戶定義的pre_handler 和 post_handler 回調函數。并通過 single-step 技術執行了被探測指令。當一次kprobe 執行周期完成后,又開始等待新一輪執行周期的到來。只有當調試者卸載了調試模塊后,kprobe的生命周期才算結束。
jprobe的實現
相關數據結構與函數分析
1) struct jprobe結構
該結構在注冊jprobe探測點時使用,它包含兩個成員:
struct kprobe kp;//這是jprobe一個內嵌的struct kprobe結構成員,因為jprobe是基于kprobe實現的。
kprobe_opcode_t *entry;//這是被探測函數的代理函數。
2) setjmp_pre_handler()函數
該函數作為jprobe內嵌kprobe的pre_handler,在探測點被觸發時首先會被調用到。
3) longjmp_break_handler()函數
該函數作為jprobe內嵌kprobe的break_handler,當再次進入int3異常時被調用。
jprobe處理流程分析
jprobe是
Kprobes
中實現的另一種調試方式,該調試方式主要為了滿足調試內核函數傳遞的參數的情況。jprobe是基于kprobe實現的,是kprobe調試的一種擴展形式。
jprobe的基本原理是利用了一個探測代理函數來接收傳入參數,做相應處理后再把控制權交回被調試函數。下圖為jprobe的執行流程圖:
1)jprobe的注冊過程
由于jprobe用于調試傳入參數情況,用戶在編寫jprobe探測模塊時與編寫kprobe模塊有所不同。jprobe結構定義時只要給entry成員 賦值成調試代理函數,該函數的參數類型必須與被探測函數完全相同。當一個 jprobe 探測模塊插入內核后,jprobe 的注冊過程會被啟動。首先,jp->kp.pre_handler 會 被 設 置 成 setjmp_pre_handler , jp->kp.break_handler 被設置成longjmp_break_handler,這兩個函數會在以后討論。之后會調用kprobe的注冊函數進行探測點的插入。在之前的章節中已經 詳細分析了kprobe的注冊過程,這里不再重復。
2)jprobe探測點觸發過程
與 kprobe 相同,當內核執行到由 jprobe 插入的探測點時同樣會產生 int3 CPU 異常。在kprobe實現一節,已經詳細的分析了 kprobe對int3 CPU異常的處理方式。最終pre_handler函數會被調用。在 jprobe 中,pre_handler 不是由調試者定義的,而是在注冊時被賦值成了setjmp_pre_handler函數。該函數會把異常發生時的堆棧內容保存起來,并且把異常返回時的 EIP值設為調試代理函數的地址。
經過以上處理,int3中斷返回時就會去執行調試代理函數而不是被探測的函數。代理函數執行完用戶定義操作后必須調用 jprobe_return()函數,目的是為了確保執行流程能回到被探測的函數中。
jprobe_return()函數利用內嵌匯編再次執行int3指令,此時又會發生一次int3 CPU異常處理。
當異常處理執行到kprobe_handler()函數進行判斷是否有kprobe正在運行時,發現目前有kprobe正在運行,而此時產生異常的地址并 沒有被注冊過。這種情況下,struct kprobe 結構中的break_handler函數會被調用,也就是在jprobe注冊階段注冊的longjmp_break_handler()函數開始執行。 該函數主要作用是把在setjmp_pre_handler函數中保存起來的堆棧內容以及寄存器進行恢復,當異常返回時其環境與jprobe探測點異常發 生時完全相同。
接著kprobe_handler()又會準備好單步執行的環境,并單步執行被探測指令,同時產生 Debug異常。在 kprobe 實現一節已經詳細分析了 Debug 異常處理過程。當 Debug 異常返回時也就回到了jprobe探測指令的下一條指令的位置繼續執行。
通過以上分析可以看出,jprobe是基于kprobe調試方式實現的,jprobe利用了三次CPU異常,產生的前兩次CPU異常都是int3異常,第 三次產生了Debug異常。jprobe主要通過代理函數的方式來實現傳入參數的調試,并利用修改異常返回地址的方式來控制執行的流程。
kretprobe的實現
相關數據結構與函數分析
1) struct kretprobe結構
該結構是kretprobe實現的基礎數據結構,以下是該結構的成員:
struct kprobe kp; //該成員是kretprobe內嵌的struct kprobe結構。
kretprobe_handler_t handler;//該成員是調試者定義的回調函數。
int maxactive;//該成員是最多支持的返回地址實例數。
int nmissed;//該成員記錄有多少次該函數返回沒有被回調函數處理。
struct hlist_head free_instances;
用于鏈接未使用的返回地址實例,在注冊時初始化。
struct hlist_head used_instances;//該成員是正在被使用的返回地址實例鏈表。
2) struct kretprobe_instance結構
該結構表示一個返回地址實例。因為函數每次被調用的地方不同,這造成了返回地址不同,因此需要為每一次發生的調用記錄在這樣一個結構里面。以下是該結構的成員:
struct hlist_node uflist;
該成員被鏈接入kretprobe的used_instances或是free_instances鏈表。
struct kretprobe *rp;//該成員指向所屬的kretprobe結構。
kprobe_opcode_t *ret_addr;//該成員用于記錄被探測函數正真的返回地址。
struct task_struct *task;//該成員記錄當時運行的進程。
3) pre_handler_kretprobe()函數
該函數在kretprobe探測點被執行到后,用于修改被探測函數的返回地址。
4) trampoline_handler()函數
該函數用于執行調試者定義的回調函數以及把被探測函數的返回地址修改回原來的返回地址。
kretprobe處理流程分析
kretprobe探測方式是基于kprobe實現的又一種內核探測方式,該探測方式主要用于在函數返回時進行探測,獲得內核函數的返回值,還可以用于計算函數執行時間等方面。
1) kretprobe的注冊過程
調試者要進行kretprobe調試首先要注冊處理,這需要在調試模塊中調用register_kretprobe(),下文中稱該函數為 kretprobe 注冊器。kretprobe 注冊器對傳入的kretprobe結構的中kprobe.pre_handler賦值為pre_handler_kretprobe()函數,用于在探測 點被觸發時被調用。接著,kretprobe注冊器還會初始化kretprobe的一些成員,比如分配返回地址實例的空間等操作。最后, kretprobe注冊器會利用 kretprobe內嵌的struct kprobe結構進行kropbe的注冊。自此,kretprobe注冊過程就完成了。
2) kretprobe探測點的觸發過程
kretprobe觸發是在剛進入被探測函數的第一條匯編指令時發生的,因為 kretprobe注冊時把該地址修改位int3指令。
此時發生了一次CPU異常處理,這與kprobe探測點被觸發相同。但與kprobe處理不同的是,這里并不是運行用戶定義的 pre_handler函數,而是執行pre_handler_kretprobe()函數,該函數又會調用 arch_prepare_kretprobe()函數。arch_prepare_kretprobe()函數的主要功能是把被探測函數的返回地址變換 為&kretprobe_trampoline所在的地址,這是一個匯編地址標簽。這個標簽的地址在 kretprobe_trampoline_holder()中用匯編偽指令定義。替換函數返回地址是kretprobe實現的關鍵。當被探測函數返回 時,返回到&kretprobe_trampoline地址處開始運行。
接著,在一些保護現場的處理后,又去調用trampoline_handler()函數。該函數的主要有兩個作用,一是根據當前的實例去運行用戶定義的調 試函數,也就是 kretprobe結構中的handler所指向的函數,二是把返回值設成被探測函數正真的返回地址。最后,在進行一些堆棧的處理后,被探測函數又返回到 了正常執行流程中去。
以上討論的就是kretprobe的執行過程,可以看出,該調試方式的關鍵點在于修改被探測函數的返回地址到
kprobes
的控制流程中,之后再把返回地址修改到原來的返回地址并使得該函數繼續正常執行。
結束語
Kprobes
內核調試技術的優勢是顯而易見的:調試者可以通過
Kprobes
提供的簡單接口對
Kprobes
探測點進行注冊,通過編寫回調函數就可以實現調試,使用起來非常簡便。利用
Kprobes
調試時,可以以內核模塊的形式編寫調試代碼。這樣做可以在不重新編譯內核的情況下實現內核調試,大大提高了調試的效率。目前
Kprobes
提供了三種調試手段:kprobe,jprobe,kretprobe,這三種手段可以滿足不同的調試目的。同時
Kprobes
支持除了與
Kprobes
實現相關代碼外的任意內核代碼的調試,能滿足大多數調試需要。
Kprobes
的分為體系結構無關和體系結構相關兩部分代碼實現,這樣做增加了
Kprobes
的可擴展性。雖然優勢明顯,但
Kprobes
還是存在一些不足。比如,
Kprobes
雖然提供了豐富的調試手段,但調試者無法對插入的調試模塊有
效的控制和管理。另外,
Kprobes
是一個匯編級的調試技術,只能對寄存器以及內存地址級別進行調試,目前還無法對內核函數中的局部變量等,也就是源碼級別進行有效的調試。目前
Kprobes
還不能支持所有的CPU架構。這些不足在一定程度上限制了
Kprobes
的使用。
http://zfpillar.devebar.net/2008/11/821.html#more-821
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
