偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深入剖析SystemTap:Linux動(dòng)態(tài)追蹤的神兵利器

系統(tǒng) Linux
無論是快速定位生產(chǎn)環(huán)境的性能瓶頸,還是深度排查疑難故障,亦或是構(gòu)建定制化監(jiān)控方案,SystemTap 都能以低侵入性、高靈活性的優(yōu)勢,成為你手中的 “神兵利器”。接下來,我們就從原理到實(shí)戰(zhàn),一步步揭開 SystemTap 的神秘面紗,帶你掌握這門 Linux 動(dòng)態(tài)追蹤的核心技術(shù)。

作為 Linux 開發(fā)者或運(yùn)維工程師,你是否曾陷入這樣的困境:系統(tǒng)突然出現(xiàn)性能瓶頸,CPU 利用率莫名飆升,卻找不到具體消耗資源的內(nèi)核函數(shù);用戶進(jìn)程頻繁崩潰,日志只留下模糊報(bào)錯(cuò),無法定位到關(guān)鍵調(diào)用鏈路;想監(jiān)控某個(gè)系統(tǒng)調(diào)用的實(shí)時(shí)行為,又怕傳統(tǒng)調(diào)試工具帶來額外性能開銷…… 這些 “看不見、摸不著” 的內(nèi)核層問題,往往讓排查工作陷入僵局。

而 SystemTap 的出現(xiàn),就像給 Linux 系統(tǒng)裝上了一副 “透視鏡”。它無需重啟系統(tǒng)、無需修改內(nèi)核代碼,就能通過動(dòng)態(tài)插樁技術(shù),精準(zhǔn)捕捉內(nèi)核與用戶空間的關(guān)鍵事件 —— 從函數(shù)調(diào)用、系統(tǒng)調(diào)用觸發(fā),到內(nèi)存分配、網(wǎng)絡(luò)包傳輸,甚至自定義業(yè)務(wù)邏輯的執(zhí)行狀態(tài),都能實(shí)時(shí)追蹤與分析。

無論是快速定位生產(chǎn)環(huán)境的性能瓶頸,還是深度排查疑難故障,亦或是構(gòu)建定制化監(jiān)控方案,SystemTap 都能以低侵入性、高靈活性的優(yōu)勢,成為你手中的 “神兵利器”。接下來,我們就從原理到實(shí)戰(zhàn),一步步揭開 SystemTap 的神秘面紗,帶你掌握這門 Linux 動(dòng)態(tài)追蹤的核心技術(shù)。

一、SystemTap 是什么?

1.1 SystemTap概述

SystemTap 是一款極為強(qiáng)大的 Linux 系統(tǒng)動(dòng)態(tài)追蹤工具,它就像是給系統(tǒng)運(yùn)維和開發(fā)人員配備的 “透視眼鏡”,能夠讓我們深入到 Linux 系統(tǒng)的內(nèi)核以及用戶空間程序內(nèi)部,實(shí)時(shí)地監(jiān)控和診斷程序的運(yùn)行狀況。在復(fù)雜的系統(tǒng)環(huán)境中,它能幫助我們快速定位問題,優(yōu)化性能。

以往在排查系統(tǒng)問題時(shí),如果沒有類似 SystemTap 這樣的工具,開發(fā)人員往往需要經(jīng)歷繁瑣的流程。比如想要獲取內(nèi)核或用戶空間程序的某些運(yùn)行信息,可能需要先修改代碼,添加一些打印語句或者調(diào)試代碼,然后重新編譯整個(gè)程序或內(nèi)核,接著安裝新的程序或內(nèi)核版本,最后還得重啟系統(tǒng)才能生效。這個(gè)過程不僅耗時(shí)費(fèi)力,而且還可能對(duì)正在運(yùn)行的業(yè)務(wù)造成影響。但有了 SystemTap,這些問題就迎刃而解,它不需要對(duì)目標(biāo)程序或內(nèi)核進(jìn)行重新編譯和重啟,就能動(dòng)態(tài)地插入探測點(diǎn),獲取我們想要的信息。

從技術(shù)原理上來說,SystemTap 主要基于 Linux 內(nèi)核的 Kprobe 機(jī)制。Kprobe 是 Linux 內(nèi)核提供的一種動(dòng)態(tài)探測機(jī)制,它允許在不修改內(nèi)核代碼的情況下,在指定的內(nèi)核函數(shù)或指令處插入斷點(diǎn)或探測點(diǎn)。SystemTap 利用這個(gè)機(jī)制,通過編寫特定的腳本,定義我們感興趣的事件(比如某個(gè)內(nèi)核函數(shù)的調(diào)用、某個(gè)系統(tǒng)調(diào)用的發(fā)生、定時(shí)器的觸發(fā)等),當(dāng)這些事件發(fā)生時(shí),就會(huì)執(zhí)行相應(yīng)的處理程序,從而實(shí)現(xiàn)對(duì)系統(tǒng)運(yùn)行狀態(tài)的監(jiān)控和分析。

SystemTap 的腳本語言設(shè)計(jì)得非常簡潔且強(qiáng)大,它類似于 C 語言和 Awk 語言的混合,對(duì)于有一定編程基礎(chǔ)的人來說很容易上手。在腳本中,我們可以定義各種探針(Probes),這些探針就是我們?cè)O(shè)置的探測點(diǎn),每個(gè)探針都關(guān)聯(lián)著一個(gè)或多個(gè)處理程序(Handlers)。當(dāng)探針?biāo)鶎?duì)應(yīng)的事件觸發(fā)時(shí),相應(yīng)的處理程序就會(huì)被執(zhí)行 ,在處理程序中,我們可以獲取事件的相關(guān)信息,比如函數(shù)的參數(shù)、返回值、當(dāng)前進(jìn)程的 ID、進(jìn)程名等等,還可以對(duì)這些信息進(jìn)行處理和分析,比如打印輸出、統(tǒng)計(jì)計(jì)數(shù)、數(shù)據(jù)存儲(chǔ)等等。

1.2 SystemTap 的起源與發(fā)展

SystemTap 的誕生源于人們對(duì) Linux 系統(tǒng)調(diào)試和性能分析的迫切需求。在早期,Linux 系統(tǒng)的調(diào)試手段相對(duì)有限,開發(fā)人員在面對(duì)系統(tǒng)性能問題或程序錯(cuò)誤時(shí),往往需要耗費(fèi)大量的時(shí)間和精力去排查。而傳統(tǒng)的調(diào)試方法,如添加打印語句、使用調(diào)試器等,在復(fù)雜的系統(tǒng)環(huán)境下存在諸多局限性,尤其是在處理內(nèi)核級(jí)別的問題時(shí),這些方法顯得力不從心 。

它的出現(xiàn)與另一個(gè)著名的動(dòng)態(tài)追蹤工具 DTrace 有著千絲萬縷的聯(lián)系。DTrace 起源于 Sun Solaris 操作系統(tǒng),它為 Solaris 系統(tǒng)提供了強(qiáng)大的動(dòng)態(tài)追蹤能力,允許開發(fā)人員在不重啟系統(tǒng)的情況下,深入系統(tǒng)內(nèi)部獲取各種運(yùn)行時(shí)信息。DTrace 的成功激發(fā)了 Linux 社區(qū)開發(fā)類似工具的熱情,SystemTap 便是在這樣的背景下應(yīng)運(yùn)而生,它借鑒了 DTrace 的一些設(shè)計(jì)理念和技術(shù)思路,旨在為 Linux 系統(tǒng)提供同樣強(qiáng)大且靈活的動(dòng)態(tài)追蹤功能 。

