디버깅과 테스트
디버깅과 테스트 (Debugging and Testing)
이 문서는 BPF 프로그램과 맵을 디버깅하고 테스트하는 방법을 다뤄요. bpftool을 사용한 검사, 커널 BPF selftest, JIT 디버깅, perf를 통한 프로파일링, 트레이스포인트 관찰, 추적 파이프 사용법을 소개합니다.
본문
bpftool
bpftool은 BPF 주변의 주요 인트로스펙션·디버깅 도구이며, 리눅스 커널 트리와 함께 tools/bpf/bpftool/ 아래에서 개발·제공돼요.
이 도구는 시스템에 현재 로드된 모든 BPF 프로그램과 맵을 덤프하거나, 특정 프로그램이 사용하는 모든 BPF 맵을 나열·연관지을 수 있어요. 또한 전체 맵의 키/값 쌍을 덤프하거나, 개별 항목을 lookup·update·delete하거나 맵에서 키의 이웃 키를 가져올 수도 있어요. 이러한 연산은 BPF 프로그램이나 맵 ID를 기반으로, 또는 BPF 파일 시스템에 고정된 프로그램·맵 위치를 지정해 수행할 수 있어요. 이 도구는 맵이나 프로그램을 BPF 파일 시스템에 고정(pin)하는 옵션도 제공해요.
호스트에 현재 로드된 모든 BPF 프로그램의 빠른 개요를 보려면 다음 명령을 실행하세요:
# bpftool prog
398: sched_cls tag 56207908be8ad877
loaded_at Apr 09/16:24 uid 0
xlated 8800B jited 6184B memlock 12288B map_ids 18,5,17,14
399: sched_cls tag abc95fb4835a6ec9
loaded_at Apr 09/16:24 uid 0
xlated 344B jited 223B memlock 4096B map_ids 18
400: sched_cls tag afd2e542b30ff3ec
loaded_at Apr 09/16:24 uid 0
xlated 1720B jited 1001B memlock 4096B map_ids 17
401: sched_cls tag 2dbbd74ee5d51cc8
loaded_at Apr 09/16:24 uid 0
xlated 3728B jited 2099B memlock 4096B map_ids 17
[...]
마찬가지로 모든 활성 맵의 개요를 얻으려면:
# bpftool map
5: hash flags 0x0
key 20B value 112B max_entries 65535 memlock 13111296B
6: hash flags 0x0
key 20B value 20B max_entries 65536 memlock 7344128B
7: hash flags 0x0
key 10B value 16B max_entries 8192 memlock 790528B
8: hash flags 0x0
key 22B value 28B max_entries 8192 memlock 987136B
9: hash flags 0x0
key 20B value 8B max_entries 512000 memlock 49352704B
[...]
각 명령에 대해 bpftool은 명령줄 끝에 --json을 추가해 JSON 기반 출력도 지원해요. 추가 --pretty는 출력을 더 읽기 쉽게 개선해요.
# bpftool prog --json --pretty
특정 BPF 프로그램의 post-verifier BPF 명령어 이미지를 덤프하려면, 예를 들어 tc ingress 훅에 부착된 특정 프로그램을 검사하는 것부터 시작할 수 있어요:
# tc filter show dev cilium_host egress
filter protocol all pref 1 bpf chain 0
filter protocol all pref 1 bpf chain 0 handle 0x1 bpf_host.o:[from-netdev] \
direct-action not_in_hw id 406 tag e0362f5bd9163a0a jited
오브젝트 파일 bpf_host.o의 from-netdev 섹션에 있는 프로그램은 id 406에 명시된 대로 BPF 프로그램 ID가 406이에요. 이 정보를 바탕으로 bpftool은 프로그램 특정의 고급 메타데이터를 제공할 수 있어요:
# bpftool prog show id 406
406: sched_cls tag e0362f5bd9163a0a
loaded_at Apr 09/16:24 uid 0
xlated 11144B jited 7721B memlock 12288B map_ids 18,20,8,5,6,14
ID 406 프로그램은 유형 sched_cls(BPF_PROG_TYPE_SCHED_CLS)이고 e0362f5bd9163a0a의 tag(명령어 시퀀스에 대한 SHA 합)를 가지며, Apr 09/16:24에 루트 uid 0에 의해 로드됐어요. BPF 명령어 시퀀스는 11,144 bytes 길이이고 JIT된 이미지는 7,721 bytes예요. 프로그램 자체(맵 제외)는 사용자 uid 0에 부과되는(charge) 12,288 bytes를 소비해요. 그리고 BPF 프로그램은 ID가 18, 20, 8, 5, 6, 14인 BPF 맵을 사용해요. 후자의 ID로 맵 자체에 대한 정보를 얻거나 덤프할 수 있어요.
또한 bpftool은 프로그램이 실행하는 BPF 명령어의 덤프 요청을 내릴 수 있어요:
# bpftool prog dump xlated id 406
0: (b7) r7 = 0
1: (63) *(u32 *)(r1 +60) = r7
2: (63) *(u32 *)(r1 +56) = r7
3: (63) *(u32 *)(r1 +52) = r7
[...]
47: (bf) r4 = r10
48: (07) r4 += -40
49: (79) r6 = *(u64 *)(r10 -104)
50: (bf) r1 = r6
51: (18) r2 = map[id:18] <-- BPF map id 18
53: (b7) r5 = 32
54: (85) call bpf_skb_event_output#5656112 <-- BPF helper call
55: (69) r1 = *(u16 *)(r6 +192)
[...]
bpftool은 위와 같이 BPF 맵 ID를 명령어 스트림에 연관시키고, BPF 헬퍼나 다른 BPF 프로그램에 대한 호출도 연관시켜요.
명령어 덤프는 커널의 BPF verifier와 같은 'pretty-printer'를 재사용해요. 프로그램이 JIT됐고 따라서 위 xlated 명령어에서 생성된 실제 JIT 이미지가 실행되므로, 이를 bpftool로도 덤프할 수 있어요:
# bpftool prog dump jited id 406
0: push %rbp
1: mov %rsp,%rbp
4: sub $0x228,%rsp
b: sub $0x28,%rbp
f: mov %rbx,0x0(%rbp)
13: mov %r13,0x8(%rbp)
17: mov %r14,0x10(%rbp)
1b: mov %r15,0x18(%rbp)
1f: xor %eax,%eax
21: mov %rax,0x20(%rbp)
25: mov 0x80(%rdi),%r9d
[...]
주로 BPF JIT 개발자를 위해 역어셈블리를 실제 네이티브 opcode와 인터리브하는 옵션도 있어요:
# bpftool prog dump jited id 406 opcodes
0: push %rbp
55
1: mov %rsp,%rbp
48 89 e5
4: sub $0x228,%rsp
48 81 ec 28 02 00 00
b: sub $0x28,%rbp
48 83 ed 28
f: mov %rbx,0x0(%rbp)
48 89 5d 00
13: mov %r13,0x8(%rbp)
4c 89 6d 08
17: mov %r14,0x10(%rbp)
4c 89 75 10
1b: mov %r15,0x18(%rbp)
4c 89 7d 18
[...]
같은 인터리브는 일반 BPF 명령어에서도 할 수 있으며, 커널에서 디버깅할 때 유용할 수 있어요:
# bpftool prog dump xlated id 406 opcodes
0: (b7) r7 = 0
b7 07 00 00 00 00 00 00
1: (63) *(u32 *)(r1 +60) = r7
63 71 3c 00 00 00 00 00
2: (63) *(u32 *)(r1 +56) = r7
63 71 38 00 00 00 00 00
3: (63) *(u32 *)(r1 +52) = r7
63 71 34 00 00 00 00 00
4: (63) *(u32 *)(r1 +48) = r7
63 71 30 00 00 00 00 00
5: (63) *(u32 *)(r1 +64) = r7
63 71 40 00 00 00 00 00
[...]
프로그램의 기본 블록은 graphviz를 사용해 시각화할 수도 있어요. 이를 위해 bpftool은 나중에 png 파일로 변환할 수 있는 일반 BPF xlated 명령어 덤프 대신 dot 파일을 생성하는 visual 덤프 모드가 있어요:
# bpftool prog dump xlated id 406 visual &> output.dot
$ dot -Tpng output.dot -o output.png
또 다른 옵션은 dot 파일을 dotty에 뷰어로 전달하는 것인데, 즉 dotty output.dot이며, bpf_host.o 프로그램에 대한 결과는 다음과 같습니다(일부 발췌):

