【linux內(nèi)核調(diào)試】ftrace/kprobes/SystemTap內(nèi)核調(diào)試方法對(duì)比

一嘁傀、調(diào)試簡(jiǎn)介

本文主要實(shí)踐一下linux內(nèi)核調(diào)試方式靴寂,并進(jìn)行比較蛾魄。內(nèi)核調(diào)試方式在這篇blog中講解的非常詳細(xì)征峦,本文只介紹幾種動(dòng)態(tài)的調(diào)試方法情屹。

1.ftrace

Linux當(dāng)前版本中柏锄, 功能最強(qiáng)大的調(diào)試酿箭、跟蹤手段。其最基本的功能是提供了動(dòng)態(tài)和靜態(tài)探測(cè)點(diǎn)趾娃,用于探測(cè)內(nèi)核中指定位置上的相關(guān)信息缭嫡。

靜態(tài)探測(cè)點(diǎn):是在內(nèi)核代碼中調(diào)用 ftrace 提供的相應(yīng)接口實(shí)現(xiàn),稱之為靜態(tài)是因?yàn)樘疲窃趦?nèi)核代碼中寫死的妇蛀,靜態(tài)編譯到內(nèi)核代碼中的耕突,在內(nèi)核編譯后,就不能再動(dòng)態(tài)修改评架。在開(kāi)啟 ftrace 相關(guān)的內(nèi)核配置選項(xiàng)后眷茁,內(nèi)核中已經(jīng)在一些關(guān)鍵的地方設(shè)置了靜態(tài)探測(cè)點(diǎn),需要使用時(shí)纵诞,即可查看到相應(yīng)的信息上祈。

動(dòng)態(tài)探測(cè)點(diǎn):基本原理為,利用 mcount 機(jī)制浙芙,在內(nèi)核編譯時(shí)登刺,在每個(gè)函數(shù)入口保留數(shù)個(gè)字節(jié),然后在使用 ftrace時(shí)嗡呼,將保留的字節(jié)替換為需要的指令塘砸,比如跳轉(zhuǎn)到需要的執(zhí)行探測(cè)操作的代碼。

ftrace的前端工具trace-cmd晤锥,相當(dāng)于是一個(gè) /sys/kernel/debug/tracing 中文件系統(tǒng)接口的封裝掉蔬,為用戶提供了更加直接和方便的操作。其本質(zhì)就是對(duì)/sys/kernel/debug/tracing/events 下各個(gè)模塊進(jìn)行操作矾瘾,收集數(shù)據(jù)并解析女轿。

ftrace—使用ftrace學(xué)習(xí)linux內(nèi)核函數(shù)調(diào)用

ftrace—ftrace:跟蹤你的內(nèi)核函數(shù)!

ftrace—使用ftrace調(diào)試Linux內(nèi)核壕翩,第1部分

ftrace—使用ftrace調(diào)試Linux內(nèi)核蛉迹,第2部分

ftrace—使用ftrace調(diào)試Linux內(nèi)核,第3部分

ftrace—ftrace官方文檔

2.kprobe

kprobe是一個(gè)輕量級(jí)的內(nèi)核調(diào)試工具放妈,也是其他更高級(jí)的內(nèi)核調(diào)試(如perf和systemtap的基礎(chǔ))北救。Kprobes 提供了一個(gè)強(qiáng)行進(jìn)入任何內(nèi)核例程并從中斷處理器無(wú)干擾地收集信息的接口,使用 Kprobes 可以收集處理器寄存器和全局?jǐn)?shù)據(jù)結(jié)構(gòu)等調(diào)試信息芜抒。開(kāi)發(fā)者甚至可以使用 Kprobes 來(lái)修改 寄存器值和全局?jǐn)?shù)據(jù)結(jié)構(gòu)的值珍策。

工作原理:kprobe可以在運(yùn)行的內(nèi)核中動(dòng)態(tài)插入探測(cè)點(diǎn),執(zhí)行你預(yù)定義的操作宅倒。用戶指定一個(gè)探測(cè)點(diǎn)攘宙,并把一個(gè)用戶定義的處理函數(shù)關(guān)聯(lián)到該探測(cè)點(diǎn), 當(dāng)內(nèi)核執(zhí)行到該探測(cè)點(diǎn)時(shí), 相應(yīng)的關(guān)聯(lián)函數(shù)被執(zhí)行,然后繼續(xù)執(zhí)行正常的代碼路徑拐迁。

kprobe 實(shí)現(xiàn)了三種類型的探測(cè)點(diǎn):kprobes蹭劈、jprobes和 kretprobes(也叫返回探測(cè)點(diǎn))。kprobes 是可以被插入到內(nèi)核的任何指令位置的探測(cè)點(diǎn)线召, jprobes 則只能被插入到一個(gè)內(nèi)核函數(shù)的入口铺韧,而 kretprobes 則是在指定的內(nèi)核函數(shù)返回時(shí)才被執(zhí)行。

kprobe—kprobe原理解析(二)

kprobe—Linux kprobe調(diào)試技術(shù)使用

3.前端工具systemtap

SystemTap 是監(jiān)控和跟蹤運(yùn)行中的 Linux 內(nèi)核的操作的動(dòng)態(tài)方法缓淹。這句話的關(guān)鍵詞是動(dòng)態(tài)哈打,因?yàn)?SystemTap 沒(méi)有使用工具構(gòu)建一個(gè)特殊的內(nèi)核工窍,而是允許您在運(yùn)行時(shí)動(dòng)態(tài)地安裝該工具。它通過(guò)一個(gè) Kprobes 的應(yīng)用編程接口 (API) 來(lái)實(shí)現(xiàn)該目的前酿。

但在Systemtap中,用戶可以指定原文件鹏溯,原代碼的某一行罢维,或者一個(gè)異步事件,探測(cè)點(diǎn)處理函數(shù)能夠立刻輸出數(shù)據(jù)丙挽,與printk很類似肺孵,它也能查看內(nèi)核數(shù)據(jù)。腳本然后被一個(gè)翻譯器轉(zhuǎn)換成C代碼并編譯成一個(gè)內(nèi)核模塊颜阐。生成的C代碼編譯鏈接之后生成一個(gè)可加載的內(nèi)核模塊平窘。

SystemTap—在Ubuntu上安裝使用Systemtap

SystemTap—SystemTap使用技巧

SystemTap—SystemTap Language Reference

SystemTap—SystemTap使用技巧【一】

SystemTap—SystemTap使用技巧【二】

SystemTap—SystemTap使用技巧【三】

SystemTap—SystemTap使用技巧【四】

SystemTap—systemtap學(xué)習(xí)總結(jié)

對(duì)比:systemtap配置較麻煩,kprobe可以不用重新編譯內(nèi)核就弄清各個(gè)函數(shù)之間的調(diào)用關(guān)系凳怨。

二瑰艘、ftrace

1.配置

  • 編譯內(nèi)核時(shí)需配置以下選項(xiàng):
# cat /boot/config-2.6.36 | grep FTRACE
CONFIG_HAVE_FTRACE_NMI_ENTER=y
CONFIG_HAVE_DYNAMIC_FTRACE=y
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
CONFIG_FTRACE_NMI_ENTER=y
CONFIG_FTRACE=y                           #FTRACE打開(kāi)后,編譯內(nèi)核時(shí)會(huì)打開(kāi)-pg選項(xiàng)肤舞。
CONFIG_FTRACE_SYSCALLS=y
CONFIG_DYNAMIC_FTRACE=y
CONFIG_FTRACE_MCOUNT_RECORD=y
# CONFIG_FTRACE_STARTUP_TEST is not set
  • 也可以用make menuconfig圖形化界面來(lái)配置:

