天天看點

跟蹤 linux 核心調用_Linux使用者和核心空間中的動态跟蹤

跟蹤 linux 核心調用

您是否曾經遇到過這樣的情況,即您意識到沒有在代碼中的某些點插入

調試列印

,是以現在您将不知道您的CPU是否命中了特定的代碼行來執行,直到您重新編譯該代碼為止。調試語句? 不用擔心,這裡有一個更簡單的解決方案。 基本上,您需要在源代碼彙編指令的不同位置插入動态探測點。

對于進階使用者,核心文檔/跟蹤和手動性能提供了大量有關不同類型核心和使用者空間跟蹤機制的資訊。 但是,普通使用者隻需要幾個簡單的步驟和一個快速入門的示例。 這就是本文的幫助之處。

讓我們從定義開始。

探測點

探測點是一個調試語句,它有助于探索軟體的執行特性(即,執行探測語句時的執行流程和軟體資料結構的狀态)。

printk

是probe語句的最簡單形式,并且是開發人員用于核心黑客的基本工具之一。

靜态與動态探測

因為它需要重新編譯源代碼,是以

printk

插入是一種靜态探測方法。 核心代碼中重要位置上還有許多其他靜态跟蹤點可以動态啟用或禁用。 Linux核心具有一些架構,可以幫助開發人員探測核心或使用者空間應用程式而無需重新編譯源代碼。

Kprobe

是在核心代碼中插入探測點的一種動态方法,而

uprobe

在使用者應用程式中這樣做。

使用uprobe跟蹤使用者空間

可以使用sysfs接口或perf工具将uprobe跟蹤點插入任何使用者空間代碼中。

使用sysfs界面插入長袍

考慮以下沒有列印語句的簡單測試代碼,我們希望在某些指令中插入探針:

[ 
     [ app-listing 
     ] 
     ] 
     


     [ 
     source ,c 
     ] 
     

.test.c
    
              
#include <stdio.h> 
     


     #include <stdlib.h> 
     


     #include <unistd.h> 
     


     

static int func_1_cnt;
     

static int func_2_cnt;
     


     

static void func_1 
     ( void 
     ) 
     


     { 
     

        func_1_cnt++;
     


     } 
     


     

static void func_2 
     ( void 
     ) 
     


     { 
     

        func_2_cnt++;
     


     } 
     


     

int main 
     ( int argc, void 
     ** argv 
     ) 
     


     { 
     

        int number;
     


     

        
     while 
     ( 
     1 
     ) 
     { 
     

                
     sleep 
     ( 
     1 
     ) ;
     

                number = rand 
     ( 
     ) 
     % 
     10 ;
     

                
     if 
     ( number 
     < 
     5 
     ) 
     

                        func_2 
     ( 
     ) ;
     

                
     else 
     

                        func_1 
     ( 
     ) ;
     

        
     } 
     


     } 
    
              

編譯代碼并找到要探測的指令位址:

# gcc -o test test.c 
     


     # objdump -d test 
    
              

假設我們在ARM64平台上具有以下目标代碼:

0000000000400620 
     < func_1 
     > :
     

  
     400620 :       
     90000080        adrp    x0, 
     410000 
     < __FRAME_END__+0xf6f8 
     > 
     

  
     400624 :       912d4000        add     x0, x0, 
     #0xb50 
     

  
     400628 :       b9400000        ldr     w0, 
     [ x0 
     ] 
     

  40062c:       
     11000401        add     w1, w0, 
     #0x1 
     

  
     400630 :       
     90000080        adrp    x0, 
     410000 
     < __FRAME_END__+0xf6f8 
     > 
     

  
     400634 :       912d4000        add     x0, x0, 
     #0xb50 
     

  
     400638 :       b9000001        str     w1, 
     [ x0 
     ] 
     

  40063c:       d65f03c0        ret
     


     

0000000000400640 
     < func_2 
     > :
     

  
     400640 :       
     90000080        adrp    x0, 
     410000 
     < __FRAME_END__+0xf6f8 
     > 
     

  
     400644 :       912d5000        add     x0, x0, 
     #0xb54 
     

  
     400648 :       b9400000        ldr     w0, 
     [ x0 
     ] 
     

  40064c:       
     11000401        add     w1, w0, 
     #0x1 
     

  
     400650 :       
     90000080        adrp    x0, 
     410000 
     < __FRAME_END__+0xf6f8 
     > 
     

  
     400654 :       912d5000        add     x0, x0, 
     #0xb54 
     

  
     400658 :       b9000001        str     w1, 
     [ x0 
     ] 
     

  40065c:       d65f03c0        ret
    
              

