گسترش eBPF Compile یک بار، اجرا در همه جا (CO-RE) به سازگاری با فضای کاربری
یوشنگ
eBPF، مخفف Extended Berkeley Packet Filter، یک فناوری قدرتمند و همه کاره است که در سیستم های لینوکس مدرن استفاده می شود. این امکان اجرای برنامههای sandboxed را در محیطی شبیه ماشین مجازی در هسته فراهم میکند و راهی امن برای گسترش قابلیتهای هسته بدون خطر از کار افتادن سیستم یا به خطر انداختن امنیت ارائه میدهد.
Co-RE که مخفف عبارت 'Compile Once, Run Everywhere' است، به مسئله حیاتی سازگاری برنامه eBPF در نسخه های مختلف هسته می پردازد. این ویژگی به برنامههای eBPF اجازه میدهد تا بر روی نسخههای مختلف هسته بدون نیاز به کامپایل، سادهسازی استقرار و نگهداری اجرا شوند.
با eBPF Uprobe، میتوانید اپلیکیشنهای فضای کاربران را ردیابی کرده و به ساختارهای داده داخلی آنها دسترسی داشته باشید. با این حال، CO-RE برای برنامه های کاربردی فضای کاربر طراحی نشده است. این وبلاگ نحوه استفاده از CO-RE را برای برنامههای کاربردی فضای کاربر معرفی میکند و تضمین میکند که برنامههای eBPF Uprobe با نسخههای مختلف برنامهها بدون نیاز به کامپایلهای متعدد سازگار هستند.
این رویکرد ممکن است به ویژه برای ردیابی برنامههایی مانند OpenSSL مفید باشد، جایی که حفظ برنامههای eBPF جداگانه برای هر نسخه غیرعملی است. با استفاده از زمانهای اجرا فضای کاربری eBPF مانند bpftime، میتوانید CO-RE را به موارد استفاده بیشتری از جمله برنامههای افزودنی، شبکهسازی و وصلههای پویا گسترش دهید و راهحلهای همهکاره و کارآمد را ارائه دهید.
برای پیاده سازی ویژگی Co-RE eBPF در برنامه های کاربردی فضای کاربر، ما همچنین باید از فرمت نوع BPF (BTF) برای غلبه بر برخی از محدودیت های برنامه های سنتی eBPF استفاده کنیم. کلید این رویکرد در ارائه برنامههای فضای کاربر با اطلاعات نوع مشابه و پشتیبانی از سازگاری مانند هسته نهفته است، در نتیجه برنامههای eBPF را قادر میسازد تا نسخههای مختلف برنامهها و کتابخانههای فضای کاربر را با انعطافپذیری بیشتری مدیریت کنند.
این مقاله بخشی از آموزش توسعه دهندگان eBPF است و برای محتوای دقیق تر، می توانید به https://eunomia.dev/tutorials/ مراجعه کنید. کد منبع در https://github.com/eunomia-bpf/bpf-developer-tutorial موجود است.
چرا به CO-RE نیاز داریم؟
- وابستگی های هسته: برنامه های سنتی eBPF به شدت با نسخه هسته لینوکس خاصی که برای آن کامپایل شده اند همراه هستند. این به این دلیل است که آنها به ساختارهای داده داخلی خاص و APIهای هسته متکی هستند که می توانند بین نسخه های هسته تغییر کنند.
- مسائل حمل و نقل: اگر میخواهید یک برنامه eBPF را بر روی سیستمهای مختلف لینوکس با نسخههای هسته متفاوت اجرا کنید، به طور سنتی باید برنامه eBPF را برای هر نسخه هسته مجدداً کامپایل کنید، که فرآیندی دست و پا گیر و ناکارآمد است.
راه حل Co-RE
- انتزاع وابستگی های هسته: Co-RE برنامههای eBPF را قادر میسازد تا با حذف وابستگیهای خاص هسته قابل حملتر باشند. این از طریق استفاده از فرمت نوع BPF (BTF) و جابجایی به دست می آید.
- فرمت نوع BPF (BTF): BTF اطلاعات نوع غنی در مورد ساختارهای داده و توابع در هسته ارائه می دهد. این ابرداده به برنامه های eBPF اجازه می دهد تا طرح ساختارهای هسته را در زمان اجرا درک کنند.
- جابجایی ها: برنامه های eBPF که با پشتیبانی Co-RE کامپایل شده اند حاوی جابجایی هایی هستند که در زمان بارگذاری حل می شوند. این جابجایی ها ارجاعات برنامه را به ساختارها و عملکردهای داده هسته با توجه به طرح و آدرس های موجود در هسته در حال اجرا تنظیم می کند.
مزایای Co-RE
- یک بار بنویس، هر جا اجرا کن: برنامه های eBPF کامپایل شده با Co-RE می توانند بدون نیاز به کامپایل مجدد بر روی نسخه های مختلف هسته اجرا شوند. این امر استقرار و نگهداری برنامه های eBPF را در محیط های متنوع بسیار ساده می کند.
- ایمنی و ثبات: Co-RE تضمین های ایمنی eBPF را حفظ می کند و تضمین می کند که برنامه ها هسته را خراب نمی کنند و به محدودیت های امنیتی پایبند نیستند.
- سهولت توسعه: توسعه دهندگان نیازی به نگرانی در مورد ویژگی های هر نسخه هسته ندارند، که توسعه برنامه های eBPF را ساده می کند.
مشکل: برنامه فضای کاربران CO-RE
eBPF همچنین از برنامه های کاربردی فضای کاربر ردیابی پشتیبانی می کند. Uprobe یک کاوشگر فضای کاربر است که امکان ابزار دقیق پویا را در برنامه های فضای کاربر فراهم می کند. مکان های کاوشگر شامل ورود تابع، افست های خاص و برگرداندن تابع می باشد.
BTF برای هسته طراحی شده و از vmlinux تولید شده است، می تواند به برنامه eBPF کمک کند تا به راحتی با نسخه های مختلف کرنل سازگار شود.
با این حال، اپلیکیشن فضای کاربران نیز به CO-RE نیاز دارد. به عنوان مثال، SSL/TLS uprobe به طور گسترده ای برای گرفتن داده های متن ساده از ترافیک رمزگذاری شده استفاده می شود. با کتابخانه userspace مانند OpenSSL، GnuTLS، NSS و غیره پیادهسازی میشود. اپلیکیشن و کتابخانههای فضای کاربران نیز نسخههای مختلفی دارند، اگر نیاز به کامپایل و نگهداری برنامه eBPF برای هر نسخه داشته باشیم، پیچیده است.
بیایید ببینیم اگر CO-RE برای برنامههای کاربردی فضای کاربر فعال نشود، چه اتفاقی میافتد و چگونه BTF از برنامههای فضای کاربران میتواند این مشکل را حل کند.
بدون برنامه BTF برای فضای کاربری
این یک مثال ساده uprobe است، میتواند فراخوانی تابع و آرگومانهای آن را ضبط کند add_test
عملکرد در برنامه userspace می توانید اضافه کنید #define BPF_NO_PRESERVE_ACCESS_INDEX
در uprobe.bpf.c
برای اطمینان از اینکه برنامه eBPF را می توان بدون BTF برای کامپایل کرد struct data
.
#define BPF_NO_GLOBAL_DATA
#define BPF_NO_PRESERVE_ACCESS_INDEX
#include
#include
#include
struct data {
int a;
int c;
int d;
};
SEC("uprobe/examples/btf-base:add_test")
int BPF_UPROBE(add_test, struct data *d)
{
int a = 0, c = 0;
bpf_probe_read_user(&a, sizeof(a), &d->a);
bpf_probe_read_user(&c, sizeof(c), &d->c);
bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c);
return a + c;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";
سپس، ما دو نسخه مختلف از برنامه userspace داریم، examples/btf-base
و examples/btf-base-new
. ساختار data
در دو نسخه متفاوت است.
examples/btf-base
:
// use a different struct
struct data {
int a;
int c;
int d;
};
int add_test(struct data *d) {
return d->a + d->c;
}
int main(int argc, char **argv) {
struct data d = {1, 3, 4};
printf("add_test(&d) = %d\n", add_test(&d));
return 0;
}
examples/btf-base-new
:
struct data {
int a;
int b;
int c;
int d;
};
int add_test(struct data *d) {
return d->a + d->c;
}
int main(int argc, char **argv) {
struct data d = {1, 2, 3, 4};
printf("add_test(&d) = %d\n", add_test(&d));
return 0;
}
ما میتوانیم از pahole و clang برای تولید btf برای هر نسخه از اپلیکیشنهای فضای کاربری استفاده کنیم. ابزار pahole به سادگی می تواند BTF را از اطلاعات اشکال زدایی تولید کند: https://linux.die.net/man/1/pahole
مثال بسازید و btf برای آنها تولید کنید:
make -C example # it's like: pahole --btf_encode_detached base.btf btf-base.o
ما برنامه eBPF را با برنامه userspace اجرا می کنیم. برای btf-base
:
sudo ./uprobe examples/btf-base
و همچنین برنامه userspace:
$ examples/btf-base
add_test(&d) = 4
خواهیم دید:
$ sudo cat /sys/kernel/debug/tracing/trace_pipe\
<...>-25458 [000] ...11 27694.081465: bpf_trace_printk: add_test(&d) 1 + 3 = 4
برای btf-base-new
:
sudo ./uprobe examples/btf-base-new
و همچنین برنامه userspace:
$ examples/btf-base-new
add_test(&d) = 4
اما خواهیم دید:
$ sudo cat /sys/kernel/debug/tracing/trace_pipe\
<...>-25809 [001] ...11 27828.314224: bpf_trace_printk: add_test(&d) 1 + 2 = 3
نتیجه متفاوت است، زیرا ساختار data
در دو نسخه متفاوت است. برنامه eBPF نمی تواند با نسخه های مختلف برنامه userspace سازگار باشد، بنابراین ما نمی توانیم اطلاعات صحیح را دریافت کنیم.
از برنامه BTF برای Userspace استفاده کنید
نظر دهید #define BPF_NO_PRESERVE_ACCESS_INDEX
در uprobe.bpf.c
برای اطمینان از اینکه برنامه eBPF را می توان با BTF برای کامپایل کرد struct data
.
#define BPF_NO_GLOBAL_DATA
// #define BPF_NO_PRESERVE_ACCESS_INDEX
#include
#include
#include
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
#endif
struct data {
int a;
int c;
int d;
};
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
#pragma clang attribute pop
#endif
SEC("uprobe/examples/btf-base:add_test")
int BPF_UPROBE(add_test, struct data *d)
{
int a = 0, c = 0;
bpf_probe_read_user(&a, sizeof(a), &d->a);
bpf_probe_read_user(&c, sizeof(c), &d->c);
bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c);
return a + c;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";
رکورد از struct data
در برنامه eBPF حفظ می شود. سپس، ما می توانیم استفاده کنیم btf-base.btf
برای کامپایل برنامه eBPF.
کاربر btf را با کرنل btf ادغام کنید، بنابراین ما یک btf کامل برای هسته و فضای کاربران داریم:
./merge-btf /sys/kernel/btf/vmlinux examples/base.btf target-base.btf
سپس برنامه eBPF را با برنامه userspace اجرا می کنیم. برای btf-base
:
$ sudo ./uprobe examples/btf-base target-base.btf
...
libbpf: prog 'add_test': relo #1: patched insn #4 (ALU/ALU64) imm 0 -> 0
libbpf: prog 'add_test': relo #2: [7] struct data.c (0:1 @ offset 4)
libbpf: prog 'add_test': relo #2: matching candidate #0 [133110] struct data.c (0:1 @ offset 4)
libbpf: prog 'add_test': relo #2: patched insn #11 (ALU/ALU64) imm 4 -> 4
...
برنامه userspace را اجرا کنید و نتیجه بگیرید:
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
[sudo] password for yunwei37:
<...>-26740 [001] ...11 28180.156220: bpf_trace_printk: add_test(&d) 1 + 3 = 4
همچنین، ما همین کار را برای نسخه دیگری از برنامه userspace انجام می دهیم btf-base-new
:
$ ./merge-btf /sys/kernel/btf/vmlinux examples/base-new.btf target-base-new.btf
$ sudo ./uprobe examples/btf-base-new target-base-new.btf
....
libbpf: sec 'uprobe/examples/btf-base:add_test': found 3 CO-RE relocations
libbpf: CO-RE relocating [2] struct pt_regs: found target candidate [357] struct pt_regs in [vmlinux]
libbpf: prog 'add_test': relo #0: [2] struct pt_regs.di (0:14 @ offset 112)
libbpf: prog 'add_test': relo #0: matching candidate #0 [357] struct pt_regs.di (0:14 @ offset 112)
libbpf: prog 'add_test': relo #0: patched insn #0 (LDX/ST/STX) off 112 -> 112
libbpf: CO-RE relocating [7] struct data: found target candidate [133110] struct data in [vmlinux]
libbpf: prog 'add_test': relo #1: [7] struct data.a (0:0 @ offset 0)
libbpf: prog 'add_test': relo #1: matching candidate #0 [133110] struct data.a (0:0 @ offset 0)
libbpf: prog 'add_test': relo #1: patched insn #4 (ALU/ALU64) imm 0 -> 0
libbpf: prog 'add_test': relo #2: [7] struct data.c (0:1 @ offset 4)
libbpf: prog 'add_test': relo #2: matching candidate #0 [133110] struct data.c (0:2 @ offset 8)
libbpf: prog 'add_test': relo #2: patched insn #11 (ALU/ALU64) imm 4 -> 8
libbpf: elf: symbol address match for 'add_test' in 'examples/btf-base-new': 0x1140
Successfully started! Press Ctrl+C to stop.
نتیجه درست است:
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
[sudo] password for yunwei37:
<...>-26740 [001] ...11 28180.156220: bpf_trace_printk: add_test(&d) 1 + 3 = 4
برای کد منبع کامل، می توانید برای جزئیات بیشتر به https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/38-btf-uprobe مراجعه کنید.
برنامه ردیابی uprobe eBPF تقریباً به هیچ تغییری نیاز ندارد. ما فقط باید BTF را بارگذاری کنیم که حاوی افست های هسته و ساختارهای فضای کاربر است. این همان کاربرد فعال کردن CO-RE در نسخه های قدیمی هسته بدون اطلاعات BTF است:
LIBBPF_OPTS(bpf_object_open_opts , opts,
);
LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
if (argc != 3 && argc != 2) {
fprintf(stderr, "Usage: %s [] \n", argv[0]);
return 1;
}
if (argc == 3)
opts.btf_custom_path = argv[2];
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Cleaner handling of Ctrl-C */
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
/* Load and verify BPF application */
skel = uprobe_bpf__open_opts(&opts);
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}
در واقع، اجرای BTF برای جابجایی به دو بخش نیاز دارد: اطلاعات BTF در زمان کامپایل که توسط برنامه BPF حمل میشود، و اطلاعات BTF هسته هنگام بارگذاری برنامه eBPF. هنگام بارگیری برنامه eBPF، libbpf دستورالعملهای احتمالی نادرست eBPF را بر اساس اطلاعات دقیق BTF هسته فعلی تغییر میدهد و از سازگاری با نسخههای مختلف هسته اطمینان حاصل میکند.
جالب اینجاست که libbpf تفاوتی نمیکند که آیا این اطلاعات BTF از برنامههای فضای کاربر یا هسته میآیند. بنابراین با ادغام اطلاعات BTF فضای کاربر با هسته BTF و ارائه آنها به libbpf مشکل حل می شود.
و همچنین، از آنجایی که جابجایی در بارگذار فضای کاربر (مانند libbpf) اتفاق میافتد، هم زمان اجرا هسته eBPF و هم زمان اجرا eBPF فضای کاربر (مانند bpftime) میتوانند از CO-RE بهره ببرند. bpftime (https://github.com/eunomia-bpf/bpftime) یک زمان اجرا eBPF فضای کاربر منبع باز مبتنی بر LLVM JIT/AOT است. این امکان اجرای برنامه های eBPF را در فضای کاربر، سازگار با هسته-فضای eBPF فراهم می کند. در حالی که از uprobes، syscall trace و افزونههای کلی پلاگین پشتیبانی میکند، از جابجایی زمینه بین هسته و فضاهای کاربر جلوگیری میکند و در نتیجه کارایی اجرای برنامههای uprobe را افزایش میدهد. با پشتیبانی از libbpf و BTF، bpftime همچنین میتواند به صورت پویا برنامههای فضای کاربر را گسترش دهد و به سازگاری در نسخههای مختلف برنامههای فضای کاربر دست یابد.
برای جزئیات بیشتر در مورد جابجایی BTF، می توانید به https://nakryiko.com/posts/bpf-core-reference-guide/ مراجعه کنید.
نتیجه گیری
- انعطاف پذیری و سازگاری: استفاده از BTF در برنامههای eBPF فضای کاربر، انعطافپذیری و سازگاری آنها را در نسخههای مختلف برنامهها و کتابخانههای فضای کاربر افزایش میدهد.
- کاهش پیچیدگی: این رویکرد به طور قابل توجهی پیچیدگی مربوط به نگهداری برنامه های eBPF را برای نسخه های مختلف برنامه های کاربردی فضای کاربر کاهش می دهد، زیرا نیاز به نسخه های برنامه های متعدد را از بین می برد.
- پتانسیل برای کاربرد گسترده تر: در حالی که مثال شما بر نظارت SSL/TLS متمرکز است، این روش ممکن است کاربردهای گسترده تری در نظارت بر عملکرد، امنیت و اشکال زدایی برنامه های کاربردی فضای کاربر داشته باشد.
این مثال پیشرفت قابل توجهی را در کاربرد عملی eBPF نشان میدهد و ویژگیهای قدرتمند آن را برای مدیریت پویاتر برنامههای فضای کاربر در محیط لینوکس گسترش میدهد. این یک راه حل قانع کننده برای مهندسان نرم افزار و مدیران سیستم است که با پیچیدگی های سیستم های لینوکس مدرن سر و کار دارند.
اگر میخواهید درباره دانش و شیوههای eBPF بیشتر بدانید، میتوانید از مخزن کد آموزشی ما https://github.com/eunomia-bpf/bpf-developer-tutorial یا وبسایت https://eunomia.dev/tutorials/ دیدن کنید. نمونه های بیشتر و آموزش های کامل