Kernel hacking ---> —> Tracers ---> —> 可勾選所有紫新。

  • 將debugfs編譯進(jìn)內(nèi)核:

設(shè)置CONFIG_DEBUG_FS=yKernel hacking ---> —> Debug Filesystem

說(shuō)明:ftrace通過(guò)debugfs向用戶態(tài)提供訪問(wèn)接口李剖。配置內(nèi)核時(shí)激活debugfs后會(huì)創(chuàng)建目錄/sys/kernel/debug芒率,debug文件系統(tǒng)就是掛載到該目錄。

掛載方式:在init腳本下加入mount -t debugfs nodev /sys/kernel/debug篙顺。啟動(dòng)后會(huì)創(chuàng)建目錄/sys/kernel/debug/tracing偶芍,改目錄下包含ftrace的控制和輸出文件。

/sys/kernel/debug/tracing $ ls
README                      set_event_pid
available_events            set_ftrace_filter
available_filter_functions  set_ftrace_notrace
available_tracers           set_ftrace_pid
buffer_size_kb              set_graph_function
buffer_total_size_kb        set_graph_notrace
current_tracer              snapshot
dyn_ftrace_total_info       stack_max_size
enabled_functions           stack_trace
events                      stack_trace_filter
free_buffer                 trace
function_profile_enabled    trace_clock
instances                   trace_marker
kprobe_events               trace_options
kprobe_profile              trace_pipe
max_graph_depth             trace_stat
options                     tracing_cpumask
per_cpu                     tracing_max_latency
printk_formats              tracing_on
saved_cmdlines              tracing_thresh
saved_cmdlines_size         uprobe_events
set_event                   uprobe_profile

2.ftrace數(shù)據(jù)文件

/sys/kernel/debug/trace目錄下數(shù)據(jù)文件的操作德玫,通常使用 echo 命令來(lái)修改其值匪蟀,也可以在程序中通過(guò)文件讀寫相關(guān)的函數(shù)來(lái)操作這些文件的值。

主要文件用途如下:

  • README文件提供了一個(gè)簡(jiǎn)短的使用說(shuō)明宰僧,展示了 ftrace 的操作命令序列萄窜。可以通過(guò) cat 命令查看該文件以了解概要的操作流程撒桨。
  • current_tracer用于設(shè)置或顯示當(dāng)前使用的跟蹤器查刻;使用 echo 將跟蹤器名字寫入該文件可以切換到不同的跟蹤器。系統(tǒng)啟動(dòng)后凤类,其缺省值為 nop 穗泵,即不做任何跟蹤操作。在執(zhí)行完一段跟蹤任務(wù)后谜疤,可以通過(guò)向該文件寫入 nop 來(lái)重置跟蹤器佃延。
  • available_tracers記錄了當(dāng)前編譯進(jìn)內(nèi)核的跟蹤器的列表现诀,可以通過(guò) cat 查看其內(nèi)容;其包含的跟蹤器與圖 3 中所激活的選項(xiàng)是對(duì)應(yīng)的履肃。寫 current_tracer 文件時(shí)用到的跟蹤器名字必須在該文件列出的跟蹤器名字列表中仔沿。
  • trace文件提供了查看獲取到的跟蹤信息的接口〕咂澹可以通過(guò) cat 等命令查看該文件以查看跟蹤到的內(nèi)核活動(dòng)記錄封锉,也可以將其內(nèi)容保存為記錄文件以備后續(xù)查看。
  • tracing_enabled用于控制 current_tracer 中的跟蹤器是否可以跟蹤內(nèi)核函數(shù)的調(diào)用情況膘螟。寫入 0 會(huì)關(guān)閉跟蹤活動(dòng)成福,寫入 1 則激活跟蹤功能;其缺省值為 1 荆残。
  • set_graph_function設(shè)置要清晰顯示調(diào)用關(guān)系的函數(shù)奴艾,顯示的信息結(jié)構(gòu)類似于 C 語(yǔ)言代碼,這樣在分析內(nèi)核運(yùn)作流程時(shí)會(huì)更加直觀一些内斯。在使用 function_graph 跟蹤器時(shí)使用蕴潦;缺省為對(duì)所有函數(shù)都生成調(diào)用關(guān)系序列,可以通過(guò)寫該文件來(lái)指定需要特別關(guān)注的函數(shù)俘闯。
  • buffer_size_kb用于設(shè)置單個(gè) CPU 所使用的跟蹤緩存的大小品擎。跟蹤器會(huì)將跟蹤到的信息寫入緩存,每個(gè) CPU 的跟蹤緩存是一樣大的备徐。跟蹤緩存實(shí)現(xiàn)為環(huán)形緩沖區(qū)的形式萄传,如果跟蹤到的信息太多,則舊的信息會(huì)被新的跟蹤信息覆蓋掉蜜猾。注意秀菱,要更改該文件的值需要先將 current_tracer 設(shè)置為 nop 才可以。
  • tracing_on用于控制跟蹤的暫停蹭睡。有時(shí)候在觀察到某些事件時(shí)想暫時(shí)關(guān)閉跟蹤衍菱,可以將 0 寫入該文件以停止跟蹤,這樣跟蹤緩沖區(qū)中比較新的部分是與所關(guān)注的事件相關(guān)的肩豁;寫入 1 可以繼續(xù)跟蹤脊串。
  • available_filter_functions記錄了當(dāng)前可以跟蹤的內(nèi)核函數(shù)。對(duì)于不在該文件中列出的函數(shù)清钥,無(wú)法跟蹤其活動(dòng)琼锋。
  • set_ftrace_filterset_ftrace_notrace在編譯內(nèi)核時(shí)配置了動(dòng)態(tài) ftrace (選中 CONFIG_DYNAMIC_FTRACE 選項(xiàng))后使用。前者用于顯示指定要跟蹤的函數(shù)祟昭,后者則作用相反缕坎,用于指定不跟蹤的函數(shù)。如果一個(gè)函數(shù)名同時(shí)出現(xiàn)在這兩個(gè)文件中篡悟,則這個(gè)函數(shù)的執(zhí)行狀況不會(huì)被跟蹤谜叹。這些文件還支持簡(jiǎn)單形式的含有通配符的表達(dá)式匾寝,這樣可以用一個(gè)表達(dá)式一次指定多個(gè)目標(biāo)函數(shù);具體使用在后續(xù)文章中會(huì)有描述荷腊。注意艳悔,要寫入這兩個(gè)文件的函數(shù)名必須可以在文件 available_filter_functions 中看到。缺省為可以跟蹤所有內(nèi)核函數(shù)女仰,文件 set_ftrace_notrace 的值則為空猜年。

3.ftrace跟蹤器類型

可通過(guò)cat available_tracers查看可用的跟蹤器類型《裕可跟蹤的信息如進(jìn)程調(diào)度、中斷關(guān)閉等企孩。

  • nop跟蹤器不會(huì)跟蹤任何內(nèi)核活動(dòng)锭碳,將 nop 寫入 current_tracer 文件可以刪除之前所使用的跟蹤器,并清空之前收集到的跟蹤信息勿璃,即刷新 trace 文件擒抛。
  • function跟蹤器可以跟蹤內(nèi)核函數(shù)的執(zhí)行情況;可以通過(guò)文件 set_ftrace_filter 顯示指定要跟蹤的函數(shù)补疑。
  • function_graph跟蹤器可以顯示類似 C 源碼的函數(shù)調(diào)用關(guān)系圖歧沪,這樣查看起來(lái)比較直觀一些;可以通過(guò)文件 set_grapch_function 顯示指定要生成調(diào)用流程圖的函數(shù)莲组。
  • sched_switch跟蹤器可以對(duì)內(nèi)核中的進(jìn)程調(diào)度活動(dòng)進(jìn)行跟蹤诊胞。
  • irqsoff跟蹤器和 preemptoff跟蹤器分別跟蹤關(guān)閉中斷的代碼和禁止進(jìn)程搶占的代碼,并記錄關(guān)閉的最大時(shí)長(zhǎng)锹杈,preemptirqsoff跟蹤器則可以看做它們的組合撵孤。

