Android での eBpf の統合とデバッグ

eBPF(Extended Berkeley Packet Filter)は、カーネルコードを変更することなく柔軟かつ動的にプログラムをロードし、安全性の確保を前提としたカーネル機能の拡張を実現する、新たなLinuxカーネル機能拡張技術です。

Android プラットフォームでは、eBpf テクノロジーのサポートも導入されています。この記事では、いくつかの典型的な使用シナリオを使用して Android での eBpf 使用プロセスを実行し、携帯電話で eBpf プログラムを統合およびデバッグする方法を示します。

以下の図は、Android にも適用できる bpf の基本的な展開プロセスを示しています。

78c83e859fcf97261208e44301694166.png

1.BPFプログラミング

Android の eBpf プログラムのソース コードは system/bpfprogs にあります。たとえば、time_in_state.c を開くと、プログラムが通常 3 つの部分に分かれていることがわかります。

  • DEFINE_BPF_MAP を使用して、ユーザー プログラムとカーネル間のデータ転送を実装するために使用される共有キャッシュであるいくつかの Map データ構造を定義します。

  • DEFINE_BPF_PROG を使用して Bpf 関数を定義し、コンパイル後にこの関数をカーネルにロードすることでフック関数の機能を実現できます。

  • LICENSE("GPL") ライセンス契約書。

上記のうち、Map の型、Bpf のフック型には、機能に応じて多くの種類があります。 https://github.com/iovisor/bcc/blob/master/docs/kernel-versions を参照してください。 .md#program-types に詳細な説明があります。

2、Bpf プログラムの生成

Bpf プログラムを C 言語形式で作成した後、コンパイルによって「.o」ファイルを取得できます。このファイルはBTF(BPF Type Format)バイトコードでエンコードされたメタデータ形式のファイルであり、直接実行することはできず、カーネルにロードしてカーネルで解析して実行するか、JIT変換後に実行する必要があります。

BTF 形式ファイルはドキュメントを表示できます。

https://www.kernel.org/doc/html/latest/bpf/btf.html

3. Bpf プログラムをロードします。

Android では、Bpf プログラムには厳格な権限制御があり、bpfloader.te には bpf の実行を制限する sepolicy があり、bpfloader が bpf プログラムをロードできる唯一のプログラムに制限されます。

Neverallow { ドメイン -bpfloader } *:bpf { map_create prog_load };

bpfloader は、携帯電話の起動時に 1 回だけ実行されます。これにより、カーネルのセキュリティへの損傷を防ぐために、他のモジュールがシステムの外部に bpf プログラムをさらにロードすることができなくなります。

system/bpf/bpfloader/BpfLoader.cpp では、bpfloader は、loadAllElfObjects を使用して、/system/etc/bpf にある btf 形式の「.o」ファイルを走査します。次に、android::bpf::loadProg を使用して bpf プログラム ファイルを解析し、Bpf プログラムと対応するマップの作成を実現します。

Bpfloader はロードを実行した後、すぐに終了します。Bpf プログラムのライフサイクル管理は、ファイル ハンドル fd と同様に参照カウントであり、参照がすべて失われると、Bpf プログラムやマップなどのオブジェクトが破棄されます。

bpfloader の実行後に bpf prog オブジェクトとマップ オブジェクトが破棄されるのを防ぐために、これらの bpf オブジェクトは、最後に bpf_obj_pin を介して /sys/fs/bpf ファイル ノードにマップされます。マップされたファイル ノードには、他のプログラムがファイル パス名を通じて対応する bpf プログラムを見つけることができるように、その命名に関する特定の規則があります。

4.Bpfプログラムをアタッチする

Bpf プログラムはロードされた後、カーネル関数にアタッチされないため、この時点では bpf プログラムは実行されず、アタッチ操作を実行する必要があります。Attach は、bpf プログラムをフックするカーネル監視ポイントを指定します。具体的には、tracepoint や kprobe など、数十の種類があります。アタッチが成功すると、bpf プログラムはカーネル コードの関数に変換されます。

たとえば、attach task_rename のトレースポイント タイプを使用できます。

cat/sys/kernel/tracing/events/task/task_rename/format を使用してパラメーターを確認し、定義された bpf 関数が特定のトレースポイント関数パラメーターと一致するようにします。

生のトレースポイントをアタッチする場合、生のトレースポイントはパラメータのカプセル化なしでイベントの元のパラメータにアクセスするため、パラメータを自分で構築する必要があり、比較するとパフォーマンスが向上します。

たとえば、task_rename のトレースポイントには、次の 2 種類のパラメータの違いがあります。

77c47b0b3219441d0cc60a32c003cbbf.png

五、地図を更新する