xlated 명령어 덤프는 post-verifier BPF 명령어 이미지를 제공하는데, 즉 BPF 인터프리터로 실행되는 것처럼 명령어를 덤프한다는 뜻이에요. 커널에서 verifier는 BPF 로더가 제공한 원래 명령어에 대해 다양한 다시 쓰기(rewrite)를 수행해요.
다시 쓰기의 한 예는 런타임 성능을 개선하기 위한 헬퍼 함수의 인라인화인데, 여기서는 해시 테이블에 대한 맵 lookup의 경우예요:
# bpftool prog dump xlated id 3
0: (b7) r1 = 2
1: (63) *(u32 *)(r10 -4) = r1
2: (bf) r2 = r10
3: (07) r2 += -4
4: (18) r1 = map[id:2] <-- BPF map id 2
6: (85) call __htab_map_lookup_elem#77408 <-+ BPF helper inlined rewrite
7: (15) if r0 == 0x0 goto pc+2 |
8: (07) r0 += 56 |
9: (79) r0 = *(u64 *)(r0 +0) <-+
10: (15) if r0 == 0x0 goto pc+24
11: (bf) r2 = r10
12: (07) r2 += -4
[...]
bpftool은 헬퍼 함수 호출이나 BPF to BPF 호출을 kallsyms를 통해 연관시켜요. 따라서 JIT된 BPF 프로그램이 kallsyms에 노출되고(bpf_jit_kallsyms), kallsyms 주소가 난독화되지 않도록 해야 해요(그렇지 않으면 호출이 call bpf_unspec#0으로 표시됨):
# echo 0 > /proc/sys/kernel/kptr_restrict
# echo 1 > /proc/sys/net/core/bpf_jit_kallsyms
BPF to BPF 호출도 인터프리터·JIT 양쪽 모두에서 연관돼요. 후자의 경우 서브프로그램의 태그가 호출 대상으로 표시돼요. 각 경우 pc+2는 호출 대상의 pc-상대 오프셋이며, 서브프로그램을 나타내요.
# bpftool prog dump xlated id 1
0: (85) call pc+2#__bpf_prog_run_args32
1: (b7) r0 = 1
2: (95) exit
3: (b7) r0 = 2
4: (95) exit
JIT된 덤프 변형:
# bpftool prog dump xlated id 1
0: (85) call pc+2#bpf_prog_3b185187f1855c4c_F
1: (b7) r0 = 1
2: (95) exit
3: (b7) r0 = 2
4: (95) exit
tail call의 경우 커널이 내부적으로 이를 단일 명령어로 매핑하는데, bpftool은 디버깅 편의를 위해 여전히 헬퍼 호출로 연관시켜요:
# bpftool prog dump xlated id 2
[...]
10: (b7) r2 = 8
11: (85) call bpf_trace_printk#-41312
12: (bf) r1 = r6
13: (18) r2 = map[id:1]
15: (b7) r3 = 0
16: (85) call bpf_tail_call#12
17: (b7) r1 = 42
18: (6b) *(u16 *)(r6 +46) = r1
19: (b7) r0 = 0
20: (95) exit
# bpftool map show id 1
1: prog_array flags 0x0
key 4B value 4B max_entries 1 memlock 4096B
전체 맵 덤프는 map dump 하위 명령으로 가능하며, 이는 존재하는 모든 맵 요소를 순회하며 키/값 쌍을 덤프해요.
주어진 맵에 BTF(BPFT Type Format) 데이터가 없으면 키/값 쌍이 hex로 덤프돼요:
# bpftool map dump id 5
key:
f0 0d 00 00 00 00 00 00 0a 66 00 00 00 00 8a d6
02 00 00 00
value:
00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
key:
0a 66 1c ee 00 00 00 00 00 00 00 00 00 00 00 00
01 00 00 00
value:
00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[...]
Found 6 elements
하지만 BTF가 있으면 맵은 키와 값 구조에 대한 디버깅 정보도 보유해요. 예를 들어 BTF를 BPF 맵 및 iproute2의 BPF_ANNOTATE_KV_PAIR() 매크로와 결합하면 다음 덤프가 나옵니다(커널 selftest의 test_xdp_noinline.o):
# cat tools/testing/selftests/bpf/test_xdp_noinline.c
[...]
struct ctl_value {
union {
__u64 value;
__u32 ifindex;
__u8 mac[6];
};
};
struct bpf_map_def __attribute__ ((section("maps"), used)) ctl_array = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(struct ctl_value),
.max_entries = 16,
.map_flags = 0,
};
BPF_ANNOTATE_KV_PAIR(ctl_array, __u32, struct ctl_value);
[...]
BPF_ANNOTATE_KV_PAIR() 매크로는 빈 키와 값을 포함하는 맵 특정 ELF 섹션을 강제하는데, 이를 통해 iproute2 BPF 로더가 그 섹션과 BTF 데이터를 연관시켜 맵 로딩에 해당 유형을 BTF에서 선택할 수 있게 해요.
LLVM으로 컴파일하고 pahole로 디버깅 정보를 통해 BTF를 생성:
# clang [...] -O2 --target=bpf -g -emit-llvm -c test_xdp_noinline.c -o - |
llc -march=bpf -mcpu=probe -mattr=dwarfris -filetype=obj -o test_xdp_noinline.o
# pahole -J test_xdp_noinline.o
이제 커널에 로드하고 bpftool로 맵을 덤프:
# ip -force link set dev lo xdp obj test_xdp_noinline.o sec xdp-test
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric/id:227 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
[...]
# bpftool prog show id 227
227: xdp tag a85e060c275c5616 gpl
loaded_at 2018-07-17T14:41:29+0000 uid 0
xlated 8152B not jited memlock 12288B map_ids 381,385,386,382,384,383
# bpftool map dump id 386
[{
"key": 0,
"value": {
"": {
"value": 0,
"ifindex": 0,
"mac": []
}
}
},{
"key": 1,
"value": {
"": {
"value": 0,
"ifindex": 0,
"mac": []
}
}
},{
[...]
특정 키에 대한 맵의 lookup, update, delete, 'get next key' 연산도 bpftool로 수행할 수 있어요.
BPF 프로그램이 BTF 디버깅 정보와 함께 성공적으로 로드되면 prog show 명령 결과에 btf_id로 표시되는 BTF ID가 보여요.
# bpftool prog show id 72
72: xdp name balancer_ingres tag acf44cabb48385ed gpl
loaded_at 2020-04-13T23:12:08+0900 uid 0
xlated 19104B jited 10732B memlock 20480B map_ids 126,130,131,127,129,128
btf_id 60
이것은 시스템에 로드된 모든 BTF 오브젝트를 덤프하는 btf show 명령으로도 확인할 수 있어요.
# bpftool btf show
60: size 12243B prog_ids 72 map_ids 126,130,131,127,129,128
그리고 btf dump 하위 명령은 BTF에 포함된 디버깅 정보를 확인하는 데 사용할 수 있어요. 이 명령으로 BTF 덤프는 'raw' 또는 C 코드에서 쓰는 'c' 형식으로 지정할 수 있어요.
# bpftool btf dump id 60 format c
[...]
struct ctl_value {
union {
__u64 value;
__u32 ifindex;
__u8 mac[6];
};
};
typedef unsigned int u32;
[...]
비디오: bpftool에 대해 더 배우려면 bpftool 메인테이너 Quentin Monnet와 함께하는 eCHO episode 11: Exploring bpftool을 확인하세요.
커널 테스트 (Kernel Testing)
리눅스 커널은 tools/testing/selftests/bpf/ 아래 커널 소스 트리에서 찾을 수 있는 BPF selftest 제품군을 제공해요.
$ cd tools/testing/selftests/bpf/
$ make
# make run_tests
테스트 제품군은 BPF verifier, 프로그램 태그, 다양한 BPF 맵 인터페이스·맵 유형에 대한 테스트 케이스를 포함해요. LLVM 백엔드를 확인하는 C 코드의 다양한 런타임 테스트와, 인터프리터와 JIT를 테스트하기 위해 커널에서 실행되는 eBPF·cBPF asm 코드를 포함해요.
JIT 디버깅 (JIT Debugging)
감사를 수행하거나 확장을 작성하는 JIT 개발자를 위해 각 컴파일 실행이 생성된 JIT 이미지를 커널 로그로 출력할 수 있어요:
# echo 2 > /proc/sys/net/core/bpf_jit_enable
새 BPF 프로그램이 로드될 때마다 JIT 컴파일러가 출력을 덤프하며, 이는 예를 들어 dmesg로 검사할 수 있어요:
[ 3389.935842] flen=6 proglen=70 pass=3 image=ffffffffa0069c8f from=tcpdump pid=20583
[ 3389.935847] JIT code: 00000000: 55 48 89 e5 48 83 ec 60 48 89 5d f8 44 8b 4f 68
[ 3389.935849] JIT code: 00000010: 44 2b 4f 6c 4c 8b 87 d8 00 00 00 be 0c 00 00 00
[ 3389.935850] JIT code: 00000020: e8 1d 94 ff e0 3d 00 08 00 00 75 16 be 17 00 00
[ 3389.935851] JIT code: 00000030: 00 e8 28 94 ff e0 83 f8 01 75 07 b8 ff ff 00 00
[ 3389.935852] JIT code: 00000040: eb 02 31 c0 c9 c3
flen은 BPF 프로그램 길이(여기서는 6개 BPF 명령어)이고, proglen은 JIT가 opcode 이미지에 생성한 바이트 수(여기서는 70바이트)를 알려줘요. pass는 이미지가 3개의 컴파일러 패스에서 생성됐음을 의미하며, 예를 들어 x86_64는 가능할 때 이미지 크기를 더 줄이기 위한 다양한 최적화 패스를 가질 수 있어요. image는 생성된 JIT 이미지의 주소를 포함하고, from과 pid는 컴파일 과정을 트리거한 사용자 공간 애플리케이션 이름과 PID를 각각 나타내요. eBPF와 cBPF JIT의 덤프 출력 형식은 같아요.
커널 트리의 tools/bpf/ 아래에 bpf_jit_disasm이라는 도구가 있어요. 이 도구는 최신 덤프를 읽고 추가 검사를 위해 역어셈블리를 출력해요:
# ./bpf_jit_disasm
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
1: mov %rsp,%rbp
4: sub $0x60,%rsp
8: mov %rbx,-0x8(%rbp)
c: mov 0x68(%rdi),%r9d
10: sub 0x6c(%rdi),%r9d
14: mov 0xd8(%rdi),%r8
1b: mov $0xc,%esi
20: callq 0xffffffffe0ff9442
25: cmp $0x800,%eax
2a: jne 0x0000000000000042
2c: mov $0x17,%esi
31: callq 0xffffffffe0ff945e
36: cmp $0x1,%eax
39: jne 0x0000000000000042
3b: mov $0xffff,%eax
40: jmp 0x0000000000000044
42: xor %eax,%eax
44: leaveq
45: retq
대안으로 이 도구는 역어셈블리와 함께 관련 opcode를 덤프할 수도 있어요.
# ./bpf_jit_disasm -o
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
55
1: mov %rsp,%rbp
48 89 e5
4: sub $0x60,%rsp
48 83 ec 60
8: mov %rbx,-0x8(%rbp)
48 89 5d f8
c: mov 0x68(%rdi),%r9d
44 8b 4f 68
10: sub 0x6c(%rdi),%r9d
44 2b 4f 6c
14: mov 0xd8(%rdi),%r8
4c 8b 87 d8 00 00 00
1b: mov $0xc,%esi
be 0c 00 00 00
20: callq 0xffffffffe0ff9442
e8 1d 94 ff e0
25: cmp $0x800,%eax
3d 00 08 00 00
2a: jne 0x0000000000000042
75 16
2c: mov $0x17,%esi
be 17 00 00 00
31: callq 0xffffffffe0ff945e
e8 28 94 ff e0
36: cmp $0x1,%eax
83 f8 01
39: jne 0x0000000000000042
75 07
3b: mov $0xffff,%eax
b8 ff ff 00 00
40: jmp 0x0000000000000044
eb 02
42: xor %eax,%eax
31 c0
44: leaveq
c9
45: retq
c3
더 최근에는 bpftool이 시스템에 이미 로드된 주어진 BPF 프로그램 ID를 기반으로 BPF JIT 이미지를 덤프하는 같은 기능을 채택했어요(bpftool 섹션 참고).
JIT된 BPF 프로그램의 성능 분석에는 평소처럼 perf를 사용할 수 있어요. 전제 조건으로 JIT된 프로그램이 kallsyms 인프라를 통해 내보내져야 해요.
# echo 1 > /proc/sys/net/core/bpf_jit_enable
# echo 1 > /proc/sys/net/core/bpf_jit_kallsyms
bpf_jit_kallsyms를 활성화/비활성화해도 관련 BPF 프로그램 재로드는 필요 없어요. 다음으로 BPF 프로그램 프로파일링을 위한 작은 워크플로 예시를 제공해요. 데모 목적으로 정교한 tc BPF 프로그램이 사용되며, perf는 bpf_clone_redirect() 헬퍼 내부의 실패한 할당을 기록해요. 직접 쓰기(direct write) 사용으로 인해 bpf_try_make_head_writable()이 실패했고, 이어서 복제된 skb를 다시 해제하고 오류 메시지와 함께 반환돼요. 따라서 perf는 모든 kfree_skb 이벤트를 기록해요.
# tc qdisc add dev em1 clsact
# tc filter add dev em1 ingress bpf da obj prog.o sec main
# tc filter show dev em1 ingress
filter protocol all pref 49152 bpf
filter protocol all pref 49152 bpf handle 0x1 prog.o:[main] direct-action id 1 tag 8227addf251b7543
# cat /proc/kallsyms
[...]
ffffffffc00349e0 t fjes_hw_init_command_registers [fjes]
ffffffffc003e2e0 d __tracepoint_fjes_hw_stop_debug_err [fjes]
ffffffffc0036190 t fjes_hw_epbuf_tx_pkt_send [fjes]
ffffffffc004b000 t bpf_prog_8227addf251b7543
# perf record -a -g -e skb:kfree_skb sleep 60
# perf script --kallsyms=/proc/kallsyms
[...]
ksoftirqd/0 6 [000] 1004.578402: skb:kfree_skb: skbaddr=0xffff9d4161f20a00 protocol=2048 location=0xffffffffc004b52c
7fffb8745961 bpf_clone_redirect (/lib/modules/4.10.0+/build/vmlinux)
7fffc004e52c bpf_prog_8227addf251b7543 (/lib/modules/4.10.0+/build/vmlinux)
7fffc05b6283 cls_bpf_classify (/lib/modules/4.10.0+/build/vmlinux)
7fffb875957a tc_classify (/lib/modules/4.10.0+/build/vmlinux)
7fffb8729840 __netif_receive_skb_core (/lib/modules/4.10.0+/build/vmlinux)
7fffb8729e38 __netif_receive_skb (/lib/modules/4.10.0+/build/vmlinux)
7fffb872ae05 process_backlog (/lib/modules/4.10.0+/build/vmlinux)
7fffb872a43e net_rx_action (/lib/modules/4.10.0+/build/vmlinux)
7fffb886176c __do_softirq (/lib/modules/4.10.0+/build/vmlinux)
7fffb80ac5b9 run_ksoftirqd (/lib/modules/4.10.0+/build/vmlinux)
7fffb80ca7fa smpboot_thread_fn (/lib/modules/4.10.0+/build/vmlinux)
7fffb80c6831 kthread (/lib/modules/4.10.0+/build/vmlinux)
7fffb885e09c ret_from_fork (/lib/modules/4.10.0+/build/vmlinux)
perf가 기록한 스택 트레이스는 호출 트레이스의 일부로 bpf_prog_8227addf251b7543() 심볼을 보여주는데, 이는 태그 8227addf251b7543의 BPF 프로그램이 kfree_skb 이벤트와 관련됐고 tc가 보여주듯 ingress 훅의 netdevice em1에 부착됐음을 의미해요.
인트로스펙션 (Introspection)
리눅스 커널은 BPF와 XDP 주변에 다양한 트레이스포인트를 제공하며, 예를 들어 사용자 공간 프로그램과 bpf 시스템 콜의 상호작용을 추적하는 추가 인트로스펙션에 사용할 수 있어요.
BPF용 트레이스포인트:
# perf list | grep bpf:
bpf:bpf_map_create [Tracepoint event]
bpf:bpf_map_delete_elem [Tracepoint event]
bpf:bpf_map_lookup_elem [Tracepoint event]
bpf:bpf_map_next_key [Tracepoint event]
bpf:bpf_map_update_elem [Tracepoint event]
bpf:bpf_obj_get_map [Tracepoint event]
bpf:bpf_obj_get_prog [Tracepoint event]
bpf:bpf_obj_pin_map [Tracepoint event]
bpf:bpf_obj_pin_prog [Tracepoint event]
bpf:bpf_prog_get_type [Tracepoint event]
bpf:bpf_prog_load [Tracepoint event]
bpf:bpf_prog_put_rcu [Tracepoint event]
perf와 함께 사용하는 예(여기 사용된 sleep 예 대신 물론 tc 같은 특정 애플리케이션을 사용해도 됨):
# perf record -a -e bpf:* sleep 10
# perf script
sock_example 6197 [005] 283.980322: bpf:bpf_map_create: map type=ARRAY ufd=4 key=4 val=8 max=256 flags=0
sock_example 6197 [005] 283.980721: bpf:bpf_prog_load: prog=a5ea8fa30ea6849c type=SOCKET_FILTER ufd=5
sock_example 6197 [005] 283.988423: bpf:bpf_prog_get_type: prog=a5ea8fa30ea6849c type=SOCKET_FILTER
sock_example 6197 [005] 283.988443: bpf:bpf_map_lookup_elem: map type=ARRAY ufd=4 key=[06 00 00 00] val=[00 00 00 00 00 00 00 00]
[...]
sock_example 6197 [005] 288.990868: bpf:bpf_map_lookup_elem: map type=ARRAY ufd=4 key=[01 00 00 00] val=[14 00 00 00 00 00 00 00]
swapper 0 [005] 289.338243: bpf:bpf_prog_put_rcu: prog=a5ea8fa30ea6849c type=SOCKET_FILTER
BPF 프로그램의 경우 개별 프로그램 태그가 표시돼요.
디버깅을 위해 XDP는 예외가 발생할 때 트리거되는 트레이스포인트도 있어요:
# perf list | grep xdp:
xdp:xdp_exception [Tracepoint event]
예외는 다음 시나리오에서 트리거돼요:
- BPF 프로그램이 잘못된/알 수 없는 XDP 액션 코드를 반환한 경우.
- BPF 프로그램이 비우아한 종료를 나타내는
XDP_ABORTED로 반환한 경우. - BPF 프로그램이
XDP_TX로 반환했지만 전송 시 오류가 있는 경우. 예를 들어 포트가 up이 아니거나, 전송 링이 가득 차거나, 할당 실패 등으로. 두 트레이스포인트 클래스 모두 하나 이상의 트레이스포인트에 부착된 BPF 프로그램 자체로 검사할 수 있으며, 맵에서 추가 정보를 수집하거나 예를 들어bpf_perf_event_output()헬퍼를 통해 그러한 이벤트를 사용자 공간 수집기로 보낼 수 있어요.
추적 파이프 (Tracing pipe)
BPF 프로그램이 bpf_trace_printk()를 호출하면 출력이 커널 추적 파이프로 전송돼요. 사용자는 이 파일에서 이 버퍼로 추적되는 이벤트를 소비할 수 있어요:
# tail -f /sys/kernel/debug/tracing/trace_pipe
...
기타 (Miscellaneous)
BPF 프로그램과 맵은 perf와 유사하게 RLIMIT_MEMLOCK에 대해 메모리 계정이 부과돼요. 메모리에 잠글 수 있는 시스템 페이지 단위의 현재 사용 가능한 크기는 ulimit -l로 확인할 수 있어요. setrlimit 시스템 콜 매뉴얼 페이지가 추가 세부 정보를 제공해요.
기본 제한은 더 복잡한 프로그램이나 더 큰 BPF 맵을 로드하기에 보통 불충분해서, BPF 시스템 콜이 EPERM의 errno로 반환될 수 있어요. 그러한 상황에서는 ulimit -l unlimited 또는 충분히 큰 제한으로 해결할 수 있어요. RLIMIT_MEMLOCK은 주로 비특권 사용자에 대한 제한을 강제해요. 설정에 따라 특권 사용자에 대해 더 높은 제한을 설정하는 것이 종종 허용돼요.