eBPF가 처음 등장했을 당시에는 프로그램을 커널에 로드하기 위해 개발자가 직접 bpf() 시스템 콜(System Call)을 호출하고, Map 생성, Program 로드, Verifier 처리, Hook 연결까지 모두 구현해야 했습니다.
이러한 방식은 매우 복잡했으며 작은 eBPF 프로그램 하나를 실행하기 위해 수백 줄 이상의 코드가 필요했습니다.
이 문제를 해결하기 위해 Linux Kernel 개발자들은 libbpf라는 공식 라이브러리를 만들었습니다.
libbpf는 eBPF 프로그램을 쉽게 로드하고 관리할 수 있도록 다양한 기능을 제공하며, 현재는 대부분의 eBPF 프로젝트에서 표준 라이브러리로 사용되고 있습니다.
이번 글에서는 libbpf의 개념과 구조, 주요 기능, 내부 동작 원리, Skeleton 및 CO-RE와의 관계, 실무 활용 방법을 자세히 알아보겠습니다.
Linux libbpf란?
libbpf는 eBPF 프로그램을 사용자 공간에서 관리하기 위한 공식 C 라이브러리입니다.
기본 구조는 다음과 같습니다.
Application
↓
libbpf
↓
BPF System Call
↓
Linux Kernel
개발자는 복잡한 시스템 콜을 직접 호출하지 않고 libbpf API만 사용하면 됩니다.
libbpf가 필요한 이유
초기의 eBPF 개발에서는 다음과 같은 작업을 모두 직접 구현해야 했습니다.
- ELF Object 분석
- BPF Map 생성
- Program 로드
- Verifier 처리
- Hook 연결
- Program 제거
- Map 관리
libbpf는 이러한 과정을 자동으로 수행하여 개발 부담을 크게 줄여 줍니다.
libbpf의 주요 역할
libbpf는 다양한 기능을 제공합니다.
대표적으로
- eBPF Object 로드
- BPF Map 생성
- Program 로드
- Program Attach
- CO-RE Relocation
- Skeleton 지원
- Link 관리
- Pinning 지원
현재 eBPF 개발에 필요한 거의 모든 기능을 포함하고 있습니다.
libbpf의 동작 구조
전체 과정은 다음과 같습니다.
Application
↓
libbpf
↓
Read ELF
↓
Read BTF
↓
CO-RE
↓
Verifier
↓
Load Program
↓
Attach Hook
대부분의 복잡한 작업을 내부적으로 자동 처리합니다.
libbpf와 ELF
libbpf는 먼저 컴파일된 ELF Object를 분석합니다.
대표적으로 다음 Section을 읽습니다.
.text
.maps
.BTF
.BTF.ext
license
각 Section의 정보를 이용하여 필요한 작업을 수행합니다.
libbpf와 BPF Map
Map 정의를 자동으로 읽어 생성합니다.
예를 들어
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
} counter_map SEC(".maps");
Object 파일에 포함된 Map 정보를 읽어 커널에 자동으로 생성합니다.
libbpf와 CO-RE
CO-RE를 사용할 경우
Kernel BTF
↓
libbpf
↓
Relocation
↓
Correct Offset
현재 실행 중인 커널의 구조체 레이아웃에 맞게 자동으로 보정합니다.
libbpf와 Skeleton
Skeleton 역시 libbpf를 기반으로 동작합니다.
대표 함수는 다음과 같습니다.
demo_bpf__open();
demo_bpf__load();
demo_bpf__attach();
demo_bpf__destroy();
이 함수들은 내부적으로 모두 libbpf API를 호출합니다.
libbpf의 주요 API
실무에서 자주 사용하는 API는 다음과 같습니다.
| API | 설명 |
|---|---|
bpf_object__open() | Object 열기 |
bpf_object__load() | Program 로드 |
bpf_program__attach() | Hook 연결 |
bpf_map__fd() | Map FD 가져오기 |
bpf_program__fd() | Program FD 가져오기 |
bpf_link__destroy() | Link 제거 |
bpf_object__close() | Object 종료 |
대부분의 작업은 이 API들로 수행할 수 있습니다.
libbpf와 bpftool의 관계
많은 사용자가 혼동하지만 두 도구는 역할이 다릅니다.
| 항목 | libbpf | bpftool |
|---|---|---|
| 형태 | 라이브러리 | 명령행 도구 |
| 목적 | 애플리케이션 개발 | 관리 및 디버깅 |
| 사용 언어 | C API | CLI |
| Skeleton 생성 | 내부 지원 | 생성 기능 제공 |
bpftool도 내부적으로 libbpf를 활용하는 경우가 많습니다.
libbpf의 장점
libbpf를 사용하면 다음과 같은 장점이 있습니다.
- 코드량 감소
- Loader 자동화
- CO-RE 지원
- Skeleton 지원
- Verifier 처리 자동화
- 유지보수 편리
- 최신 Kernel 지원
현재는 eBPF 개발의 사실상 표준 라이브러리입니다.
libbpf 사용 시 주의사항
libbpf를 사용할 때는 다음 사항을 확인하는 것이 좋습니다.
- 최신 libbpf 버전 사용
- Clang 버전 확인
- Kernel BTF 지원 여부
- bpftool 최신 버전 유지
- CO-RE 지원 환경 확인
구형 libbpf는 최신 eBPF 기능을 지원하지 않을 수 있습니다.
실무에서 자주 사용하는 명령어
libbpf 버전 확인
pkg-config --modversion libbpf
설치 여부 확인
pkg-config --exists libbpf && echo Installed
Kernel 기능 확인
sudo bpftool feature
Program 확인
sudo bpftool prog show
Map 확인
sudo bpftool map show
BTF 확인
sudo bpftool btf show
실무 사례
예를 들어 Kubernetes 환경에서 XDP 기반 네트워크 모니터링 프로그램을 개발한다고 가정해 보겠습니다.
과거에는 개발자가 직접 시스템 콜을 호출하고 Map 생성, Program 로드, Hook 연결 등을 구현해야 했습니다.
하지만 libbpf를 사용하면 다음과 같은 과정으로 단순화됩니다.
- eBPF 프로그램 컴파일
- ELF Object 생성
- libbpf가 Object 분석
- BTF 기반 CO-RE Relocation 수행
- Map 자동 생성
- Program 로드
- Link 생성 및 Hook 연결
- 애플리케이션에서 Map을 통해 통계 조회
이처럼 libbpf는 eBPF 개발의 복잡성을 크게 줄여 생산성과 유지보수성을 향상시킵니다.
자주 묻는 질문
libbpf는 반드시 사용해야 하나요?
필수는 아닙니다. bpf() 시스템 콜을 직접 호출해 구현할 수도 있습니다. 하지만 구현 난이도가 매우 높기 때문에 현재는 대부분의 프로젝트에서 libbpf를 사용합니다.
libbpf와 Skeleton은 같은 것인가요?
아닙니다. Skeleton은 bpftool이 생성하는 자동 코드이며, 내부적으로 libbpf API를 호출하여 eBPF 프로그램을 관리합니다. 즉, Skeleton은 libbpf를 더 쉽게 사용할 수 있도록 만든 래퍼(Wrapper)입니다.
libbpf는 어떤 언어에서 사용할 수 있나요?
공식적으로는 C 라이브러리입니다. 다만 Go, Rust, Python 등 다양한 언어에서 libbpf를 기반으로 한 바인딩(Binding)이나 래퍼 라이브러리를 제공하여 사용할 수 있습니다.
마무리
Linux libbpf는 eBPF 프로그램을 손쉽게 개발하고 관리할 수 있도록 지원하는 공식 라이브러리입니다. ELF Object 분석, Map 생성, Program 로드, CO-RE Relocation, Skeleton 지원, Link 관리 등 복잡한 작업을 자동으로 처리하여 개발 생산성을 크게 향상시킵니다. 현대적인 eBPF 개발에서는 libbpf를 중심으로 대부분의 프로젝트가 구성되므로 구조와 사용법을 반드시 이해하는 것이 중요합니다.