kprobe原理解析(一)


kprobe是linux內核的一個重要特性,是一個輕量級的內核調試工具,同時它又是其他一些更高級的內核調試工具(比如perf和systemtap)的“基礎設施”,4.0版本的內核中,強大的eBPF特性也寄生於kprobe之上,所以kprobe在內核中的地位就可見一斑了。本文想把kprobe的原理掰碎了給大家看。

怎么講kprobe,我把整個講述分為兩部分,第一部分是kprobe怎么用,第二是kprobe的原理。本篇博客先說kprobe怎么用。

kprobe是什么?

如何高效地調試內核?printk是一種方法,但是printk終歸是毫無選擇地全量輸出,某些場景下不實用,於是你可以試一下tracepoint,我使能tracepoint機制的時候才輸出。對於傻傻地放置printk來輸出信息的方式,tracepoint是個進步,但是tracepoint只是內核在某些特定行為(比如進程切換)上部署的一些靜態錨點,這些錨點並不一定是你需要的,所以你仍然需要自己部署tracepoint,重新編譯內核。那么kprobe的出現就很有必要了,它可以在運行的內核中動態插入探測點,執行你預定義的操作。

kprobe怎么使用?

kprobe主要有兩種使用方法,一是通過模塊加載;二是通過debugfs接口。

模塊加載的方式:內核源碼下有目錄下 samples/kprobes,該目錄下有許多kprobes的例子,可以仿照這些例子寫自己的kprobe模塊。以kprobe_example.c為例,首先聲明一個kprobe結構體,然后定義其中幾個關鍵成員變量,包括symbol_name,pre_handler,post_handler。其中,symbol_name是函數名(kprobe_example.c中該項為do_fork),告訴內核我的探測點放置在了函數do_fork處,pre_hander和post_hander分別表示在執行探測點之前和之后執行的鈎子函數。然后通過register_kprobe函數注冊kprobe即可。將kprobe_example.ko inmod進內核之后,每當系統新啟動一個進程,比如執行ls,cat等,都會輸出:

              pre_hander: p->addr = 0x***, ip = ****.

              post_handler: p->addr = 0x***, pc = ****.

第一行是執行pre_handler鈎子函數的輸出,第二行是執行post_handler鈎子函數的輸出,當然這些都是內核中案例的寫法,你可以寫自己的鈎子函數。

通過debugfs接口注冊kprobe:模塊加載的終究不是很方便,尤其對於一些不帶gcc的嵌入式系統,需要交叉編譯ko,將ko拷貝到單板,然后insmod,不便。debugfs下(確切地說,應該是ftrace)提供了一套注冊、使能、注銷kprobe的接口,可以很方便地操作kprobe。

用法如下:

  1) cd /sys/kernel/debug/tracing【有些系統沒有掛載debugfs,需要先掛載下 mount -t debugfs nodev /sys/kernel/debug】

  2)進入到tracing目錄,這里就是傳說中ftrace的天下了,執行:

   echo "p:sys_write_event sys_write" > kprobe_events    

      向kprobe_events寫入"p:sys_write sys_write",注冊kprobe事件。你會發現,當前目錄下的events下,新增一個kprobes目錄,該目錄下:

           root@station:/sys/kernel/debug/tracing/events/kprobes# ls
           enable  filter  sys_write_event

   即,我們注冊的kprobe事件生效了。那么"p:sys_write_event sys_write"是什么意思呢?首先p表示我們要注冊一個kprobe,如果要注冊retprobe,此處應為r;sys_write_event表示這個kprobe叫什么名字;sys_write表示我們的插入點在哪里。那么,“p:sys_write_event sys_write”的語義就很明顯了:在函數sys_write處插入一個kprobe點,這個點的名字叫sys_write_event。

      3)使能kprobe。執行:

      cd /sys/kernel/debug/tracing/events/kprobes/events/sys_write_event

           echo 1 > enable

      cd ../../.. 【退回到/sys/kernel/debug/tracing,查看trace文件的輸出】

    cat trace 

           trace文件的輸出是如下的:

           .....

   bash-808   [003] d... 42715.347565: sys_write_event: (SyS_write+0x0/0xb0)

           .....

           解釋下置紅的這條輸出:pid為808的進程bash,在自本次開機42715.345565秒的時候,調用了一次函數sys_write。

       4)撤消kprobe。執行

             cd /sys/kernel/debug/tracing/events/kprobes/events/sys_write_event

             echo 0 > enable【首先先關閉kprobe】

             cd ../../..

        echo "-:kprobes/sys_write_event" >> kprobe_events 【注銷kprobe】     

以上就是kprobe的兩種注冊及使用方式:通過模塊加載以及通過debugfs注冊。這兩種使用方法有什么聯系?

使用模塊加載的方式,是kprobe的一種原始用法:在kprobe結構體里定義插入點、鈎子函數,然后通過register_kprobe注冊上這個kprobe即可。ftrace接口是kprobe的一種應用,它是一套trace的框架,下面的trace機制包括tracepoint、function trace等,kprobe僅僅是這些trace機制中的一員。上面的講述我們也已經看出來了,通過ftrace注冊的kprobe的輸出是在ftrace的輸出:trace文件。模塊加載模式中我們可以自定義kprobe的鈎子函數pre_handler和post_handler,但是在ftrace下注冊的kprobe的鈎子是ftrace接口默認的,我們設置不了,但是具體輸出什么,我們可以在echo “p:sys_write_event sys_write"時指定,比如指定x1寄存器的內容等,所以ftrace下注冊的kprobe功能同樣很強大。同時,由於ftrace下kprobe的輸出基於ftrace的輸出框架,所以輸出信息包含當前進程、CPU、時間戳等信息,對於trace來說非常有用。

好了,kprobe的用法先介紹到這里了,這都是最簡單的用法,高級用法可以參看內核文檔:kprobes.txt 以及 kprobetrace.txt。kprobe的原理將在下面一篇博客中詳細介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM