一、kprobe簡介
kprobe是一個動態地收集調試和性能信息的工具,它從Dprobe項目派生而來,是一種非破壞性工具,用戶用它幾乎可以跟蹤任何函數或被執行的指令以及一些異步事件(如timer)。它的基本工作機制是:用戶指定一個探測點,並把一個用戶定義的處理函數關聯到該探測點,當內核執行到該探測點時,相應的關聯函數被執行,然後繼續執行正常的代碼路徑。
kprobe實現了三種類型的探測點: kprobes, jprobes和kretprobes (也叫返回探測點)。 kprobes是可以被插入到內核的任何指令位置的探測點,jprobes則只能被插入到一個內核函數的入口,而kretprobes則是在指定的內核函數返回時才被執行。
一般,使用kprobe的程序實現作一個內核模塊,模塊的初始化函數來負責安裝探測點,退出函數卸載那些被安裝的探測點。kprobe提供了接口函數(APIs)來安裝或卸載探測點。目前kprobe支持如下架構:i386、x86_64、ppc64、ia64(不支持對slot1指令的探測)、sparc64 (返回探測還沒有實現)。
二、kprobe實現原理
當安裝一個kprobes探測點時,kprobe首先備份被探測的指令,然後使用斷點指令(即在i386和x86_64的int3指令)來取代被探測指令的頭一個或幾個字節。當CPU執行到探測點時,將因運行斷點指令而執行trap操作,那將導致保存CPU的寄存器,調用相應的trap處理函數,而trap處理函數將調用相應的notifier_call_chain(內核中一種異步工作機制)中註冊的所有notifier函數,kprobe正是通過向trap對應的notifier_call_chain註冊關聯到探測點的處理函數來實現探測處理的。當kprobe註冊的notifier被執行時,它首先執行關聯到探測點的pre_handler函數,並把相應的kprobe
struct和保存的寄存器作爲該函數的參數,接着,kprobe單步執行被探測指令的備份,最後,kprobe執行post_handler。等所有這些運行完畢後,緊跟在被探測指令後的指令流將被正常執行。
jprobe通過註冊kprobes在被探測函數入口的來實現,它能無縫地訪問被探測函數的參數。jprobe處理函數應當和被探測函數有同樣的原型,而且該處理函數在函數末必須調用kprobe提供的函數jprobe_return()。當執行到該探測點時,kprobe備份CPU寄存器和棧的一些部分,然後修改指令寄存器指向jprobe處理函數,當執行該jprobe處理函數時,寄存器和棧內容與執行真正的被探測函數一模一樣,因此它不需要任何特別的處理就能訪問函數參數, 在該處理函數執行到最後時,它調用jprobe_return(),那導致寄存器和棧恢復到執行探測點時的狀態,因此被探測函數能被正常運行。需要注意,被探測函數的參數可能通過棧傳遞,也可能通過寄存器傳遞,但是jprobe對於兩種情況都能工作,因爲它既備份了棧,又備份了寄存器,當然,前提是jprobe處理函數原型必須與被探測函數完全一樣。
kretprobe也使用了kprobes來實現,當用戶調用register_kretprobe()時,kprobe在被探測函數的入口建立了一個探測點,當執行到探測點時,kprobe保存了被探測函數的返回地址並取代返回地址爲一個trampoline的地址,kprobe在初始化時定義了該trampoline並且爲該trampoline註冊了一個kprobe,當被探測函數執行它的返回指令時,控制傳遞到該trampoline,因此kprobe已經註冊的對應於trampoline的處理函數將被執行,而該處理函數會調用用戶關聯到該kretprobe上的處理函數,處理完畢後,設置指令寄存器指向已經備份的函數返回地址,因而原來的函數返回被正常執行。
被探測函數的返回地址保存在類型爲kretprobe_instance的變量中,結構kretprobe的maxactive字段指定了被探測函數可以被同時探測的實例數,函數register_kretprobe()將預分配指定數量的kretprobe_instance。如果被探測函數是非遞歸的並且調用時已經保持了自旋鎖(spinlock),那麼maxactive爲1就足夠了; 如果被探測函數是非遞歸的且運行時是搶佔失效的,那麼maxactive爲NR_CPUS就可以了;如果maxactive被設置爲小於等於0, 它被設置到缺省值(如果搶佔使能,
即配置了 CONFIG_PREEMPT,缺省值爲10和2*NR_CPUS中的最大值,否則缺省值爲NR_CPUS)。
如果maxactive被設置的太小了,一些探測點的執行可能被丟失,但是不影響系統的正常運行,在結構kretprobe中nmissed字段將記錄被丟失的探測點執行數,它在返回探測點被註冊時設置爲0,每次當執行探測函數而沒有kretprobe_instance可用時,它就加1。
三、kprobe的接口函數
kprobe爲每一類型的探測點提供了註冊和卸載函數。
1.register_kprobe
它用於註冊一個kprobes類型的探測點,其函數原型爲:
int register_kprobe(struct kprobe *kp);
爲了使用該函數,用戶需要在源文件中包含頭文件linux/kprobes.h。
該函數的參數是struct kprobe類型的指針,struct kprobe包含了字段addr、pre_handler、post_handler和fault_handler,addr指定探測點的位置,pre_handler指定執行到探測點時執行的處理函數,post_handler指定執行完探測點後執行的處理函數,fault_handler指定錯誤處理函數,當在執行pre_handler、post_handler以及被探測函數期間發生錯誤時,它會被調用。在調用該註冊函數前,用戶必須先設置好struct kprobe的這些字段,用戶可以指定任何處理函數爲NULL。
該註冊函數會在kp->addr地址處註冊一個kprobes類型的探測點,當執行到該探測點時,將調用函數kp->pre_handler,執行完被探測函數後,將調用kp->post_handler。如果在執行kp->pre_handler或kp->post_handler時或在單步跟蹤被探測函數期間發生錯誤,將調用kp->fault_handler。
該函數成功時返回0,否則返回負的錯誤碼。
探測點處理函數pre_handler的原型如下:
int pre_handler(struct kprobe *p, struct pt_regs *regs);
用戶必須按照該原型參數格式定義自己的pre_handler,當然函數名取決於用戶自己。參數p就是指向該處理函數關聯到的kprobes探測點的指針,可以在該函數內部引用該結構的任何字段,就如同在使用調用register_kprobe時傳遞的那個參數。參數regs指向運行到探測點時保存的寄存器內容。kprobe負責在調用pre_handler時傳遞這些參數,用戶不必關心,只是要知道在該函數內你能訪問這些內容。
一般地,它應當始終返回0,除非用戶知道自己在做什麼。
探測點處理函數post_handler的原型如下:
void post_handler(struct kprobe *p, struct pt_regs *regs, unsigned long flags);
前兩個參數與pre_handler相同,最後一個參數flags總是0。
錯誤處理函數fault_handler的原刑如下:
int fault_handler(struct kprobe *p, struct pt_regs *regs, int trapnr);
前兩個參數與pre_handler相同,第三個參數trapnr是與錯誤處理相關的架構依賴的trap號(例如,對於i386,通常的保護錯誤是13,而頁失效錯誤是14)。
如果成功地處理了異常,它應當返回1。
2.register_jprobe
該函數用於註冊jprobes類型的探測點,它的原型如下:
int register_jprobe(struct jprobe *jp);
爲了使用該函數,用戶需要在源文件中包含頭文件linux/kprobes.h。
用戶在調用該註冊函數前需要定義一個struct jprobe類型的變量並設置它的kp.addr和entry字段,kp.addr指定探測點的位置,它必須是被探測函數的第一條指令的地址,entry指定探測點的處理函數,該處理函數的參數表和返回類型應當與被探測函數完全相同,而且它必須正好在返回前調用jprobe_return()。如果被探測函數被聲明爲asmlinkage、fastcall或影響參數傳遞的任何其他形式,那麼相應的處理函數也必須聲明爲相應的形式。
該註冊函數在jp->kp.addr註冊一個jprobes類型的探測點,當內核運行到該探測點時,jp->entry指定的函數會被執行。
如果成功,該函數返回0,否則返回負的錯誤碼。
3.register_kretprobe
該函數用於註冊類型爲kretprobes的探測點,它的原型如下:
int register_kretprobe(struct kretprobe *rp);
爲了使用該函數,用戶需要在源文件中包含頭文件linux/kprobes.h。
該註冊函數的參數爲struct kretprobe類型的指針,用戶在調用該函數前必須定義一個struct kretprobe的變量並設置它的kp.addr、handler以及maxactive字段,kp.addr指定探測點的位置,handler指定探測點的處理函數,maxactive指定可以同時運行的最大處理函數實例數,它應當被恰當設置,否則可能丟失探測點的某些運行。
該註冊函數在地址rp->kp.addr註冊一個kretprobe類型的探測點,當被探測函數返回時,rp->handler會被調用。
如果成功,它返回0,否則返回負的錯誤碼。
kretprobe處理函數的原型如下:
int kretprobe_handler(struct kretprobe_instance *ri, struct pt_regs *regs);
參數regs指向保存的寄存器,ri指向類型爲struct kretprobe_instance的變量,該結構的ret_addr字段表示返回地址,rp指向相應的kretprobe_instance變量,task字段指向相應的task_struct。結構struct kretprobe_instance是註冊函數register_kretprobe根據用戶指定的maxactive值來分配的,kprobe負責在調用kretprobe處理函數時傳遞相應的kretprobe_instance。
4.unregister_*probe
對應於每一個註冊函數,有相應的卸載函數。
void unregister_kprobe(struct kprobe *kp); void unregister_jprobe(struct jprobe *jp); void unregister_kretprobe(struct kretprobe *rp);
上面是對應與三種探測點類型的卸載函數,當使用探測點的模塊卸載或需要卸載已經註冊的探測點時,需要使用相應的卸載函數來卸載已經註冊的探測點,kp,jp和rp分別爲指向結構struct kprobe,struct jprobe和struct kretprobe的指針,它們應當指向調用對應的註冊函數時使用的那個結構,也就說註冊和卸載必須針對同樣的探測點,否則會導致系統崩潰。這些卸載函數可以在註冊後的任何時刻調用。
四、kprobe的特點和限制
kprobe允許在同一地址註冊多個kprobes,但是不能同時在該地址上有多個jprobes。
通常,用戶可以在內核的任何位置註冊探測點,特別是可以對中斷處理函數註冊探測點,但是也有一些例外。如果用戶嘗試在實現kprobe的代碼(包括kernel/kprobes.c和arch/*/kernel/kprobes.c以及do_page_fault和notifier_call_chain)中註冊探測點,register_*probe將返回-EINVAL.
如果爲一個內聯(inline)函數註冊探測點,kprobe無法保證對該函數的所有實例都註冊探測點,因爲gcc可能隱式地內聯一個函數。因此,要記住,用戶可能看不到預期的探測點的執行。
一個探測點處理函數能夠修改被探測函數的上下文,如修改內核數據結構,寄存器等。因此,kprobe可以用來安裝bug解決代碼或注入一些錯誤或測試代碼。
如果一個探測處理函數調用了另一個探測點,該探測點的處理函數不將運行,但是它的nmissed數將加1。多個探測點處理函數或同一處理函數的多個實例能夠在不同的CPU上同時運行。
除了註冊和卸載,kprobe不會使用mutexe或分配內存。
探測點處理函數在運行時是失效搶佔的,依賴於特定的架構,探測點處理函數運行時也可能是中斷失效的。因此,對於任何探測點處理函數,不要使用導致睡眠或進程調度的任何內核函數(如嘗試獲得semaphore)。
kretprobe是通過取代返回地址爲預定義的trampoline的地址來實現的,因此棧回溯和gcc內嵌函數__builtin_return_address()調用將返回trampoline的地址而不是真正的被探測函數的返回地址。
如果一個函數的調用次數與它的返回次數不相同,那麼在該函數上註冊的kretprobe探測點可能產生無法預料的結果(do_exit()就是一個典型的例子,但do_execve() 和 do_fork()沒有問題)。
當進入或退出一個函數時,如果CPU正運行在一個非當前任務所有的棧上,那麼該函數的kretprobe探測可能產生無法預料的結果,因此kprobe並不支持在x86_64上對__switch_to()的返回探測,如果用戶對它註冊探測點,註冊函數將返回-EINVAL。
五、如何讓內核支持kprobe
kprobe已經被包含在2.6內核中,但是隻有最新的內核才提供了上面描述的全部功能,因此如果讀者想實驗本文附帶的內核模塊,需要最新的內核,作者在2.6.18內核上測試的這些代碼。內核缺省時並沒有使能kprobe,因此用戶需使能它。
爲了使能kprobe,用戶必須在編譯內核時設置CONFIG_KPROBES,即選擇在“Instrumentation Support“中的“Kprobes”項。如果用戶希望動態加載和卸載使用kprobe的模塊,還必須確保“Loadable module support” (CONFIG_MODULES)和“Module unloading” (CONFIG_MODULE_UNLOAD)設置爲y。如果用戶還想使用kallsyms_lookup_name()來得到被探測函數的地址,也要確保CONFIG_KALLSYMS設置爲y,當然設置CONFIG_KALLSYMS_ALL爲y將更好。
六、kprobe使用實例
本文附帶的包包含了三個示例模塊,kprobe-exam.c是kprobes使用示例,jprobe-exam.c是jprobes使用示例,kretprobe-exam.c是kretprobes使用示例,讀者可以下載該包並執行如下指令來實驗這些模塊:
$ tar -jxvf kprobes-examples.tar.bz2 $ cd kprobes-examples $ make … $ su - … $ insmod kprobe-example.ko $ dmesg … $ rmmod kprobe-example $ dmesg … $ insmod jprobe-example.ko $ cat kprobe-example.c $dmesg … $ rmmod jprobe-example $ dmesg … $ insmod kretprobe-example.ko $ dmesg … $ ls -Rla / > /dev/null & $ dmesg … $ rmmod kretprobe-example $ dmesg … $
示例模塊kprobe-exame.c探測schedule()函數,在探測點執行前後分別輸出當前正在運行的進程、所在的CPU以及preempt_count(),當卸載該模塊時將輸出該模塊運行時間以及發生的調度次數。這是該模塊在作者系統上的輸出:
kprobe registered current task on CPU#1: swapper (before scheduling), preempt_count = 0 current task on CPU#1: swapper (after scheduling), preempt_count = 0 current task on CPU#0: insmod (before scheduling), preempt_count = 0 current task on CPU#0: insmod (after scheduling), preempt_count = 0 current task on CPU#1: klogd (before scheduling), preempt_count = 0 current task on CPU#1: klogd (after scheduling), preempt_count = 0 current task on CPU#1: klogd (before scheduling), preempt_count = 0 current task on CPU#1: klogd (after scheduling), preempt_count = 0 current task on CPU#1: klogd (before scheduling), preempt_count = 0 … Scheduling times is 5918 during of 7655 milliseconds. kprobe unregistered
示例模塊jprobe-exam.c是一個jprobes探測例子,它示例了獲取系統調用open的參數,但讀者不要試圖在實際的應用中這麼使用,因爲copy_from_user可能導致睡眠,而kprobe並不允許在探測點處理函數中這麼做(請參看前面內容瞭解詳細描述)。
這是該模塊在作者系統上的輸出:
Registered a jprobe. process 'cat' call open('/etc/ld.so.cache', 0, 0) process 'cat' call open('/lib/libc.so.6', 0, -524289) process 'cat' call open('/usr/lib/locale/locale-archive', 32768, 1) process 'cat' call open('/usr/share/locale/locale.alias', 0, 438) process 'cat' call open('/usr/lib/locale/en_US.UTF-8/LC_CTYPE', 0, 0) process 'cat' call open('/usr/lib/locale/en_US.utf8/LC_CTYPE', 0, 0) process 'cat' call open('/usr/lib/gconv/gconv-modules.cache', 0, 0) process 'cat' call open('kprobe-exam.c', 32768, 0) … process 'rmmod' call open('/etc/ld.so.cache', 0, 0) process 'rmmod' call open('/lib/libc.so.6', 0, -524289) process 'rmmod' call open('/proc/modules', 0, 438) jprobe unregistered
示例模塊kretprobe-exam.c是一個返回探測例子,它探測系統調用open並輸出返回值小於0的情況。它也有意設置maxactive爲1,以便示例丟失探測運行的情況,當然,只有系統併發運行多個sys_open纔可能導致這種情況,因此,讀者需要有SMP的系統或者有超線程支持才能看到這種情況。如果讀者比較仔細,會看到在前面的命令有”ls -Rla / > /dev/null & ,那是專門爲了導致出現丟失探測運行的。
這是該模塊在作者系統上的輸出:
Registered a return probe. sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 sys_open returns -2 … kretprobe unregistered Missed 11 sys_open probe instances.
Kprobe機制是內核提供的一種調試機制,它提供了一種方法,能夠在不修改現有代碼的基礎上,靈活的跟蹤內核函數的執行。它的基本工作原理是:用戶指定一個探測點,並把一個用戶定義的處理函數關聯到該探測點,當內核執行到該探測點時,相應的關聯函數被執行,然後繼續執行正常的代碼路徑。
點擊(此處)摺疊或打開
- struct kprobe
{
- /*用於保存kprobe的全局hash表,以被探測的addr爲key*/
- struct hlist_node hlist;
- /* list of kprobes for multi-handler support */
- /*當對同一個探測點存在多個探測函數時,所有的函數掛在這條鏈上*/
- struct list_head
list;
- /*count the number of times this probe was temporarily disarmed */
- unsigned long nmissed;
- /* location of the probe point */
- /*被探測的目標地址*/
- kprobe_opcode_t *addr;
- /* Allow user to indicate symbol name of the probe point */
- /*symblo_name的存在,允許用戶指定函數名而非確定的地址*/
- const char
*symbol_name;
- /* Offset into the symbol */
- /*如果被探測點爲函數內部某個指令,需要使用addr + offset的方式*/
- unsigned int offset;
- /* Called before addr is executed. */
- /*探測函數,在目標探測點執行之前調用*/
- kprobe_pre_handler_t pre_handler;
- /* Called after addr is executed, unless... */
- /*探測函數,在目標探測點執行之後調用*/
- kprobe_post_handler_t post_handler;
- /*
- * ... called if executing addr causes a fault (eg. page fault).
- * Return 1 if it handled fault, otherwise kernel will see it.
- */
- kprobe_fault_handler_t fault_handler;
- /*
- * ... called if breakpoint trap occurs in probe handler.
- * Return 1 if it handled break, otherwise kernel will see it.
- */
- kprobe_break_handler_t break_handler;
- /*opcode 以及 ainsn 用於保存被替換的指令碼*/
-
- /* Saved opcode (which has been replaced with breakpoint) */
- kprobe_opcode_t opcode;
- /* copy of the original instruction */
- struct arch_specific_insn ainsn;
- /*
- * Indicates various status flags.
- * Protected by kprobe_mutex after this kprobe is registered.
- */
- u32 flags;
- };
對於kprobe功能的實現主要利用了內核中的兩個功能特性:異常(尤其是int 3),單步執行(EFLAGS中的TF標誌)。
點擊(此處)摺疊或打開
- 初始化代碼位於kernel/kprobes.c中
- static int __init init_kprobes(void)
- {
- int i, err
= 0;
- ....
- /*kprobe_blacklist中保存的是kprobe實現的關鍵代碼路徑,這些函數不應該被kprobe探測*/
- /*
- * Lookup and populate the kprobe_blacklist.
- *
- * Unlike the kretprobe blacklist, we'll need to determine
- * the range of addresses that belong to the said functions,
- * since a kprobe need not necessarily be at the beginning
- * of a function.
- */
- for (kb
= kprobe_blacklist; kb->name
!=
NULL; kb++)
{
- kprobe_lookup_name(kb->name, addr);
- if (!addr)
- continue;
- kb->start_addr
= (unsigned
long)addr;
- symbol_name = kallsyms_lookup(kb->start_addr,
- &size,
&offset,
&modname, namebuf);
- if (!symbol_name)
- kb->range
= 0;
- else
- kb->range
= size;
- }
- ....
- if (!err)
- /*註冊通知鏈到die_notifier,用於接收int 3的異常信息*/
- err = register_die_notifier(&kprobe_exceptions_nb);
- ....
- }
- 其中的通知鏈:
- static struct notifier_block kprobe_exceptions_nb
= {
- .notifier_call
= kprobe_exceptions_notify,
- /*優先級最高,保證最先執行*/
- .priority
= 0x7fffffff /* we need to be notified first */
- };
點擊(此處)摺疊或打開
- int __kprobes register_kprobe(struct kprobe
*p)
- {
- int ret = 0;
- struct kprobe
*old_p;
- struct module
*probed_mod;
- kprobe_opcode_t *addr;
- /*獲取被探測點的地址,指定了symbol_name,則從kallsyms中獲取;指定了offset,則返回addr + offset*/
- addr = kprobe_addr(p);
- if (!addr)
- return
-EINVAL;
- p->addr
= addr;
- /*判斷同一個kprobe是否被重複註冊*/
- ret = check_kprobe_rereg(p);
- if (ret)
- return ret;
- jump_label_lock();
- preempt_disable();
- /*判斷被註冊的函數是否位於內核的代碼段內,或位於不能探測的kprobe實現路徑中*/
- if (!kernel_text_address((unsigned
long) p->addr)
||
- in_kprobes_functions((unsigned
long) p->addr)
||
- ftrace_text_reserved(p->addr, p->addr)
||
- jump_label_text_reserved(p->addr, p->addr))
- goto fail_with_jump_label;
- /* User can pass only KPROBE_FLAG_DISABLED to register_kprobe */
- p->flags
&= KPROBE_FLAG_DISABLED;
- /*
- * Check if are we probing a module.
- */
- /*判斷被探測的地址是否屬於某一個模塊,並且位於模塊的text section內*/
- probed_mod = __module_text_address((unsigned
long) p->addr);
- if (probed_mod)
{
- /*如果被探測的爲模塊地址,首先要增加模塊的引用計數*/
- /*
- * We must hold a refcount of the probed module while updating
- * its code to prohibit unexpected unloading.
- */
- if (unlikely(!try_module_get(probed_mod)))
- goto fail_with_jump_label;
- /*
- * If the module freed .init.text, we couldn't insert
- * kprobes in there.
- */
- /*如果被探測的地址位於模塊的init地址段內,但該段代碼區間已被釋放,則直接退出*/
- if (within_module_init((unsigned
long)p->addr, probed_mod)
&&
- probed_mod->state
!= MODULE_STATE_COMING)
{
- module_put(probed_mod);
- goto fail_with_jump_label;
- }
- }
- preempt_enable();
- jump_label_unlock();
- p->nmissed
= 0;
- INIT_LIST_HEAD(&p->list);
- mutex_lock(&kprobe_mutex);
- jump_label_lock();
/* needed to call jump_label_text_reserved() */
- get_online_cpus(); /* For avoiding text_mutex deadlock. */
- mutex_lock(&text_mutex);
- /*判斷在同一個探測點是否已經註冊了其他的探測函數*/
- old_p = get_kprobe(p->addr);
- if (old_p)
{
- /* Since this may unoptimize old_p, locking text_mutex. */
- /*如果已經存在註冊過的kprobe,則將探測點的函數修改爲aggr_pre_handler,並將所有的handler掛載到其鏈表上,由其負責所有handler函數的執行*/
- ret = register_aggr_kprobe(old_p, p);
- goto out;
- }
- /* 分配特定的內存地址用於保存原有的指令
- * 按照內核註釋,被分配的地址必須must be on special executable page on x86.
- * 該地址被保存在kprobe->ainsn.insn
- */
- ret = arch_prepare_kprobe(p);
- if (ret)
- goto out;
- /*將kprobe加入到相應的hash表內*/
- INIT_HLIST_NODE(&p->hlist);
- hlist_add_head_rcu(&p->hlist,
- &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
- if (!kprobes_all_disarmed
&&
!kprobe_disabled(p))
- /*將探測點的指令碼修改爲int 3指令*/
- __arm_kprobe(p);
- /* Try to optimize kprobe */
- try_to_optimize_kprobe(p);
- out:
- mutex_unlock(&text_mutex);
- put_online_cpus();
- jump_label_unlock();
- mutex_unlock(&kprobe_mutex);
- if (probed_mod)
- module_put(probed_mod);
- return ret;
- fail_with_jump_label:
- preempt_enable();
- jump_label_unlock();
- return -EINVAL;
點擊(此處)摺疊或打開
- /* May run on IST stack. */
- dotraplinkage void __kprobes do_int3(struct pt_regs
*regs,
long error_code)
- {
- #ifdef CONFIG_KGDB_LOW_LEVEL_TRAP
- if (kgdb_ll_trap(DIE_INT3,
"int3", regs, error_code, 3, SIGTRAP)
- == NOTIFY_STOP)
- return;
- #endif /* CONFIG_KGDB_LOW_LEVEL_TRAP */
- #ifdef CONFIG_KPROBES
- /*在這裏以DIE_INT3,通知kprobe註冊的通知鏈*/
- if (notify_die(DIE_INT3,
"int3", regs, error_code, 3, SIGTRAP)
- == NOTIFY_STOP)
- return;
- #else
- if (notify_die(DIE_TRAP,
"int3", regs, error_code, 3, SIGTRAP)
- == NOTIFY_STOP)
- return;
- #endif
- preempt_conditional_sti(regs);
- do_trap(3, SIGTRAP,
"int3", regs, error_code,
NULL);
- preempt_conditional_cli(regs);
- }
點擊(此處)摺疊或打開
- int __kprobes kprobe_exceptions_notify(struct notifier_block
*self,
- unsigned long val,
void *data)
- {
- struct die_args
*args = data;
- int ret = NOTIFY_DONE;
- if (args->regs
&& user_mode_vm(args->regs))
- return ret;
- switch (val)
{
- case DIE_INT3:
- /*對於kprobe,進入kprobe_handle*/
- if (kprobe_handler(args->regs))
- ret = NOTIFY_STOP;
- break;
- case DIE_DEBUG:
- if (post_kprobe_handler(args->regs))
{
- /*
- * Reset the BS bit in dr6 (pointed by args->err) to
- * denote completion of processing
- */
- (*(unsigned
long *)ERR_PTR(args->err))
&=
~DR_STEP;
- ret = NOTIFY_STOP;
- }
- break;
- case DIE_GPF:
- /*
- * To be potentially processing a kprobe fault and to
- * trust the result from kprobe_running(), we have
- * be non-preemptible.
- */
- if (!preemptible()
&& kprobe_running()
&&
- kprobe_fault_handler(args->regs, args->trapnr))
- ret = NOTIFY_STOP;
- break;
- default:
- break;
- }
- return ret;
- }
點擊(此處)摺疊或打開
- static int __kprobes kprobe_handler(struct pt_regs
*regs)
- {
- kprobe_opcode_t *addr;
- struct kprobe
*p;
- struct kprobe_ctlblk
*kcb;
- /*對於int 3中斷,其被Intel定義爲Trap,那麼異常發生時EIP寄存器內指向的爲異常指令的後一條指令*/
- addr = (kprobe_opcode_t
*)(regs->ip
- sizeof(kprobe_opcode_t));
- /*
- * We don't want to be preempted for the entire
- * duration of kprobe processing. We conditionally
- * re-enable preemption at the end of this function,
- * and also in reenter_kprobe() and setup_singlestep().
- */
- preempt_disable();
- kcb = get_kprobe_ctlblk();
- /*獲取addr對應的kprobe*/
- p = get_kprobe(addr);
- if (p)
{
- /*如果異常的進入是由kprobe導致,則進入reenter_kprobe(jprobe需要,到時候分析)*/
- if (kprobe_running())
{
- if
(reenter_kprobe(p, regs, kcb))
- return 1;
- } else
{
- set_current_kprobe(p, regs, kcb);
- kcb->kprobe_status
= KPROBE_HIT_ACTIVE;
- /*
- * If we have no pre-handler or it returned 0, we
- * continue with normal processing. If we have a
- * pre-handler and it returned non-zero, it prepped
- * for calling the break_handler below on re-entry
- * for jprobe processing, so get out doing nothing
- * more here.
- */
- /*執行在此地址上掛載的pre_handle函數*/
- if
(!p->pre_handler
||
!p->pre_handler(p, regs))
- /*設置單步調試模式,爲post_handle函數的執行做準備*/
- setup_singlestep(p, regs, kcb, 0);
- return 1;
- }
- } else
if (*addr
!= BREAKPOINT_INSTRUCTION)
{
- /*
- * The breakpoint instruction was removed right
- * after we hit it. Another cpu has removed
- * either a probepoint or a debugger breakpoint
- * at this address. In either case, no further
- * handling of this interrupt is appropriate.
- * Back up over the (now missing) int3 and run
- * the original instruction.
- */
- regs->ip
= (unsigned
long)addr;
- preempt_enable_no_resched();
- return 1;
- } else
if (kprobe_running())
{
- p = __this_cpu_read(current_kprobe);
- if (p->break_handler
&& p->break_handler(p, regs))
{
- setup_singlestep(p, regs, kcb, 0);
- return 1;
- }
- } /* else: not a kprobe fault; let the kernel handle it */
- preempt_enable_no_resched();
- return 0;
- }
點擊(此處)摺疊或打開
- static void __kprobes setup_singlestep(struct kprobe
*p,
struct pt_regs *regs,
- struct kprobe_ctlblk
*kcb, int reenter)
- {
- if (setup_detour_execution(p, regs, reenter))
- return;
- #if !defined(CONFIG_PREEMPT)
- if (p->ainsn.boostable
== 1
&& !p->post_handler)
{
- /* Boost up -- we can execute copied instructions directly */
- if (!reenter)
- reset_current_kprobe();
- /*
- * Reentering boosted probe doesn't reset current_kprobe,
- * nor set current_kprobe, because it doesn't use single
- * stepping.
- */
- regs->ip
= (unsigned
long)p->ainsn.insn;
- preempt_enable_no_resched();
- return;
- }
- #endif
- /*jprobe*/
- if (reenter)
{
- save_previous_kprobe(kcb);
- set_current_kprobe(p, regs, kcb);
- kcb->kprobe_status
= KPROBE_REENTER;
- } else
- kcb->kprobe_status
= KPROBE_HIT_SS;
- /* Prepare real single stepping */
- /*準備單步模式,設置EFLAGS的TF標誌位,清楚IF標誌位(禁止中斷)*/
- clear_btf();
- regs->flags
|= X86_EFLAGS_TF;
- regs->flags
&=
~X86_EFLAGS_IF;
- /* single step inline if the instruction is an int3 */
- if (p->opcode
== BREAKPOINT_INSTRUCTION)
- regs->ip
= (unsigned
long)p->addr;
- else
- /*設置異常返回的指令爲保存的被探測點的指令*/
- regs->ip
= (unsigned
long)p->ainsn.insn;
- }
點擊(此處)摺疊或打開
- dotraplinkage void __kprobes do_debug(struct pt_regs
*regs,
long error_code)
- {
- ....
- /*在do_debug中,以DIE_DEBUG再一次觸發kprobe的通知鏈*/
- if (notify_die(DIE_DEBUG,
"debug", regs, PTR_ERR(&dr6),
error_code,
- SIGTRAP)
== NOTIFY_STOP)
- return;
- ....
- return;
- }
點擊(此處)摺疊或打開
- /*對於kprobe_exceptions_notify,其DIE_DEBUG處理流程*/
- case DIE_DEBUG:
- if (post_kprobe_handler(args->regs))
{
- /*
- * Reset the BS bit in dr6 (pointed by args->err) to
- * denote completion of processing
- */
- (*(unsigned
long *)ERR_PTR(args->err))
&=
~DR_STEP;
- ret = NOTIFY_STOP;
- }
- break;
- static int __kprobes post_kprobe_handler(struct pt_regs
*regs)
- {
- struct kprobe
*cur = kprobe_running();
- struct kprobe_ctlblk
*kcb = get_kprobe_ctlblk();
- if (!cur)
- return 0;
- /*設置異常返回的EIP爲下一條需要執行的指令*/
- resume_execution(cur, regs, kcb);
- /*恢復異常執行前的EFLAGS*/
- regs->flags
|= kcb->kprobe_saved_flags;
- /*執行post_handler函數*/
- if ((kcb->kprobe_status
!= KPROBE_REENTER)
&& cur->post_handler)
{
- kcb->kprobe_status
= KPROBE_HIT_SSDONE;
- cur->post_handler(cur, regs, 0);
- }
- /* Restore back the original saved kprobes variables and continue. */
- if (kcb->kprobe_status
== KPROBE_REENTER)
{
- restore_previous_kprobe(kcb);
- goto out;
- }
- reset_current_kprobe();
- out:
- preempt_enable_no_resched();
- /*
- * if somebody else is singlestepping across a probe point, flags
- * will have TF set, in which case, continue the remaining processing
- * of do_debug, as if this is not a probe hit.
- */
- if (regs->flags
& X86_EFLAGS_TF)
- return 0;
- return 1;
- }