并且我們想在偏移量

0x620

0x644

處插入一個探針。 執行以下指令:

# echo 'p:func_2_entry test:0x620' > /sys/kernel/debug/tracing/uprobe_events 
     


     # echo 'p:func_1_entry test:0x644' >> /sys/kernel/debug/tracing/uprobe_events 
     


     # echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable 
     


     # ./test& 
    
              

在上面的第一和第二第二個echo語句中,

p

告訴我們這是一個簡單的探針 。 (探測可以是簡單的,也可以是return 。)

func_n_entry

是我們在跟蹤輸出中看到的名稱。

名稱

是一個可選字段; 如果未提供,則應使用

p_test_0x644之

類的名稱。

test

是我們要在其中插入探針的可執行二進制檔案。 如果

test

不在目前目錄中,則需要指定

path_to_test / test

0x620

0x640

是距程式開頭的指令偏移量。 請注意第二個echo語句中的

>>

,因為我們想再添加一個探針。 是以,當在前兩個指令中插入探測點之後啟用uprobe跟蹤時,當我們寫入

events / uprobes / enable

時,它将啟用所有uprobe事件。 通過寫入

events

目錄中建立的特定事件檔案,我們也可以啟用單個事件。 插入并啟用探測點後,隻要執行所探測的指令,我們就可以看到跟蹤條目。

閱讀跟蹤檔案以檢視輸出:

# cat /sys/kernel/debug/tracing/trace 
     


     # tracer: nop 
     


     # 
     


     # entries-in-buffer/entries-written: 8/8   #P:8 
     


     # 
     


     #                              _-----=> irqs-off 
     


     #                             / _----=> need-resched 
     


     #                            | / _---=> hardirq/softirq 
     


     #                            || / _--=> preempt-depth 
     


     #                            ||| /     delay 
     


     #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION 
     


     #              | |       |   ||||       |         | 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1740.674740 : func_1_entry: 
     ( 0x400644 
     ) 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1741.674854 : func_1_entry: 
     ( 0x400644 
     ) 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1742.674949 : func_2_entry: 
     ( 0x400620 
     ) 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1743.675065 : func_2_entry: 
     ( 0x400620 
     ) 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1744.675158 : func_1_entry: 
     ( 0x400644 
     ) 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1745.675273 : func_1_entry: 
     ( 0x400644 
     ) 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1746.675390 : func_2_entry: 
     ( 0x400620 
     ) 
     

            test- 
     2788  
     [ 003 
     ] ....  
     1747.675503 : func_2_entry: 
     ( 0x400620 
     ) 
    
              

我們可以看到什麼任務是由哪個CPU完成的,以及它在什麼時候執行了被探測的指令。

傳回探針也可以插入任何指令中。 當傳回具有該指令的函數時,這将記錄一個條目:

# echo 0 > /sys/kernel/debug/tracing/events/uprobes/enable 
     


     # echo 'r:func_2_exit test:0x620' >> /sys/kernel/debug/tracing/uprobe_events 
     


     # echo 'r:func_1_exit test:0x644' >> /sys/kernel/debug/tracing/uprobe_events 
     


     # echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable 
    
              

在這裡,我們使用

r

代替

p

,所有其他參數都相同。 請注意,如果要插入新的探測點,則需要禁用uprobe事件:

            test- 
     3009  
     [ 002 
     ] ....  
     4813.852674 : func_1_entry: 
     ( 0x400644 
     ) 
     

            test- 
     3009  
     [ 002 
     ] ....  
     4813.852691 : func_1_exit: 
     ( 0x4006b0 
     < - 0x400644 
     ) 
     

            test- 
     3009  
     [ 002 
     ] ....  
     4814.852805 : func_2_entry: 
     ( 0x400620 
     ) 
     

            test- 
     3009  
     [ 002 
     ] ....  
     4814.852807 : func_2_exit: 
     ( 0x4006b8 
     < - 0x400620 
     ) 
     

            test- 
     3009  
     [ 002 
     ] ....  
     4815.852920 : func_2_entry: 
     ( 0x400620 
     ) 
     

            test- 
     3009  
     [ 002 
     ] ....  
     4815.852921 : func_2_exit: 
     ( 0x4006b8 
     < - 0x400620 
     ) 
    
              

上面的記錄告訴我們

func_1

在時間戳

4813.852691

傳回位址