從 2005 年開始,SystemTap 進(jìn)入了開發(fā)階段,眾多來自 Red Hat、IBM、Intel 和 Hitachi 等公司的工程師參與到了項(xiàng)目中,他們各自發(fā)揮專長,為 SystemTap 的開發(fā)貢獻(xiàn)力量。例如,Red Hat 主要負(fù)責(zé)腳本轉(zhuǎn)換 / 翻譯器和運(yùn)行時(shí)庫的開發(fā),使得用戶能夠通過簡潔的腳本語言來定義追蹤規(guī)則;IBM 則在 kprobe 和 relayfs 方面投入精力,kprobe 是 SystemTap 實(shí)現(xiàn)動(dòng)態(tài)追蹤的關(guān)鍵底層技術(shù),而 relayfs 則用于高效地在用戶空間和內(nèi)核空間之間傳輸數(shù)據(jù);Intel 專注于轉(zhuǎn)換器安全檢查以及 performance monitor tapset,保障了 SystemTap 在運(yùn)行過程中的安全性和穩(wěn)定性,同時(shí)為性能監(jiān)控提供了豐富的功能支持 。

在歷經(jīng)多年的不斷完善和發(fā)展后,SystemTap 逐漸走向成熟。如今,它已經(jīng)在各種主流的 Linux 發(fā)行版中得到了廣泛的應(yīng)用。無論是企業(yè)級(jí)的服務(wù)器系統(tǒng),還是開發(fā)者用于開發(fā)和測試的環(huán)境,SystemTap 都成為了系統(tǒng)運(yùn)維和開發(fā)人員不可或缺的工具。它的功能不斷豐富,社區(qū)也日益壯大,有大量的用戶和開發(fā)者在社區(qū)中分享經(jīng)驗(yàn)、貢獻(xiàn)代碼和腳本,進(jìn)一步推動(dòng)了 SystemTap 的發(fā)展和應(yīng)用。

二、SystemTap 的核心概念

2.1探針(Probes)

探針是SystemTap中非常關(guān)鍵的概念,它就像是我們?cè)谙到y(tǒng)運(yùn)行過程中設(shè)置的 “監(jiān)控?cái)z像頭”,用于定義事件點(diǎn) 。這些事件點(diǎn)可以是函數(shù)的入口、出口,也可以是系統(tǒng)調(diào)用的發(fā)生、定時(shí)器的觸發(fā)等等。簡單來說,只要是我們感興趣的系統(tǒng)運(yùn)行事件,都可以通過探針來進(jìn)行捕獲。

當(dāng)我們定義一個(gè)探針時(shí),實(shí)際上就是在告訴 SystemTap,我們希望在某個(gè)特定的事件發(fā)生時(shí),執(zhí)行一些特定的操作。比如,我們想要監(jiān)控open系統(tǒng)調(diào)用,就可以定義一個(gè)針對(duì)syscall.open的探針。當(dāng)系統(tǒng)中任何進(jìn)程執(zhí)行open系統(tǒng)調(diào)用時(shí),這個(gè)探針就會(huì)被觸發(fā) 。

在 SystemTap 腳本中,定義探針的語法非常簡潔。以監(jiān)控open系統(tǒng)調(diào)用為例,代碼如下:

probe syscall.open {
    printf("%s opened %s\n", execname(), filename)
}

在這段代碼中,probe syscall.open就是定義了一個(gè)針對(duì)open系統(tǒng)調(diào)用的探針。當(dāng)open系統(tǒng)調(diào)用發(fā)生時(shí),就會(huì)執(zhí)行花括號(hào)內(nèi)的處理程序。printf("%s opened %s\n", execname(), filename)這行代碼的作用是打印出當(dāng)前執(zhí)行open系統(tǒng)調(diào)用的進(jìn)程名(execname()函數(shù)獲?。┮约氨淮蜷_的文件名(filename變量表示,它是open系統(tǒng)調(diào)用相關(guān)的參數(shù),SystemTap 可以直接獲?。Mㄟ^這樣的方式,我們就能夠?qū)崟r(shí)了解系統(tǒng)中open系統(tǒng)調(diào)用的執(zhí)行情況 。

2.2處理程序(Handlers)

處理程序是與探針緊密關(guān)聯(lián)的部分,它是當(dāng)探針觸發(fā)時(shí)執(zhí)行的腳本語句集合??梢园烟幚沓绦蚩醋魇钱?dāng) “監(jiān)控?cái)z像頭”(探針)捕捉到特定事件時(shí),所采取的具體行動(dòng) 。

在上面監(jiān)控open系統(tǒng)調(diào)用的例子中,printf("%s opened %s\n", execname(), filename)這部分代碼就是處理程序。它的作用是對(duì)探針觸發(fā)時(shí)的相關(guān)信息進(jìn)行處理,這里是將進(jìn)程名和被打開的文件名打印輸出,以便我們觀察和分析 。

處理程序中可以包含各種操作,除了簡單的打印輸出,還可以進(jìn)行數(shù)據(jù)計(jì)算、統(tǒng)計(jì)、存儲(chǔ)等。比如,我們想要統(tǒng)計(jì)某個(gè)函數(shù)的調(diào)用次數(shù),可以在處理程序中使用一個(gè)全局變量來進(jìn)行計(jì)數(shù) 。示例代碼如下:

global count = 0

probe kernel.function("my_function").call {
    count++
}

probe end {
    printf("my_function was called %d times\n", count)
}

在這段代碼中,首先定義了一個(gè)全局變量count并初始化為 0。然后,針對(duì)my_function函數(shù)的調(diào)用定義了一個(gè)探針,當(dāng)my_function函數(shù)被調(diào)用時(shí)(即探針觸發(fā)),處理程序count++會(huì)將count變量的值加 1,實(shí)現(xiàn)對(duì)函數(shù)調(diào)用次數(shù)的統(tǒng)計(jì)。最后,在 SystemTap 腳本執(zhí)行結(jié)束時(shí)(probe end觸發(fā)),通過printf語句打印出my_function函數(shù)的調(diào)用次數(shù) 。

2.3 Tapset

Tapset 可以理解為是 SystemTap 的 “工具庫”,它是預(yù)定義的探針和函數(shù)庫。就好比我們?cè)谶M(jìn)行軟件開發(fā)時(shí),會(huì)使用各種類庫來簡化開發(fā)過程,Tapset 對(duì)于 SystemTap 腳本編寫也是如此,它能極大地簡化腳本編寫的工作量 。

Tapset 中包含了許多常用的探針和函數(shù)定義,這些探針和函數(shù)是針對(duì)各種常見的系統(tǒng)分析場景而設(shè)計(jì)的。例如,在網(wǎng)絡(luò)分析方面,Tapset 可能包含用于跟蹤網(wǎng)絡(luò)數(shù)據(jù)包收發(fā)的探針和計(jì)算網(wǎng)絡(luò)流量的函數(shù);在性能分析方面,可能有用于統(tǒng)計(jì) CPU 使用率、內(nèi)存分配情況的相關(guān)探針和函數(shù) 。

