
C言語
イベント
マガジン
技術ブログ
はじめに こんにちは、クラウドエース株式会社 第二開発部の田中です。 プログラムコードを単なる「文字列」として一致検索するのではなく、その「処理内容(意味)」で検索したり比較したりできたら便利だと思いませんか? 社内のコードベースが増えるほど、「あの処理、どこにあったっけ?」と意味で検索したくなる場面は多いと思います。 今回は、Google Cloud の BigQuery で提供されている AI.EMBED(エンベディング生成)を使って、異なるプログラミング言語間や、コードと日本語(自然言語)の「意味的な近さ」を測る実験をしてみました。 埋め込みモデルには gemini-embed
はじめに こんにちは、クラウドエース株式会社 第二開発部の田中です。 プログラムコードを単なる「文字列」として一致検索するのではなく、その「処理内容(意味)」で検索したり比較したりできたら便利だと思いませんか? 社内のコードベースが増えるほど、「あの処理、どこにあったっけ?」と意味で検索したくなる場面は多いと思います。 今回は、Google Cloud の BigQuery で提供されている AI.EMBED(エンベディング生成)を使って、異なるプログラミング言語間や、コードと日本語(自然言語)の「意味的な近さ」を測る実験をしてみました。 埋め込みモデルには gemini-embed
はじめに エンタープライズ 第一本部、2025 Japan AWS Jr. Champions の佐藤悠です。 私は Kubernetes を触る機会が多く、その中でも監視に最近興味を持っています。 監視を実現するセキュリティソリューションの中に Tetragon などが挙げられますが、この監視のベースとなっている技術にeBPFがあります。 このeBPFをEC2 インスタンス の Amazon Linux 上で動かして、その面白さと何が起きているのかを解説します。 はじめに Linuxユーザー空間/カーネル空間 カーネルモジュール 環境 カーネルヘッダのインストール ソースコードの保存 Makefileの作成 モジュールのビルド モジュールのロード モジュールのリスト モジュールのアンロード 感想 ようやく、eBPF eBPF Verifier 呼び出せるカーネル関数はかなり限定的 Stepの制限 メモリアクセスの制限 NULLポインタ参照がないか eBPFでHello World 環境 ファイルの作成 実行 結果 感想 そしてTetragonへ... おわりに Linux ユーザー空間/ カーネル 空間 まずはeBPFの背景知識として Linux のユーザー空間と カーネル 空間に関して理解する必要があります 下図のように Linux にはユーザー空間と カーネル 空間というものが存在しています。 これは一度 カーネル を経由させることで物理デ バイス へのアクセスを制御するのと共に、プロセスが利用するリソースの一元管理を行い配分をする狙いがあります。 そのためユーザーと カーネル の空間を区切り、 システムコール というインターフェースでのみ カーネル へアクセスを強制し、アクセス制御とリソース配分を漏れなく実現しているというわけです。 以下はlsコマンドを実行したときに呼び出されている システムコール です。 さまざまな システムコール を組み合わせて カーネル にアクセスして、コマンドの実行結果を出力していることが理解できますね。 $strace -c ls % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 7 read 0.00 0.000000 0 3 write 0.00 0.000000 0 23 close 0.00 0.000000 0 30 mmap 0.00 0.000000 0 7 mprotect 0.00 0.000000 0 1 munmap 0.00 0.000000 0 3 brk 0.00 0.000000 0 2 ioctl 0.00 0.000000 0 4 pread64 0.00 0.000000 0 2 2 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 2 2 statfs 0.00 0.000000 0 2 1 arch_prctl 0.00 0.000000 0 1 futex 0.00 0.000000 0 2 getdents64 0.00 0.000000 0 1 set_tid_address 0.00 0.000000 0 34 13 openat 0.00 0.000000 0 22 newfstatat 0.00 0.000000 0 1 set_robust_list 0.00 0.000000 0 1 prlimit64 0.00 0.000000 0 1 getrandom 0.00 0.000000 0 1 rseq ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 0 151 18 total ここでプロセスは カーネル を経由するから、アプリケーションの振る舞いは カーネル を見ていれば監視できるのでは?という気づきがあるわけです。 カーネル モジュール じゃあ「 Linux の カーネル にコードの変更をしよう!」と思っても、 Linux の利用されている規模、そもそものコード量などを鑑みると、コミュニティの理解を得て、開発のコストをかけて、アップストリームになるまで待つという気の遠くなる時間がかかる作業となってしまいます。 この問題に対して、 Linux は カーネル の変更・拡張のためにモジュールを受け入れるようにできています。 これが カーネル モジュールの概念です。 では、 カーネル モジュールを作成してみます。 私は C言語 の経験がないので以下の書籍「 Linux Device Drivers, 3rd Edition」のChapter2 p16 The Hello world Moduleの項を参考にしました。 参考: https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch02.html 適当に変更してgogo.cを作成します。 以下のモジュールを カーネル にロードするとgo!go!、アンロードするとbyebye!とログバッファに出力します。 #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE ( "GPL" ); //ロードされたら実行される関数 static int gogo ( void ) { printk (KERN_ALERT "go!go! \n " ); return 0 ; } //アンロードされたら実行される関数 static void byebye ( void ) { printk (KERN_ALERT "byebye! \n " ); } module_init (gogo); module_exit (byebye); これが今回のモジュールです。 ではロード/アンロードしてみます。 環境 アウトバウンド443ポートの通信のみを許可したサブネット内です。 EC2 インスタンス で Amazon linux を起動し、SessionManagerで接続をしています。 sh-5.2$ cat /etc/os-release NAME="Amazon Linux" VERSION="2023" ID="amzn" ID_LIKE="fedora" VERSION_ID="2023" PLATFORM_ID="platform:al2023" PRETTY_NAME="Amazon Linux 2023.8.20250804" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023" HOME_URL="https://aws.amazon.com/linux/amazon-linux-2023/" DOCUMENTATION_URL="https://docs.aws.amazon.com/linux/" SUPPORT_URL="https://aws.amazon.com/premiumsupport/" BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023" VENDOR_NAME="AWS" VENDOR_URL="https://aws.amazon.com/" SUPPORT_END="2029-06-30" カーネル ヘッダのインストール sudo dnf install gcc make kernel-devel kernel-headers Last metadata expiration check: 1:37:35 ago on Sat Oct 18 05:49:32 2025. Package gcc-11.5.0-5.amzn2023.0.4.x86_64 is already installed. Package make-1:4.3-5.amzn2023.0.2.x86_64 is already installed. Package kernel-devel-1:6.1.147-172.259.amzn2023.x86_64 is already installed. Package kernel-headers-1:6.1.147-172.259.amzn2023.x86_64 is already installed. Dependencies resolved. Nothing to do. Complete! ソースコード の保存 #先ほどのファイルをコピペ sh-5.2$ sudo vim gogo.c sh-5.2$ cat gogo.c #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE("GPL"); //ロードされたら実行される関数 static int gogo(void) { printk(KERN_ALERT "go!go!\n"); return 0; } //アンロードされたら実行される関数 static void byebye(void) { printk(KERN_ALERT "byebye!\n"); } module_init(gogo); module_exit(byebye); Makefile の作成 同じ ディレクト リで Makefile を作成 sh-5.2$ sudo vim Makefile sh-5.2$ sudo cat Makefile obj-m += gogo.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean モジュールのビルド make gogo.koが出来ました。 モジュールのロード ※私にはこの末尾のログは紫に光って見えています。 sh-5.2$ sudo insmod gogo.ko sh-5.2$ sudo dmesg | tail ... [ 6689.540149] gogo: loading out-of-tree module taints kernel. [ 6689.540977] gogo: module verification failed: signature and/or required key missing - tainting kernel [ 6689.542802] go!go! モジュールのリスト lsmodで確認しました。gogoモジュールがあります。 sh-5.2$ lsmod Module Size Used by gogo 16384 0 ・・・ モジュールのアンロード sh-5.2$ sudo rmmod gogo sh-5.2$ sudo dmesg | tail ... [ 6689.540149] gogo: loading out-of-tree module taints kernel. [ 6689.540977] gogo: module verification failed: signature and/or required key missing - tainting kernel [ 6689.542802] go!go! [ 6999.290169] byebye! 感想 こんなに手軽に カーネル が拡張できるんですね。 ところで、 カーネル モジュールをロードしたタイミングで以下のログが出ています。 loading out-of-tree module taints kernel. ツリー外(公式外)のモジュールをロードしたため、 カーネル が汚染されました。(翻訳:ChatGPT) まあ自作のモジュールなので、メッセージの通りですね。 ただ、安易なモジュールのロードって自作や第 三者 の提供、問わず カーネル が壊れる可能性がありリスクが大きそうだと思えてきます。 ようやく、eBPF そんな カーネル モジュールの代わりに登場するのが eBPF です。 eBPFは カーネル の ソースコード の改変や、 カーネル モジュールのロードを必要とせずに、安全かつ効率的な カーネル の機能拡張に利用されています。 参考: https://ebpf.io/ja/what-is-ebpf/ この 安全な拡張 は主にeBPF Verifierによって保障されています eBPF Verifier eBPF プログラムはクラッシュしたりシステムに悪影響を及ぼしたりしません。 プログラムは常に実行が完了します(つまり、プログラムは無限ループに陥って処理が実行し続けられることはありません)。 参考: https://ebpf.io/ja/what-is-ebpf/ このeBPF Verifier(検証器)でチェックしている内容は多岐にわたっているため、私が調査した範囲で代表的な項目を以下に列挙します。 呼び出せる カーネル 関数はかなり限定的 基本的には安全性が検証されたヘルパー関数しか使えません。 ヘルパー関数でも関係外の関数を呼ぼうとするとエラーとなります。 ※BPFプログラムタイプの概念 Stepの制限 100万Stepの指示数に以内の時に限り許可することで、CPUを占有しないようにしています メモリアクセスの制限 アクセス可能な範囲外のメモリに触れません。 ※eBPF mapという概念を使用して カーネル とユーザー空間でデータのやり取りをします。 NULLポインタ参照がないか ( ・∀・) | | ガッ と ) | | Y /ノ 人 / ) < >__Λ∩ _/し' //. V`Д´)/ (_フ彡 / これ以上にも、もっとたくさんの検証事があって安全性を保障しています。 ここに踏み込むと、分からないことだらけになってしまうので、「さまざまな検証を経て、 カーネル 空間での実行を許される」としておきます。 eBPFで Hello World ではeBPFプログラムの実装をします。 これは以下の書籍「入門eBPF」2章 p15 eBPFの「 Hello World 」を参考にしています。 参考: https://www.oreilly.co.jp/books/9784814400560/ BCC の Python ライブラリで C言語 で書いたeBPFコードを バイトコード へ変換し、 カーネル 空間にロードさせます。 #!/usr/bin/python from bcc import BPF program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ b = BPF(text=program) syscall = b.get_syscall_fnname( "execve" ) b.attach_kprobe(event=syscall, fn_name= "hello" ) b.trace_print() それぞれ分解しながら解説します。 以下のコードが カーネル にロードされるeBPFコードです。 program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ eBPFのプログラム作成時には、先述のヘルパー関数の1つ、bpf_trace_printk()を用いて出力していることが分かります。 この C言語 を コンパイル して動作するようにします。 b = BPF(text=program) 以上のように書くことで、BPFオブジェクトの生成時に引数に渡すことで BCC に コンパイル させます。 syscall = b.get_syscall_fnname( "execve" ) 次のコードでバイナリのパスを渡して実行する システムコール 「execve()」の関数名を取得します。 b.attach_kprobe(event=syscall, fn_name= "hello" ) ここでexecve()に関数helloをアタッチします。 kprobeとは Linux カーネル の任意の関数や命令の実行時に動的にフックして処理を挿入できる仕組みです このプログラムが カーネル にロードされると、execve()が実行された際には hello World がtrace_pipe(ユーザー空間から取得可能)に出力されます。 b.trace_print() 最後に、トレースデータを Python が実行されている標準出力へ出力します。 実際に動かしてみます。 環境 カーネル モジュールの検証時と同様 ファイルの作成 sh-5.2$ sudo vim hello.py sh-5.2$ cat hello.py #!/usr/bin/python from bcc import BPF program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ b = BPF(text=program) syscall = b.get_syscall_fnname("execve") b.attach_kprobe(event=syscall, fn_name="hello") b.trace_print() 実行 #実行権限の付与 h-5.2$ sudo chmod +x hello.py sh-5.2$ sudo ./hello.py ちなみにeBPFは原則特権ユーザーでしか実行できないので、./hello.pyの実行だと以下のようになります sh-5.2$ ./hello.py bpf: Failed to load program: Operation not permitted Traceback (most recent call last): File "/home/ssm-user/./hello.py", line 13, in <module> b.attach_kprobe(event=syscall, fn_name="hello") File "/usr/lib/python3.9/site-packages/bcc/__init__.py", line 841, in attach_kprobe fn = self.load_func(fn_name, BPF.KPROBE) File "/usr/lib/python3.9/site-packages/bcc/__init__.py", line 523, in load_func raise Exception("Need super-user privileges to run") Exception: Need super-user privileges to run 結果 sh-5.2$ sudo ./hello.py b' <...>-46202 [001] ...21 84764.107466: bpf_trace_printk: Hello World!' b' <...>-46203 [001] ...21 84823.020341: bpf_trace_printk: Hello World!' 何も操作してなくても裏でexecve()って呼ばれているんですね。 別の接続を開いてls等のコマンドを打つと、 Hello World !が同時に出力されるのが分かります。 感想 このように特定の システムコール に紐づけて、監視できると分かりました。 まあ、今回の実験では何かが実行されたという情報しかないですが... そしてTetragonへ... このようにeBPFは カーネル で動作できるので、実行環境で監視の範囲を広げられるように感じます。 ただ当然ですが、毎回監視のためのコードを書くのは辛いものがあります。 そこで、はじめに例にあげたTetragonのようなeBPFをベースにしたサービスを利用するんですね。 ここまでくれば、 Kubernetes ではよくある監視の サイドカー コンテナ利用に対して、eBPFという切り口で監視を提供するときに拡張される範囲がわかってくると思います。 メインコンテナに対して同じボリュームを共有し、 localhost で接続可能な サイドカー コンテナとして実行する監視(例:Datadogの サイドカー インジェクション)に加えて、ノードにDaemonSetsとしてデプロイされることで、 カーネル で実行された実行イベントまで監視の目を広げることができますね。 参考: https://tetragon.io/docs/getting-started/execution/ 本記事ではeBPFの紹介なので、Tetragonの詳細の解説は省略します。 おわりに eBPFではよく蜂を見かけますが、これはeBeeという名前です。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.yu レビュー: @akutsu.masahiro ( Shodo で執筆されました )


