0x4006b0。
# echo 0 > /sys/kernel/debug/tracing/events/uprobes/enable 
     


     # echo 'p:func_2_entry test:0x630' > /sys/kernel/debug/tracing/uprobe_events count=%x1 
     


     # echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable 
     


     # echo > /sys/kernel/debug/tracing/trace 
     


     # ./test& 
    
              

在這裡,當執行偏移量為

0x630

的指令時,我們将

ARM64 x1寄存器

的值列印為

count =

輸出如下所示:

            test- 
     3095  
     [ 003 
     ] ....  
     7918.629728 : func_2_entry: 
     ( 0x400630 
     ) 
     count =0x1
     

            test- 
     3095  
     [ 003 
     ] ....  
     7919.629884 : func_2_entry: 
     ( 0x400630 
     ) 
     count =0x2
     

            test- 
     3095  
     [ 003 
     ] ....  
     7920.629988 : func_2_entry: 
     ( 0x400630 
     ) 
     count =0x3
     

            test- 
     3095  
     [ 003 
     ] ....  
     7922.630272 : func_2_entry: 
     ( 0x400630 
     ) 
     count =0x4
     

            test- 
     3095  
     [ 003 
     ] ....  
     7923.630429 : func_2_entry: 
     ( 0x400630 
     ) 
     count =0x5
    
              

使用perf插入長袍

總是在需要插入探針的位置找到指令或函數的偏移量很麻煩,而且知道配置設定給任何局部變量的CPU寄存器的名稱甚至更加複雜。

perf

是有用的工具,可幫助您準備uprobe并将其插入源代碼的任何行中。

除了

perf

之外,還有一些其他工具,例如

SystemTap

DTrace

LTTng

,可用于核心和使用者空間跟蹤。 但是,

perf

與核心完全耦合,是以受到核心開發人員的青睐。

# gcc -g -o test test.c 
     


     # perf probe -x ./test func_2_entry=func_2 
     


     # perf probe -x ./test func_2_exit=func_2%return 
     


     # perf probe -x ./test test_15=test.c:15 
     


     # perf probe -x ./test test_25=test.c:25 number 
     


     # perf record -e probe_test:func_2_entry -e probe_test:func_2_exit -e probe_test:test_15  -e probe_test:test_25 ./test 
    
              

如上面的示例所示,我們可以将探測點直接插入函數的開始和傳回,源檔案的特定行号等。您可以列印局部變量。 您可以有許多其他選項,例如函數調用的所有執行個體(有關詳細資訊,請參見

man perf探針

)。

perf探測器

用于建立探測點事件,然後可以在執行

./test

可執行檔案時使用

perf記錄

來探測那些事件。 建立

perf探測

點時,我們可以有其他記錄選項,例如

perf stat

,并且可以有很多後期分析選項,例如

perf腳本

perf report

使用

perf腳本

,以上示例的輸出如下所示:

# perf script 
     

            
     test  
     2741 
     [ 002 
     ]   
     427.584681 : probe_test:test_25: 
     ( 4006a0 
     ) 
     number = 
     3 
     

            
     test  
     2741 
     [ 002 
     ]   
     427.584717 : probe_test:test_15: 
     ( 
     400640 
     ) 
     

            
     test  
     2741 
     [ 002 
     ]   
     428.584861 : probe_test:test_25: 
     ( 4006a0 
     ) 
     number = 
     6 
     

            
     test  
     2741 
     [ 002 
     ]   
     428.584872 : probe_test:func_2_entry: 
     ( 
     400620 
     ) 
     

            
     test  
     2741 
     [ 002 
     ]   
     428.584881 : probe_test:func_2_exit: 
     ( 
     400620 
     < - 4006b8 
     ) 
     

            
     test  
     2741 
     [ 002 
     ]   
     429.585012 : probe_test:test_25: 
     ( 4006a0 
     ) 
     number = 
     7 
     

            
     test  
     2741 
     [ 002 
     ]   
     429.585021 : probe_test:func_2_entry: 
     ( 
     400620 
     ) 
     

            
     test  
     2741 
     [ 002 
     ]   
     429.585025 : probe_test:func_2_exit: 
     ( 
     400620 
     < - 4006b8 
     ) 
    
              

使用kprobe跟蹤核心空間

與uprobe一樣,可以使用sysfs接口或perf工具将kprobe跟蹤點插入核心代碼。

使用sysfs界面插入kprobe

我們可以在

/ proc / kallsyms中的

大多數符号中插入

kprobe

; 其他符号已在核心中列入黑名單。 如果将kprobe插入與kprobe插入不相容的符号,則将其插入

kprobe_events

檔案中會導緻寫入錯誤。 也可以将探針插入到距符号基礎一定距離的位置。 像uprobe一樣,我們也可以使用