當(dāng)我們編寫 SystemTap 腳本時(shí),如果需要使用 Tapset 中的功能,不需要重新定義相關(guān)的探針和函數(shù),直接調(diào)用即可。這不僅節(jié)省了時(shí)間和精力,還提高了腳本的可讀性和可維護(hù)性。比如,要使用 Tapset 中預(yù)定義的每 5 秒觸發(fā)一次的定時(shí)器探針,可以這樣寫:

probe timer.ms(5000) {
    printf("5 seconds have passed\n")
}

這里的timer.ms(5000)就是 Tapset 中定義的一個(gè)定時(shí)器探針,它表示每 5000 毫秒(即 5 秒)觸發(fā)一次。當(dāng)這個(gè)探針觸發(fā)時(shí),就會(huì)執(zhí)行后面花括號(hào)內(nèi)的處理程序,打印出 “5 seconds have passed” 的信息 。通過使用 Tapset,我們可以更方便地實(shí)現(xiàn)各種復(fù)雜的系統(tǒng)分析任務(wù)。

三、SystemTap核心原理

SystemTap 的工作過程可以看作是一個(gè)將用戶定義的腳本轉(zhuǎn)化為可在內(nèi)核中運(yùn)行的監(jiān)控程序,并最終獲取系統(tǒng)運(yùn)行數(shù)據(jù)的過程,主要分為以下幾個(gè)關(guān)鍵步驟:

(1)腳本編寫:用戶使用 SystemTap 腳本語言編寫腳本,在腳本中指定想要探測的內(nèi)核事件(如內(nèi)核函數(shù)的調(diào)用、系統(tǒng)調(diào)用的發(fā)生等)或用戶空間應(yīng)用程序事件,同時(shí)定義當(dāng)這些事件發(fā)生時(shí)要執(zhí)行的處理邏輯。例如,我們想要監(jiān)控系統(tǒng)中open系統(tǒng)調(diào)用的執(zhí)行情況,并打印出調(diào)用open的進(jìn)程名和被打開的文件名,就可以編寫如下腳本:

probe syscall.open {
    printf("%s opened %s\n", execname(), filename)
}

(2)腳本翻譯:編寫好的 SystemTap 腳本并不能直接在內(nèi)核中運(yùn)行,需要通過stap命令將其翻譯成 C 代碼。在這個(gè)過程中,stap會(huì)讀取腳本指令,進(jìn)行語法分析和語義檢查,將腳本中的探針定義、處理程序以及相關(guān)的變量和函數(shù)定義等,轉(zhuǎn)化為對(duì)應(yīng)的 C 語言代碼結(jié)構(gòu) 。同時(shí),它還會(huì)生成一個(gè)內(nèi)核模塊框架,將翻譯后的 C 代碼整合到這個(gè)框架中,最終生成一個(gè)完整的內(nèi)核模塊,這個(gè)模塊與 SystemTap 運(yùn)行時(shí)庫鏈接,運(yùn)行時(shí)庫提供了一些通用的功能和接口,方便內(nèi)核模塊與系統(tǒng)進(jìn)行交互。

(3)編譯成內(nèi)核模塊:生成的 C 代碼會(huì)被編譯成內(nèi)核模塊(.ko文件)。這個(gè)編譯過程使用系統(tǒng)默認(rèn)的 C 編譯器(通常是 GCC),編譯器會(huì)根據(jù)目標(biāo)系統(tǒng)的內(nèi)核版本、架構(gòu)等信息,對(duì) C 代碼進(jìn)行編譯和鏈接,生成可在內(nèi)核中加載和運(yùn)行的二進(jìn)制模塊。在編譯過程中,會(huì)進(jìn)行一系列的優(yōu)化和錯(cuò)誤檢查,確保生成的內(nèi)核模塊能夠正確運(yùn)行。例如,如果腳本中存在語法錯(cuò)誤或者對(duì)內(nèi)核符號(hào)的引用錯(cuò)誤,編譯過程就會(huì)報(bào)錯(cuò),提示用戶進(jìn)行修改。

(4)模塊加載與執(zhí)行:編譯好的內(nèi)核模塊通過insmod或者modprobe等命令被加載到正在運(yùn)行的 Linux 內(nèi)核中(實(shí)際上stap命令會(huì)自動(dòng)完成這一步)。一旦模塊加載成功,系統(tǒng)會(huì)安排該模塊與內(nèi)核進(jìn)行交互。模塊會(huì)根據(jù)腳本中定義的探針,在內(nèi)核中相應(yīng)的位置插入探測點(diǎn)。這些探測點(diǎn)就像是在內(nèi)核代碼中埋下的 “鉤子”,當(dāng)內(nèi)核執(zhí)行到這些位置時(shí),即觸發(fā)相應(yīng)的事件,與該事件關(guān)聯(lián)的探針就會(huì)捕獲到這個(gè)事件,并執(zhí)行預(yù)先定義好的處理程序。在處理程序中,可以進(jìn)行各種操作,如獲取內(nèi)核數(shù)據(jù)結(jié)構(gòu)中的信息、計(jì)算統(tǒng)計(jì)數(shù)據(jù)、打印輸出調(diào)試信息等。比如在前面監(jiān)控open系統(tǒng)調(diào)用的例子中,當(dāng)系統(tǒng)中某個(gè)進(jìn)程執(zhí)行open系統(tǒng)調(diào)用時(shí),對(duì)應(yīng)的探針被觸發(fā),處理程序中的printf函數(shù)就會(huì)被執(zhí)行,將進(jìn)程名和被打開的文件名打印輸出。

(5)數(shù)據(jù)輸出與清理:在探測任務(wù)執(zhí)行過程中,處理程序中收集到的數(shù)據(jù)會(huì)根據(jù)用戶的設(shè)定進(jìn)行輸出??梢暂敵龅狡聊簧?,讓用戶實(shí)時(shí)查看;也可以輸出到指定的文件中,方便后續(xù)分析。當(dāng)用戶完成探測任務(wù),通常通過按下Ctrl + C組合鍵來終止 SystemTap 會(huì)話。此時(shí),staprun命令(stap命令內(nèi)部調(diào)用)會(huì)負(fù)責(zé)卸載已經(jīng)加載的內(nèi)核模塊,并清理相關(guān)的資源,包括釋放內(nèi)存、關(guān)閉文件描述符等,確保系統(tǒng)恢復(fù)到探測前的狀態(tài),避免對(duì)系統(tǒng)造成不必要的影響 。

通過這樣的工作流程,SystemTap 實(shí)現(xiàn)了在不重啟系統(tǒng)和重新編譯內(nèi)核的情況下,對(duì) Linux 系統(tǒng)進(jìn)行動(dòng)態(tài)的監(jiān)控和分析,為開發(fā)人員和系統(tǒng)管理員提供了一種高效、靈活的系統(tǒng)調(diào)試和性能分析手段。

四、SystemTap 的安裝與運(yùn)行

4.1安裝步驟

