---
# System prepended metadata

title: 第四講：透過 eBPF 觀察作業系統行為
tags: [Linux]

---

# 第四講：透過 eBPF 觀察作業系統行為

> 本筆記僅為個人紀錄，相關教材之 Copyright 為[jserv](http://wiki.csie.ncku.edu.tw/User/jserv)及其他相關作者所有
* 直播：==[Linux 核心設計：透過 eBPF 觀察作業系統行為 - 2018/11/15](https://youtu.be/UmCnh6mELwA)==
* 詳細共筆：[Linux 核心設計: 透過 eBPF 觀察作業系統行為](https://hackmd.io/@sysprog/linux-ebpf?type=view)
* 主要參考資料：
    * [Linux BPF Superpowers](https://www.slideshare.net/slideshow/linux-bpf-superpowers/58986111)
    * [Meet cute-between-ebpf-and-tracing](https://www.slideshare.net/slideshow/meet-cutebetweenebpfandtracing/62446985)
    * [Kernel Analysis Using eBPF](https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Kernel-Analysis-Using-eBPF-Daniel-Thompson-Linaro.pdf)


---

本課旨在闡述 Linux 核心的動態追蹤機制，特別是 eBPF (Extended Berkeley Packet Filter) 技術，以及如何利用它來觀察和分析作業系統的內部行為。

---
## 前言：為何需要觀察作業系統行為

現代軟體系統面臨著前所未有的規模和複雜度挑戰。無論是使用者規模、機房規模還是軟體本身的層次 (作業系統核心、系統軟體、高階語言虛擬機、應用邏輯等)，都在快速增長。在這樣的大規模生產環境中，詭異問題頻發，佔據工程人員大量時間。

**傳統的除錯方法**，如在程式碼中插入 `printk`，在**複雜的核心環境中可能導致行為改變甚至系統崩潰，且難以捕捉瞬態問題**。因此，需要更有效的機制來「活體分析」仍在運作並持續提供服務的軟體系統。

---
## 核心動態追蹤機制

動態追蹤技術 (dynamic tracing) 是一種進階的除錯和追蹤機制，允許工程師以非常低的成本，在短時間內克服不明顯的問題。其核心思想是**將運作中的軟體系統視為一個即時變化的資料庫**，透過「探針」(probe) 進行查詢。

*   **探針 (Probe)**：好比中醫針灸，在軟體系統的特定「穴位」 (如**函式入口、特定事件點**) 安置探針，探針上帶有自定義的「傳感器」 (**處理程式**)，用於採集關鍵資訊。
*   **非侵入式分析**：動態追蹤的關鍵優勢在於其非侵入性。如同醫生給病人拍 X 光或做核磁共振，而非直接開膛破肚。它**允許在不修改作業系統核心、應用程式或任何系統配置的情況下**，快速高效地獲取所需資訊。

若動態追蹤機制內建於作業系統，**使用者層級的程式**即可隨時採集資訊，構建完整的軟體樣貌。

主流的動態追蹤工具包括：
*   **DTrace**：源自 Solaris 作業系統。Oracle 已重新以 GNU GPL 釋出 DTrace 並嘗試整合到 Linux 核心。值得注意的是，[DTrace for Linux 2.0 已在 eBPF 的基礎上重新實作](https://lore.kernel.org/all/ZhBRSM2j0v7cOLn%2F@oracle.com/T/#u)，不再依賴專屬的核心模組。
*   **SystemTap**：Red Hat 的實作。
*   **[ktap](https://github.com/ktap/ktap)**：華為科技的實作，使用 Lua 作為腳本語言。

> 延伸閱讀：[動態追蹤技術漫談](https://openresty.org/posts/dynamic-tracing/)

---
## eBPF 概覽

eBPF (Extended Berkeley Packet Filter) 最初源於 Berkeley Packet Filter (BPF)，其設計初衷確實是**封包過濾機制**。然而，擴充後的 eBPF 已經演變為 Linux 核心內建的強大**內部行為分析工具**。

eBPF 的功能涵蓋：
*   動態追蹤 (dynamic tracing)。
*   靜態追蹤 (static tracing)。
*   效能剖析事件 (profiling events)。
![image alt](https://i.imgur.com/1HVZIzK.png)

### eBPF 在安全方面的應用
*   **沙盒 (Sandbox) 環境**：eBPF 程式碼在核心內部的**沙盒虛擬機器**中執行，確保安全。
*   **DDoS 防禦**：透過 [eXpress Data Path (XDP)](https://www.iovisor.org/technology/xdp)，eBPF 可以在網路堆疊處理早期 (甚至在 `sk_buff` 分配前) 丟棄惡意封包，有效緩解 DDoS 攻擊。
*   **入侵偵測系統 (Intrusion Detection System, IDS)**：eBPF 也可用於實現入侵偵測機制。

### eBPF 的運作流程
使用者在使用者層級 (user mode) 準備 BPF 程式碼，用於測量延遲、堆疊追蹤等資訊。其運作方式如下：
1.  **編譯**：BPF 程式碼在 user mode 被編譯成 BPF bytecode。
    *   Linux 核心 v5.2 之前：最多 4096條指令，512B 堆疊空間，核心內建 JIT (Just-In-Time) 編譯器。
    *   Linux 核心 v5.2 之後：指令數量上限放寬到 1 百萬條。
2.  **載入與驗證**：BPF bytecode 被載入到核心後，會經過核心驗證器 (verifier) 的檢查。若驗證不通過 (例如，不安全或包含無限迴圈)，則會被拒絕。
3.  **掛載與執行**：若驗證通過，BPF 程式可以掛載到以下幾種不同的追蹤點執行：
    *   **kprobes**：**核心**動態追蹤 (kernel dynamic tracing)，用於探測核心函式的入口或特定指令。
    *   **uprobes**：**使用者層級**動態追蹤 (user level dynamic tracing)，用於探測使用者態程式的函式。
    *   **tracepoints**：核心靜態追蹤 (kernel static tracing)，由核心開發者**預先定義在程式碼中的追蹤點**，通常 ABI 較穩定。
    *   **perf\_events**：基於效能事件的取樣 (timed sampling and Performance Monitoring Counters, PMCs)。
![image alt](https://i.imgur.com/i7b3DkP.png)
4.  **資料回傳**：分析結果可以透過兩種主要方式傳回 user mode：
    *   **Per-event details**：每個事件的詳細資料，通常透過 `perf` 事件的環形緩衝區 (ring buffer) 或 `bpf_trace_printk` (主要用於除錯，不建議在生產環境大量使用) 傳遞。
    *   **BPF maps**：一種通用的鍵值對 (key-value) 儲存結構，存在於核心空間，**user mode 和 BPF 程式都可以存取**。BPF maps 可以用於匯總數據，如製作直方圖、統計計數或相關性矩陣等。

### 使用 eBPF 的先決條件
*   核心編譯選項 `CONFIG_BPF_SYSCALL` 必須啟用 (在`/proc/config.gz`)。
*   核心版本建議在 4.4 或以上，以獲得較完善的功能支援 (如直方圖、統計和追蹤每個事件的功能)。
![image alt](https://i.imgur.com/4jgY4mj.png)
(上圖$\color{green}{綠色}$字樣指該功能所需的最低核心版本)

---

## 作業系統的核心內建虛擬機器？！

從形式上說，BPF 就是一個核心內虛擬機器 (in-kernel virtual machine)。Linux 核心早在 2.1.75 版本 (1997年) 就已收錄 Berkeley Packet Filter (BPF) [JITC (A JIT for packet filters)](https://lwn.net/Articles/437981/)。
![image](https://hackmd.io/_uploads/S1t1h0e-ex.png)

### `tcpdump` 與 BPF
`tcpdump` 是一個常用的**網路封包分析工具**，它不僅能分析封包流向，還能「監聽」封包內容。其**底層實現嚴重依賴 BPF**。

當使用 `tcpdump` 並**指定過濾條件**時 (例如 `ip and udp`)，這些條件會被編譯成 BPF bytecode，然後載入到核心中的 BPF 虛擬機器執行，以高效過濾封包。
![image](https://hackmd.io/_uploads/Hyg1eJbblg.png)

#### 範例：使用 `tcpdump` 觀察網路流量
以 IP 與 port number 捕捉 `eth0` 網路介面的封包：
```shell
$ sudo tcpdump -i eth0 -nn
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
...
22:02:05.803000 ARP, Request who-has 140.131.178.251 tell 140.131.179.254, length 46
22:06:46.922722 IP 140.131.178.244.22 > 110.50.189.175.57657: Flags [P.], seq 1502584330:1502584518, ack 1360901079, win 306, options [nop,nop,TS val 3241491854 ecr 590330642], length 188
...
```
輸出解讀 (需掌握[網路基礎](https://linux.vbird.org/linux_server/centos6/0110network_basic.php)概念)：
*   `22:02:05.803000`：封包擷取時間。
*   `ARP`：通訊協定為**位址解析協定** (Address Resolution Protocol)。
    *   ARP 利用乙太網路的**廣播特性**，動態查詢 IP 位址與實體位址 (MAC 位址) 的對應關係。
![image alt](https://i.imgur.com/JKwqT9e.png)
*   `140.131.178.244.22 > 110.50.189.175.57657`：表示封包從 IP `140.131.178.244` 的 port `22` 發送到 IP `110.50.189.175` 的 port `57657`。`>` 指示傳輸方向。
*   `[P.], seq 1502584330:1502584518`：TCP 旗標為 PUSH，表示帶有資料傳輸，序號範圍如示。
*   `ack 1360901079`：確認號。

#### 查看 `tcpdump` 編譯的 BPF bytecode
使用 `-d` 選項可以將編譯後的封包匹配程式碼以**人類可讀**的形式傾印出來：
```shell
$ sudo tcpdump -p -ni eth0 -d "ip and udp"
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 5
(002) ldb      [23]
(003) jeq      #0x11            jt 4	jf 5
(004) ret      #262144
(005) ret      #0
```
根據 [tcpdump(8)](https://linux.die.net/man/8/tcpdump) 手冊：
*   `-d`：傾印**編譯後**的封包匹配程式碼。 (開發者檢視)
*   `-dd`：將封包匹配程式碼傾印為 **C 語言** 結構陣列格式。 (嵌入 `libpcap` 函式庫)
*   `-ddd`：將封包匹配程式碼傾印為 **十進位格式** 的純數字陣列。 (機器處理或嵌入其他語言)
*   `jt`：jump if true。
*   `jf`：jump if false。

上述 BPF bytecode 初步解讀：
*   `(000) ldh [12]`：從封包位移量 `12` (**乙太網路幀頭部**，表示**網路層**協定類型) 載入一個 half-word (2 bytes)。
*   `(001) jeq #0x800 jt 2 jf 5`：檢查該值是否為 `0x0800` (IP 協定)。若是，跳到指令 `(002)`；否則跳到指令 `(005)` (返回 0，表示不匹配)。
*   `(002) ldb [23]`：從封包位移量 `23` (**IP 協定頭部**，表示**傳輸層**協定類型) 載入一個 byte。
*   `(003) jeq #0x11 jt 4 jf 5`：檢查該值是否為 `0x11` (UDP 協定)。若是，跳到指令 `(004)`；否則跳到指令 `(005)`。
*   `(004) ret #262144`：返回非零值，表示**封包匹配**，擷取最多 `262144` bytes。
*   `(005) ret #0`：返回 0，表示**封包不匹配**。
<!-- ![image alt](https://i.imgur.com/EnKGoYn.png) -->
![image](https://hackmd.io/_uploads/BJQP4AgZge.png)

使用 `-dd` 選項：
```shell
$ sudo tcpdump -p -ni eth0 -dd "ip and udp"
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 1, 0x00000011 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
```
這段 C 陣列形式的程式碼可以被 `libpcap` 這樣的函式庫使用，並透過系統呼叫傳遞給核心內的 BPF 模組。

#### BPF 架構設計
位於核心的 BPF 模組是整個流程的核心：它接受 `tcpdump` 經由 `libpcap` 轉譯而來的過濾條件 (BPF bytecode)，並將符合條件的封包訊息從核心模式複製到使用者層級，最終由 `libpcap` 發送給 `tcpdump`。
<!-- ![image](https://i.imgur.com/ZfmR4Tl.png) -->
![image](https://hackmd.io/_uploads/B1wCQRxbex.png)

> 延伸閱讀：[Linux Socket Filtering aka Berkeley Packet Filter (BPF)](https://www.kernel.org/doc/Documentation/networking/filter.txt)

### BPF JIT (Just-In-Time) 編譯器
自 Linux 核心 v3.0 起，BPF 引入了 JIT 編譯器。若 `CONFIG_BPF_JIT` 編譯選項開啟，傳入的 BPF bytecode 會被編譯成平台相關的**本地機器碼**執行，以提高效率，替換掉預設的直譯器 `sk_run_filter()`。
![image](https://hackmd.io/_uploads/rk2RvAlble.png)
![image](https://hackmd.io/_uploads/Bk3R0AeWge.png)
*   可透過向 `/proc/sys/net/core/bpf_jit_enable` 寫入：
    *    `1`：啟用 JIT。
    *    `2`：在核心訊息 (`dmesg`) 中看到 JIT 生成的最佳化程式碼。
*   可以使用核心工具 `bpf_jit_disasm.c` (位於 `tools/bpf/`) 將核心訊息中的**二進位碼**轉換為對應的**組合語言**。
*   **安全問題**：Project Zero 曾[揭露 eBPF JIT 搭配 Spectre and Meltdown 漏洞發動攻擊的手段](https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html) ([中文解說](https://web.archive.org/web/20200205085508/https://look3little.blogspot.com/2018/01/spectre-and-meltdown.html))。這意味著如果 JIT 本身存在漏洞，攻擊者可能透過注入特製的 BPF 程式來利用硬體漏洞。

### BPF 的演進：cBPF vs eBPF
*   **v3.4**：BPF 引入 [seccomp (secure computing mode)](http://man7.org/linux/man-pages/man2/seccomp.2.html)，用於**限制行程可呼叫的系統呼叫**，增強安全性。
*   **v3.14**：新增 `bpf_asm` (BPF 組譯器) 和 `bpf_dbg` (BPF 除錯器)。
*   **v3.17**：引入 **extended BPF (eBPF)**。傳統的 BPF 被重命名為 classical BPF (cBPF)。

| 特性        | BPF                                               | eBPF                                       |
| ----------- | ------------------------------------------------- | ------------------------------------------ |
| registers   | A, X                                              | R0 ~ R10                                   |
| width       | 32 bit                                            | 64 bit                                     |
| opcode      | op:16, jt:8, jf:8, k:32                           | op:8, dst_reg:4, src_reg:4, off:16, imm:32 |
| JIT support | x86_64, SPARC, PowerPC, ARM, ARM64, MIPS, s390 | x86-64, aarch64, s390x                     |
| Communication with kernel | `recv()`   | maps   |

#### eBPF 帶來了巨大的改變：
*   **暫存器與指令集擴展**：eBPF 擁有更多的暫存器 (10個通用暫存器 + 1個唯讀幀指標)，指令集也更通用，使其更像一個通用的 RISC CPU。
*   **Map 機制**：cBPF 和核心溝通的機制主要是透過 `recv()` 系統呼叫複製封包資料。eBPF 引入了**全新的 `map` 機制**。
    *   **運作原理**：使用者層級的程式透過 `bpf(BPF_MAP_CREATE, ...)` 系統呼叫在核心中建立一個特定類型的 map (如 hash map, array map)。這個 map 是一個**鍵值對**儲存，**eBPF 程式**和**使用者層級程式**都可以存取它。
    *   **優勢**：
        *   **效率**：顯著減少了核心態與使用者態之間的資料**複製和模式切換成本**。
        *   **資料多樣性**：Map 可以儲存**任意複雜的資料結構**，而不僅僅是封包。
![image alt](https://i.imgur.com/XtkwqKY.png)
*   **輔助函式 (Helper Functions)**：eBPF 程式可以呼叫一組**預先定義的核心輔助函式**，以執行如讀取時間戳、存取 map、修改封包等操作，擴展了其能力。
*   **主要應用範圍**：
    *   cBPF：網路封包過濾、seccomp
    *   eBPF：**核心追蹤**、**程式效能監控**、**流量控制 (traffic control)** ... 等

在 kernel space 處理 uprobe 觸發的成本，有機會比在 userspace 用 `SIGTRAP` 或 `ptrace` 處理 breakpoint 觸發還低。uprobe 觸發後，驅動 eBPF 執行環境，再用 eBPF map 將資料傳遞給使用者層級。


### XDP (eXpress Data Path)
XDP 允許 eBPF 程式在網路驅動程式的**極早期階段執行**，甚至在核心為封包分配 `sk_buff` 結構之前。這使得 XDP 在高速封包處理 (如 DDoS 防禦、負載平衡) 方面具有極高效能。
![image](https://hackmd.io/_uploads/rkDJ0AlZlx.png)
Cloudflare 的文章 [How to drop 10 million packets per second](https://blog.cloudflare.com/how-to-drop-10-million-packets/) 展示了其強大能力。
![image](https://hackmd.io/_uploads/HkjEo0gWeg.png)

> 延伸閱讀：
> *   [BPF - the forgotten bytecode](https://blog.cloudflare.com/bpf-the-forgotten-bytecode/)
> *   [eBPF 簡史](https://www.ibm.com/developerworks/cn/linux/l-lo-eBPF-history/)
> *   [eBPF, Sockets, Hop Distance and manually writing eBPF assembly](https://blog.cloudflare.com/epbf_sockets_hop_distance/)
> *   [Why is the kernel community replacing iptables with BPF?](https://cilium.io/blog/2018/04/17/why-is-the-kernel-community-replacing-iptables/)
> *   [History and Context of BPF](https://github.com/byoman/intro-ebpf/blob/master/notes.md)

> 解說資料：
> *   [Introduction to Linux kernel tracing and eBPF integration](http://www.slideshare.net/vh21/meet-cutebetweenebpfandtracing)
> *   [Netronome: Demystify eBPF JIT Compiler](https://docs.google.com/document/d/1NL1TBK3yZIkqjVtCxxHOv6X_1j_LnZgi1cv4FBGEzFc/edit)
> *   [Introduction to eBPF and XDP 筆記](https://docs.google.com/document/d/19gf8C1sCcbPPlrL0RUMoaZvhPXg4j7B9y1SZKC38Td8/edit)

---
## eBPF 開發工具集

### IO Visor 專案
2015 年，Linux 基金會成立了 [IO Visor](https://www.iovisor.org/) 專案，目標是實現高度彈性的資料平面 (data plane)，以加速網路功能虛擬化 (NFV) 等。eBPF 是其關鍵軟體元件，允許在核心內部實作網路封包處理，避免繁瑣的系統呼叫和使用者層級資料處理。

[PLUMgrid](https://www.crunchbase.com/organization/plumgrid) (後被 VMware 收購) 是 eBPF 的重要貢獻公司，推動了 eBPF 相關成果整合到 Linux 核心。

### BPF Compiler Collection (BCC)
儘管可用 C 語言編寫 BPF 程式，但直接處理編譯、解析 ELF 檔案、載入 BPF 程式碼塊以及建立 map 等操作對開發者而言較為繁瑣。
[BPF Compiler Collection (BCC)](https://github.com/iovisor/bcc) 作為 IOVisor 的子專案被提出，它是一個工具套件，極大簡化了 eBPF 程式的開發。
*   **優點**：允許開發者**專注於用 C 語言編寫注入核心的邏輯**，其餘工作 (編譯、解析 ELF、載入 BPF 程式碼、建立 map 等) 大多**由 BCC 自動處理**。
*   **語言使用**：BCC 通常與高階語言 (如 Python、Lua) 的綁定一起使用，開發者在高階語言腳本中**嵌入 C 語言的 eBPF 程式碼片段**。
![image](https://hackmd.io/_uploads/Bye93SkZWgx.png)

> 相關資源：
> * **安裝：[官方說明文件](https://github.com/iovisor/bcc/blob/master/INSTALL.md)**
> *   [Writing eBPF tracing tools in Rust](https://jvns.ca/blog/2018/02/05/rust-bcc/)
> *   [Linux System Monitoring with eBPF](https://web.archive.org/web/20201109030908/https://www.circonus.com/2018/05/linux-system-monitoring-with-ebpf/) by Circonus
>     *   [BPF metrics](https://github.com/circonus-labs/nad/tree/master/plugins/linux/bccbpf)：Circonus 提供的插件，用於捕獲系統底層指標，如系統呼叫延遲直方圖、系統呼叫計數、塊設備延遲直方圖、排程器執行佇列延遲直方圖等。

> 解說：Kernel Analysis Using eBPF - Daniel Thompson, Linaro ([投影片](https://events.linuxfoundation.org/wp-content/uploads/2017/12/Kernel-Analysis-Using-eBPF-Daniel-Thompson-Linaro.pdf)/[影片](https://www.youtube.com/watch?v=AZTtTgni7LQ))

### LLVM 與 Clang 在 eBPF 開發中的角色
直接撰寫 eBPF bytecode 並不容易。而 Clang (基於 LLVM 的 C/C++ 編譯器前端) 從 3.7 版本開始支援 BPF 後端，這意味著可以**將 C 語言程式碼編譯成 eBPF bytecode**。
*   `a.c`
    ```c
    int f(int x){return x + 1;}
    ```
*   **編譯 C 到 LLVM IR**:
    `$ clang -c -S -emit-llvm a.c`  (產生 `a.ll`)
*   **編譯 C 到 eBPF 組合語言**:
    `$ clang -c -S -target bpf a.c` (產生 `a.s`)
*   **編譯 C 到 eBPF ELF 物件檔**:
    `$ clang -c -target bpf a.c` (產生 `a.o`)
    ```shell
    $ file a.o
    a.o: ELF 64-bit LSB relocatable, no machine, version 1 (SYSV), not stripped
    ```
*   **從 ELF 物件檔中抽取 eBPF bytecode**:
    `$ objcopy -I elf64-little -O binary a.o a.bin`
*   **反組譯 eBPF bytecode (使用 [ubpf](https://github.com/iovisor/ubpf)-disassembler 工具)**:
    `$ ubpf-disassembler a.bin`

---
## eBPF 實際操作與範例

>[!Warning] 實驗前請確保安裝必要的套件 (以 Ubuntu 為例):
> ```shell
> $ sudo apt install -y linux-headers-$(uname -r) bpfcc-tools python3-bpfcc libbpfcc libbpfcc-dev
> ```

> [在 WSL 的安裝方法](https://hackmd.io/@Jaychao2099/wsl-ebpf)

Linux 的行程 (process) 和執行緒 (thread) 模型經歷了演變。早期 LinuxThreads 套件的實作將執行緒視為共享定址空間和資源的行程。NPTL (Native POSIX Thread Library) 的出現改進了這一點。

[clone(2)](http://man7.org/linux/man-pages/man2/clone.2.html) 系統呼叫是建立 new process 或 thread 的底層機制。它與 [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html) 的不同之處在於，`clone()` 建立的 child process 從指定的函式 (`fn` 參數) 開始執行，而 `fork()` 建立的 child process 從 `fork()` 呼叫點之後繼續執行。

以下透過 eBPF 追蹤 `clone()` 系統呼叫的使用狀況。

:::info
#### 注意：
較新版本的 Linux 核心中，`sys_clone` 符號可能已變更 (例如變為 `__x64_sys_clone` 或其他架構特定的名稱，參考 [commit d5a0052](https://github.com/torvalds/linux/commit/d5a00528b58cdb2c71206e18bd021e34c4eab878))。可以使用以下命令檢查符號是否存在：
```shell
$ cat /proc/kallsyms | grep sys_clone
# 或者針對特定架構
$ cat /proc/kallsyms | grep __x64_sys_clone
```
在後續的 Python 腳本中，`event="sys_clone"` 可能需要根據實際的核心符號進行調整。BCC 通常會嘗試自動解析，但明確指定有時是必要的。
:::

### 範例 1：追蹤 `clone` 系統呼叫觸發
`hello.py`：每當 `clone` 系統呼叫觸發，就印出訊息。
```python=
#!/usr/bin/env python3
from bcc import BPF

# eBPF program in C
prog = """
int hello_world(void *ctx) {
  bpf_trace_printk("Hello, World! sys_clone called.\\n");
  return 0;
}
"""

# Load BPF program
try:
    b = BPF(text=prog)
    b.attach_kprobe(event="sys_clone", fn_name="hello_world")
except Exception as e:
    print(f"Error attaching kprobe: {e}")
    print("Try checking /proc/kallsyms for the correct clone syscall name (e.g., __x64_sys_clone).")
    exit()

# Print messages from /sys/kernel/debug/tracing/trace_pipe
print("Tracing clone syscalls... Press Ctrl-C to end.")
try:
    # trace_print() 會持續讀取 trace_pipe
    # fmt 參數用於格式化輸出，{0} 代表行程名，{5} 代表 bpf_trace_printk 的訊息
    b.trace_print(fmt="Program:{0} Message:{5}")
except KeyboardInterrupt:
    pass
```
執行時，每當有行程呼叫 `clone`，就會在 `/sys/kernel/debug/tracing/trace_pipe` 中看到 "Hello, World! sys_clone called." 的訊息。

### 範例 2：統計 `clone` 系統呼叫次數
`count.py`：計算 `clone` 系統呼叫在一段時間內的總次數。
```python=
#!/usr/bin/env python3
from bcc import BPF
import time

# eBPF program
prog = """
#include <uapi/linux/ptrace.h>

// 定義一個名為 'stats' 的 BPF_ARRAY map
// Key type: u32 (只有一個 key, index 0)
// Leaf type: u32 (儲存記數值)
// Map size: 1 element
BPF_ARRAY(stats, u32, 1);

int count_clone(struct pt_regs *ctx) {
  u32 key = 0; // 陣列只有一個元素, 所以 key 恆為 0
  u32 *leaf = stats.lookup(&key);
  if (leaf) {
    (*leaf)++; // Increment the counter
    
    // bpf_trace_printk 用於偵錯, 可能會產生非常詳細的輸出
    bpf_trace_printk("Total clone syscalls: %u\\n", *leaf);
  }
  return 0;
}
"""

b = BPF(text=prog)
try:
    # 自動取得 clone 的名稱: "sys_clone" 或 "__x64_sys_clone" 或 其他...
    syscall_fnname = b.get_syscall_fnname("clone")
    b.attach_kprobe(event=syscall_fnname, fn_name="count_clone")
except Exception as e:
    print(f"Error attaching kprobe: {e}")
    exit()

print("Counting clone syscalls... Press Ctrl-C to print results and exit.")

# 初始化 map 中的 counter
stats_map = b.get_table("stats")
key = stats_map.Key(0) # Create a key object
leaf = stats_map.Leaf(0) # Create a leaf object with initial value 0
stats_map[key] = leaf # Initialize the counter at index 0 to 0

try:
    while True:
        time.sleep(1)
        # 定期印出或僅等待 Ctrl+C
        current_val = stats_map[key].value
        print(f"Current clone count: {current_val}", end='\r')
except KeyboardInterrupt:
    print("\nDetaching...")

# 取得並印出最終計數
final_val = stats_map[key].value
print(f"Total clone syscalls recorded: {final_val}")
```
此範例使用了 `BPF_ARRAY` 類型的 map 來儲存計數。

### 範例 3：計算 `clone` 系統呼叫的發生頻率
`freq.py`：計算 `clone` 系統呼叫在特定時間間隔內的發生頻率。
```python=
#!/usr/bin/env python3
from bcc import BPF
from time import sleep

# eBPF program
prog = """
#include <uapi/linux/ptrace.h>

// 名為 'stats' 的 BPF_ARRAY，包含一個用於計數的 u32 元素
BPF_ARRAY(stats, u32, 1);

int increment_clone_count(struct pt_regs *ctx) {
  u32 key = 0;
  // 以原子方式遞增 counter
  stats.atomic_increment(key);    // BCC 提供的 map 原子操作方法
  return 0;
}
"""

b = BPF(text=prog)
try:
    # 自動取得 clone 的名稱: "sys_clone" 或 "__x64_sys_clone" 或 其他...
    syscall_fnname = b.get_syscall_fnname("clone")
    b.attach_kprobe(event=syscall_fnname, fn_name="increment_clone_count")
except Exception as e:
    print(f"Error attaching kprobe: {e}")
    exit()

print("Calculating clone syscall frequency (per second)... Press Ctrl-C to exit.")

# 取得 BPF map
stats_map = b.get_table("stats")

interval_seconds = 1
iterations = 20

try:
    for i in range(iterations):
        # 在每個間隔開始時重設計數器
        # 或者，更常見的是，讀取數值，然後減去前一個數值
        
        # 方法一: 計算差值
        prev_val = stats_map[stats_map.Key(0)].value if i > 0 else 0
        sleep(interval_seconds)
        current_val = stats_map[stats_map.Key(0)].value
        rate = (current_val - prev_val) / interval_seconds
        print(f"Interval {i+1}: Total sys_clone per second = {rate:.2f} (Cumulative: {current_val})")
        
        # 方法二: 對於基於重置的簡單速率計算
        # (對於隨時間變化的真實頻率而言不太準確):
        # stats_map[stats_map.Key(0)] = stats_map.Leaf(0) # 重設 counter
        # sleep(interval_seconds)
        # count_in_interval = stats_map[stats_map.Key(0)].value
        # print(f"Interval {i+1}: Total sys_clone per second = {count_in_interval / interval_seconds}")
        
except KeyboardInterrupt:
    print("\nExiting...")
```
此範例展示了如何定期讀取 map 中的計數值來計算頻率。更精確的頻率計算通常涉及讀取計數值的差量。

> 延伸閱讀：
> *   [Hist Triggers in Linux 4.7](http://www.brendangregg.com/blog/2016-06-08/linux-hist-triggers.html) (雖然與 eBPF 直接相關性不大，但提供了另一種基於事件的統計思路)
> *   [BPF Socket Filtering Demo](https://github.com/MatrixAI/Overwatch/tree/master/BPFSocketFilteringDemo)：展示了 BPF 在 socket 層級過濾 UDP 封包的範例。

---

## 總結

eBPF 是 Linux 核心中一項革命性的技術，它提供了一個安全、高效、靈活的框架，用於觀察、追蹤和甚至修改核心與使用者空間的行為。

透過其核心內的虛擬機器、驗證器和高效的 Maps 資料交換機制，eBPF 克服了傳統追蹤方法 (如 `printk`, `ptrace`) 的效能瓶頸和安全問題。

結合 BCC 等開發工具包，使得開發者能夠更容易地利用 eBPF 的強大能力來診斷效能問題、進行網路監控、增強安全性以及探索作業系統的內部運作，是理解和優化現代 Linux 系統不可或缺的工具。

---

## 待整理/延伸學習
eBPF 是一個快速發展的領域，有大量資源和工具可供學習：
*   [eBPF - Understanding How It Works](https://blog.sofiane.cc/eBPFunderstanding/)
*   [Extended BPF：A New Type of Software](https://www.slideshare.net/brendangregg/um2019-bpf-a-new-type-of-software) / [video](https://youtu.be/7pmXdG8-7WU)
*   [BPF and formal verification](https://www.sccs.swarthmore.edu/users/16/mmcconv1/pl-reflection.html)
*   [From High Ceph Latency to Kernel Patch with eBPF/BCC](https://habr.com/en/company/selectel/blog/450818/)
*   [Extending Vector with eBPF to inspect host and container performance](https://medium.com/netflix-techblog/extending-vector-with-ebpf-to-inspect-host-and-container-performance-5da3af4c584b)
*   [lockstat using eBPF](https://github.com/prathyushpv/lockstat)
*   [eBPF can't count?!](https://blog.cloudflare.com/ebpf-cant-count/) (探討 eBPF map 在計數時的原子性與效能)
*   [Full-system dynamic tracing on Linux using eBPF and bpftrace](https://www.joyfulbikeshedding.com/blog/2019-01-31-full-system-dynamic-tracing-on-linux-using-ebpf-and-bpftrace.html) (bpftrace 是另一個基於 eBPF 的高階追蹤語言)
*   [awesome-ebpf](https://github.com/zoidbergwill/awesome-ebpf) (eBPF 相關資源列表)
*   [Extending the Kernel with eBPF (Android)](https://source.android.com/devices/architecture/kernel/bpf)
*   [eBPF Traffic Monitoring (Android)](https://source.android.com/devices/tech/datausage/ebpf-traffic-monitor)
*   [MicroBPF](https://github.com/alvenwong/MicroBPF)：使用 BCC 探測核心的 TCP 指標和延遲。
*   [eBPF Standard Documentation](https://github.com/ebpffoundation/ebpf-docs) (eBPF 基金會的官方文件)

---

回[主目錄](https://hackmd.io/@Jaychao2099/Linux-kernel)

---