kretprobe

跟蹤函數的傳回。 局部變量的值也可以列印在跟蹤輸出中。

本示例說明了如何執行此操作:

; disable all events, just to insure that we see only kprobe output 
     in trace.
     


     # echo 0 > /sys/kernel/debug/tracing/events/enable 
     

; disable kprobe events 
     until probe points are inseted.
     


     # echo 0 > /sys/kernel/debug/tracing/events/kprobes/enable 
     

; 
     clear out all the events from kprobe_events, to insure that we see output 
     for 
     

; only those 
     for 
     which we have enabled 
     


     # echo > /sys/kernel/debug/tracing/kprobe_events 
     

; insert probe point at kfree
     


     # echo "p kfree" >> /sys/kernel/debug/tracing/kprobe_events 
     

; insert probe point at kfree+0x10 with name kfree_probe_10
     


     # echo "p:kree_probe_10 kfree+0x10" >> /sys/kernel/debug/tracing/kprobe_events 
     

; insert probe point at kfree 
     return 
     


     # echo "r:kfree_probe kfree" >> /sys/kernel/debug/tracing/kprobe_events 
     

; 
     enable kprobe events 
     until probe points are inseted.
     


     # echo 1 > /sys/kernel/debug/tracing/events/kprobes/enable 
    
              
[ root 
     @ pratyush ~ 
     ] 
     # more /sys/kernel/debug/tracing/trace 
     


     # tracer: nop 
     


     # 
     


     # entries-in-buffer/entries-written: 9037/9037   #P:8 
     


     # 
     


     #                              _-----=> irqs-off 
     


     #                             / _----=> need-resched 
     


     #                            | / _---=> hardirq/softirq 
     


     #                            || / _--=> preempt-depth 
     


     #                            ||| /     delay 
     


     #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION 
     


     #              | |       |   ||||       |         | 
     

            sshd- 
     2189  
     [ 002 
     ] dn..  
     1908.930731 : kree_probe: 
     ( __audit_syscall_exit+0x194 
     / 0x218 
     < - kfree 
     ) 
     

            sshd- 
     2189  
     [ 002 
     ] d...  
     1908.930744 : p_kfree_0: 
     ( kfree+0x0 
     / 0x214 
     ) 
     

            sshd- 
     2189  
     [ 002 
     ] d...  
     1908.930746 : kree_probe_10: 
     ( kfree+0x10 
     / 0x214 
     ) 
    
              

使用perf插入kprobe

與uprobe一樣,我們可以使用perf在核心代碼中插入kprobe。 我們可以将一個探針點直接插入函數的開始和傳回,源檔案的特定行号等。我們可以為

-k

選項提供

vmlinux

,或者可以為

-s

選項提供核心源代碼路徑。 :

# perf probe -k vmlinux kfree_entry=kfree 
     


     # perf probe -k vmlinux kfree_exit=kfree%return 
     


     # perf probe -s ./  kfree_mid=mm/slub.c:3408 x 
     


     # perf record -e probe:kfree_entry -e probe:kfree_exit -e probe:kfree_mid sleep 10 
    
              

使用

perf腳本

,我們将在上面的示例中看到以下輸出:

# perf script 
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291224 : probe:kfree_entry: 
     ( fffffe0000201944 
     ) 
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291229 : probe:kfree_mid: 
     ( fffffe0000201978 
     ) 
     x =0x0
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291231 : probe:kfree_exit: 
     ( fffffe0000201944 
     < - fffffe000019f67c 
     ) 
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291241 : probe:kfree_entry: 
     ( fffffe0000201944 
     ) 
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291242 : probe:kfree_mid: 
     ( fffffe0000201978 
     ) 
     x =0xfffffe01db8f6000
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291243 : probe:kfree_exit: 
     ( fffffe0000201944 
     < - fffffe000019f67c 
     ) 
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291249 : probe:kfree_entry: 
     ( fffffe0000201944 
     ) 
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291250 : probe:kfree_mid: 
     ( fffffe0000201978 
     ) 
     x =0xfffffe01db8f6000
     

           
     sleep  
     2379 
     [ 001 
     ]  
     2702.291251 : probe:kfree_exit: 
     ( fffffe0000201944 
     < - fffffe000019f67c 
     ) 
    
              

我希望本教程已經說明了如何破解可執行代碼并将一些探測點插入其中。 追蹤愉快!

翻譯自: https://opensource.com/article/17/7/dynamic-tracing-linux-user-and-kernel-space

跟蹤 linux 核心調用