ftrace 框架支持?jǐn)U展添加新的跟蹤器,參考官方開(kāi)發(fā)文檔可添加自己的跟蹤器竭望;參考官方使用文檔學(xué)習(xí)如何使用ftrace邪码。

4.ftrace使用

步驟如下:

$ cd /sys/kernel/debug/tracing/——切換到目錄

$ cat available_tracers ——獲取可用跟蹤器

$ echo 1 > /proc/sys/kernel/ftrace_enabled——激活ftrace

將所選擇的跟蹤器的名字寫入文件 current_tracer。

將要跟蹤的函數(shù)寫入文件 set_ftrace_filter 咬清,將不希望跟蹤的函數(shù)寫入文件 set_ftrace_notrace闭专。

$ echo 1 > /proc/sys/kernel/tracing_on——控制跟蹤器的暫停

查看文件 trace 獲取跟蹤信息,對(duì)內(nèi)核的運(yùn)行進(jìn)行分析調(diào)試

(1)function跟蹤器
/sys/kernel/debug/tracing $ echo 0 > tracing_on
/sys/kernel/debug/tracing $ echo function > current_tracer 
/sys/kernel/debug/tracing $ echo 1 > tracing_on  #運(yùn)行一個(gè)程序
/sys/kernel/debug/tracing $ echo 0 > tracing_on
/sys/kernel/debug/tracing $ cat trace | heap -20
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
   reverse_shell-128   [000] ....  5024.031776: eth_type_trans <-loopback_xmit
   reverse_shell-128   [000] ....  5024.031778: netif_rx <-loopback_xmit
   reverse_shell-128   [000] ....  5024.031779: netif_rx_internal <-netif_rx
   reverse_shell-128   [000] ....  5024.031780: enqueue_to_backlog <-netif_rx_internal
   reverse_shell-128   [000] d...  5024.031781: _raw_spin_lock <-enqueue_to_backlog
   reverse_shell-128   [000] d...  5024.031782: __raise_softirq_irqoff <-enqueue_to_backlog
   reverse_shell-128   [000] d...  5024.031783: _raw_spin_unlock <-enqueue_to_backlog
   reverse_shell-128   [000] ....  5024.031785: __local_bh_enable_ip <-__dev_queue_xmit
   reverse_shell-128   [000] ....  5024.031786: __local_bh_enable_ip <-ip_finish_output2

(2)function_graph跟蹤器

function_graph 跟蹤器則可以提供類似 C 代碼的函數(shù)調(diào)用關(guān)系信息旧烧。通過(guò)寫文件 set_graph_function 可以顯示指定要生成調(diào)用關(guān)系的函數(shù)影钉,缺省會(huì)對(duì)所有可跟蹤的內(nèi)核函數(shù)生成函數(shù)調(diào)用關(guān)系圖。如下掘剪,將內(nèi)核函數(shù) __do_fault 作為觀察對(duì)象斧拍。

/sys/kernel/debug/tracing $ echo 0 > /proc/sys/kernel/ftrace_enabled 
/sys/kernel/debug/tracing $ echo 0 > tracing_on
/sys/kernel/debug/tracing $ echo function_graph > current_tracer 
/sys/kernel/debug/tracing $ echo __do_fault > set_graph_function
/sys/kernel/debug/tracing $ echo 1 > /proc/sys/kernel/ftrace_enabled
/sys/kernel/debug/tracing $ echo 1 > tracing_on
/sys/kernel/debug/tracing $ echo 0 > tracing_on
/sys/kernel/debug/tracing $ cat trace | head -20
# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 0)   0.471 us    |                            } /* __compute_runnable_contrib */
 0)   0.929 us    |                            account_entity_enqueue();
 0)   1.501 us    |                            update_cfs_shares();
 0)   0.499 us    |                            place_entity();
 0)   0.539 us    |                            __enqueue_entity();
 0) + 26.142 us   |                          } /* enqueue_entity */
 0)               |                          enqueue_entity() {
 0)               |                            update_curr() {
 0)   0.754 us    |                              update_min_vruntime();
 0)   0.889 us    |                              cpuacct_charge();
 0)   8.973 us    |                            }
 0)   0.398 us    |                            __compute_runnable_contrib();
 0)   0.478 us    |                            account_entity_enqueue();
 0)   0.580 us    |                            update_cfs_shares();
 0)   0.412 us    |                            place_entity();
 0)   0.411 us    |                            __enqueue_entity();
/sys/kernel/debug/tracing # echo > set_graph_function

CPU 字段給出了執(zhí)行函數(shù)的 CPU 號(hào),本例中都為 0 號(hào) CPU杖小。DURATION 字段給出了函數(shù)執(zhí)行的時(shí)間長(zhǎng)度肆汹,以 us 為單位愚墓。FUNCTION CALLS 則給出了調(diào)用的函數(shù),并顯示了調(diào)用流程昂勉。注意浪册,對(duì)于不調(diào)用其它函數(shù)的函數(shù),其對(duì)應(yīng)行以“;”結(jié)尾岗照,而且對(duì)應(yīng)的 DURATION 字段給出其運(yùn)行時(shí)長(zhǎng)村象;對(duì)于調(diào)用其它函數(shù)的函數(shù),則在其“}”對(duì)應(yīng)行給出了運(yùn)行時(shí)長(zhǎng)攒至,該時(shí)間是一個(gè)累加值厚者,包括了其內(nèi)部調(diào)用的函數(shù)的執(zhí)行時(shí)長(zhǎng)。DURATION 字段給出的時(shí)長(zhǎng)并不是精確的迫吐,它還包含了執(zhí)行 ftrace 自身的代碼所耗費(fèi)的時(shí)間库菲,所以示例中將內(nèi)部函數(shù)時(shí)長(zhǎng)累加得到的結(jié)果會(huì)與對(duì)應(yīng)的外圍調(diào)用函數(shù)的執(zhí)行時(shí)長(zhǎng)并不一致;不過(guò)通過(guò)該字段還是可以大致了解函數(shù)在時(shí)間上的運(yùn)行開(kāi)銷的志膀。最后通過(guò) echo 命令重置了文件 set_graph_function 熙宇。

(3)sched_switch 跟蹤器

sched_switch 跟蹤器可以對(duì)進(jìn)程的調(diào)度切換以及之間的喚醒操作進(jìn)行跟蹤。

[root@linux tracing]$ echo 1 > /proc/sys/kernel/ftrace_enabled 
[root@linux tracing]$ echo 0 > tracing_on 
[root@linux tracing]$ echo sched_switch > current_tracer 
[root@linux tracing]$ echo 1 > tracing_on 
# 運(yùn)行一段時(shí)間溉浙,使ftrace收集一些跟蹤信息烫止。
[root@linux tracing]$ echo 0 > tracing_on
[root@linux tracing]$ cat trace | head -10 
# tracer: sched_switch 
# 
#  TASK-PID    CPU#    TIMESTAMP  FUNCTION 
#     | |       |          |         | 
     bash-1408  [000] 26208.816058:   1408:120:S   + [000]  1408:120:S bash 
     bash-1408  [000] 26208.816070:   1408:120:S   + [000]  1408:120:S bash 
     bash-1408  [000] 26208.816921:   1408:120:R   + [000]     9:120:R events/0 
     bash-1408  [000] 26208.816939:   1408:120:R ==> [000]     9:120:R events/0 
 events/0-9     [000] 26208.817081:      9:120:R   + [000]  1377:120:R gnome-terminal
 events/0-9     [000] 26208.817088:      9:120:S ==> [000]  1377:120:R gnome-terminal