SystemTap 的安裝過程相對(duì)簡單,不過具體的安裝命令會(huì)因 Linux 發(fā)行版的不同而有所差異。

對(duì)于 Debian 或 Ubuntu 系統(tǒng),可以使用apt包管理器進(jìn)行安裝,只需在終端中輸入以下命令:

sudo apt-get install systemtap

這條命令會(huì)自動(dòng)從軟件源中下載并安裝 SystemTap 及其依賴的軟件包。安裝完成后,你可以通過運(yùn)行一些簡單的 SystemTap 腳本來驗(yàn)證是否安裝成功 。

在 CentOS 或 RHEL 系統(tǒng)上,安裝 SystemTap 則需要使用yum包管理器,命令如下:

sudo yum install systemtap

同樣,yum會(huì)自動(dòng)處理依賴關(guān)系,完成 SystemTap 的安裝。

需要注意的是,在某些情況下,為了讓 SystemTap 能夠更全面地發(fā)揮其功能,比如深入分析內(nèi)核函數(shù)的調(diào)用細(xì)節(jié)、獲取內(nèi)核變量的值等,可能還需要安裝內(nèi)核調(diào)試信息包(kernel-debuginfo)。這是因?yàn)檫@些調(diào)試信息包中包含了內(nèi)核符號(hào)表等關(guān)鍵信息,SystemTap 在進(jìn)行一些高級(jí)的內(nèi)核追蹤操作時(shí)會(huì)依賴這些信息 。例如,在 CentOS 系統(tǒng)中,如果要安裝對(duì)應(yīng)內(nèi)核版本的調(diào)試信息包,可以通過yum搜索并安裝,具體命令可能如下:

sudo yum install kernel-debuginfo-$(uname -r)

這里$(uname -r)會(huì)獲取當(dāng)前系統(tǒng)正在運(yùn)行的內(nèi)核版本,從而確保安裝的調(diào)試信息包與當(dāng)前內(nèi)核版本匹配 。

4.2運(yùn)行方式

SystemTap 提供了多種靈活的運(yùn)行方式,以滿足不同的使用場景和需求。

最常見的方式是從文件中讀入腳本并運(yùn)行。假設(shè)我們編寫了一個(gè)名為test.stp的 SystemTap 腳本,那么可以在終端中使用以下命令來運(yùn)行它:

stap test.stp

這種方式適用于我們已經(jīng)編寫好較為復(fù)雜的腳本,需要對(duì)其進(jìn)行測試和執(zhí)行的情況。在運(yùn)行腳本時(shí),還可以通過添加一些選項(xiàng)來調(diào)整運(yùn)行行為,比如使用-v選項(xiàng)可以增加輸出的詳細(xì)程度,幫助我們了解腳本的執(zhí)行過程和中間結(jié)果 。例如:

stap -v test.stp

也可以從標(biāo)準(zhǔn)輸入中讀入腳本并運(yùn)行。當(dāng)我們需要快速測試一些簡單的 SystemTap 語句,或者希望通過管道將其他命令的輸出作為 SystemTap 的輸入時(shí),這種方式就非常方便。在終端中輸入以下命令:

stap -

然后在出現(xiàn)的輸入提示符下,逐行輸入 SystemTap 腳本內(nèi)容,輸入完成后,按下Ctrl+D組合鍵表示輸入結(jié)束,SystemTap 就會(huì)立即執(zhí)行輸入的腳本 。

如果腳本內(nèi)容比較簡短,我們還可以直接在命令行中運(yùn)行。使用-e選項(xiàng),后面跟上要執(zhí)行的腳本內(nèi)容即可。例如,要監(jiān)控系統(tǒng)中open系統(tǒng)調(diào)用,并打印出調(diào)用進(jìn)程名和被打開的文件名,可以使用以下命令:

stap -e 'probe syscall.open {printf("%s opened %s\n", execname(), filename)}'

對(duì)于那些經(jīng)常需要執(zhí)行的 SystemTap 腳本,我們可以為其賦予可執(zhí)行屬性,并在腳本的第一行加上#!/usr/bin/stap,這樣就可以像執(zhí)行普通可執(zhí)行文件一樣直接運(yùn)行腳本文件 。首先,使用chmod命令賦予腳本可執(zhí)行權(quán)限:

chmod +x test.stp

然后,直接運(yùn)行腳本:

./test.stp

五、SystemTap 腳本語法與示例

5.1基本語法結(jié)構(gòu)

SystemTap 腳本的基本結(jié)構(gòu)圍繞著探針(probe)語句展開,探針用于定義事件和相應(yīng)的處理程序。其語法形式如下:

probe probe_point [, probe_point] {
    handler_statement
}

其中,probe_point是探測點(diǎn),它指定了要監(jiān)控的事件,比如syscall.open表示監(jiān)控open系統(tǒng)調(diào)用,kernel.function("my_function").call表示監(jiān)控內(nèi)核函數(shù)my_function的調(diào)用。handler_statement則是當(dāng)探測點(diǎn)事件觸發(fā)時(shí)執(zhí)行的處理程序語句,可以是一條或多條語句,這些語句被包含在花括號(hào){}內(nèi) 。

例如,下面的腳本用于監(jiān)控系統(tǒng)中write系統(tǒng)調(diào)用,并打印出調(diào)用進(jìn)程的名稱和寫入的字節(jié)數(shù):

probe syscall.write {
    printf("%s wrote %d bytes\n", execname(), $count)
}

在這個(gè)例子中,probe syscall.write定義了一個(gè)針對(duì)write系統(tǒng)調(diào)用的探測點(diǎn),當(dāng)write系統(tǒng)調(diào)用發(fā)生時(shí),就會(huì)執(zhí)行花括號(hào)內(nèi)的printf語句,execname()函數(shù)獲取當(dāng)前執(zhí)行write系統(tǒng)調(diào)用的進(jìn)程名,$count是write系統(tǒng)調(diào)用相關(guān)的參數(shù),表示寫入的字節(jié)數(shù) 。

5.2變量與數(shù)據(jù)類型

在 SystemTap 腳本中,變量不需要顯式聲明類型,系統(tǒng)會(huì)根據(jù)其使用方式自動(dòng)判定類型 。變量名由字母、數(shù)字、下劃線和美元符號(hào)組成,但不能以數(shù)字開頭。變量可以在腳本中的任何位置使用,默認(rèn)情況下,變量是局部變量,作用域僅限于包含它的探針處理程序或函數(shù)內(nèi)部。如果需要定義全局變量,使用global關(guān)鍵字 。例如:

global total_writes = 0

probe syscall.write {
    total_writes++
    printf("Total write syscalls so far: %d\n", total_writes)
}

這里定義了一個(gè)全局變量total_writes,用于統(tǒng)計(jì)write系統(tǒng)調(diào)用的次數(shù)。每次write系統(tǒng)調(diào)用發(fā)生時(shí),total_writes的值就會(huì)加 1,并打印出當(dāng)前的統(tǒng)計(jì)結(jié)果 。

SystemTap 還支持關(guān)聯(lián)數(shù)組,關(guān)聯(lián)數(shù)組的索引可以是字符串或整數(shù),或者是它們的組合 。關(guān)聯(lián)數(shù)組必須定義為全局變量,常用于聚合數(shù)據(jù)。比如,統(tǒng)計(jì)每個(gè)進(jìn)程對(duì)不同文件的讀取次數(shù),可以這樣寫:

global read_count[execname(), filename]

probe syscall.read {
    read_count[execname(), filename]++
}

probe end {
    foreach ( [proc, file] in read_count ) {
        printf("%s read %s %d times\n", proc, file, read_count[proc, file])
    }
}

在這個(gè)腳本中,read_count是一個(gè)關(guān)聯(lián)數(shù)組,它的索引由進(jìn)程名execname()和文件名filename組成。每次read系統(tǒng)調(diào)用發(fā)生時(shí),對(duì)應(yīng)的read_count數(shù)組元素值加 1 。在腳本執(zhí)行結(jié)束時(shí)(probe end觸發(fā)),通過foreach循環(huán)遍歷read_count數(shù)組,打印出每個(gè)進(jìn)程對(duì)不同文件的讀取次數(shù) 。

5.3條件語句與循環(huán)

條件語句在 SystemTap 腳本中用于根據(jù)不同的條件執(zhí)行不同的操作,其語法與 C 語言類似,使用if - else結(jié)構(gòu) 。例如,我們只想監(jiān)控某個(gè)特定進(jìn)程(假設(shè)進(jìn)程名為target_process)的open系統(tǒng)調(diào)用,可以這樣寫:

probe syscall.open {
    if (execname() == "target_process") {
        printf("%s opened %s\n", execname(), filename)
    }
}

在這個(gè)例子中,if (execname() == "target_process")是條件判斷部分,如果當(dāng)前執(zhí)行open系統(tǒng)調(diào)用的進(jìn)程名等于target_process,就會(huì)執(zhí)行花括號(hào)內(nèi)的printf語句,打印出進(jìn)程名和被打開的文件名 。

循環(huán)語句在 SystemTap 中也有廣泛的應(yīng)用,常見的循環(huán)結(jié)構(gòu)有for循環(huán)和while循環(huán),它們的語法同樣與 C 語言類似 。另外,SystemTap 還提供了一種特殊的foreach循環(huán),用于遍歷關(guān)聯(lián)數(shù)組 。比如,在前面統(tǒng)計(jì)每個(gè)進(jìn)程對(duì)不同文件讀取次數(shù)的例子中,就使用了foreach循環(huán)來遍歷read_count關(guān)聯(lián)數(shù)組:

probe end {
    foreach ( [proc, file] in read_count ) {
        printf("%s read %s %d times\n", proc, file, read_count[proc, file])
    }
}

這里的foreach ( [proc, file] in read_count )表示對(duì)read_count關(guān)聯(lián)數(shù)組中的每一個(gè)鍵值對(duì)進(jìn)行遍歷,proc和file分別代表數(shù)組索引中的進(jìn)程名和文件名,通過這種方式可以方便地處理關(guān)聯(lián)數(shù)組中的數(shù)據(jù) 。

5.4示例腳本解析

下面通過一個(gè)具體的示例腳本來深入理解 SystemTap 腳本的各個(gè)部分:

# 監(jiān)控進(jìn)程的execve系統(tǒng)調(diào)用
probe syscall.execve {
    # 獲取當(dāng)前進(jìn)程的名稱
    local proc_name = execname()
    # 獲取當(dāng)前進(jìn)程的ID
    local proc_id = pid()
    # 獲取被執(zhí)行的程序路徑
    local exec_path = argstr
    printf("%s(%d) executed %s\n", proc_name, proc_id, exec_path)
}

這個(gè)腳本的作用是監(jiān)控系統(tǒng)中所有進(jìn)程的execve系統(tǒng)調(diào)用,execve系統(tǒng)調(diào)用用于執(zhí)行一個(gè)新的程序,當(dāng)這個(gè)系統(tǒng)調(diào)用發(fā)生時(shí),腳本會(huì)獲取相關(guān)信息并打印出來 。

腳本開頭的probe syscall.execve定義了一個(gè)針對(duì)execve系統(tǒng)調(diào)用的探針,當(dāng)execve系統(tǒng)調(diào)用發(fā)生時(shí),就會(huì)觸發(fā)這個(gè)探針 。

在探針的處理程序中,首先使用local關(guān)鍵字定義了三個(gè)局部變量:proc_name用于存儲(chǔ)當(dāng)前進(jìn)程的名稱,通過execname()函數(shù)獲??;proc_id用于存儲(chǔ)當(dāng)前進(jìn)程的 ID,通過pid()函數(shù)獲??;exec_path用于存儲(chǔ)被執(zhí)行的程序路徑,通過argstr變量獲取(argstr是execve系統(tǒng)調(diào)用相關(guān)的參數(shù),包含了被執(zhí)行程序的路徑信息) 。

最后,使用printf函數(shù)將獲取到的信息打印輸出,輸出格式為 “進(jìn)程名 (進(jìn)程 ID) executed 被執(zhí)行程序路徑” 。通過這個(gè)腳本,我們可以實(shí)時(shí)了解系統(tǒng)中哪些進(jìn)程執(zhí)行了哪些程序,對(duì)于系統(tǒng)的監(jiān)控和分析非常有幫助 。

六、SystemTap 的高級(jí)特性

6.1條件過濾

在實(shí)際的系統(tǒng)監(jiān)控和分析中,我們往往并不需要對(duì)所有的事件進(jìn)行處理,而是希望根據(jù)特定的條件來篩選出感興趣的部分。SystemTap 提供了強(qiáng)大的條件過濾功能,讓我們能夠在探針中設(shè)置各種條件,只對(duì)滿足條件的事件執(zhí)行處理程序 。

比如,我們想要監(jiān)控某個(gè)特定進(jìn)程的系統(tǒng)調(diào)用,就可以通過進(jìn)程 ID(PID)來進(jìn)行過濾。假設(shè)我們要監(jiān)控進(jìn)程 ID 為1234的進(jìn)程的所有系統(tǒng)調(diào)用,可以編寫如下 SystemTap 腳本:

probe syscall.* if (pid() == 1234) {
    printf("%s called syscall %s\n", execname(), name)
}

