Tuesday, March 29, 2022

[libbpf] 為何要使用 libbpf? 什麼是libbpfgo?


在開始說清楚講明白什麼是libbpfgo之前,我們可以先看這一投影片介紹初學者使用Go開發eBPF程式指南: Beginner's Guide to eBPF programming with Go,此作者也提供 GitHub: https://github.com/lizrice/libbpfgo-beginners,可體驗一下用libbpfgo開發的小範例程式Basic eBPF examples in Golang using libbpfgo.


看過了簡單的小範例之後,接下來這篇文章主要是解釋什麼是vmlinux.h 以及為什麼在編寫 eBPF 程序時應該開始使用它: What is vmlinux.h and Why is It Important for Your eBPF Programs?

重點如下:

eBPF 程序需要知道它們正在處理什麼數據結構。您可以通過簡單的一行來實現:#include "vmlinux.h"。

vmlinux.h 簡而言之是生成的代碼。它包含正在運行的 Linux 內核在其自己的源代碼中使用的所有類型定義。構建 Linux 時,其中一個輸出工件是一個名為vmlinux的文件。它通常也與主要發行版打包在一起。這是一個ELF二進製文件,其中包含已編譯的可引導內核。

有一個工具,恰當地命名為bpftool,在 Linux 存儲庫中維護。它具有讀取vmlinux 目標文件並生成vmlinux.h 文件的功能。由於它包含已安裝內核使用的所有類型定義,因此它是一個非常大的頭文件。

實際命令為: 

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

現在,當import此header file時,你的 bpf 程序可以讀取原始內存並知道哪些字節對應於您要使用的結構的哪些字段。

由於vmlinux.h文件是從你安裝的內核生成的,因此如果你嘗試在運行不同內核版本的另一台機器上運行它而不重新編譯,你的 bpf 程序可能會中斷。這是因為,從版本到版本,內部結構的定義在 linux 源代碼中會發生變化。

但是,使用 libbpf 可以啟用稱為"CO:RE"或是稱為 "編譯一次,到處運行"的東西。libbpf 中定義了一些Macro(例如BPF_CORE_READ),它們將分析你嘗試訪問的vmlinux.h中定義的類型中的哪些字段。如果你要訪問的字段已在運行內核使用的結構定義中移動,宏/幫助程序將為你找到它。因此,是否使用 從自己的內核生成的vmlinux.h文件編譯 bpf 程序然後在另一個內核上運行它並不重要。


接下來重點來了,為何要使用 libbpf? 

這篇文章說明得很好 Libbpf: A Beginners Guide

Libbpf 是一組用於構建 BPF 應用程序的替代工具。對於網絡、安全和分析應用程序,它提供了優於 BCC 的幾個潛在優勢。


Libbpf 和 BPF CO-RE

Libbpf 通常與 BPF CO-RE 一起使用(編譯一次,到處運行)。BPF CO-RE 旨在解決 BPF 的可移植性問題,允許您創建在不同內核版本上運行的二進製文件。

它包括BPF 類型格式 (BTF)信息。這意味著您需要使用在編譯時設置了CONFIG_DEBUG_INFO_BTF=y的內核構建。如果您使用的是標準的消費者 Linux 版本,則需要進行自定義編譯以啟用此功能;否則,你會遇到錯誤。


Libbpf 相對於 BCC 的優勢

Libbpf 通過消除各種令人頭疼的問題,使開發人員可以專注於手頭的任務。

它生成簡單的二進製文件,編譯一次就可以在任何地方運行。它消除了許多依賴關係並儘可能接近一對一地執行您的代碼。

需要安裝以運行使用 BCC 編譯的程序的 LLVM、內核和頭文件依賴項可以運行到超過 100 MB。消除包含 LLVM 和 Clang 庫的開銷會導致更小的二進製文件。