Bpf がカーネル関数に付加されると、フック関数として機能します。フック関数はデタッチ前に常にカーネルの実行を検出できますが、場合によっては検出範囲を変更したり、検出結果を報告したりする必要がありますが、その際にはユーザー監視プログラム間のデータ交換の媒体であるMapを使用する必要がありますそしてカーネル。ユーザー モードとカーネル モードの両方で、同様のインターフェイスを使用して Map にアクセスできます。

一般的な操作

  • マップ内のレコードを検索する

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

  • マップ内のレコードを更新する

long bpf_map_update_elem(struct bpf_map *map、const void *key、c​​onst void *value、u64 フラグ)

  • マップ内のレコードを削除する

long bpf_map_delete_elem(struct bpf_map *map, const void *key)

6. イベント報告

一般的な地図データでは、内部のデータを積極的に読み取る必要があります。データを読み取るためにポーリングするのではなく、データがあるときに通知を受け取りたい場合があります。この時点で、データの変更をリッスンする機能は、perf イベント マップを通じて実現できます。カーネル データはカスタム データ構造に保存し、パフォーマンス イベント リング キャッシュを通じてユーザー空間プロセスに送信およびブロードキャストできます。

パフォーマンス イベント マップの構築プロセス:

5cad61ce4903a28ef7cf2112d21e16ea.png

上記の構築プロセスが完了すると、ユーザー状態とカーネル状態の間にイベント fd の関連付けが確立されます。次に、ユーザー モードは epoll を使用して fd 上の通知を継続的にリッスンし、fd が実際にキャッシュにマップされるため、変更が検出されたときに特定のデータをキャッシュから読み取ることができます。

9d5767a3ffead8262bdedff4f7ab18a9.png

カーネル内で、次のように渡します。

bpf_perf_event_output(ctx,&events,BPF_F_CURRENT_CPU, &data, sizeof(data));

データを通知します。

BPF_F_CURRENT_CPU パラメータは、現在の CPU のインデックス値を使用してイベント マップ内の fd にアクセスし、fd に対応するキャッシュにデータを埋めることを指定します。これにより、複数の CPU が同時にデータを送信するという同期の問題を回避できます。また、上記のイベント マップが初期化される理由についても説明します。CPU の数と同じサイズを作成します。

7、デバッグ中

実際の開発ではデバッグを繰り返すプロセスが避けられませんが、bpf の原理に従って、bpf プログラムを Android に再デプロイするには次の手順を実行できます。

  1. 新しい bpf.o ファイルを /system/etc/bpf/ にプッシュします。

  2. 古いバージョンの bpf プログラムとマップのマッピング ファイルがまだ存在するため、/sys/fs/bpf と入力して、マッピング ファイルを rm する必要があります。古い bpf は参照がないため破棄されます。

  3. 次に/./system/bin/bpfloaderを再度実行すると、bpfloader はブート時と同様に新しい bpf.o を再度ロードできます。

注: bpfloader はロード中にあまりにも多くのログを出力するため、レート制限がトリガーされます。場合によっては、bpfloader が新しい bpf プログラムをロードできず、エラー メッセージが見つからないことがわかります。まず「echo on > /proc/sys/kernel/printk_devkmsg」コマンドを使用してレート制限をオフにすると、通常どおりエラーを見つけることができます。

bpf プログラムのマウントに成功したら、カーネルでの実行を確認し、bpf_printk を使用してカーネル ログを出力する必要があります。

/* デバッグ メッセージを出力するヘルパー マクロ */

#define bpf_printk(fmt, ...) \

({ \

char ____fmt[] = fmt; \

bpf_trace_printk(____fmt, sizeof(____fmt), \

##__VA_ARGS__); \

})

利用可能なカーネル ログを表示します。

$ echo 1 > /sys/kernel/tracing/tracing_on

$ cat /sys/kernel/tracing/trace_pipe

注: bpf プログラムは C コード形式で記述されていますが、最終的にはカーネル検証によって実行され、多くのセキュリティと機能の制限があります。典型的な例は bpf_printk で、これは 3 つのパラメーター出力のみをサポートし、それを超えると、エラーが報告されます。

8. 結論

Bpf は、システム コール、トレースポイント、カーネル関数などをフックすることができ、その応用シナリオは非常に広範囲にわたり、現在 Android で使用されていますが、実際にはまだ検討の余地がたくさんあります。

参考文献:

1.https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#program-types

2.https://man7.org/linux/man-pages/man2/bpf.2.html

3.https://man7.org/linux/man-pages/man7/bpf-helpers.7.html

1db442b8d865e3637e1d0a311eab5f04.gif

長押ししてカーネル職人WeChatをフォローしてください

Linux カーネル ブラック テクノロジー | 技術記事 | 注目のチュートリアル

おすすめ

転載: blog.csdn.net/feelabclihu/article/details/130652632