systemtap 探祕(一)- 基本介紹

Linux 內核(如下簡稱內核)提供了 kprobe 和 uprobe 的機制,容許用戶經過編寫本身的內核模塊,掛載特定的事件來執行本身的函數。好比咱們能夠在 accept 系統調用結束時記錄下新建立的 fd;或者在 VFS 讀寫操做先後記錄時間戳,統計它們的耗時。數據庫

直接裸寫內核模塊費時費力,其中有部分工做仍是能夠套模板的(好比從掛載 accept 系統調用改爲掛載 write 調用)。並且更大的問題是它不夠安全。要是內核態的程序崩潰了,那就是 kernel panic 的事了,準備去重啓服務器吧。因而乎像是 systemtap 這樣的項目就應運而生了。這一類項目提供了本身的 DSL,而後編譯成內核模塊。用戶不用直接跟 C 代碼和內核接口打交道,而是改用能力受限但安全得多的 DSL 編寫本身的小工具。打個比方,就像寫 SQL 查詢數據庫和直接編寫數據庫 C 插件的區別。後端

須要強調的是,systemtap DSL(如下簡稱 stp)並不是絕對地安全。首先,stp 容許咱們嵌入 C 代碼,而這部分天然是不安全的。其次,stp 容許咱們經過宏來調整編譯出來的內核模塊的行爲,而有些宏是有反作用的。舉個例子,stp 裏面的數組大小是預先分配好的,大小取決於 MAXMAPENTRIES這個宏(默認 2048)。有些時候,因爲要插入不少的數據,你須要調大這個宏。若是簡單粗暴地隨便在後面加若干個 0,可能會致使內核分配內存失敗,進而產生一系列問題(包括 kernel panic)。數組

對於 4.x 高版本的內核,咱們可使用 ebpf 機制而不是編寫或者間接編寫內核模塊。ebpf 是個在內核態運行的,解釋執行字節碼的虛擬機。它從底子上提供了更多的安全限制,要比內核模塊安全得多。並且編譯成 ebpf 字節碼要比編譯內核模塊快不少,這也是基於 ebpf 的內核 profile 的一大優點。安全

systemtap 除了能夠生成內核模塊,它還有生成 ebpf 字節碼和生成 ptrace 代碼的後端。2018 年,我曾嘗試過 systmtap 的 ebpf 後端,發現該後端因爲正在開發中,支持的 stp 語法特性較少,只有一部分 stp 文件能成功跑起來。相比較而言,BCC 支持的語言特性卻是足夠多,然而其對 debuginfo 的支持幾乎等於沒有,以至於用戶態 profile 只能基於 USDT (刻薄一點,就是幾乎不能用)。不知道它們倆如今發展得怎樣了。服務器

回到一開始的話題上來。一次完整的 stap test.stp ... 會經歷下面的階段:函數

  1. parse stp 代碼(詞法分析和語法分析)
  2. 解讀 stp 代碼(語義分析)
  3. 生成 C 代碼(中間代碼生成)
  4. 編譯內核模塊
  5. 運行

其中最後一個階段由 stap 建立的 staprun 子進程執行。這一階段能夠單獨拆開來,即前四個階段在開發機器上生成編譯好的內核模塊,而後第五個階段在目標機器上執行。具體怎麼作參考 systemtap 的文檔。工具

若是你對其中的細節感興趣,或者須要搞明白 systemtap 那些奇奇怪怪的錯誤信息,能夠在運行時加上 -vvv 來顯示更多上下文。優化

另外在命令中加入 -p 階段序號 可讓 systemtap 在執行完特定階段後停下來。好比 stap -p3 會在生成完 C 代碼後停下來。插件

本系列會按這五個階段,一步步介紹整個流程。重點會放在第三階段生成 C 代碼的部分。咱們會講講那些 stp 語言的元素,好比全局數組、聚合函數,是怎樣編譯成 C 代碼的。debug

不過在此以前,先從第一階段開始吧。

詞法分析和語法分析

這一階段沒什麼好說的,就是解析 stp 代碼,生成一棵 AST 樹。

若是解析過程當中出錯,會報告 parse error

parse error: expected literal string or number
        saw: operator ')' at test.stp:3:15
     source: probe begin() {
                           ^

1 parse error.
Pass 1: parse failed.  [man error::pass1]

上面是 systemtap 奇奇怪怪的報錯信息的一個例子。正確的寫法是 probe begin {,begin 後面不帶括號。

systemtap 雖然發現了語法上的問題,可是它的錯誤信息裏面沒有考慮到當前正在解析 probe 的上下文。它覺得是在解析某些帶參數的 probe,如 probe process("path").function("xxx"),因此才聲稱 expected literal string or number

不過好在 stp 的語法比較簡單,即便看不懂錯誤提示也能改對。

語義分析

上一階段結束後,stp 代碼已經被解析成一棵 AST 樹了。這一階段主要是對這棵樹作修剪,爲下一階段生成 C 代碼作準備。

這一階段由一系列分階段組成。每一個分階段會執行一個或多個 visitor,遍歷整棵樹,處理每一個節點。這些分階段包括 stp 語言宏的展開、查找 stp 函數定義、匹配 stp 變量類型、debuginfo 相關的一些操做、若干種優化器等等。做爲承上啓下的一個階段,該階段涉及了不少細節。因爲這些細節跟 systemtap 內部實現緊密相關,要想理解它們,最好須要閱讀其實現代碼。

這裏咱們就只提下優化器部分。在生成 C 代碼以前,systemtap 會執行一系列優化器,如常量摺疊、冗餘分支消除等,對 AST 樹進行剪枝。對於大型的 stp 腳本,花在這一階段的時間可能多達 10 秒,快遇上第四階段編譯內核模塊的耗時了。若是你對 systemtap 腳本的預備耗時比較敏感,能夠在運行時加上 -u 選項,跳過這些優化階段。

預告

接下來,我會花上幾篇文章的篇幅,講講第三階段中 C 代碼的生成,重點是 stp 語言的一些特性。

相關文章
相關標籤/搜索