例如,一個包含它們的工具使用 BCC 編譯為 645 KB。使用 libbpf 工具重新編譯的工俱生成了一個只有 151 KB 的可移植二進製文件。這是一個重大的尺寸減小。

Libbpf 還創建使用更少內存的二進製文件,例如,與 BCC 的 Python 的 80 MB 相比,內存佔用為 9 MB 。


延伸閱讀:

Why We Switched from BCC to libbpf for Linux BPF Performance Analysis

Tips and Tricks for Writing Linux BPF Applications with libbpf

bpftool 是一個通用工具,用於檢查 BPF 資源,並提供各種額外的 BPF 相關工具,例如 BPF 程序骨架的代碼生成。這些工具大量使用後一種功能來加載 BPF 程序並與之交互。
鑑於 bpftool 包還不能在許多發行版中廣泛使用,bpftool 二進製文件被簽入到 bin/ 子目錄中的 BCC 存儲庫中。一旦 bpftool 包更廣泛地可用,就可以改變這有利於使用 bpftool 的預打包版本。

Libbpf-tools —— 讓Tracing 工具身輕如燕

隨著BPF CO-RE 的落地,直接使用內核開發人員提供的libbpf 庫來開發BPF 程序,開發方式和編寫普通C 用戶態程序一樣:一次編譯生成小型的二進製文件。libbpf 作為BPF 程序加載器,接管了重定向、加載、驗證等功能,BPF 程序開發者只需要關注BPF 程序的正確性和性能即可。這種方式將開銷降到了最低,且去除了龐大的依賴關係,使得整體開發流程更加順暢。
性能優化大師Brendan Gregg 在用libbpf + BPF CO-RE 轉換一個BCC 工具後給出了性能對比數據:
 

As my colleague Jason pointed out, the memory footprint of opensnoop as CO-RE is much lower than opensnoop.py. 9 Mbytes for CO-RE vs 80 Mbytes for Python. 

我們可以看到在運行時相比BCC 版本,libbpf + BPF CO-RE 版本節約了近 9 倍 的內存開銷,這對於物理內存資源已經緊張的服務器來說會更友好。


什麼是libbpfgo?

這篇文章 How to Build eBPF Programs with libbpfgo? 提到了重點:

近年來,作者一直在使用一個名為 BCC 的項目來編譯、加載和與他的 bpf 程序交互。(我個人也遇到了BCC 需要 kernel header packages,造成佈署上的不方便性與程式碼為明碼的問題)。

他最近了解了一種更好的方法來構建名為libbpf的 ebpf 項目。在開發基於 libbpf 的程序時,有一些很好的資源可供使用,但入門仍然會讓人不知所措。

為了說明 libbpf 的本質以及如何使用它,他將編寫一個簡單的 bpf 程序,它會告訴我們每次進程使用 mmap 系統調用的時間。然後他將用 C 語言編寫一個用戶空間程序,它會加載已編譯的 bpf 程序並監聽它的輸出。

libbpfgo 是 libbpf 本身的一個wrapper。libbpfgo 的目標是實現 libbpf 的所有公共 API,以便您可以輕鬆地從 Go 中使用它。libbpfgo已經開始被使用於 Tracee(Aqua 的開源項目之一)需要的功能。

作者所介紹的範例在這: 

https://github.com/grantseltzer/libbpfgo-example

執行方式如下:

#Install packages
~$ sudo apt-get update
~$ sudo apt-get install libbpf-dev make clang llvm libelf-dev
~$ git clone https://github.com/grantseltzer/libbpfgo-example
~$ cd libbpfgo-example
~/libbpfgo-example$ make && sudo ./libbpfgo-prog

P.S: 如果有發生找不到 libbpf.a 的錯誤訊息,則會需要修改Makefile 內的 go_target內的CGO_LDFLAGS.

例如: CGO_LDFLAGS="/usr/lib/x86_64-linux-gnu/libbpf.a"

延伸閱讀:

use libbpfgo to build your first eBPF project

No comments: