پرش به محتوا
آرش حاتمی

hatamiarash7/ ebpf-process-monitor

سال توسعه: ۱۴۰۴ متن‌باز

پایش اجرای برنامه‌ها در لینوکس با eBPF

ابزار خط فرمان برای مشاهدهٔ زندهٔ فراخوانی‌های execve در لینوکس، با نمایش شناسهٔ فرایند و والد، شناسهٔ کاربر و نتیجهٔ فراخوانی؛ نوشته‌شده با Go و C.

فرایندهای کوتاه‌عمر ممکن است بین دو بار بررسی فهرست فرایندها اجرا شوند و به پایان برسند. برای دیدن تلاش‌های اجرای برنامه در لحظه، می‌توان به‌جای نمونه‌برداری دوره‌ای، رویدادهای مرتبط را مستقیماً از کرنل دریافت کرد.

پروژهٔ eBPF Process Monitor یک ابزار خط فرمان برای این کار است. بخش eBPF با زبان C رویدادهای ورود و خروج از فراخوانی execve را ثبت می‌کند و برنامهٔ Go آن‌ها را در ترمینال نمایش می‌دهد. این خروجی برای بررسی اجرای برنامه‌ها و آشنایی عملی با ارتباط میان eBPF و فضای کاربر کاربرد دارد.

روش کار

برنامه به دو Tracepoint لینوکس متصل می‌شود:

  • syscalls/sys_enter_execve برای مشاهدهٔ ورود به فراخوانی؛
  • syscalls/sys_exit_execve برای مشاهدهٔ نتیجهٔ همان فراخوانی.

در بخش کرنلی پروژه، شناسهٔ فرایند، شناسهٔ والد و شناسهٔ کاربر جمع‌آوری می‌شود. رویدادها از طریق یک Map از نوع BPF_MAP_TYPE_PERF_EVENT_ARRAY به فضای کاربر می‌رسند. برنامهٔ Go با کتابخانهٔ cilium/ebpf آن‌ها را می‌خواند و با پیشوندهای ENTER و EXIT چاپ می‌کند.

عبارت EXIT در این ابزار به معنی پایان عمر فرایند نیست. این رویداد خروج از فراخوانی execve را نشان می‌دهد. اجرای موفق execve تصویر برنامهٔ جاری را جایگزین می‌کند؛ بنابراین مقدار Retval را نباید با کد خروج نهایی برنامه اشتباه گرفت.

پیش‌نیازها و ساخت

برای ساخت و اجرای پروژه به این موارد نیاز دارید:

  • Linux با پشتیبانی از eBPF، Tracepointهای بالا و اطلاعات BTF کرنل در مسیر /sys/kernel/btf/vmlinux؛
  • Go نسخهٔ ۱٫۲۴٫۴ یا جدیدتر، مطابق فایل ماژول پروژه؛
  • Git، Make و ابزار bpftool؛
  • Clang با پشتیبانی از هدف BPF، ابزار llvm-strip از مجموعهٔ LLVM و هدرهای توسعهٔ libbpf، از جمله bpf_helpers.h و bpf_core_read.h؛
  • دسترسی کاربر ریشه برای اجرای روش مستندشدهٔ پروژه.

ابتدا مخزن را دریافت و برنامه را بسازید:

git clone https://github.com/hatamiarash7/ebpf-process-monitor.git
cd ebpf-process-monitor
make build

Makefile ابتدا با bpftool فایل ebpf/vmlinux.h را از BTF کرنل میزبان تولید می‌کند. سپس go generate برنامهٔ C و کد اتصال آن به Go را می‌سازد و در پایان، فایل اجرایی monitor تولید می‌شود.

راهنمای مخزن نصب مستقل bpf2go را هم پیشنهاد می‌کند، اما دستور go:generate فعلی آن را با go run github.com/cilium/ebpf/cmd/bpf2go فراخوانی می‌کند. در این مسیر، نسخهٔ کتابخانه از go.mod گرفته می‌شود و نصب جداگانهٔ باینری bpf2go لازم نیست. در اولین ساخت، دریافت وابستگی‌های Go به دسترسی شبکه نیاز دارد.

اگر ساخت در مرحلهٔ تولید هدر یا کامپایل C متوقف شد، وجود فایل BTF، نصب bpftool، دسترسی به Clang و LLVM در PATH و نصب هدرهای libbpf را بررسی کنید. وجود Go به‌تنهایی برای ساخت بخش eBPF کافی نیست.

