24.01.20 최초 작성
Extended Berkeley Packet filter
바이트 코드로 변환되어 리눅스 커널에 로드에 실행 가능할 수 있는 프로그램
이벤트 발생 시점 등을 등록해 코드를 실행 가능함
C언어로 eBPF 프로그램 작성
LLVM이 eBPF를 바이트 코드로 변환
리눅스의 API를 통해 변환된 바이트 코드를 커널에 로드
프로그램이 특정 이벤트를 감지하면 프로그램 호출
eBPF를 바이트 코드로 변환해주는 컴파일러 (C, C++, RUST)
eBPF > LLVM IR (최적화 수행) > 머신 코드
메모리 접근, 무한 루프 등 프로그램을 검증
JIT 컴파일을 통해 바이트 코드 > 네이티브 코드로 변환
프로그램이 감지할 이벤트를 등록
CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_DEBUG_INFO, CONFIG_IKHEADERS : BPF 기능 활성화, 시스템 콜 활성화, BTF 디버깅 정보
CONFIG_NET, CONFIG_BPF_STREAM_PARSER : 네트워크 관련
CONFIG_HAVE_BPF_JIT, CONFIG_BPF_JIT : JIT 컴파일관련
CONFIG_BPF_EVENTS : tracepoint 활성화
CONFIG_CGROUP_BPF : cgroup에 연결 가능
커널 공간에서 직접 실행되어 공유된 메모리에 데이터를 저장해 성능이 뛰어남
사용자가 직접 프로그램 작성 가능
다양한 분야에서 사용 가능
컴파일 과정에서 검증을 거쳐 안전함
커널을 잘 알고 있어야 함
trace point를 잘 잡아낼 수 있어야 함
사용 시 오버헤드를 고려해야 함
system call을 추적할 수 있는 매크로tracepoint는 커널 영역에 로드되고 나머지 프로그램은 사용자 영역에 로드되어 커널 영역에서 유저 영역으로 결과를 전달함from bcc import BPF
prog = """ #tracepoint 구축
TRACEPOINT_PROBLE(syscalls, sys_enter_execve){ #execve 시스템 콜이 호출될 때 아래 코드 실행
bpf_trace_printk("Hello, World!\\n");
return 0;
}
"""
b = BPF(text=prog) #해당 클래스를 tracepoint를 바이트코드로 변환해 메모리에 올려 줌
while True:
try:
(_, _, _, _, _, msg) = b.trace_fields()
print(msg)
except KeyboardInterrupt:
break
BCC (BPF Compiler Collection) : BPF를 사용해 리눅스 커널에서 사용되는 프로그램을 개발/디버깅하는데 도움을 주는 도구Kprobe, Kretprobe : 지정한 함수가 실행되기 전/후에 유저 레벨에 데이터 전송from bcc import BPF
prog = """
#include <uapi/linux/ptrace.h>
struct data_t {
u32 pid;
char fname[256];
};
// 유저 레벨에 정보를 전송하는 매크로
BPF_PERF_OUTPUT(events);
// do_sys_open이 실행되기 전 kprobe를 통해 유저 공간에 전달
// 뒤 인자들은 do_sys_open의 인자들
int kprobe__do_sys_open(struct pt_regs *ctx, int dfd, const char __user *filename, int flags) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
bpf_probe_read_user(&data.fname, sizeof(data.fname), (void*)filename);
events.perf_submit(ctx, &data, sizeof(data));
return 0;
};
"""
b = BPF(text=prog)
def print_event(cpu, data, size):
event = b["events"].event(data)
print("PID %d called open on file %s" % (event.pid, event.fname))
b["events"].open_perf_buffer(print_event)
while True:
b.perf_buffer_poll()
USDT (User Statically Defined Tracing) : 동작 중인 프로그램의 특정 부분에 대한 정보를 얻을 수 있음from bcc import BPF, USDT
from bcc.utils import printb
# load BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
int do_trace(struct pt_regs *ctx) {
uint64_t addr;
char query[128];
/*
* Read the first argument from the query-start probe, which is the query.
* The format of this probe is:
* query-start(query, connectionid, database, user, host)
* see: https://dev.mysql.com/doc/refman/5.7/en/dba-dtrace-ref-query.html
*/
bpf_usdt_readarg(1, ctx, &addr);
bpf_probe_read_user(&query, sizeof(query), (void *)addr);
bpf_trace_printk("%s\\n", query);
return 0;
};
"""
Raw Trace Point : 리눅스에서 제공하는 직접적인 접근이 가능한 trace pointfrom bcc import BPF
bpf_text_raw_tp = """
RAW_TRACEPOINT_PROBE(nfs_initiate_commit)
{
// TP_PROTO(const struct nfs_commit_data *data)
struct nfs_commit_data *cd = (struct nfs_commit_data *)ctx->args[0];
return trace_initiate_commit(cd);
}
"""
Systemcall tracepoint : 리눅스 커널에서 지원하는 동적 trace pointbpf_text = """
int syscall__execve(struct pt_regs *ctx,
const char __user *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
// create data here and pass to submit_arg to save stack space (#555)
struct data_t data = {};
struct task_struct *task;
data.pid = bpf_get_current_pid_tgid() >> 32;
task = (struct task_struct *)bpf_get_current_task();
// Some kernels, like Ubuntu 4.13.0-generic, return 0
// as the real_parent->tgid.
// We use the get_ppid function as a fallback in those cases. (#1883)
data.ppid = task->real_parent->tgid;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
data.type = EVENT_ARG;
__submit_arg(ctx, (void *)filename, &data);
// skip first arg, as we submitted filename
#pragma unroll
for (int i = 1; i < MAXARG; i++) {
if (submit_arg(ctx, (void *)&__argv[i], &data) == 0)
goto out;
}
// handle truncated argument list
char ellipsis[] = "...";
__submit_arg(ctx, (void *)ellipsis, &data);
out:
return 0;
}
"""
Kfuncs / Kretfuncs : 등록한 함수가 호출되었을 때 실행되는 동작bpf_text_kfunc = """
KFUNC_PROBE(vfs_read) { stats_try_increment(S_READ); return 0; }
KFUNC_PROBE(vfs_write) { stats_try_increment(S_WRITE); return 0; }
KFUNC_PROBE(vfs_fsync_range) { stats_try_increment(S_FSYNC); return 0; }
KFUNC_PROBE(vfs_open) { stats_try_increment(S_OPEN); return 0; }
KFUNC_PROBE(vfs_create) { stats_try_increment(S_CREATE); return 0; }
"""
LSM probes : 커널의 보안 관련 동작을 추적from bcc import BPF, BPFAttachType, BPFProgType
from bcc.libbcc import lib
@skipUnless(kernel_version_ge(5,7), "requires kernel >= 5.7")
def test_lsm_probe(self):
# Skip if the kernel is not compiled with CONFIG_BPF_LSM
if not BPF.support_lsm():
return
b = BPF(text=b"""
LSM_PROBE(bpf, int cmd, union bpf_attr *uattr, unsigned int size) {
return 0;
}""")
BPF Iterators : 연결된 자료구조를 순회하는 동작BPF_ITER(task)
{
struct seq_file *seq = ctx->meta->seq;
struct task_struct *task = ctx->task;
if (task == (void *)0)
return 0;
... task->pid, task->tgid, task->comm, ...
return 0;
}