你不記得如何在代碼中插入探針點了嗎? 沒問題!了解如何使用uprobe和kprobe來動態插入它們吧。 基本上,程序員需要在源代碼匯編指令的不同位置插入動態探針點。
探針點
探針點是一個調試語句,有助於探索軟件的執行特性(即,執行流程以及當探針語句執行時軟件數據結構的狀態)。printk是探針語句的最簡單形式,也是黑客用於內核攻擊的基礎工具之一。
因為它需要重新編譯源代碼,所以printk插入是靜態的探測方法。內核代碼中重要位置上還有許多其他靜態跟蹤點可以動態啟用或禁用。 Linux內核有一些框架可以幫助程序員探測內核或用戶空間應用程序,而無需重新編譯源代碼。Kprobe是在內核代碼中插入探針點的動態方法之一,並且uprobe在用戶應用程序中執行此操作。
使用uprobe跟蹤用戶空間
可以通過使用thesysfs接口或perf工具將uprobe跟蹤點插入用戶空間代碼。
使用sysfs接口插入uprobe
考慮以下簡單測試代碼,沒有打印語句,我們想在某個指令中插入探針:
[source,c] .test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
編譯代碼並找到要探測的指令地址:
#gcc -o test test.c
#objdump -d test
假設我們在ARM64平台上有以下目標代碼:
0000000000400620 <func_1>: 400620: 90000080
adrp x0, 410000 <__FRAME_END__+0xf6f8>
並且我們想在偏移量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告訴我們這是一個簡單的測試。(探測器可以是簡單的或返回的。)func_n_entry是我們在跟蹤輸出中看到的名稱,名稱是可選字段,如果沒有提供,我們應該期待像p_test_0x644這樣的名字。test 是我們要插入探針的可執行二進制文件。如果test 不在當前目錄中,則需要指定path_to_test / test。
0x620或0x640是從程序啟動開始的指令偏移量。請注意>>在第二個echo語句中,因為我們要再添加一個探針。所以,當我們在前兩個命令中插入探針點之後,我們啟用uprobe跟蹤,當我們寫入events/ uprobes / enable時,它將啟用所有的uprobe事件。程序員還可以通過寫入在該事件目錄中創建的特定事件文件來啟用單個事件。一旦探針點被插入和啟用,每當執行探測指令時,我們可以看到一個跟蹤條目。
讀取跟蹤文件以查看輸出:
#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# | | | |||| | |
我們可以看到哪個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)
上面的日志表明,func_1返回到地址0x4006b0,時間戳為4813.852691。
# 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
使用perf插入uprobe
找到需要插入探針的指令或功能的偏移量很麻煩,而且需要知道分配給局部變量的CPU寄存器的名稱更為復雜。 perf是一個有用的工具,用於幫助引導探針插入源代碼中。
除了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
如上所示,程序員可以將探針點直接插入函數start和return,源文件的特定行號等。可以獲取打印的局部變量,並擁有許多其他選項,例如調用函數的所有實例。 perf探針用於創建探針點事件,那麼在執行./testexecutable時,可以使用perf記錄來探測這些事件。當創建一個perf探測點時,可以使用其他錄音選項,例如perf stat,可以擁有許多後期分析選項,如perf腳本或perf報告。
使用perf腳本,上面的例子輸出如下:
# perf script
使用kprobe跟蹤內核空間
與uprobe一樣,可以使用sysfs接口或perf工具將kprobe跟蹤點插入到內核代碼中。
使用sysfs接口插入kprobe
程序員可以在/proc/kallsyms中的大多數符號中插入kprobe;其他符號已被列入內核的黑名單。還有一些與kprobe插入不兼容的符號,比如kprobe_events文件中的kprobe插入將導致寫入錯誤。 也可以在符號基礎的某個偏移處插入探針,像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
[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# | | | |||| | |
使用perf插入kprobe
與uprobe一樣,程序員可以使用perf在內核代碼中插入一個kprobe,可以直接將探針點插入到函數start和return中,源文件的特定行號等。程序員可以向-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