進(jìn)程間的喚醒操作和調(diào)度切換信息,可以通過(guò)符號(hào)‘ + ’和‘ ==> ’區(qū)分戳稽。描述進(jìn)程狀態(tài)的格式為“Task-PID:Priority:Task-State”馆蠕。以示例跟蹤信息中的第一條跟蹤記錄為例,可以看到進(jìn)程 bash 的 PID 為 1408 惊奇,其對(duì)應(yīng)的內(nèi)核態(tài)優(yōu)先級(jí)為 120 荆几,當(dāng)前狀態(tài)為 S(可中斷睡眠狀態(tài)),當(dāng)前 bash 并沒(méi)有喚醒其它進(jìn)程赊时;從第 3 條記錄可以看到吨铸,進(jìn)程 bash 將進(jìn)程 events/0 喚醒,而在第 4 條記錄中發(fā)生了進(jìn)程調(diào)度祖秒,進(jìn)程 bash 切換到進(jìn)程 events/0 執(zhí)行诞吱。

在 Linux 內(nèi)核中,進(jìn)程的狀態(tài)在內(nèi)核頭文件 include/linux/sched.h 中定義竭缝,包括可運(yùn)行狀態(tài) TASK_RUNNING(對(duì)應(yīng)跟蹤信息中的符號(hào)‘ R ’)房维、可中斷阻塞狀態(tài) TASK_INTERRUPTIBLE(對(duì)應(yīng)跟蹤信息中的符號(hào)‘ S ’)等。同時(shí)該頭文件也定義了用戶態(tài)進(jìn)程所使用的優(yōu)先級(jí)的范圍抬纸,最小值為 MAX_USER_RT_PRIO(值為 100 )咙俩,最大值為 MAX_PRIO - 1(對(duì)應(yīng)值為 139 ),缺省為 DEFAULT_PRIO(值為 120 );在本例中阿趁,進(jìn)程優(yōu)先級(jí)都是缺省值 120 膜蛔。

(4)irqsoff 跟蹤器

詳細(xì)說(shuō)明

irqsoff 跟蹤器可以對(duì)中斷被關(guān)閉的狀況進(jìn)行跟蹤,有助于發(fā)現(xiàn)導(dǎo)致較大延遲的代碼脖阵;當(dāng)出現(xiàn)最大延遲時(shí)皂股,跟蹤器會(huì)記錄導(dǎo)致延遲的跟蹤信息,文件 tracing_max_latency 則記錄中斷被關(guān)閉的最大延時(shí)命黔。

[root@linux tracing]# echo irqsoff > current_tracer
(5)跟蹤指定模塊中的函數(shù)

可使用簡(jiǎn)單格式的通配符(用單引號(hào)括起來(lái)):

  • begin*選擇所有名字以 begin 字串開(kāi)頭的函數(shù)
  • *middle*選擇所有名字中包含 middle 字串的函數(shù)
  • *end選擇所有名字以 end 字串結(jié)尾的函數(shù)

指定屬于特定模塊的函數(shù)呜呐,用mod指令:

$ echo ':mod:[module_name]' > set_ftrace_filter
#Eg,指定跟蹤模塊 ipv6 中的函數(shù)
$ echo ':mod:ipv6' > set_ftrace_filter 
#Eg悍募,跟蹤ext3模塊中write開(kāi)頭的函數(shù)
$ echo 'write*:mod:ext3' > set_ftrace_filter
#Eg蘑辑,排除ext3模塊中write開(kāi)頭的函數(shù),用"!"
$ echo '!writeback*:mod:ext3' >> set_ftrace_filter

5. 在代碼中使用ftrace

(1)trace_printk——打印跟蹤信息

簡(jiǎn)介:使用方式與 printk() 類似坠宴,用于向 ftrace 跟蹤緩沖區(qū)輸出跟蹤信息洋魂。配置CONFIG_TRACING選項(xiàng)后就會(huì)定義trace_printk()宏(見(jiàn)include/linux/kernel.h)。

理解:就是在內(nèi)核代碼中嵌入trace_printk()語(yǔ)句啄踊,這樣在用ftrace的function_graph跟蹤器對(duì)內(nèi)核進(jìn)行監(jiān)控時(shí)就能在trace文件中看到trace_printk()輸出的信息忧设。

例如示例模塊ftrace_demo

/*                                                     
* ftrace_demo.c 
*/                                                    
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/kernel.h> 
 
MODULE_LICENSE("GPL"); 
 
static int ftrace_demo_init(void) 
{ 
    trace_printk("Can not see this in trace unless loaded for the second time\n"); 
    return 0; 
} 
 
static void ftrace_demo_exit(void) 
{ 
    trace_printk("Module unloading\n"); 
} 
 
module_init(ftrace_demo_init); 
module_exit(ftrace_demo_exit);

對(duì)模塊ftrace_demo進(jìn)行跟蹤

[root@linux tracing]$ echo 1 > /proc/sys/kernel/ftrace_enabled 
[root@linux tracing]$ echo function_graph > current_tracer 
# 事先加載模塊 ftrace_demo (加載后才能在寫文件 set_ftrace_filter 時(shí)找到該模塊)
 
[root@linux tracing]$ echo ':mod:ftrace_demo' > set_ftrace_filter 
[root@linux tracing]$ cat set_ftrace_filter 
ftrace_demo_init 
ftrace_demo_exit 
 
# 將模塊 ftrace_demo 卸載
 
[root@linux tracing]$ echo 1 > tracing_enabled 
 
# 重新進(jìn)行模塊 ftrace_demo 的加載與卸載操作
 
[root@linux tracing]# cat trace 
# tracer: function_graph 
# 
# CPU  DURATION                  FUNCTION CALLS 
# |     |   |                     |   |   |   | 
1)               |  /* Can not see this in trace unless loaded for the second time */ 
0)               |  /* Module unloading */
(2)tracing_on/tracing_off —— 控制跟蹤信息的記錄

代碼中刁标,可以利用tracing_on() 和 tracing_off()來(lái)繼續(xù)和暫停跟蹤颠通,類似于對(duì) /sys/kernel/debug/tracing 下的文件 tracing_on 分別執(zhí)行寫 1 和 寫 0 的操作(前者節(jié)省上下文切換、系統(tǒng)調(diào)度控制等的時(shí)間)膀懈。

使用 tracing_off 的模塊 ftrace_demo:

/*                                                     
* ftrace_demo.c 
*     modified to demostrate the usage of tracing_off 
*/                                                    
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/kernel.h> 
 
MODULE_LICENSE("GPL"); 
 
static int ftrace_demo_init(void) 
{      
    trace_printk("ftrace_demo_init called\n"); 
    tracing_off(); 
    return 0; 
} 
 
static void ftrace_demo_exit(void) 
{ 
    trace_printk("ftrace_demo_exit called\n"); 
    tracing_off(); 
} 
 
module_init(ftrace_demo_init); 
module_exit(ftrace_demo_exit);

跟蹤

[root@linux tracing]$ echo 1 > /proc/sys/kernel/ftrace_enabled 
[root@linux tracing]$ echo 1 > tracing_on 
[root@linux tracing]$ echo function > current_tracer 
[root@linux tracing]$ echo 1 > tracing_enabled 
 
