一嘁傀、調(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=y
或 Kernel 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_filter和 set_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 跟蹤器
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-tracer和RBTree_Kprobe_LinuxKernel。
四岭粤、SystemTap
1.配置
自動(dòng)化安裝請(qǐng)參考自動(dòng)化腳本惜索,執(zhí)行sudo ./install_all.sh
即可(注意缺generate_ko.sh
文件)。
(1)安裝systemtap
apt-get:sudo 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)蛾坯?
參考:
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é)