<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      eBPF(Linux內核安全方案)教程2快速入門

      2 快速入門

      在本章中,我將使用簡單的"Hello World"示例來讓你更好地了解它。
      在閱讀本書的過程中,你會了解到有多種不同的庫和框架可用于編寫eBPF應用程序。作為熱身,我將向你展示一種從編程角度來看可能是最易學的方法:BCC Python 框架。這為編寫基本的eBPF程序提供了一種非常簡單的方法。現在我不推薦你使用這種方法來編寫你打算發布給其他用戶的應用程序,但它非常適合你邁出第一步。

      2.1 BCC Hello World”

      BCC的安裝參見:https://github.com/iovisor/bcc/blob/master/INSTALL.md

      下面是 hello.py 的完整源代碼,它是使用 BCC 的 Python 庫編寫的 eBPF "Hello World "應用程序1:

      #!/usr/bin/python3
      from bcc import BPF
      
      program = r"""
      int hello(void *ctx) {
          bpf_trace_printk("Hello World!");
          return 0;
      }
      """
      
      b = BPF(text=program)
      syscall = b.get_syscall_fnname("execve")
      b.attach_kprobe(event=syscall, fn_name="hello")
      
      b.trace_print()
      

      這段代碼由兩部分組成:將在內核中運行的 eBPF 程序本身,以及將 eBPF 程序加載到內核中并讀出其產生的跟蹤的一些用戶空間代碼。hello.py 是該應用程序的用戶空間部分,而 hello() 則是在內核中運行的 eBPF 程序。
      image

      eBPF 程序所做的就是使用一個輔助函數 bpf_trace_printk() 來編寫一條信息。輔助函數是 "擴展 "BPF 區別于其 "經典 "前身的另一個特征。它們是一組函數,eBPF 程序可以調用這些函數與系統交互;我將在第 5 章進一步討論它們。現在,你可以把它想象成打印一行文本。

      整個 eBPF 程序在 Python 代碼中被定義為一個名為 program 的字符串。這個 C 語言程序在執行之前需要編譯,但 BCC 可以幫你完成。eBPF 程序需要附加到一個事件中,在本例中,我選擇附加到系統調用 execve 中,它是用來執行程序的系統調用。每當有任何事物或任何人在這臺機器上啟動一個新程序時,都會調用 execve(),從而觸發 eBPF 程序。雖然 "execve() "名稱是 Linux 的標準接口,但內核中實現它的函數名稱取決于芯片架構,不過 BCC 提供了一種方便的方法,讓我們可以查找所運行機器的函數名稱:syscall = b.get_syscall_fnname("execve") 。

      現在,syscall 表示我要使用 kprobe 附加的內核函數名稱(第 1 章中介紹了 kprobe 的概念):b.attach_kprobe(event=syscall, fn_name="hello") 。
      至此,eBPF 程序已加載到內核中,并附加到一個事件上,因此每當有新的可執行文件在機器上啟動時,程序就會被觸發。在 Python 代碼中要做的就是讀取內核輸出的跟蹤信息并將其寫入屏幕:
      b.trace_print() 。這個 trace_print() 函數將無限循環(直到您停止程序,也許用 Ctrl+C 停止),顯示任何跟蹤。

      下圖展示了這段代碼。Python 程序編譯 C 代碼,將其加載到內核,并將其連接到 execve_syscall_kprobe。只要該(虛擬)機器上的任何應用程序調用 execve(),就會觸發 eBPF hello() 程序,該程序會將一行跟蹤記錄寫入特定的偽文件。(Python 程序會從偽文件中讀取跟蹤信息并顯示給用戶。

      image

      2.2 運行“Hello World”

      運行此程序,根據您所使用的(虛擬)機器上的情況,您可能會立即看到跟蹤信息生成,因為其他進程可能正在使用execve系統調用執行程序。如果您沒有看到任何內容,請打開第二個終端并執行任何您喜歡的命令,您將看到“Hello World”生成的相應跟蹤信息:

      # python hello.py
      b'           agent-3189934 [075] d... 5010918.056552: bpf_trace_printk: Hello World!'
      b'            bash-3189935 [076] d... 5010918.057489: bpf_trace_printk: Hello World!'
      b'            bash-3189937 [077] d... 5010918.058402: bpf_trace_printk: Hello World!'
      b'            bash-3189938 [078] d... 5010918.058468: bpf_trace_printk: Hello World!'
      b'           <...>-3189939 [079] d... 5010918.058530: bpf_trace_printk: Hello World!'
      b'            bash-3189940 [080] d... 5010918.058646: bpf_trace_printk: Hello World!'
      b'            bash-3189943 [080] d... 5010918.061652: bpf_trace_printk: Hello World!'
      b'            bash-3189945 [079] d... 5010918.062962: bpf_trace_printk: Hello World!'
      

      由于eBPF非常強大,使用它需要特殊權限。權限會自動分配給root用戶,因此運行eBPF程序最簡單的方法是以root身份運行,例如使用sudo。

      CAP_BPF 在內核版本 5.8 中引入,它提供了足夠的權限來執行某些 eBPF 操作,如創建特定類型的映射。然而,您可能還需要額外權限:

      • CAP_PERFMON 和 CAP_BPF 均需啟用才能加載跟蹤程序。
      • CAP_NET_ADMIN 和 CAP_BPF 均需啟用才能加載網絡程序。

      關于此內容的更多細節,請參閱 Milan Landaverde 的博客文章《CAP_BPF 入門》(https://mdaverde.com/posts/cap-bpf/)。

      一旦 hello eBPF 程序加載并綁定到事件,它將被現有進程生成的事件觸發。這應強化你在第 1 章中學習的幾個要點:

      • eBPF 程序可用于動態更改系統行為。無需重啟機器或重新啟動現有進程。eBPF 代碼在綁定到事件后立即生效。
      • 無需對其他應用程序進行任何修改即可使其對eBPF可見。只要您在該機器上擁有終端訪問權限,無論在終端中運行何種可執行文件,該文件都會調用execve()系統調用。若您已將hello程序附加到該系統調用,它將被觸發以生成跟蹤輸出。同樣,若您運行一個調用可執行文件的腳本,該腳本也會觸發hello eBPF程序。您無需對終端的 shell、腳本或正在運行的可執行文件進行任何更改。

      跟蹤輸出不僅顯示“Hello World”字符串,還包含觸發 hello eBPF 程序運行的事件的一些額外上下文信息。在本節開頭顯示的示例輸出中,執行 execve 系統調用的進程的進程 ID 為 3189935,且正在運行 bash 命令。對于跟蹤消息,此上下文信息作為內核跟蹤基礎設施的一部分添加(該基礎設施并非特定于 eBPF),但如本章后文所述,您也可以在 eBPF 程序內部檢索此類上下文信息。

      你可能會好奇 Python 代碼如何知道從哪里讀取跟蹤輸出。答案并不復雜——內核中的 bpf_trace_printk() 輔助函數始終將輸出發送到同一個預定義的偽文件位置:/sys/kernel/debug/tracing/trace_pipe。你可以使用 cat 命令查看其內容;訪問該文件需要 root 權限。

      對于簡單的“Hello World”示例或基本調試目的,單一跟蹤管道位置已足夠,但其功能非常有限。輸出格式幾乎沒有靈活性,且僅支持字符串輸出,因此不適合傳遞結構化信息。更重要的是,整個(虛擬)機器上僅存在一個此類位置。若有多個 eBPF 程序同時運行,它們將把跟蹤輸出寫入同一跟蹤管道,這會讓人工操作者感到非常困惑。

      從 eBPF 程序中獲取信息的更好方式是使用 eBPF 映射。

      2.3 BPF映射

      映射是一種數據結構,可從 eBPF 程序和用戶空間訪問。映射是擴展 BPF 與經典 BPF 相比的真正顯著特征之一。(你可能會認為這意味著它們通常被稱為“eBPF 映射”,但你經常會看到“BPF 映射”。與通常情況一樣,這兩個術語可以互換使用。)

      映射可用于在多個 eBPF 程序之間共享數據,或在用戶空間應用程序與內核中運行的 eBPF 代碼之間進行通信。典型用途包括:

      • 用戶空間將配置信息寫入映射,供 eBPF 程序讀取
      • eBPF 程序存儲狀態信息,供另一個 eBPF 程序(或同一程序的未來運行實例)后續讀取
      • eBPF 程序將結果或指標寫入映射,供用戶空間應用程序檢索并展示結果

      在 Linux 的uapi/linux/bpf.h文件中定義了多種類型的 BPF 映射,內核文檔中也對它們有相關說明。總體而言,它們都是鍵值存儲結構,本章將展示用于哈希表、性能監控(perf)和環形緩沖區(ring buffers)的映射示例,以及 eBPF 程序數組的映射示例。

      某些映射類型被定義為數組,其鍵類型始終為 4 字節索引;其他映射則是哈希表,可使用任意數據類型作為鍵。

      還有一些映射類型針對特定類型的操作進行了優化,例如先進先出隊列、先進后出棧、最近最少使用數據存儲、最長前綴匹配以及布隆過濾器(一種概率數據結構,設計用于快速判斷元素是否存在)。

      某些 eBPF 映射類型存儲特定類型對象的信息。例如,sockmaps 和 devmaps 存儲關于套接字和網絡設備的信息,并被網絡相關的 eBPF 程序用于重定向流量。程序數組映射存儲一組索引的 eBPF 程序,(如本章后文所述)這用于實現尾調用,即一個程序可以調用另一個程序。甚至還有一種映射的映射類型,用于存儲關于映射的信息。

      某些映射類型具有按 CPU 核心劃分的變體,也就是說,內核為每個 CPU 核心的映射版本使用不同的內存塊。這可能會讓你對非按 CPU 核心劃分的映射的并發問題產生疑問,因為多個 CPU 核心可能同時訪問同一映射。在內核版本 5.1 中為(某些)映射添加了自旋鎖支持,我們將在第 5 章中再次討論這一主題。

      下一個示例(hello-map.py)展示了使用哈希表映射的一些基本操作。它還演示了 BCC 的一些便捷抽象,使使用映射變得非常簡單。

      2.3.1 哈希表映射

      與本章之前的示例類似,此 eBPF 程序將附加到 execve 系統調用入口處的 kprobe。它將用鍵值對填充哈希表,其中鍵是用戶 ID,值是該用戶 ID 下運行的進程調用 execve 的次數計數器。實際上,這個示例將顯示每個不同用戶運行程序的次數。

      首先,讓我們看看 eBPF 程序本身的 C 代碼:

      hello-map.py:

      #!/usr/bin/python3
      from bcc import BPF
      from time import sleep
      
      program = r"""
      BPF_HASH(counter_table);
      
      int hello(void *ctx) {
         u64 uid;
         u64 counter = 0;
         u64 *p;
      
         uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
         p = counter_table.lookup(&uid);
         if (p != 0) {
            counter = *p;
         }
         counter++;
         counter_table.update(&uid, &counter);
         return 0;
      }
      """
      
      b = BPF(text=program)
      syscall = b.get_syscall_fnname("execve")
      b.attach_kprobe(event=syscall, fn_name="hello")
      
      # Attach to a tracepoint that gets hit for all syscalls
      # b.attach_raw_tracepoint(tp="sys_enter", fn_name="hello")
      
      while True:
          sleep(2)
          s = ""
          for k,v in b["counter_table"].items():
              s += f"ID {k.value}: {v.value}\t"
          print(s)
      
      

      BPF_HASH() 是 BCC 宏,用于定義哈希表映射。bpf_get_current_uid_gid() 是一個輔助函數,用于獲取觸發此 kprobe 事件的進程的用戶 ID。用戶 ID 存儲在返回的 64 位值的最低 32 位中。(最高 32 位存儲組 ID,但該部分被屏蔽。)在哈希表中查找與用戶 ID 匹配的鍵的條目。它返回哈希表中對應值的指針。如果哈希表中存在該用戶 ID 的條目,將計數器變量設置為哈希表中當前值(由 p 指針指向)。如果哈希表中不存在該用戶 ID 的條目,指針將為 0,計數器值保持為 0。無論當前計數器值為何,均將其加1。
      C 語言不支持在結構體上定義方法,說明 BCC 的 C 語言版本是一種非常松散的 C 樣式語言,BCC 會在將代碼發送給編譯器之前對其進行重寫。BCC 提供了一些方便的快捷方式和宏,它會將這些轉換為“標準”的 C 代碼。

      執行結果:

      # python hello-map.py
      
      ID 0: 1 ID 1084: 1
      ID 0: 1 ID 1084: 16
      ID 0: 2 ID 1084: 17
      ID 0: 2 ID 1084: 32
      
      # python hello-map.py
      ID 1084: 1      ID 0: 1
      ID 1084: 16     ID 0: 1
      ID 1084: 16     ID 0: 1
      ID 1084: 17     ID 0: 2
      ID 1084: 32     ID 0: 2
      
      

      $ ./hello-map.py

      此示例每兩秒生成一行輸出,無論是否發生任何變化。在輸出結束時,哈希表中包含兩個條目:

      在第二個終端中,我的用戶 ID 為 1084。使用此用戶 ID 運行 ls 命令會增加 execve 計數器。當我運行 sudo ls 時,這會導致兩次 execve 調用:一次是 sudo 的執行,用戶 ID 為 501;另一次是 ls 的執行,用戶 ID 為 root 的 0。

      image

      在此示例中,我使用哈希表將數據從 eBPF 程序傳遞到用戶空間。(我也可以在此處使用數組類型的映射,因為鍵是一個整數;哈希表允許使用任意類型作為鍵。)當數據自然以鍵值對形式存在時,哈希表非常方便,但用戶空間代碼必須定期輪詢該表。Linux 內核已支持 perf 子系統,用于從內核向用戶空間發送數據,而 eBPF 包含對 perf 緩沖區及其繼任者 BPF 環形緩沖區的支持。讓我們來看看。

      2.3.2 Perf 和環形緩沖區映射(Ring Buffer Map)

      在本節中,我將描述一個略微復雜的“Hello World”示例,該示例使用 BCC 的 BPF_PERF_OUTPUT 功能,允許你將數據寫入自定義結構的 perf 環形緩沖區映射中。

      有一種較新的構造稱為“BPF 環形緩沖區”,現在通常優于 BPF perf 緩沖區,如果你使用的是 5.8 版本或更高的內核。Andrii Nakryiko 在他的 BPF 環形緩沖區博客文章中討論了它們的區別。你將在第 4 章中看到 BCC 的 BPF_RINGBUF_OUTPUT 的示例。

      • 環形緩沖區

      環形緩沖區絕非 eBPF 獨有,但我還是解釋一下,以防您之前未接觸過。您可以將環形緩沖區視為邏輯上以環形組織的一塊內存,擁有獨立的“寫入”和“讀取”指針。任意長度的數據會被寫入寫入指針所在的位置,數據的長度信息包含在該數據的頭部中。寫入指針會移動到該數據末尾之后,準備進行下一次寫入操作。

      同樣,對于讀取操作,數據會從讀取指針所在的位置讀取,并通過頭部信息確定讀取多少數據。讀取指針與寫入指針沿相同方向移動,以便指向下一個可用的數據塊。如圖所示,這是一個包含三個不同長度數據項的環形緩沖區,這些數據項可供讀取。

      如果讀指針追上寫指針,這僅僅意味著沒有數據可讀。如果寫操作會導致寫指針超過讀指針,數據將不會被寫入,且丟失計數器會被遞增。讀操作會包含丟失計數器,以指示自上次成功讀取以來是否丟失了數據。

      如果讀寫操作以完全相同的速率且無波動發生,且每次操作包含相同數量的數據,那么理論上可以使用一個剛好能容納該數據大小的環形緩沖區。但在大多數應用中,讀寫操作之間的時間間隔會存在波動,因此緩沖區大小需要進行調整以適應這種情況。

      image

      代碼:

      #!/usr/bin/python3
      from bcc import BPF
      
      program = r"""
      BPF_PERF_OUTPUT(output);
      
      struct data_t {
         int pid;
         int uid;
         char command[16];
         char message[12];
      };
      
      int hello(void *ctx) {
         struct data_t data = {};
         char message[12] = "Hello World";
      
         data.pid = bpf_get_current_pid_tgid() >> 32;
         data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
      
         bpf_get_current_comm(&data.command, sizeof(data.command));
         bpf_probe_read_kernel(&data.message, sizeof(data.message), message);
      
         output.perf_submit(ctx, &data, sizeof(data));
      
         return 0;
      }
      """
      
      b = BPF(text=program)
      syscall = b.get_syscall_fnname("execve")
      b.attach_kprobe(event=syscall, fn_name="hello")
      
      def print_event(cpu, data, size):
         data = b["output"].event(data)
         print(f"{data.pid} {data.uid} {data.command.decode()} {data.message.decode()}")
      
      b["output"].open_perf_buffer(print_event)
      while True:
         b.perf_buffer_poll()
      

      BCC 定義了宏 BPF_PERF_OUTPUT,用于創建一個用于從內核向用戶空間傳遞消息的映射。我將此映射命名為 output。每次運行 hello() 時,代碼將寫入一個結構體的數據。這是該結構體的定義,其中包含進程 ID、當前運行命令的名稱以及一條文本消息的字段。data 是用于存儲待提交數據結構的局部變量,而 message 則存儲“Hello World”字符串。bpf_get_current_pid_tgid() 是一個輔助函數,用于獲取觸發此 eBPF 程序運行的進程 ID。它返回一個 64 位值,其中前 32 位為進程 ID。bpf_get_current_uid_gid() 是你在前一個示例中看到的用于獲取用戶 ID 的輔助函數。同樣,bpf_get_current_comm() 是一個輔助函數,用于獲取在執行 execve 系統調用的進程中運行的可執行文件(或“命令”)的名稱。這是一個字符串,而非像進程 ID 和用戶 ID 那樣的數值。在 C 語言中,你不能直接使用 = 賦值字符串。你需要將字符串應寫入的字段地址 &data.command 作為參數傳遞給輔助函數。對于這個示例,消息始終是“Hello World”。bpf_probe_read_kernel() 將其復制到數據結構中的正確位置。

      執行:

      # python hello-buffer.py
      3707042 1084 agent Hello World
      3707043 1084 bash Hello World
      3707045 1084 bash Hello World
      3707046 1084 bash Hello World
      3707047 1084 bash Hello World
      3707048 1084 bash Hello World
      3707051 1084 bash Hello World
      3707053 1084 bash Hello World
      3707056 1084 bash Hello World
      3707058 1084 bash Hello World
      3707061 1084 bash Hello World
      3707064 1084 bash Hello World
      3707067 1084 bash Hello World
      3707069 1084 bash Hello World
      3707072 1084 bash Hello World
      3707081 1084 agent Hello World
      3707082 0 agent Hello World
      3707085 1084 agent Hello World
      3707086 1084 bash Hello World
      3707088 1084 bash Hello World
      3707089 1084 bash Hello World
      3707090 1084 bash Hello World
      3707091 1084 bash Hello World
      3707094 1084 bash Hello World
      

      $ sudo ./hello-buffer.py
      11654 node Hello World
      11655 sh Hello World
      ...
      與之前一樣,你可能需要在同一臺(虛擬)機器上打開第二個終端并執行一些命令以觸發輸出。

      與原始“Hello World”示例的最大區別在于,不再使用單一的中央跟蹤管道,而是通過名為 output 的環形緩沖區映射傳遞數據,該映射由本程序為自身使用而創建,如圖所示。

      image

      你可以通過執行 cat /sys/kernel/debug/tracing/trace_pipe 來驗證信息是否未發送到跟蹤管道。

      除了演示環形緩沖區映射的使用外,此示例還展示了用于獲取觸發 eBPF 程序運行的事件上下文信息的 eBPF 輔助函數。在此您已看到輔助函數用于獲取用戶 ID、進程 ID 以及當前命令名稱。如第 7 章所示,可用的上下文信息集以及可用于檢索這些信息的有效輔助函數集,取決于程序類型及觸發該程序的事件類型。

      此類上下文信息可供eBPF代碼訪問,正是其在可觀察性方面如此有價值的原因。每次發生事件時,eBPF程序不僅可以報告事件發生的事實,還可以報告觸發事件的相關信息。它還具有很高的性能,因為所有這些信息都可以在內核中收集,無需進行任何同步上下文切換到用戶空間。

      本書后續內容將展示更多示例,其中 eBPF 輔助函數用于收集其他上下文數據,以及 eBPF 程序修改上下文數據甚至完全阻止事件發生的示例。

      參考資料

      2.3.3 函數調用

      您已看到 eBPF 程序可調用內核提供的輔助函數,但若想將所寫代碼拆分為函數呢?在軟件開發中,通常認為將常見代碼提取到一個函數中,以便從多個位置調用,而不是重復編寫相同的代碼行,是一種良好的實踐。但在早期,eBPF 程序不允許調用除輔助函數以外的其他函數。為了繞過這一限制,程序員會指示編譯器“始終內聯”他們的函數,如下所示:

      static __always_inline void my_function(void *ctx, int val)
      

      通常,源代碼中的函數會導致編譯器生成跳轉指令,使執行流程跳轉到被調用函數的指令集(并在該函數完成后跳回)。如圖2-5左側所示。右側展示了函數被內聯時的行為:沒有跳轉指令;取而代之的是,函數的指令副本直接在調用函數內部生成。

      image

      如果函數從多個位置被調用,這將導致編譯后的可執行文件中包含該函數指令的多份副本。(有時編譯器可能會出于優化目的選擇內聯函數,這也是為什么你可能無法將 kprobe 附加到某些內核函數的原因。我將在第 7 章中再次討論這一點。)

      從 Linux 內核 4.16 和 LLVM 6.0 開始,取消了要求函數必須內聯的限制,以便 eBPF 程序員能夠更自然地編寫函數調用。然而,這一功能(稱為“BPF 到 BPF 函數調用”或“BPF 子程序”)目前尚未被 BCC 框架支持,因此我們將在下一章中再討論它。(當然,如果你使用的是內聯函數,仍然可以繼續使用 BCC。)

      在 eBPF 中,還有另一種將復雜功能分解為更小部分的機制:尾調用。

      2.3.4 Tail調用

      如 ebpf.io 所述,“尾調用可以調用并執行另一個 eBPF 程序,并替換執行上下文,類似于 execve() 系統調用在常規進程中的工作方式。” 換句話說,尾調用完成后,執行不會返回給調用者。

      尾調用絕非 eBPF 編程的專屬特性。尾調用的核心動機是避免在函數遞歸調用時反復向棧中添加幀,這最終可能導致棧溢出錯誤。如果你能將代碼安排為在最后一步調用遞歸函數,則與調用函數關聯的棧幀實際上并未執行任何有用操作。尾調用允許在不增長棧的情況下調用一系列函數。這在eBPF中尤為有用,因為其棧大小僅限于512字節。

      尾調用通過 bpf_tail_call() 輔助函數實現,其簽名如下:

      long bpf_tail_call(void *ctx, struct bpf_map *prog_array_map, u32 index)
      

      該函數的三個參數含義如下:

      • ctx 用于將調用 eBPF 程序的上下文傳遞給被調用函數。
      • prog_array_map 是類型為 BPF_MAP_TYPE_PROG_ARRAY 的 eBPF 映射,其中存儲了一組用于標識 eBPF 程序的文件描述符。
      • index 指示應調用該映射中的哪個 eBPF 程序。

      該輔助函數較為特殊,因為如果調用成功,它不會返回。當前運行的 eBPF 程序將被調用的程序替換到棧中。該輔助函數可能失敗,例如,如果指定的程序不存在于映射中,此時調用程序將繼續執行。

      用戶空間代碼需要將所有 eBPF 程序加載到內核中(如往常一樣),并設置程序數組映射。

      讓我們看一個用 Python 和 BCC 編寫的簡單示例:主 eBPF 程序附加在所有系統調用的通用入口點的跟蹤點上。該程序使用尾調用跟蹤特定系統調用操作碼的特定消息。如果某個操作碼沒有尾調用,程序將跟蹤一個通用消息。

      prog_array_map.call(ctx, index)
      

      如果你使用 BCC 框架,要實現尾調用可以使用稍顯簡化的形式:

      bpf_tail_call(ctx, prog_array_map, index)
      

      完整代碼

      #!/usr/bin/python3
      from bcc import BPF
      import ctypes as ct
      
      program = r"""
      BPF_PROG_ARRAY(syscall, 500);
      
      int hello(struct bpf_raw_tracepoint_args *ctx) {
          int opcode = ctx->args[1];
          syscall.call(ctx, opcode);
          bpf_trace_printk("Another syscall: %d", opcode);
          return 0;
      }
      
      int hello_exec(void *ctx) {
          bpf_trace_printk("Executing a program");
          return 0;
      }
      
      int hello_timer(struct bpf_raw_tracepoint_args *ctx) {
          int opcode = ctx->args[1];
          switch (opcode) {
              case 222:
                  bpf_trace_printk("Creating a timer");
                  break;
              case 226:
                  bpf_trace_printk("Deleting a timer");
                  break;
              default:
                  bpf_trace_printk("Some other timer operation");
                  break;
          }
          return 0;
      }
      
      int ignore_opcode(void *ctx) {
          return 0;
      }
      """
      
      b = BPF(text=program)
      b.attach_raw_tracepoint(tp="sys_enter", fn_name="hello")
      
      ignore_fn = b.load_func("ignore_opcode", BPF.RAW_TRACEPOINT)
      exec_fn = b.load_func("hello_exec", BPF.RAW_TRACEPOINT)
      timer_fn = b.load_func("hello_timer", BPF.RAW_TRACEPOINT)
      
      prog_array = b.get_table("syscall")
      
      # Ignore all syscalls initially
      for i in range(len(prog_array)):
          prog_array[ct.c_int(i)] = ct.c_int(ignore_fn.fd)
      
      # Only enable few syscalls which are of the interest
      prog_array[ct.c_int(59)] = ct.c_int(exec_fn.fd)
      prog_array[ct.c_int(222)] = ct.c_int(timer_fn.fd)
      prog_array[ct.c_int(223)] = ct.c_int(timer_fn.fd)
      prog_array[ct.c_int(224)] = ct.c_int(timer_fn.fd)
      prog_array[ct.c_int(225)] = ct.c_int(timer_fn.fd)
      prog_array[ct.c_int(226)] = ct.c_int(timer_fn.fd)
      
      b.trace_print()
      

      BCC 提供了一個 BPF_PROG_ARRAY 宏,用于輕松定義類型為 BPF_MAP_TYPE_PROG_ARRAY 的映射。我將該映射命名為 syscall,并允許包含 300 個條目,這對于本示例而言已足夠。 在稍后看到的用戶空間代碼中,我將把這個 eBPF 程序附加到 sys_enter 原生跟蹤點,該跟蹤點在每次系統調用時觸發。附加到原生跟蹤點的 eBPF 程序接收的上下文采用 bpf_raw_tracepoint_args 結構的形式。在 sys_enter 的情況下,原始跟蹤點參數包括標識正在調用哪個系統調用的操作碼。這里我們對程序數組中與操作碼匹配的條目進行尾調用。這行代碼將在 BCC 將源代碼傳遞給編譯器之前,被重寫為對 bpf_tail_call() 輔助函數的調用。如果尾調用成功,這行輸出操作碼值的代碼將永遠不會被執行。我使用此方法為映射中沒有程序條目的操作碼提供默認跟蹤行。hello_exec() 是一個將被加載到系統調用程序數組映射中的程序,當操作碼指示為 execve() 系統調用時,將作為尾調用執行。它僅會生成一條跟蹤行,告知用戶正在執行新程序。hello_timer() 是另一個將被加載到系統調用程序數組中的程序。在此情況下,它將被程序數組中的多個條目引用。ignore_opcode() 是一個尾調用程序,不執行任何操作。我將使用它來處理那些不希望生成任何跟蹤信息的系統調用。
      現在讓我們看看用戶空間代碼如何加載和管理這組 eBPF 程序:與之前看到的不同,這次用戶空間代碼將主 eBPF 程序附加到 sys_enter 跟蹤點,而不是附加到 kprobe。對 b.load_func() 的調用會為每個尾調用程序返回一個文件描述符。請注意,尾調用程序必須與父程序具有相同的程序類型——在本例中為 BPF.RAW_TRACEPOINT。此外,值得指出的是,每個尾調用程序本身就是一個獨立的 eBPF 程序。

      用戶空間代碼會在系統調用映射中創建條目。該映射無需為每個可能的操作碼都填充完整;如果某個操作碼沒有對應條目,則表示不會執行尾調用。此外,多個條目指向同一個eBPF程序也是完全可以的。在此情況下,我希望hello_timer()尾調用在任何與定時器相關的系統調用時被執行。某些系統調用被系統頻繁調用,導致每條調用都會在跟蹤輸出中占用一行,使輸出變得難以閱讀。我已為多個系統調用使用了ignore_opcode()尾調用。

      執行:

      # python hello-tail.py
      b'          python-3839308 [112] d... 5072367.259263: bpf_trace_printk: Another syscall: 280'
      b'          auditd-3295    [092] d... 5072367.259381: bpf_trace_printk: Another syscall: 207'
      b'          auditd-3295    [092] d... 5072367.259391: bpf_trace_printk: Another syscall: 64'
      b'          python-3839308 [112] d... 5072367.259399: bpf_trace_printk: Another syscall: 280'
      b'          auditd-3295    [092] d... 5072367.259400: bpf_trace_printk: Another syscall: 98'
      b'          auditd-3295    [092] d... 5072367.259404: bpf_trace_printk: Another syscall: 72'
      b'          auditd-3298    [084] d... 5072367.259408: bpf_trace_printk: Another syscall: 98'
      b'          auditd-3298    [084] d... 5072367.259412: bpf_trace_printk: Another syscall: 64'
      b'          auditd-3298    [084] d... 5072367.259417: bpf_trace_printk: Another syscall: 98'
      b'      sedispatch-3297    [085] d... 5072367.259420: bpf_trace_printk: Another syscall: 63'
      b'      sedispatch-3297    [085] d... 5072367.259427: bpf_trace_printk: Another syscall: 72'
      b'          auditd-3295    [092] d... 5072367.259625: bpf_trace_printk: Another syscall: 207'
      b'          auditd-3295    [092] d... 5072367.259633: bpf_trace_printk: Another syscall: 64'
      b'          auditd-3295    [092] d... 5072367.259641: bpf_trace_printk: Another syscall: 44'
      b'          auditd-3295    [092] d... 5072367.259653: bpf_trace_printk: Another syscall: 98'
      b'          python-3839308 [112] d... 5072367.259655: bpf_trace_printk: Another syscall: 280'
      b'          auditd-3295    [092] d... 5072367.259659: bpf_trace_printk: Another syscall: 72'
      b'          auditd-3298    [084] d... 5072367.259660: bpf_trace_printk: Another syscall: 98'
      b'          auditd-3298    [084] d... 5072367.259662: bpf_trace_printk: Another syscall: 64'
      b'          auditd-3298    [084] d... 5072367.259666: bpf_trace_printk: Another syscall: 98'
      b'      sedispatch-3297    [085] d... 5072367.259668: bpf_trace_printk: Another syscall: 63'
      b'      sedispatch-3297    [085] d... 5072367.259673: bpf_trace_printk: Another syscall: 72'
      b'          auditd-3295    [092] d... 5072367.259882: bpf_trace_printk: Another syscall: 207'
      b'          auditd-3295    [092] d... 5072367.259890: bpf_trace_printk: Another syscall: 64'
      b'          auditd-3295    [092] d... 5072367.259897: bpf_trace_printk: Another syscall: 44'
      b'          auditd-3295    [092] d... 5072367.259903: bpf_trace_printk: Another syscall: 98'
      b'          auditd-3295    [092] d... 5072367.259908: bpf_trace_printk: Another syscall: 72'
      b'          auditd-3298    [084] d... 5072367.259908: bpf_trace_printk: Another syscall: 98'
      b'          auditd-3298    [084] d... 5072367.259910: bpf_trace_printk: Another syscall: 64'
      b'          auditd-3298    [084] d... 5072367.259914: bpf_trace_printk: Another syscall: 98'
      b'      sedispatch-3297    [085] d... 5072367.259915: bpf_trace_printk: Another syscall: 63'
      b'      sedispatch-3297    [085] d... 5072367.259923: bpf_trace_printk: Another syscall: 72'
      b'CPU:50 [LOST 113 EVENTS]'
      b'          pstree-3839310 [050] d... 5072367.259962: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.259973: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.259981: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.259984: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.259989: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.259993: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.259997: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260002: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260006: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260016: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260023: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260029: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260034: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260037: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260041: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260045: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260049: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260055: bpf_trace_printk: Another syscall: 61'
      b'          pstree-3839310 [050] d... 5072367.260058: bpf_trace_printk: Another syscall: 57'
      b'          pstree-3839310 [050] d... 5072367.260063: bpf_trace_printk: Another syscall: 57'
      b'          pstree-3839310 [050] d... 5072367.260066: bpf_trace_printk: Another syscall: 56'
      b'          pstree-3839310 [050] d... 5072367.260074: bpf_trace_printk: Another syscall: 79'
      b'          pstree-3839310 [050] d... 5072367.260076: bpf_trace_printk: Another syscall: 80'
      

      具體執行的系統調用并不重要,但你可以看到不同的尾調用被調用并生成跟蹤消息。你還可以看到默認消息“另一個系統調用”,用于那些在尾調用程序映射中沒有條目的操作碼。參考: Paul Chaignon 關于不同內核版本中 BPF 尾調用成本的博客文章

      尾調用自內核版本 4.2 起在 eBPF 中得到支持,但長期以來與 BPF 到 BPF 函數調用不兼容。這一限制在內核 5.10.10 中被取消。由于可以將多達 33 個尾調用串聯起來,再加上每個 eBPF 程序的指令復雜度限制為 100 萬條指令,這意味著當今的 eBPF 程序員在內核中編寫非常復雜的代碼時有很大的靈活性。

      2.4 小結

      我希望通過展示一些 eBPF 程序的具體示例,本章能幫助你鞏固對在內核中由事件觸發的 eBPF 代碼的理解。你還看到了使用 BPF 映射將數據從內核傳遞到用戶空間的示例。

      使用BCC框架隱藏了程序構建、加載到內核以及與事件關聯的許多細節。在下一章中,我將向您展示一種不同的“Hello World”編寫方法,并深入探討這些隱藏的細節。

      posted @ 2025-07-01 11:28  磁石空杯  閱讀(213)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 欧美成人www免费全部网站| 四虎永久免费影库二三区| 久草国产视频| 国产性色的免费视频网站| 亚洲国产熟女一区二区三区| 亚洲精品一区国产| 国产精品天天看天天狠| 乱人伦中文字幕成人网站在线| 亚洲大尺度无码专区尤物| 国产在线精彩自拍视频| 2019nv天堂香蕉在线观看| 国内少妇偷人精品免费| 又黄又无遮挡AAAAA毛片| 377P欧洲日本亚洲大胆| 日本一区二区三区视频版| 亚洲精品无码乱码成人| 日韩成人无码影院| 青青草一区在线观看视频| 99中文字幕国产精品| 一区二区三区在线色视频| 国产喷水1区2区3区咪咪爱av| 亚洲中文字幕在线二页| 国产成人a在线观看视频免费| 亚洲AV日韩精品久久久久| 忘忧草在线社区www中国中文| 丁香五月婷激情综合第九色| 日日猛噜噜狠狠扒开双腿小说| 久久久精品国产精品久久| 亚洲韩国精品无码一区二区三区| 国产一区二区三区自拍视频| 中国china露脸自拍性hd| 日本亚洲欧洲无免费码在线| 亚洲最大有声小说AV网| 国产短视频精品一区二区| 国产精品二区中文字幕| 视频一区视频二区制服丝袜| 日韩在线观看 一区二区| 欧美高清精品一区二区 | 麻豆国产AV剧情偷闻女邻居内裤| 四虎国产精品永久在线| 麻豆一区二区三区香蕉视频|