# 加載模塊 ftrace_demo顿锰,模塊初始化函數(shù) ftrace_demo_init 被調(diào)用
 
[root@linux tracing]$ cat tracing_on 
0 
[root@linux tracing]$ cat trace | wc -l 
120210 
[root@linux tracing]$ cat trace | grep -n ftrace_demo_init 
120187:      insmod-2897  [000]  2610.504611: ftrace_demo_init <-do_one_initcall 
120193:      insmod-2897  [000]  2610.504667: ftrace_demo_init: ftrace_demo_init called 
 
[root@linux tracing]$ echo 1 > tracing_on   # 繼續(xù)跟蹤信息的記錄
 
# 卸載模塊 ftrace_demo,模塊函數(shù) ftrace_demo_exit 被調(diào)用
 
[root@linux tracing]$ cat tracing_on 
0 
[root@linux tracing]$ wc -l trace 
120106 trace 
[root@linux tracing]$ grep -n ftrace_demo_exit trace 
120106:           rmmod-2992  [001]  3016.884449: : ftrace_demo_exit called

優(yōu)點(diǎn):在代碼中使用 tracing_off() 可以控制將感興趣的信息保存在跟蹤緩沖區(qū)的末端位置启搂,不會(huì)很快被新的信息所覆蓋硼控,便于及時(shí)查看。還可以通過(guò)特定條件(比如檢測(cè)到某種異常狀況胳赌,等等)來(lái)控制跟蹤信息的記錄牢撼,如下所示。實(shí)踐中疑苫,可以通過(guò)宏來(lái)控制是否將對(duì)這些函數(shù)的調(diào)用編譯進(jìn)內(nèi)核模塊熏版,這樣可以在調(diào)試時(shí)將其開(kāi)啟,在最終發(fā)布時(shí)將其關(guān)閉捍掺。用戶態(tài)的應(yīng)用程序可以通過(guò)直接讀寫文件 tracing_on 來(lái)控制記錄跟蹤信息的暫停狀態(tài)撼短,以便了解應(yīng)用程序運(yùn)行期間內(nèi)核中發(fā)生的活動(dòng)。

if (condition) 
    tracing_on() or tracing_off()

總結(jié):ftrace能有效監(jiān)測(cè)內(nèi)核活動(dòng)挺勿、函數(shù)調(diào)用曲横,但是無(wú)法獲取調(diào)用參數(shù)、調(diào)用上下文不瓶、甚至修改上下文禾嫉。不過(guò)可以利用該機(jī)制往源碼插入代碼(如trace_printfk灾杰,輸出調(diào)用參數(shù)),然后動(dòng)態(tài)監(jiān)測(cè)夭织。如果要開(kāi)發(fā)代碼去監(jiān)測(cè)內(nèi)核吭露,參考/kernel/trace/ftrace.c代碼,編寫hook內(nèi)核API的代碼請(qǐng)參考以下文章和代碼尊惰。

示例代碼

Hooking linux內(nèi)核函數(shù)(一):尋找完美解決方案

Hooking linux內(nèi)核函數(shù)(二):如何使用Ftrace hook函數(shù)

Hooking linux內(nèi)核函數(shù)(三):Ftrace的主要優(yōu)缺點(diǎn)


三讲竿、Kprobes

探測(cè)手段:3種,kprobe弄屡、jprobe和kretprobe题禀。

使用方式:2種,第一種是編寫內(nèi)核模塊膀捷,向內(nèi)核注冊(cè)探測(cè)點(diǎn)迈嘹,自定義回調(diào)函數(shù);第二種是使用kprobes in ftrace全庸,這種方式結(jié)合kprobe和ftrace秀仲,可以通過(guò)kprobe來(lái)優(yōu)化ftrace跟蹤函數(shù)。

1.編寫kprobe探測(cè)模塊

(1)struct kprobe結(jié)構(gòu)體
// kprobe結(jié)構(gòu)表示一個(gè)探測(cè)點(diǎn)
struct kprobe {
    struct hlist_node hlist;// 被用于kprobe全局hash壶笼,索引值為被探測(cè)點(diǎn)的地址神僵。
    struct list_head list;  // 用于鏈接同一被探測(cè)點(diǎn)的不同探測(cè)kprobe。
    /*count the number of times this probe was temporarily disarmed */
    unsigned long nmissed;
    kprobe_opcode_t *addr;  // 被探測(cè)點(diǎn)的地址覆劈。
    const char *symbol_name;// 被探測(cè)函數(shù)的名稱保礼。
    unsigned int offset;    // 被探測(cè)點(diǎn)在函數(shù)內(nèi)部的偏移,用于探測(cè)函數(shù)內(nèi)核的指令责语,如果該值為0表示函數(shù)的入口炮障。
    kprobe_pre_handler_t pre_handler;    // 被探測(cè)點(diǎn)指令執(zhí)行之前調(diào)用的回調(diào)函數(shù)。
    kprobe_post_handler_t post_handler;  // 被探測(cè)點(diǎn)指令執(zhí)行之后調(diào)用的回調(diào)函數(shù)坤候。
    kprobe_fault_handler_t fault_handler;// 在執(zhí)行pre_handler胁赢、post_handler或單步執(zhí)行被探測(cè)指令時(shí)出現(xiàn)內(nèi)存異常則會(huì)調(diào)用該回調(diào)函數(shù)。
    kprobe_break_handler_t break_handler;// 在執(zhí)行某一kprobe過(guò)程中觸發(fā)了斷點(diǎn)指令后會(huì)調(diào)用該函數(shù)白筹,用于實(shí)現(xiàn)jprobe智末。
    kprobe_opcode_t opcode;              // 保存的被探測(cè)點(diǎn)原始指令。
    struct arch_specific_insn ainsn;     // 被復(fù)制的被探測(cè)點(diǎn)的原始指令遍蟋,用于單步執(zhí)行吹害,架構(gòu)強(qiáng)相關(guān)。
    u32 flags;                           // 狀態(tài)標(biāo)記虚青。
};
(2)kprobe API函數(shù)
int register_kprobe(struct kprobe *p);                // 注冊(cè)kprobe探測(cè)點(diǎn)
void unregister_kprobe(struct kprobe *p);             // 卸載kprobe探測(cè)點(diǎn)
int register_kprobes(struct kprobe **kps, int num);   // 注冊(cè)多個(gè)kprobe探測(cè)點(diǎn)
void unregister_kprobes(struct kprobe **kps, int num);// 卸載多個(gè)kprobe探測(cè)點(diǎn)
int disable_kprobe(struct kprobe *kp);                // 暫停指定kprobe探測(cè)點(diǎn)
int enable_kprobe(struct kprobe *kp);                 // 恢復(fù)指定kprobe探測(cè)點(diǎn)
void dump_kprobe(struct kprobe *kp);                  // 打印指定kprobe探測(cè)點(diǎn)的名稱它呀、地址、偏移
(3)kprobe_example.c 示例

示例代碼見(jiàn)/samples/kprobes/kprobe_example.c,介紹如何使用kprobe纵穿。探測(cè)目標(biāo)是_do_fork下隧,該函數(shù)會(huì)在fork系統(tǒng)調(diào)用或者kernel_kthread創(chuàng)建內(nèi)核線程時(shí)被調(diào)用。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>

#define MAX_SYMBOL_LEN    64
static char symbol[MAX_SYMBOL_LEN] = "_do_fork";
module_param_string(symbol, symbol, sizeof(symbol), 0644);

/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = { //定義一個(gè)實(shí)例kp并初始化symbol_name為"_do_fork"谓媒,將探測(cè)_do_fork函數(shù)淆院。
    .symbol_name    = symbol,
};