在這個(gè)腳本中,probe syscall.*表示對(duì)所有的系統(tǒng)調(diào)用進(jìn)行探測,if (pid() == 1234)則是條件判斷部分,只有當(dāng)當(dāng)前進(jìn)程的 ID 等于1234時(shí),才會(huì)執(zhí)行花括號(hào)內(nèi)的處理程序。printf("%s called syscall %s\n", execname(), name)這行代碼會(huì)打印出執(zhí)行系統(tǒng)調(diào)用的進(jìn)程名(execname()函數(shù)獲?。┮约跋到y(tǒng)調(diào)用的名稱(name變量表示,它是 SystemTap 內(nèi)置的與系統(tǒng)調(diào)用相關(guān)的變量) 。

通過這種方式,我們就能夠精準(zhǔn)地監(jiān)控特定進(jìn)程的系統(tǒng)調(diào)用情況,避免了大量無關(guān)信息的干擾,使得我們能夠更高效地獲取和分析關(guān)鍵數(shù)據(jù) 。

6.2關(guān)聯(lián)數(shù)組與統(tǒng)計(jì)

在系統(tǒng)分析過程中,我們常常需要對(duì)各種數(shù)據(jù)進(jìn)行聚合和統(tǒng)計(jì),以便更好地了解系統(tǒng)的運(yùn)行狀態(tài)和性能瓶頸。SystemTap 的關(guān)聯(lián)數(shù)組為我們提供了一種非常方便的數(shù)據(jù)聚合方式 。

關(guān)聯(lián)數(shù)組可以看作是一種特殊的數(shù)組,它的索引不再局限于傳統(tǒng)的整數(shù),而是可以使用字符串、整數(shù)或者它們的組合。這使得我們能夠根據(jù)不同的維度來組織和統(tǒng)計(jì)數(shù)據(jù) 。

例如,我們想要統(tǒng)計(jì)每個(gè)進(jìn)程對(duì)不同文件的讀取次數(shù),可以使用如下腳本:

global read_count[execname(), filename]

probe syscall.read {
    read_count[execname(), filename]++
}

probe end {
    foreach ( [proc, file] in read_count ) {
        printf("%s read %s %d times\n", proc, file, read_count[proc, file])
    }
}

在這個(gè)腳本中,首先定義了一個(gè)全局關(guān)聯(lián)數(shù)組read_count,它的索引由進(jìn)程名(execname())和文件名(filename)組成。每當(dāng)read系統(tǒng)調(diào)用發(fā)生時(shí),對(duì)應(yīng)的read_count數(shù)組元素的值就會(huì)加 1,實(shí)現(xiàn)了對(duì)每個(gè)進(jìn)程對(duì)不同文件讀取次數(shù)的統(tǒng)計(jì) 。

在腳本執(zhí)行結(jié)束時(shí)(probe end觸發(fā)),通過foreach循環(huán)遍歷read_count關(guān)聯(lián)數(shù)組。foreach ( [proc, file] in read_count )表示對(duì)read_count數(shù)組中的每一個(gè)鍵值對(duì)進(jìn)行遍歷,proc和file分別代表數(shù)組索引中的進(jìn)程名和文件名。在循環(huán)體中,使用printf函數(shù)打印出每個(gè)進(jìn)程對(duì)不同文件的讀取次數(shù) 。

通過這種方式,我們可以清晰地了解到系統(tǒng)中各個(gè)進(jìn)程對(duì)不同文件的訪問模式和頻率,為進(jìn)一步的系統(tǒng)優(yōu)化和問題排查提供有力的數(shù)據(jù)支持 。

6.3嵌入 C 代碼

盡管 SystemTap 的腳本語言已經(jīng)非常強(qiáng)大,能夠滿足大多數(shù)常見的系統(tǒng)分析需求,但在某些情況下,我們可能需要執(zhí)行一些更為復(fù)雜的操作,這時(shí)就可以借助 SystemTap 嵌入 C 代碼的功能 。

在 SystemTap 腳本中嵌入 C 代碼,能夠讓我們利用 C 語言的強(qiáng)大功能和豐富的庫函數(shù),實(shí)現(xiàn)一些在 SystemTap 腳本語言中難以完成的任務(wù) 。

要在腳本中嵌入 C 代碼,需要使用%{和%}將 C 代碼括起來,并且在運(yùn)行腳本時(shí)需要加上-g選項(xiàng),開啟 guru 模式 。

例如,假設(shè)我們想要在監(jiān)控vfs_read函數(shù)返回時(shí),獲取當(dāng)前進(jìn)程的詳細(xì)信息(包括進(jìn)程 ID 和進(jìn)程名),可以編寫如下腳本:

function getprocname:string(task:long) %{
    struct task_struct *task = (struct task_struct *)STAP_ARG_task;
    snprintf(STAP_RETVALUE, MAXSTRINGLEN, "pid: %d, comm: %s", task->pid, task->comm);
%}

function getprocid:long(task:long) %{
    struct task_struct *task = (struct task_struct *)STAP_ARG_task;
    STAP_RETURN(task->pid);
%}

probe kernel.function("vfs_read").return {
    task = pid2task(pid())
    printf("vfs_read return: %p, pid: %d, getprocname: %s, getprocid: %d\n", $return, $return->pid, getprocname(task), getprocid(task));
}

在這個(gè)腳本中,定義了兩個(gè)函數(shù)getprocname和getprocid,它們都是嵌入的 C 代碼函數(shù)。getprocname函數(shù)用于獲取進(jìn)程的名稱和 ID,并將其格式化為字符串返回;getprocid函數(shù)則僅返回進(jìn)程的 ID 。

在probe kernel.function("vfs_read").return探針的處理程序中,首先通過pid2task(pid())獲取當(dāng)前進(jìn)程的任務(wù)結(jié)構(gòu)指針task,然后調(diào)用getprocname和getprocid函數(shù)獲取進(jìn)程的詳細(xì)信息,并使用printf函數(shù)打印出來 。

通過嵌入 C 代碼,我們能夠深入挖掘系統(tǒng)內(nèi)部的信息,實(shí)現(xiàn)更加復(fù)雜和精細(xì)的系統(tǒng)分析任務(wù)。但需要注意的是,由于 C 代碼的執(zhí)行可能會(huì)涉及到系統(tǒng)底層的操作,所以在使用時(shí)要格外小心,確保代碼的正確性和安全性 。

七、SystemTap 的應(yīng)用場景

7.1性能瓶頸分析

在現(xiàn)代計(jì)算機(jī)系統(tǒng)中,性能瓶頸的出現(xiàn)往往是一個(gè)復(fù)雜的問題,涉及到多個(gè)層面的因素。而 SystemTap 在性能瓶頸分析方面具有獨(dú)特的優(yōu)勢,能夠幫助我們深入系統(tǒng)內(nèi)部,精確地定位問題所在。

假設(shè)我們正在維護(hù)一個(gè)大型的 Web 服務(wù)器集群,近期發(fā)現(xiàn)服務(wù)器的響應(yīng)時(shí)間明顯變長,用戶反饋頁面加載緩慢。通過常規(guī)的監(jiān)控工具,我們只能大致了解到系統(tǒng)的 CPU 使用率、內(nèi)存使用率等整體指標(biāo),但無法確定具體是哪些函數(shù)或者操作導(dǎo)致了性能下降。

這時(shí),我們可以使用 SystemTap 編寫如下腳本,來追蹤do_sys_open函數(shù)的執(zhí)行時(shí)間,因?yàn)槲募蜷_操作在 Web 服務(wù)器中是非常頻繁的,很可能是性能瓶頸的所在:

probe kernel.function("do_sys_open") {
    t = gettimeofday_us()
}
probe kernel.function("do_sys_open").return {
    printf("open took %d us\n", gettimeofday_us() - t)
}

在這個(gè)腳本中,第一個(gè)探針probe kernel.function("do_sys_open")在do_sys_open函數(shù)開始執(zhí)行時(shí)觸發(fā),記錄下當(dāng)前的時(shí)間戳gettimeofday_us()并存儲(chǔ)在變量t中。第二個(gè)探針probe kernel.function("do_sys_open").return在do_sys_open函數(shù)返回時(shí)觸發(fā),再次獲取當(dāng)前時(shí)間戳,然后計(jì)算兩次時(shí)間戳的差值,即do_sys_open函數(shù)的執(zhí)行時(shí)間,并通過printf函數(shù)打印出來。

運(yùn)行這個(gè)腳本后,我們可以得到每一次do_sys_open函數(shù)調(diào)用的執(zhí)行時(shí)間。如果發(fā)現(xiàn)某些調(diào)用的執(zhí)行時(shí)間特別長,就可以進(jìn)一步深入分析,查看這些長時(shí)間執(zhí)行的調(diào)用是否存在文件系統(tǒng) I/O 問題、權(quán)限檢查問題或者其他可能導(dǎo)致延遲的因素 。通過這種方式,我們能夠快速定位到性能瓶頸的具體位置,為后續(xù)的優(yōu)化工作提供有力的依據(jù) 。

7.2內(nèi)存泄漏檢測

內(nèi)存泄漏是軟件開發(fā)中常見的問題,尤其是在 C 和 C++ 等需要手動(dòng)管理內(nèi)存的語言編寫的程序中。內(nèi)存泄漏不僅會(huì)導(dǎo)致程序占用的內(nèi)存不斷增加,最終耗盡系統(tǒng)內(nèi)存,還可能引發(fā)程序崩潰、性能下降等一系列嚴(yán)重問題。

SystemTap 可以通過追蹤內(nèi)存分配和釋放函數(shù),有效地檢測內(nèi)存泄漏。其原理是為內(nèi)存分配函數(shù)(如malloc、calloc、kmalloc等)和內(nèi)存釋放函數(shù)(如free、kfree等)設(shè)置探測點(diǎn),分別對(duì)內(nèi)存分配和釋放的次數(shù)進(jìn)行計(jì)數(shù)。如果在程序運(yùn)行結(jié)束時(shí),發(fā)現(xiàn)內(nèi)存分配的次數(shù)大于釋放的次數(shù),那就很有可能存在內(nèi)存泄漏 。

以一個(gè)簡單的 C 程序?yàn)槔僭O(shè)我們有如下代碼,其中存在內(nèi)存泄漏的問題:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    char *p1, *p2, *p3;
    sleep(20);  // 等待SystemTap啟動(dòng)設(shè)置探測點(diǎn)
    p1 = malloc(100);
    p2 = malloc(200);
    p3 = malloc(300);
    free(p1);
    free(p2);
    // 這里p3沒有被釋放,導(dǎo)致內(nèi)存泄漏
    return 0;
}

我們可以使用 SystemTap 編寫如下腳本(假設(shè)腳本名為mem_leak.stp)來檢測內(nèi)存泄漏:

probe begin {
    printf("=============begin============\n")
}

// 記錄內(nèi)存分配和釋放的計(jì)數(shù)關(guān)聯(lián)數(shù)組
global g_mem_ref_tbl

probe process("/lib/x86_64-linux-gnu/libc.so.6").function("__libc_malloc").return {
    if (target() == pid()) {
        if (g_mem_ref_tbl[$return] == 0) {
            g_mem_ref_tbl[$return]++
        }
    }
}

probe process("/lib/x86_64-linux-gnu/libc.so.6").function("__libc_free") {
    if (target() == pid()) {
        g_mem_ref_tbl[$mem]--
        if (g_mem_ref_tbl[$mem] < 0 && $mem != 0) {
            printf("可能存在重復(fù)釋放內(nèi)存的問題\n")
        }
    }
}

probe end {
    printf("=============end============\n")
    foreach (mem in g_mem_ref_tbl) {
        if (g_mem_ref_tbl[mem] > 0) {
            printf("內(nèi)存泄漏,地址為%p,分配次數(shù)為%d\n", mem, g_mem_ref_tbl[mem])
        }
    }
}

在這個(gè)腳本中,首先定義了一個(gè)全局關(guān)聯(lián)數(shù)組g_mem_ref_tbl,用于記錄每個(gè)內(nèi)存地址的分配和釋放計(jì)數(shù) 。當(dāng)__libc_malloc函數(shù)返回時(shí)(即內(nèi)存分配成功時(shí)),如果分配的內(nèi)存地址在g_mem_ref_tbl中不存在,則將其計(jì)數(shù)設(shè)置為 1 。當(dāng)__libc_free函數(shù)被調(diào)用時(shí)(即內(nèi)存釋放時(shí)),將對(duì)應(yīng)內(nèi)存地址的計(jì)數(shù)減 1 。如果發(fā)現(xiàn)計(jì)數(shù)小于 0,說明可能存在重復(fù)釋放內(nèi)存的問題 。在腳本執(zhí)行結(jié)束時(shí),遍歷g_mem_ref_tbl數(shù)組,對(duì)于那些計(jì)數(shù)大于 0 的內(nèi)存地址,就可以確定存在內(nèi)存泄漏,并打印出泄漏的內(nèi)存地址和分配次數(shù) 。

運(yùn)行這個(gè)腳本后,就能夠準(zhǔn)確地檢測到上述 C 程序中p3所指向的內(nèi)存泄漏問題,幫助我們及時(shí)發(fā)現(xiàn)和解決內(nèi)存相關(guān)的錯(cuò)誤 。

7.3網(wǎng)絡(luò)包分析

在網(wǎng)絡(luò)通信中,深入了解網(wǎng)絡(luò)包的傳輸和處理情況對(duì)于優(yōu)化網(wǎng)絡(luò)性能、排查網(wǎng)絡(luò)故障至關(guān)重要。SystemTap 可以通過追蹤網(wǎng)絡(luò)接收事件,詳細(xì)地分析網(wǎng)絡(luò)包的各種信息,如包的來源、目的地址、大小、協(xié)議類型等 。

以追蹤特定 IP 地址的 ICMP 包為例,假設(shè)我們想要監(jiān)控目標(biāo) IP 地址為192.168.1.100的 ICMP 包的接收情況,可以使用如下 SystemTap 腳本:

global TARGET_IP = "192.168.1.100"

%{
#include <linux/ip.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
%}

# 將IP字符串轉(zhuǎn)換為整數(shù)
function ip_to_int(ip_str) %{
    struct in_addr addr;
    int ret = in4_pton(STAP_ARG_ip_str, -1, (u8 *)&addr, '\0', NULL);
    if (ret > 0) {
        STAP_RETURN(ntohl(addr.s_addr));
    } else {
        STAP_RETURN(0);
    }
%}

# 獲取IP協(xié)議、源地址、目標(biāo)地址(返回多個(gè)值)
function get_ip_protocol:long(skb) %{
    struct sk_buff *skb = (struct sk_buff *)STAP_ARG_skb;
    struct iphdr *iph = ip_hdr(skb);
    STAP_RETURN(iph->protocol);
%}

function get_saddr:long(skb) %{
    struct sk_buff *skb = (struct sk_buff *)STAP_ARG_skb;
    struct iphdr *iph = ip_hdr(skb);
    STAP_RETURN(ntohl(iph->saddr));
%}

function get_daddr:long(skb) %{
    struct sk_buff *skb = (struct sk_buff *)STAP_ARG_skb;
    struct iphdr *iph = ip_hdr(skb);
    STAP_RETURN(ntohl(iph->daddr));
%}