اجرا و یک آزمایش ساده

از دایرکتوری پروژه، مانیتور را اجرا کنید:

sudo ./monitor

برنامه پس از بارگذاری و اتصال موفق، شروع دریافت رویدادها را اعلام می‌کند. در ترمینالی دیگر یک برنامهٔ خارجی اجرا کنید؛ برای نمونه، روی سیستمی که ls در مسیر زیر قرار دارد:

/usr/bin/ls /tmp

رویدادهای مرتبط با اجرای آن در ترمینال مانیتور ظاهر می‌شوند. استفاده از یک برنامهٔ خارجی مهم است؛ دستورهای داخلی Shell لزوماً execve جدیدی ایجاد نمی‌کنند. برای توقف مانیتور از Ctrl+C استفاده کنید؛ برنامه سیگنال‌های SIGINT و SIGTERM را دریافت می‌کند و منابع بازشده را می‌بندد.

برنامه با دسترسی ریشه اجرا می‌شود و فیلتر داخلی برای محدودکردن رویدادها به یک کاربر یا PID ندارد. بنابراین خروجی می‌تواند نام و مسیر برنامه‌های اجراشده توسط کاربران دیگر را هم شامل شود.

تفسیر خروجی

هر خط، بسته به نوع رویداد، اطلاعات زیر را دارد:

  • PID: شناسهٔ گروه رشته‌های فرایند، یعنی TGID که معمولاً به‌عنوان PID فرایند شناخته می‌شود.
  • PPID: شناسهٔ گروه رشته‌های والد واقعی فرایند در زمان دریافت رویداد.
  • UID: شناسهٔ کاربر دریافت‌شده از کرنل.
  • Command در ENTER: رشتهٔ مسیر فایل ورودی execve؛ این رشته لزوماً مسیر مطلق نیست و آرگومان‌های برنامه را شامل نمی‌شود.
  • Command در EXIT: نام کوتاه Task که با bpf_get_current_comm خوانده می‌شود؛ بنابراین ممکن است با مسیر نمایش‌داده‌شده در ENTER متفاوت باشد.
  • Retval در EXIT: مقدار بازگشتی فراخوانی؛ صفر نشان‌دهندهٔ اجرای موفق و مقدار منفی نشان‌دهندهٔ خطای کرنل است.

برای مثال، اگر برنامه‌ای با موفقیت بارگذاری شود و بعداً با وضعیت ناموفق پایان یابد، Retval=0 مربوط به execve همچنان درست است. این ابزار وضعیت پایان اجرای آن برنامه را ثبت نمی‌کند.

محدودهٔ پایش و محدودیت‌ها

این پروژه روی مشاهدهٔ execve تمرکز دارد. رویدادهای fork، پایان فرایند و فراخوانی جداگانهٔ execveat در کد فعلی پایش نمی‌شوند. همچنین فهرستی از فرایندهای موجود، مصرف CPU یا حافظه ارائه نمی‌شود.

بافر مسیر در رویداد ورودی ۱۲۸ بایت است و مسیرهای بلند ممکن است کوتاه شوند. آرگومان‌ها و متغیرهای محیطی جمع‌آوری نمی‌شوند. نتیجهٔ خواندن رشته در بخش C نیز بررسی نمی‌شود، پس خالی‌بودن Command به‌تنهایی نبود مسیر را ثابت نمی‌کند.

انتقال داده با Perf Buffer تضمین ثبت همهٔ رویدادها را نمی‌دهد. در بار زیاد امکان ازدست‌رفتن نمونه‌ها وجود دارد و حلقهٔ دریافت فعلی شمارندهٔ LostSamples را جداگانه گزارش نمی‌کند. بنابراین خروجی را نباید گزارش حسابرسی کامل همهٔ اجراها دانست. رمزگشایی رویدادها در Go نیز صریحاً با binary.LittleEndian انجام می‌شود؛ سازگاری با میزبان‌های Big-endian نیازمند تغییر و بررسی است.

کد پروژه با مجوز GPL نسخهٔ ۲ منتشر شده است. برای مرور ساختار کوچک پروژه و دستورهای اصلی، راهنمای مخزن نقطهٔ شروع مناسبی است.