/* kprobe pre_handler: called just before the probed instruction is executed */
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86
    pr_info("<%s> pre_handler: p->addr = %pF, ip = %lx, flags = 0x%lx\n",
        p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
#ifdef CONFIG_ARM64
    pr_info("<%s> pre_handler: p->addr = %pF, pc = 0x%lx,"
            " pstate = 0x%lx\n",
        p->symbol_name, p->addr, (long)regs->pc, (long)regs->pstate);
#endif

    /* A dump_stack() here will give a stack backtrace */
    return 0;
}

/* kprobe post_handler: called after the probed instruction is executed */
static void handler_post(struct kprobe *p, struct pt_regs *regs,
                unsigned long flags)
{
#ifdef CONFIG_X86
    pr_info("<%s> post_handler: p->addr = %pF, flags = 0x%lx\n",
        p->symbol_name, p->addr, regs->flags);
#endif
#ifdef CONFIG_ARM64
    pr_info("<%s> post_handler: p->addr = %pF, pstate = 0x%lx\n",
        p->symbol_name, p->addr, (long)regs->pstate);
#endif
}

/*
 * fault_handler: this is called if an exception is generated for any
 * instruction within the pre- or post-handler, or when Kprobes
 * single-steps the probed instruction.
 */
static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
    pr_info("fault_handler: p->addr = %pF, trap #%dn", p->addr, trapnr);
    /* Return 0 because we don't handle the fault. */
    return 0;
}

static int __init kprobe_init(void)
{
    int ret;
    kp.pre_handler = handler_pre;// 初始化kp的三個(gè)回調(diào)函數(shù)。
    kp.post_handler = handler_post;
    kp.fault_handler = handler_fault;

    ret = register_kprobe(&kp);// 注冊(cè)kp探測(cè)點(diǎn)到內(nèi)核句惯。
    if (ret < 0) {
        pr_err("register_kprobe failed, returned %d\n", ret);
        return ret;
    }
    pr_info("Planted kprobe at %pF\n", kp.addr);
    return 0;
}

static void __exit kprobe_exit(void)
{
    unregister_kprobe(&kp);
    pr_info("kprobe at %pF unregistered\n", kp.addr);
}

module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");

模塊的編譯Makefile如下:

obj-m := kprobe_example.o

CROSS_COMPILE=''
#KDIR := /lib/modules/$(shell uname -r)/build
KDIR := /home/john/Desktop/stringIPC/linux-4.4.184
all:
    make -C $(KDIR) M=$(PWD) modules 
clean:
    rm -f *.ko *.o *.mod.o *.mod.c .*.cmd *.symvers  modul*

insmod加載土辩,等待后rmmod卸載,執(zhí)行結(jié)果如下:

$ dmesg
[   13.365009] Planted kprobe at _do_fork+0x0/0x360
[   20.849401] <_do_fork> pre_handler: p->addr = _do_fork+0x0/0x360, ip = ffffffff81083471, flags = 0x246
[   20.849458] <_do_fork> post_handler: p->addr = _do_fork+0x0/0x360, flags = 0x246
[   52.741703] <_do_fork> pre_handler: p->addr = _do_fork+0x0/0x360, ip = ffffffff81083471, flags = 0x246
[   52.741747] <_do_fork> post_handler: p->addr = _do_fork+0x0/0x360, flags = 0x246
[   73.833422] <_do_fork> pre_handler: p->addr = _do_fork+0x0/0x360, ip = ffffffff81083471, flags = 0x246
[   73.833465] <_do_fork> post_handler: p->addr = _do_fork+0x0/0x360, flags = 0x246
[   73.866238] kprobe at _do_fork+0x0/0x360 unregistered
$ cat /proc/kallsyms | grep _do_fork  
ffffffff81083470 T _do_fork
# 驗(yàn)證后發(fā)現(xiàn)地址和符號(hào)是對(duì)應(yīng)的

2.基于ftrace使用kprobe

(1)kprobe配置

make menuconfig 設(shè)置"Kernel hacking"->"Tracers"->"Enable kprobes-based dynamic events"抢野。我看默認(rèn)是設(shè)置的拷淘。

CONFIG_KPROBES=y
CONFIG_OPTPROBES=y
CONFIG_KPROBES_ON_FTRACE=y
CONFIG_UPROBES=y
CONFIG_KRETPROBES=y
CONFIG_HAVE_KPROBES=y
CONFIG_HAVE_KRETPROBES=y
CONFIG_HAVE_OPTPROBES=y
CONFIG_HAVE_KPROBES_ON_FTRACE=y
CONFIG_KPROBE_EVENT=y

掛載debugfs步驟和配置ftrace一樣:mount -t debugfs nodev /sys/kernel/debug

(2)probe trace events使用

配置后能在/sys/kernel/debug/tracing/目錄下看到相應(yīng)文件:

/sys/kernel/debug/tracing/kprobe_events                               // 配置kprobe事件屬性指孤,增加事件之后會(huì)在kprobes下面生成對(duì)應(yīng)目錄启涯。
/sys/kernel/debug/tracing/kprobe_profile                // kprobe事件統(tǒng)計(jì)屬性文件。
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/enabled // 使能kprobe事件
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/filter  // 過(guò)濾kprobe事件
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/format  // 查詢kprobe事件顯示格式

新增kprobe事件:通過(guò)寫kprobe_event來(lái)設(shè)置然后在/sys/kernel/debug/tracing/trace中看結(jié)果恃轩。

p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] // 設(shè)置一個(gè)probe探測(cè)點(diǎn)
r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS]            // 設(shè)置一個(gè)return probe探測(cè)點(diǎn)
-:[GRP/]EVENT                                                                                // 刪除一個(gè)探測(cè)點(diǎn)
// 具體解釋:
GRP        : Group name. If omitted, use "kprobes" for it. // 設(shè)置后會(huì)在events/kprobes下創(chuàng)建<GRP>目錄结洼。
 EVENT        : Event name. If omitted, the event name is generated based on SYM+offs or MEMADDR. // 指定后在events/kprobes/<GRP>生成<EVENT>目錄。
 MOD        : Module name which has given SYM.                // 模塊名叉跛,一般不設(shè)
 SYM[+offs]    : Symbol+offset where the probe is inserted.   // 被探測(cè)函數(shù)名和偏移
 MEMADDR    : Address where the probe is inserted.            // 指定被探測(cè)的內(nèi)存絕對(duì)地址
 FETCHARGS    : Arguments. Each probe can have up to 128 args.// 指定要獲取的參數(shù)信息松忍。
 %REG        : Fetch register REG                             // 獲取指定寄存器值
 @ADDR        : Fetch memory at ADDR (ADDR should be in kernel)// 獲取指定內(nèi)存地址的值
 @SYM[+|-offs]    : Fetch memory at SYM +|- offs (SYM should be a data symbol)// 獲取全局變量的值
 $stackN    : Fetch Nth entry of stack (N >= 0)        // 獲取指定棧空間值昧互,即sp寄存器+N后的位置值
 $stack    : Fetch stack address.                      // 獲取sp寄存器值
 $retval    : Fetch return value.(*)                   // 獲取返回值挽铁,用戶return kprobe
 $comm        : Fetch current task comm.               // 獲取對(duì)應(yīng)進(jìn)程名稱伟桅。
 +|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**)
 NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
 FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
          (x8/x16/x32/x64), "string" and bitfield are supported. // 設(shè)置參數(shù)的類型敞掘,可以支持字符串和比特類型
  (*) only for return probe.
  (**) this is useful for fetching a field of data structures. 

執(zhí)行如下兩條命令就會(huì)生成目錄/sys/kernel/debug/tracing/events/kprobes/myprobe;第三條命令則可以刪除指定kprobe事件楣铁,如果要全部刪除則echo > /sys/kernel/debug/tracing/kprobe_events玖雁。

$ echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events
$ echo 'r:myretprobe do_sys_open ret=$retval' >> /sys/kernel/debug/tracing/kprobe_events #這里面一定要用">>",不然就會(huì)覆蓋前面的設(shè)置盖腕。

$ echo '-:myprobe' >> /sys/kernel/debug/tracing/kprobe_events
$ echo '-:myretprobe' >> /sys/kernel/debug/tracing/kprobe_events

參數(shù)后面的寄存器是跟架構(gòu)相關(guān)的赫冬,%ax、%dx溃列、%cx表示第1/2/3個(gè)參數(shù)劲厌,超出部分使用$stack來(lái)存儲(chǔ)參數(shù)。

函數(shù)返回值保存在$retval中听隐。

kprobe使能:對(duì)kprobe事件的是能通過(guò)往對(duì)應(yīng)事件的enable寫1開(kāi)啟探測(cè)补鼻;寫0暫停探測(cè)。/sys/kernel/debug/tracing/events/kprobes/myprobe/enable

$ echo > /sys/kernel/debug/tracing/trace
$ echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events
$ echo 'r:myretprobe do_sys_open ret=$retval' >> /sys/kernel/debug/tracing/kprobe_events

$ echo 1 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
$ echo 1 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable
$ ls
$ echo 0 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
$ echo 0 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable

$ cat /sys/kernel/debug/tracing/trace
                        sourceinsight4.-3356  [000] .... 3542865.754536: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd6764a0 filename=0x8000 flags=0x1b6 mode=0xe3afff48ffffffff
            bash-26041 [001] .... 3542865.757014: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x8241 flags=0x1b6 mode=0xe0c0ff48ffffffff
              ls-18078 [005] .... 3542865.757950: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x1 mode=0xc1b7bf48ffffffff
              ls-18078 [005] d... 3542865.757953: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
              ls-18078 [005] .... 3542865.757966: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x6168 mode=0xc1b7bf48ffffffff
(3)kprobe事件過(guò)濾

利用寫filter進(jìn)行過(guò)濾(/sys/kernel/debug/tracing/events/kprobes/myprobe/filter)。它支持的格式和c語(yǔ)言的表達(dá)式類似风范,支持 ==咨跌,!=,>硼婿,<锌半,>=,<=判斷寇漫,并且支持與&&刊殉,或||,還有()州胳。

$ echo 'filename==0x8241' > /sys/kernel/debug/tracing/events/kprobes/myprobe/filter
(4)kprobe和棧配合使用

可以在現(xiàn)實(shí)函數(shù)的同事顯示其棧信息冗澈,通過(guò)配置trace_options。

$ echo stacktrace > /sys/kernel/debug/tracing/trace_options
(5)kprobe_profile統(tǒng)計(jì)信息

后面兩列分別表示命中和未命中的次數(shù)陋葡。

$ cat /sys/kernel/debug/tracing/kprobe_profile 
  myprobe                                                   11               0
  myretprobe                                                11               0

kprobe源碼分析可參考《Linux內(nèi)核調(diào)試技術(shù)——kprobe使用與實(shí)現(xiàn)》第四節(jié)《四亚亲、kprobe實(shí)現(xiàn)源碼分析》。

3.kprobe問(wèn)題

問(wèn)題:據(jù)說(shuō)是能夠修改寄存器和數(shù)據(jù)結(jié)構(gòu)腐缤,但是沒(méi)有找到修改方法捌归。

追蹤內(nèi)核棧及其他數(shù)據(jù)請(qǐng)參考代碼kprobe-tracerRBTree_Kprobe_LinuxKernel


四岭粤、SystemTap

1.配置

自動(dòng)化安裝請(qǐng)參考自動(dòng)化腳本惜索,執(zhí)行sudo ./install_all.sh即可(注意缺generate_ko.sh文件)。

(1)安裝systemtap

apt-getsudo apt-get install elfutils libcap-dev systemtap

卸載sudo apt-get remove systemtap

源碼安裝下載鏈接

./configure
make
sudo make instal

卸載sudo make uninstall

(2)安裝debug symbols

使用方式:2種剃浇,第一種是編寫內(nèi)核模塊巾兆,向內(nèi)核注冊(cè)探測(cè)點(diǎn),探測(cè)函數(shù)根據(jù)需要自行定制虎囚,但是使用不方便角塑;第二種是使用kprobes in ftrace,這種方式結(jié)合kprobe和ftrace淘讥,可以通過(guò)kprobe來(lái)優(yōu)化ftrace跟蹤函數(shù)圃伶。

# 配置ddeb repository
$ sudo cat > /etc/apt/sources.list.d/ddebs.list << EOF
$ deb http://ddebs.ubuntu.com/ precise main restricted universe multiverse
EOF

$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ECDCAD72428D7C01
$ sudo apt-get update
# 下載和你當(dāng)前內(nèi)核版本相對(duì)應(yīng)的debug symbols —— 使用一個(gè)寫的很好的腳本
$ wget http://www.domaigne.com/download/tools/get-dbgsym
$ chmod +x get-dbgsym
$ sudo ./get-dbgsy
(3)生成systemtap/libelf所需的模塊信息

將如下命令放入generate_ko.sh:

for file in `find /usr/lib/debug -name '*.ko' -print`
do
        buildid=`eu-readelf -n $file| grep Build.ID: | awk '{print $3}'`
        dir=`echo $buildid | cut -c1-2`
        fn=`echo $buildid | cut -c3-`
        mkdir -p /usr/lib/debug/.build-id/$dir
        ln -s $file /usr/lib/debug/.build-id/$dir/$fn
        ln -s $file /usr/lib/debug/.build-id/$dir/${fn}.debug
done

然后執(zhí)行該文件:$ sudo ./generate_ko.sh

(4)安裝成功

輸入以下命令,若打印“hello world”則安裝成功蒲列。

$ stap -e 'probe kernel.function("sys_open") {log("hello world") exit()}'

若報(bào)錯(cuò)如下:

stap: Symbol `SSL_ImplementedCiphers' has different size in shared object, consider re-linking
In file included from include/linux/mutex.h:15:0,
                 from /tmp/staphH2yQD/stap_6e022ad97cbe9c6f46b582f7a0eac81d_1242_src.c:25:
include/linux/spinlock_types.h:55:14: error: ‘__ARCH_SPIN_LOCK_UNLOCKED’ undeclared here (not in a function)

說(shuō)明有些共享庫(kù)需要重新readlink窒朋,執(zhí)行如下命令:

$ readlink /lib/modules/`uname -r`/build/

2.SystemTap使用

SystemTap—SystemTap官方語(yǔ)法

SystemTap—SystemTap官方示例

(1)stap命令
stap [OPTIONS] FILENAME [ARGUMENTS]
stap [OPTIONS] - [ARGUMENTS]
stap [OPTIONS] –e SCRIPT [ARGUMENTS]

比較常用和有用的參數(shù):
-e SCRIPT               # 運(yùn)行腳本
-l PROBE                # 列出匹配的探針
-L PROBE                # 列出匹配的探針和局部變量
-g                      # guru mode 
-D NM=VAL               # 向生成的c代碼注入宏定義
-o FILE                 # 腳本輸出到文件,而非stdout
-x PID                  # 設(shè)置 target() 到 PID
(2)腳本語(yǔ)法

probe探針用法

// probe probe-point { statement }
// 在Hello World例子中begin和end就是probe-point蝗岖, statement就是該探測(cè)點(diǎn)的處理邏輯侥猩,在Hello World例子中statement只有一行print,statement可以是復(fù)雜的代碼塊抵赢。
begin                          // 在腳本開(kāi)始時(shí)觸發(fā)
end                            // 在腳本結(jié)束時(shí)觸發(fā)
kernel.function(PATTERN)       // 在命名函數(shù)執(zhí)行時(shí)觸發(fā)
kernel.function(PATTERN).call  // 同上
kernel.function(PATTERN).return// 在命名函數(shù)返回時(shí)觸發(fā)
kernel.function(PATTERN).return.maxactive(VALUE)
kernel.syscall.*               // 進(jìn)行任何系統(tǒng)調(diào)用時(shí)觸發(fā)
kernel.function(PATTERN).inline
kernel.function(PATTERN).label(LPATTERN)
module(MPATTERN).function(PATTERN)
module(MPATTERN).function(PATTERN).call
module(MPATTERN).function(PATTERN).return.maxactive(VALUE)
module(MPATTERN).function(PATTERN).inline
kernel.statement(PATTERN)     // 使探針探測(cè)到確切的代碼行
kernel.statement(ADDRESS).absolute
module(MPATTERN).statement(PATTERN)
process(PROCESSPATH).function(PATTERN)
process(PROCESSPATH).function(PATTERN).call
process(PROCESSPATH).function(PATTERN).return
process(PROCESSPATH).function(PATTERN).inline
process(PROCESSPATH).statement(PATTERN)

PATTERN語(yǔ)法

// 語(yǔ)法格式: 3部分——函數(shù)名字 + @ + 源文件路徑 + (":"——絕對(duì)行號(hào)欺劳;"+"——函數(shù)入口相對(duì)行號(hào))
func[@file]
func@file:linenumber
// Eg:
kernel.function("*init*")
kernel.function(“*@kernel/fork.c:934”)   // 到達(dá) fork.c 的第 934 行時(shí)觸發(fā)
module("ext3").function("*")             // 調(diào)用 ext3 模塊中任何函數(shù)時(shí)觸發(fā)
kernel.statement("*@kernel/time.c:296")  //
kernel.statement("bio_init@fs/bio.c+3")  // 引用文件fs/bio.c 內(nèi)bio_init+3 這一行語(yǔ)句
process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request")
timer.jiffies(1000)                      // 每隔 1000 個(gè)內(nèi)核 jiffy 觸發(fā)一次
timer.ms(200).randomize(50)              // 每隔 200 毫秒觸發(fā)一次洛退,帶有線性分布的隨機(jī)附加時(shí)間(-50 到 +50)

基本語(yǔ)法:與C語(yǔ)言類似,只是每一行結(jié)尾";"是可選的杰标。主要語(yǔ)句如下: if/else兵怯、while、for/foreach腔剂、break/continue媒区、return、next掸犬、delete袜漩、try/catch 其中: next:主要在probe探測(cè)點(diǎn)邏輯處理中使用,調(diào)用此語(yǔ)句時(shí)湾碎,立刻從調(diào)用函數(shù)中退出宙攻。不同于exit()的是,執(zhí)行到next語(yǔ)句時(shí)介褥,會(huì)馬上從探測(cè)點(diǎn)處理函數(shù)中返回座掘,而此SystemTap并沒(méi)有終止,但exit()則會(huì)終止SystemTap柔滔。

變量:不需要明確聲明變量類型溢陪,用”global“聲明的變量(使用過(guò)多會(huì)有性能損失),獲取進(jìn)程中的變量(全局變量睛廊、局部變量形真、參數(shù))直接在變量名前面加$即可(后面會(huì)有例子)序苏。

函數(shù)

function indent:string (delta:long){
  return _generic_indent(-1, "",  delta)
}

function _generic_indent (idx, desc, delta)
{
  ts = __indent_timestamp ()
  if (! _indent_counters[idx]) _indent_timestamps[idx] = ts
  depth = _generic_indent_depth(idx, delta)
  return sprintf("%6d (%d:%d) %s:%-*s", (ts - _indent_timestamps[idx]), depth, delta, desc, depth, "")
}  

function strlen:long(s:string) %{
    STAP_RETURN(strlen(STAP_ARG_s));
%}

獲取stap命令行參數(shù)

腳本example.stp:probe begin { printf(“%d, %s/n”, $1, @2) }

命令:$ stap example.stp 10 mystring

$1 會(huì)被替換成10 倔喂,而@2 會(huì)被替換成”mystring” ,結(jié)果輸出:10, mystring

3.SystemTap使用技巧

請(qǐng)參考【linux內(nèi)核調(diào)試】SystemTap使用技巧

4.討論

優(yōu)點(diǎn):可以很方便的修改變量和參數(shù)轿塔。

缺點(diǎn):如何安裝到定制的內(nèi)核中嘶朱,放到qemu中運(yùn)行(如何給鏡像安裝debug symbols)蛾坯?

參考:

Linux內(nèi)核調(diào)試的方式以及工具集錦

ftrace—使用ftrace學(xué)習(xí)linux內(nèi)核函數(shù)調(diào)用

ftrace—ftrace:跟蹤你的內(nèi)核函數(shù)!

ftrace—使用ftrace調(diào)試Linux內(nèi)核见咒,第1部分

ftrace—使用ftrace調(diào)試Linux內(nèi)核偿衰,第2部分

ftrace—使用ftrace調(diào)試Linux內(nèi)核挂疆,第3部分

ftrace—ftrace官方文檔

kprobe—kprobe原理解析(二)

kprobe—Linux kprobe調(diào)試技術(shù)使用

SystemTap—在Ubuntu上安裝使用Systemtap

SystemTap—SystemTap使用技巧

SystemTap—SystemTap Language Reference

SystemTap—SystemTap使用技巧【一】

SystemTap—SystemTap使用技巧【二】

SystemTap—SystemTap使用技巧【三】

SystemTap—SystemTap使用技巧【四】

SystemTap—systemtap學(xué)習(xí)總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末改览,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缤言,更是在濱河造成了極大的恐慌宝当,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胆萧,死亡現(xiàn)場(chǎng)離奇詭異庆揩,居然都是意外死亡俐东,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門订晌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)虏辫,“玉大人,你說(shuō)我怎么就攤上這事锈拨∑鲎” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵奕枢,是天一觀的道長(zhǎng)娄昆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)缝彬,這世上最難降的妖魔是什么萌焰? 我笑而不...
    開(kāi)封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谷浅,結(jié)果婚禮上扒俯,老公的妹妹穿的比我還像新娘。我一直安慰自己一疯,他們只是感情好陵珍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著违施,像睡著了一般互纯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上磕蒲,一...
    開(kāi)封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天留潦,我揣著相機(jī)與錄音,去河邊找鬼辣往。 笑死兔院,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的站削。 我是一名探鬼主播坊萝,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼许起!你這毒婦竟也來(lái)了十偶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤园细,失蹤者是張志新(化名)和其女友劉穎惦积,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體猛频,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狮崩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年蛛勉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睦柴。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诽凌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坦敌,到底是詐尸還是另有隱情皿淋,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布恬试,位于F島的核電站窝趣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏训柴。R本人自食惡果不足惜哑舒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幻馁。 院中可真熱鬧洗鸵,春花似錦、人聲如沸仗嗦。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)稀拐。三九已至火邓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間德撬,已是汗流浹背铲咨。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜓洪,地道東北人纤勒。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像隆檀,于是被迫代替她去往敵國(guó)和親摇天。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354