probe kernel.function("ip_rcv") {
    skb = $skb
    protocol = get_ip_protocol(skb)
    saddr = get_saddr(skb)
    daddr = get_daddr(skb)
    target = ip_to_int(TARGET_IP)
    if (protocol == 1 && (saddr == target || daddr == target)) {
        printf("[RCV %05d] %s ICMP %s -> %s\n", pid(), ctime(gettimeofday_s()), ip_ntop(htonl(saddr)), ip_ntop(htonl(daddr)))
    }
}

probe begin {
    println("[+] Tracing ICMP to/from: ", TARGET_IP)
}

在這個(gè)腳本中,首先定義了目標(biāo) IP 地址TARGET_IP 。然后通過嵌入 C 代碼定義了幾個(gè)輔助函數(shù),ip_to_int函數(shù)用于將 IP 字符串轉(zhuǎn)換為整數(shù)形式,方便后續(xù)的比較 ;get_ip_protocol、get_saddr和get_daddr函數(shù)分別用于從網(wǎng)絡(luò)數(shù)據(jù)包(skb)中獲取 IP 協(xié)議類型、源地址和目標(biāo)地址 。

probe kernel.function("ip_rcv")探針在ip_rcv函數(shù)(用于接收網(wǎng)絡(luò)數(shù)據(jù)包的內(nèi)核函數(shù))被調(diào)用時(shí)觸發(fā),獲取數(shù)據(jù)包的相關(guān)信息 。如果數(shù)據(jù)包的協(xié)議類型為 ICMP(協(xié)議號(hào)為 1),并且源地址或目標(biāo)地址與我們?cè)O(shè)定的目標(biāo) IP 地址一致,就打印出接收數(shù)據(jù)包的相關(guān)信息,包括進(jìn)程 ID、接收時(shí)間、源 IP 地址和目標(biāo) IP 地址 。

運(yùn)行這個(gè)腳本后,我們就可以實(shí)時(shí)監(jiān)控到目標(biāo) IP 地址的 ICMP 包的接收情況,通過分析這些信息,我們可以判斷網(wǎng)絡(luò)連接是否正常、是否存在網(wǎng)絡(luò)丟包等問題 。

7.4使用 SystemTap 的注意事項(xiàng)與調(diào)試技巧

在使用 SystemTap時(shí),有一些關(guān)鍵的注意事項(xiàng)需要牢記。首先,由于SystemTap 會(huì)對(duì)系統(tǒng)進(jìn)行動(dòng)態(tài)追蹤,可能會(huì)對(duì)系統(tǒng)性能產(chǎn)生一定的影響。特別是在生產(chǎn)環(huán)境中使用時(shí),務(wù)必先在測試環(huán)境中充分驗(yàn)證腳本的正確性和性能影響。在生產(chǎn)環(huán)境中直接運(yùn)行未經(jīng)測試的腳本,有可能導(dǎo)致系統(tǒng)性能下降,甚至影響關(guān)鍵業(yè)務(wù)的正常運(yùn)行 。

另外,要避免在探針的處理程序中執(zhí)行耗時(shí)的操作。SystemTap 的探針處理程序是在事件觸發(fā)時(shí)立即執(zhí)行的,如果其中包含耗時(shí)的 I/O 操作(如大量的磁盤讀寫、網(wǎng)絡(luò)請(qǐng)求等)、復(fù)雜的計(jì)算操作(如大規(guī)模的數(shù)據(jù)排序、復(fù)雜的數(shù)學(xué)運(yùn)算等),可能會(huì)阻塞系統(tǒng)的正常運(yùn)行,進(jìn)而影響整個(gè)系統(tǒng)的性能 。例如,在監(jiān)控系統(tǒng)調(diào)用的腳本中,如果在處理程序中進(jìn)行大量的文件寫入操作,可能會(huì)導(dǎo)致系統(tǒng) I/O 性能下降,影響其他依賴 I/O 的進(jìn)程的運(yùn)行 。

在編寫和運(yùn)行 SystemTap 腳本時(shí),掌握一些調(diào)試技巧能夠幫助我們更快地發(fā)現(xiàn)和解決問題。其中,使用-v選項(xiàng)是一個(gè)非常簡單有效的方法。當(dāng)我們運(yùn)行stap -v script.stp時(shí),-v選項(xiàng)會(huì)增加輸出的詳細(xì)程度,它會(huì)顯示腳本解析、編譯、模塊加載等各個(gè)階段的詳細(xì)信息。通過這些信息,我們可以了解腳本執(zhí)行過程中是否出現(xiàn)錯(cuò)誤,以及錯(cuò)誤出現(xiàn)在哪個(gè)階段。比如,如果在編譯階段出現(xiàn)錯(cuò)誤,-v選項(xiàng)輸出的信息中會(huì)包含具體的錯(cuò)誤提示,幫助我們定位和修改腳本中的問題 。

-pN選項(xiàng)也很實(shí)用,這里的N是一個(gè)數(shù)字,不同的值代表不同的預(yù)處理階段。例如,-p3表示僅進(jìn)行解析和生成 C 代碼階段,不進(jìn)行編譯和運(yùn)行。當(dāng)我們懷疑腳本在語法解析或者轉(zhuǎn)換為 C 代碼的過程中存在問題時(shí),就可以使用這個(gè)選項(xiàng),查看生成的 C 代碼是否正確,以及是否有語法錯(cuò)誤或語義錯(cuò)誤的提示 。

如果腳本中嵌入了 C 代碼,那么-g選項(xiàng)就顯得尤為重要。它開啟了 guru 模式,允許腳本中嵌入 C 代碼并執(zhí)行。在使用-g選項(xiàng)時(shí),要確保嵌入的 C 代碼的正確性和安全性,因?yàn)?C 代碼直接在內(nèi)核空間執(zhí)行,如果存在錯(cuò)誤或者安全漏洞,可能會(huì)導(dǎo)致系統(tǒng)不穩(wěn)定甚至崩潰 。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2014-11-18 14:32:30

2021-11-15 04:00:07

Linux 內(nèi)核動(dòng)態(tài)

2025-04-02 00:33:00

2025-04-01 02:00:22

2016-12-08 09:57:09

LinuxDTrace技術(shù)

2010-06-17 09:48:54

UML動(dòng)態(tài)建模

2011-09-19 17:00:54

Foretuit銷售

2025-04-03 03:55:00

2020-01-16 08:00:04

工具代碼開發(fā)

2014-07-22 10:06:43

運(yùn)維監(jiān)控虛擬化

2020-11-20 07:55:55

Linux內(nèi)核映射

2009-12-23 13:17:36

Linux設(shè)備驅(qū)動(dòng)

2021-03-14 07:11:24

剪映剪視頻

2010-12-21 17:00:49

網(wǎng)康上網(wǎng)行為管理網(wǎng)絡(luò)管理

2010-01-07 18:03:03

Linux動(dòng)態(tài)庫

2010-05-25 12:59:00

Subversion

2009-09-14 15:12:40

LINQ to XML

2025-03-18 08:10:00

iodump開源I/O

2025-02-27 00:32:35

2010-06-03 13:08:51

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)