<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          LWN:對比 SystemTap 和 bpftrace!

          共 10127字,需瀏覽 21分鐘

           ·

          2021-04-27 06:57

          關(guān)注了就能看到更多這么棒的文章哦~

          Comparing SystemTap and bpftrace

          April 13, 2021
          This article was contributed by Emanuele Rocca
          DeepL assisted translation
          https://lwn.net/Articles/852112/

          有時候開發(fā)者和系統(tǒng)管理員需要分析正在運行中的代碼里的問題。被檢查的程序可能是用戶空間進程,也可能是內(nèi)核,或者兩者都要同時分析。在 Linux 上有兩個主流工具可以進行這種分析,就是 SystemTap 和 bpftrace。SystemTap 從 2005 年就出現(xiàn)了,而 bpftrace 則是最近才出現(xiàn)的,但對一些人來說,它似乎已經(jīng)可以替代 SystemTap 了。然而,SystemTap 仍然是一些實際案例中的首選分析工具。

          盡管早在 2004 年,Linux 就以 KProbes 的形式加入了動態(tài)注入(dynamic instrumentation)功能,但該功能很難使用,知道的人也不多。在一年后 Sun 發(fā)布了 DTrace,很快就成為 Solaris 的亮點之一。Linux 用戶自然也開始要求有類似的功能,于是在這種情況下 SystemTap 很快就成為了最靠譜的方案。但是 SystemTap 一直被人們批評,感到很難運行起來,而 Solaris 上的 DTrace 基本上就是開箱即用,非常簡單。

          雖然 DTrace 的 tracing 功能既支持 kernel 也支持 user-space,但直到 2012 年,Linux 才實現(xiàn)了 Uprobes 來支持對 user-space 的 tracing 功能。2019 年左右,bpftrace 得到了巨大關(guān)注,部分原因是人們開始普遍關(guān)注 BPF 的各種使用實例。最近,甲骨文公司(Oracle)一直在為 Linux 來重新實現(xiàn) DTrace,希望能基于內(nèi)核中最新的 tracing 功能。不過,目前來說,考慮到這個領(lǐng)域已經(jīng)有了現(xiàn)有的方案,DTrace 可能來得太晚了。

          SystemTap 和 bpftrace 所使用的底層內(nèi)核基礎(chǔ)功能基本相同,都是用 KProbes 來對 kernel 函數(shù)進行動態(tài) tracing,使用 tracepoints 來進行 static kernel instrumentation(靜態(tài)內(nèi)核注入),使用 Uprobes 來對 user-level 的函數(shù)進行動態(tài) tracing,使用 user-level statically defined tracing(USDT)來進行 user-space 的靜態(tài)注入。這兩個方案都允許通過更高級語言 "腳本(script)" 來對內(nèi)核和用戶空間程序進行控制,用腳本來指定需要探測(probe)的內(nèi)容和方式。

          兩者之間最重要的設(shè)計上的區(qū)別在于,SystemTap 會將用戶提供的腳本翻譯成 C 代碼,然后作為 module 編譯并加載到所運行的 Linux 內(nèi)核中。bpftrace 則相反,會將腳本轉(zhuǎn)換為 LLVM 中間表示(intermediate representation,類似于偽代碼),然后再編譯為 BPF 程序。使用 BPF 有幾個優(yōu)點:創(chuàng)建和運行一個 BPF 程序比編譯并加載一個內(nèi)核 module 要快很多。對由鍵值對(key/value pair)組成的數(shù)據(jù)結(jié)構(gòu),可以通過使用 BPF maps 來輕松支持。BPF verifier 確保了 BPF 程序不會導(dǎo)致系統(tǒng)崩潰,而 SystemTap 使用的內(nèi)核 module 的方法意味著需要在運行時添加各種安全檢查(safety check)。另一方面,使用 BPF 會使得某些功能比較難以實現(xiàn),例如比較難以實現(xiàn) custom stack walker(自定義的堆棧遍歷),我們將在本后后半部分介紹這一點。

          下面的例子從用戶的角度展示了這兩個系統(tǒng)的相似方面。下面是一個簡單的 SystemTap 程序,用來注入分析內(nèi)核里的 icmp_echo() 函數(shù),看起來是這個樣子的:

          probe kernel.function("icmp_echo") {
          println("icmp_echo was called")
          }

          實現(xiàn)相同功能的 bpftrace 程序是這樣的:

          kprobe:icmp_echo {
          print("icmp_echo was called")
          }

          接下來我們來看一下 SystemTap 和 bpftrace 在安裝過程、程序結(jié)構(gòu)(program structure)和功能上的區(qū)別。

          Installation

          SystemTap 和 bpftrace 在所有主流 Linux 發(fā)行版中都已經(jīng)缺省包含了,可以用各位熟悉的軟件包管理器來輕松地完成安裝。SystemTap 需要安裝 Linux kernel header 頭文件才能正常工作,而 bpftrace 則不需要,只要內(nèi)核啟用了 BPF 類型格式(BTF,BPF Type Format)支持就好。根據(jù)用戶想分析的是用戶空間程序還是內(nèi)核,還會有一些額外要求。在分析用戶空間軟件時,SystemTap 和 bpftrace 都需要被分析的軟件的調(diào)試符號(debugging symbol)。如何安裝這些 symbol 數(shù)據(jù),需要根據(jù)各位所用的發(fā)行版上的特有方式來安裝了。

          在使用 elfutils 0.178 版本或更高版本的系統(tǒng)上,SystemTap 可以使用遠程的 debuginfod 服務(wù)器,從而使得尋找和安裝正確的 debugging symbol 的過程完全自動化。例如,在 Debian 系統(tǒng)上可以這樣做:

          $ export DEBUGINFOD_URLS=https://debuginfod.debian.net
          $ export DEBUGINFOD_PROGRESS=1
          $ stap -ve 'probe process("/bin/ls").function("format_user_or_group") { println(pp()) }'
          Downloading from https://debuginfod.debian.net/
          [...]

          bpftrace 還沒有類似的功能。

          要對內(nèi)核進行注入分析的話,SystemTap 還需要 kernel debugging symbol(內(nèi)核調(diào)試 symbol)文件,這樣才可以使用一些高級的調(diào)試功能,比如說查找出函數(shù)調(diào)用時的具體參數(shù)或者某個局部變量,針對函數(shù)體中的具體某行代碼進行注入分析等。這種情況下也可以使用遠程的 debuginfod 服務(wù)器來自動完成符號表文件的加載。

          Program structure

          這兩個方案都提供了一種類似 AWK 的語言,靈感均來自于 DTrace 的 D 腳本語言,用來要做什么動作。bpftrace 的腳本語言與 D 基本相同,并遵循如下這個通常的結(jié)構(gòu):

          probe-descriptions
          /predicate/
          {
          action-statements
          }

          也就是說:當(dāng)這個 probe 被觸發(fā)時,如果指定的(可選)predicate 信息匹配上了,就執(zhí)行指定的 action-statements。

          SystemTap 程序的結(jié)構(gòu)略有不同:

          probe PROBEPOINT [, PROBEPOINT] {
          [STMT …]
          }

          在 SystemTap 中,語言本身并沒支持設(shè)置特定的 predicate,但可以用條件判斷語句來達到同樣的目的。

          例如,下面的 bpftrace 程序打印了 PID 為 31316 的進程發(fā)起的所有 mmap() 函數(shù)調(diào)用:

          uprobe:/lib/x86_64-linux-gnu/libc.so.6:mmap
          /pid == 31316/
          {
          print("mmap by 31316")
          }

          SystemTap 中的對應(yīng)實現(xiàn)是:

          probe process("/lib/x86_64-linux-gnu/libc.so.6").function("mmap") {
          if (pid() == 31316) {
          println("mmap by 31316")
          }
          }

          bpftrace 中的數(shù)據(jù)聚合和報告的方式與 DTrace 中的完全相同。例如,下面的程序?qū)?nèi)核中 tcp_sendmsg()函數(shù)發(fā)送的字節(jié)數(shù)進行了按 PID 的匯總、聚合:

          $ sudo bpftrace -e 'kprobe:tcp_sendmsg { @bytes[pid] = sum(arg2); }'
          Attaching 1 probe...
          ^C

          @bytes[58832]: 75
          @bytes[58847]: 77
          @bytes[58852]: 857

          和 DTrace 一樣,bpftrace 在程序退出時默認會自動打印匯總結(jié)果,因此不需要寫代碼來按上面的 PID 來分類在進行打印輸出。這種缺省行為也有缺點,為了避免自動打印所有的數(shù)據(jù)結(jié)構(gòu),用戶必須明確顯式調(diào)用 clear() 來清除掉那些不應(yīng)該被打印的數(shù)據(jù)。例如,可以改變上面的腳本,只打印最多的 5 個進程,bytes map 就必須要在程序結(jié)束時被 clear 掉:

          kprobe:tcp_sendmsg {
          @bytes[pid] = sum(arg2);
          }

          END {
          print(@bytes, 5);
          clear(@bytes);
          }

          此外還有其他一些強大功能,比如生成直方圖(histogram),從而可以寫出非常簡潔的腳本,比如下面的例子就對 vfs_read() 調(diào)用所讀取的字節(jié)數(shù)進行了展示:

          $ sudo bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); }'
          Attaching 1 probe...
          ^C

          @bytes:
          (..., 0) 169 |@@ |
          [0] 206 |@@@ |
          [1] 1579 |@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
          [2, 4) 13 | |
          [4, 8) 9 | |
          [8, 16) 2970 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
          [16, 32) 45 | |
          [32, 64) 91 |@ |
          [64, 128) 108 |@ |
          [128, 256) 10 | |
          [256, 512) 8 | |
          [512, 1K) 69 |@ |
          [1K, 2K) 97 |@ |
          [2K, 4K) 37 | |
          [4K, 8K) 64 |@ |
          [8K, 16K) 24 | |
          [16K, 32K) 29 | |
          [32K, 64K) 80 |@ |
          [64K, 128K) 18 | |
          [128K, 256K) 0 | |
          [256K, 512K) 2 | |
          [512K, 1M) 1 | |

          SystemTap 中也提供了統(tǒng)計數(shù)據(jù)的匯總。<<< 操作符就是用來向統(tǒng)計數(shù)據(jù)匯總中添加數(shù)據(jù)的。SystemTap 在程序退出時不會自動打印匯總結(jié)果,所以需要顯式地打印:

          global bytes
          probe kernel.function("vfs_read").return {
          bytes <<< $return
          }

          probe end {
          print(@hist_log(bytes))
          }

          Features

          這些 DTrace 的類似系統(tǒng)有一個非常有用的功能,就是能夠取得 stack trace,從而可以查看哪個函數(shù)調(diào)用流程觸發(fā)了某個具體的 probe 位置。kernel stack trace 在 bpftrace 中可以按如下方式獲取:

          kprobe:icmp_echo {
          print(kstack);
          exit()
          }

          用 SystemTap 的話,相同的功能是這么寫的:

          probe kernel.function("icmp_echo") {
          print_backtrace();
          exit()
          }

          bpftrace 有一個重要局限,就是不能生成用戶空間的 stack trace,除非被分析的目標程序在編譯時啟用了 frame pointer(幀指針)。絕大多數(shù)情況下,這都意味著用戶必須重新編譯目標程序之后才能對其進行注入分析。

          而 SystemTap 的用戶空間堆棧回溯機制(user-space stack backtrace mechanism),則是利用調(diào)試信息來遍歷堆棧,從而提供完整的 stack trace。這意味著不需要重新編譯了:

          probe process("/bin/ls").function("format_user_or_group") {
          print_ubacktrace();
          exit()
          }

          上面的腳本就會生成一個完整的 backtrace,下面只節(jié)選了幾項來展示:

          0x55767a467f60 : format_user_or_group+0x0/0xc0 [/bin/ls]
          0x55767a46d26a : print_long_format+0x58a/0x9f0 [/bin/ls]
          0x55767a46d840 : print_current_files+0x170/0x3e0 [/bin/ls]
          0x55767a465d8d : main+0x62d/0x1a00 [/bin/ls]

          這不太可能在 bpftrace 中實現(xiàn),因為需要在內(nèi)核或 BPF bytecode 字節(jié)碼中來實現(xiàn)這個功能才行。

          Real world uses

          下面舉個在真實生產(chǎn)環(huán)境中調(diào)查的例子,由于 backtrace 的限制,bpftrace 無法進行更進一步的分析了,所以需要用 SystemTap 來繼續(xù)調(diào)查。Wikimedia 使用 LuaJIT 的時候遇到了一個有趣的問題,可以看到 Apache Traffic Server 會產(chǎn)生很高的 CPU 使用率,我們可以通過如下命令來確認出這是由于 mmap()被調(diào)用得過于頻繁了:

          $ sudo bpftrace -e 'kprobe:do_mmap /pid == 31316/ { @[arg2]=count(); } interval:s:1 { exit(); }'
          Attaching 2 probes...
          @[65536]: 64988

          如果不能用 SystemTap 生成 user-space backtrace 的話,調(diào)查只能到此為止了。請注意,這個例子中的問題牽涉到了 Lua JIT 組件,也就是說就算打開 frame pointer 來重新編譯 Apache Traffic Server 從而使得 bpftrace 可以生成 stack trace 也仍然是不夠的,我們還必須重新編譯 LuaJIT。

          與 bpftrace 相比,SystemTap 的另一個重要優(yōu)勢是允許通過變量名來訪問函數(shù)的參數(shù)和局部變量。在 bpftrace 中,只有在調(diào)試內(nèi)核時才能用名字訪問到參數(shù),還需要是使用 static kernel tracepoint 或者最近版本 kernel 中的實驗性的 kfunc 功能才行。kfunc 功能是基于 BPF trampolines 的,看起來前景很不錯。當(dāng)使用常規(guī)的 kprobes 時,或者在對用戶空間的軟件進行檢測時,bpftrace 只能通過位置信息(arg0, arg1, … argN)來訪問到參數(shù)。

          SystemTap 還能按源代碼文件來列出可用的 probe 點,在定義 probe 的時候也能按文件名來進行匹配。這個功能可以用來只針對代碼庫的特定領(lǐng)域來進行分析的情況。例如,下面的命令可以用來列出(-L 參數(shù)的效果)Apache Traffic Server 中的 iocore/cache/Cache.cc 中定義的所有函數(shù):

          $ stap -L 'process("/usr/bin/traffic_server").function("*@./iocore/cache/Cache.cc")

          經(jīng)常有必要在函數(shù)內(nèi)部的某個地方設(shè)置一個 probe 點,而不是每次都只分析函數(shù)入口點或返回語句位置的。在 SystemTap 中,這可以使用 statement probe 來實現(xiàn)。下面的代碼會列出每個可用的 probe 點,以及每個位置下可用的變量:

          $ stap -L 'process("/bin/ls").statement("format_user_or_group@src/ls.c:*")'
          process("/bin/ls").statement("format_user_or_group@src/ls.c:4110") \
          $name:char const* $id:long unsigned int $width:int
          process("/bin/ls").statement("format_user_or_group@src/ls.c:4115") \
          $name:char const* $id:long unsigned int $width:int
          process("/bin/ls").statement("format_user_or_group@src/ls.c:4116") \
          $width_gap:int $name:char const* $id:long unsigned int $width:int
          process("/bin/ls").statement("format_user_or_group@src/ls.c:4118") \
          $pad:int $name:char const* $id:long unsigned int $width:int
          [...]
          process("/bin/ls").statement("format_user_or_group@src/ls.c:4131") \
          $name:char const* $id:long unsigned int $width:int $len:size_t

          從完整的輸出內(nèi)容中可以看到,在函數(shù) format_user_or_group()中,有 10 個不同的位置可以添加 probe,還有其他可以看到的各種變量。通過查看源代碼,我們可以看到到底需要對哪一行進行 probe,相應(yīng)地撰寫 SystemTap 程序。

          如果想用 bpftrace 來實現(xiàn)同樣的效果,那么我們需要對函數(shù)進行反匯編,然后根據(jù)匯編指令位置來提供正確的偏移量給 Uprobe,這還是很麻煩的。此外,bpftrace 需要在編譯時打開 Binary File Descriptor(BFD),這樣此功能才可以生效。

          雖然所有的軟件基本都會有 bug,但那些會影響到調(diào)試工具的問題就特別棘手。有一個問題會影響到使用了某些特定 LLVM 版本的系統(tǒng)中的 bpftrace,這個問題值得一提。由于 LLVM 中的一個 bug 導(dǎo)致 IR(intermediate representation)中的 load/store 指令在不應(yīng)該被 reorder 的時候發(fā)生了 reorder,導(dǎo)致一些原本有效的 bpftrace 腳本會產(chǎn)生無法預(yù)料的結(jié)果。只要添加或刪除一些無關(guān)代碼就可能會導(dǎo)致行為變化,不一定能觸發(fā)這個錯誤了。這個底層 LLVM 錯誤同樣導(dǎo)致其他一些的 bpftrace 腳本也失敗。這個問題最近在 LLVM 12 中得到了修復(fù),因此 bpftrace 用戶應(yīng)該確保他們運行的是不受這個問題影響的最新版本 LLVM。

          Conclusions

          SystemTap 和 bpftrace 提供了類似的功能,但在設(shè)計方案上有很大區(qū)別,一個在底層使用了可加載的內(nèi)核 module,另一個使用了 BPF。基于內(nèi)核 module 的方法提供了更大的靈活性,并允許實現(xiàn)使用 BPF 很難甚至不可能實現(xiàn)的功能。另一方面,BPF 顯然是一個理想的 tracing 工具,因為它提供了一個快速而安全的環(huán)境來來實現(xiàn)對系統(tǒng)的可觀察性。

          對于許多應(yīng)用場景來說,bpftrace 開箱即用,而 SystemTap 通常需要安裝額外的依賴關(guān)系才能充分利用其所有功能。Bpftrace 的速度一般比較快,而且提供了各種快速匯總和報告的功能,可以說比 SystemTap 提供的類似功能更簡單易用。另一方面,SystemTap 提供了一些與眾不同的功能,比如:生成用戶空間 backtrace 而不需要 frame pointer、通過變量名來訪問函數(shù)參數(shù)和局部變量、以及對任意語句進行 probe 的能力。在今天的 Linux 系統(tǒng)診斷問題時,兩者似乎都有其價值。

          全文完
          LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。

          歡迎分享、轉(zhuǎn)載及基于現(xiàn)有協(xié)議再創(chuàng)作~

          長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~



          瀏覽 110
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本免费内射 | 大鸡巴在线免费观看视频 | 欧美精品操B | 日韩毛片中文字幕 | 国产在线最新地址 |