跟蹤 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 核心調用