- Published on
eBPF Verifier는 멈춘 곳만 알려준다 — 거부 235건을 재현해 측정한 진단 격차
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 에러는 멈춘 곳을 가리키고, 버그는 다른 곳에 있다
- 진단 격차란 무엇인가 — 증명을 잃은 지점 vs 검증이 멈춘 지점
- 데이터셋을 어떻게 만들었나 — 936건에서 235건으로
- 발견 1 — 거부의 19%는 소스가 멀쩡하다
- 발견 2 — 원인 12종, 그중 10종이 eBPF 고유
- 발견 3 — 에러 문자열 하나가 원인 9가지를 가리킨다
- 기존 도구들이 이 격차를 못 메우는 이유
- bpfix — 로그에서 증명의 생애를 복원한다
- 실제 사례 — aya 이슈 1002
- LLM은 verifier 에러를 고칠 수 있나 — bpfix-bench
- 이 수치를 어디까지 믿을 것인가
- 한편, verifier 자체도 바뀌고 있다
- 그래서 실무자는 무엇을 할 것인가
- 마치며
- 참고 자료
들어가며 — 에러는 멈춘 곳을 가리키고, 버그는 다른 곳에 있다
eBPF 프로그램을 한 번이라도 진지하게 작성해 본 사람은 이 경험을 압니다. 패킷 포인터를 분명히 바운드 체크했는데, 한참 뒤의 로드 명령에서 R5 invalid mem access 'scalar'가 뜹니다. 에러가 가리키는 줄에는 의심할 만한 게 아무것도 없습니다. 그래서 어떻게 하냐면 — 고칠 만한 데를 아무 데나 고쳐 보고, 다시 로드해 보고, 또 튕기면 또 고쳐 봅니다. eBPF 개발이 시행착오의 반복이 되는 지점이 여기입니다.
eBPF 기초 편에서 정리했듯, verifier의 존재 이유는 명확합니다. 커널 안에서 임의의 코드를 돌리려면 누군가 그게 안전하다는 걸 증명해야 하고, verifier가 그 증명을 대신 해 줍니다. 문제는 증명에 실패했을 때 verifier가 실패를 설명하는 방식입니다.
2026년 7월 2일 arXiv에 올라온 Characterizing and Bridging the Diagnostic Gap in eBPF Verifier Rejections(Zheng 등, arXiv:2607.02748)는 이 답답함을 처음으로 제대로 측정했습니다. 저자들은 UC Santa Cruz, Virginia Tech, Telecom Paris, University of Washington, University of Connecticut, 그리고 eunomia-bpf 소속입니다.
논문의 한 문장이 전체를 요약합니다 — 에러는 검증이 멈춘 곳을 보고할 뿐, 프로그램이 verifier가 요구한 증명을 잃은 곳을 보고하지 않는다.
진단 격차란 무엇인가 — 증명을 잃은 지점 vs 검증이 멈춘 지점
이 구분이 이 글 전체의 핵심이라 조금 천천히 가겠습니다.
verifier는 명령어 하나하나를 따라가며 각 값에 대해 "이 값은 안전하게 쓸 수 있다"는 증명을 쌓습니다. 예컨대 패킷 포인터라면 "이 포인터는 바운드 안에 머문다"는 증명입니다. 어떤 명령에서 그 증명이 없는 채로 값을 쓰려고 하면, 거기서 검증이 멈추고 프로그램이 거부됩니다.
그런데 증명이 없어진 시점과 증명이 필요해진 시점은 대개 다릅니다. 논문이 드는 실행 예시가 선명합니다. 어떤 중간 상태에서 R5는 pkt(off=34,r=42), 즉 바운드 정보를 가진 패킷 포인터입니다. 그런데 명령 37 직전의 상태에서 같은 R5가 스칼라 값 40으로 표현됩니다. 그리고 명령 37이 R5를 역참조합니다.
- 증명을 잃은 지점: 패킷 포인터가 스칼라로 바뀐 어딘가 (논문 용어로 loss point)
- 검증이 멈춘 지점: 명령 37 (에러가 알려주는 유일한 곳)
에러는 후자만 알려줍니다. 개발자가 고쳐야 하는 곳은 전자입니다. 논문의 표현대로, 수정은 잃어버린 증명을 복원해야 하는데 에러는 그 증명을 필요로 한 연산만 이름 붙여 줍니다. 그래서 개발자는 트레이스로부터 "어떤 증명이 필요했고, 그게 어디서 사라졌는지"를 손으로 재구성해야 합니다.
데이터셋을 어떻게 만들었나 — 936건에서 235건으로
논문의 실증 파트가 정직한 이유는 방법론을 숨기지 않기 때문입니다.
저자들은 네 군데에서 후보 936건을 모았습니다 — Stack Overflow 질문, GitHub 이슈, GitHub 수정 커밋, 커널 셀프테스트. 그리고 각 후보를 하나의 고정 툴체인에서 다시 빌드했습니다. 커널 6.15.11, clang 18, 로그 레벨 2입니다. (6.15는 2025년 5월에 나온 메인라인 계열의 stable 지점 릴리스입니다.)
936건 중 실제로 이 빌드에서 거부된 것은 235건이었고, 이 235건이 bpfix-empirical이라는 데이터셋이 됩니다. 나머지가 탈락한 이유도 명시돼 있습니다 — 저자들의 툴체인에서는 거부되지 않거나, 특정 환경을 필요로 하거나, 다시 빌드할 소스가 없어서입니다.
각 사례는 문제의 소스와 개발자 본인이 실제로 적용한 수정을 함께 갖고 있습니다. 이게 중요합니다. "무엇이 진짜 원인이었나"의 정답을 저자들이 추측한 게 아니라, 원 보고서에 남은 실제 수정이 알려 주기 때문입니다.
발견 1 — 거부의 19%는 소스가 멀쩡하다
235건을 "수정이 어디에 착지했는가"로 분류한 결과가 첫 번째 놀라움입니다.
- 191건(81%): 수정이 프로그램 소스를 바꿨다. 즉 진짜 프로그램 버그.
- 44건(19%): 소스는 옳은데 거부됐다. 수정은 다른 층에서 이뤄졌다 — 컴파일러 18건, 환경 14건, verifier 자체 12건.
논문이 드는 예가 인상적입니다. 명백히 안전한 컨텍스트 필드 읽기가 거부되는데, 이유는 -O0이 그 읽기를 스칼라로 낮춰 버려서(lowering) verifier가 포인터 출처를 못 보기 때문입니다. 수정은 소스를 건드리지 않는 컴파일러 플래그입니다. 논문 Figure 2의 코드는 이렇게 단순합니다.
// 올바른 소스. -O0이 필드 읽기를 스칼라로 낮춘다
static int add_one(int x) { return x + 1; }
int prog(struct xdp_md *ctx) { return add_one(ctx->rx_queue_index) & 1; }
// verifier: R2 invalid mem access 'scalar'
이건 실무적으로 꽤 중요한 이야기입니다. verifier가 거부했다고 해서 당신 코드가 틀렸다는 뜻이 아닙니다. 다섯 번 중 한 번쯤은 코드가 옳고, 컴파일러가 증명을 가렸거나 환경이 안 맞거나 verifier가 아직 그 패턴을 이해하지 못하는 것입니다. 다만 에러 메시지는 이 둘을 구분해 주지 않습니다.
발견 2 — 원인 12종, 그중 10종이 eBPF 고유
191건의 진짜 버그는 12가지 근본 원인으로 나뉩니다. 여기서 "근본 원인"은 verifier가 실패한 검사가 아니라 개발자가 소스 수준에서 저지른 실수를 뜻합니다.
| 근본 원인 범주 | 사례 수 |
|---|---|
| 클램프되지 않은 스칼라를 오프셋이나 길이로 사용 | 24 |
| 손상되었거나 오래된 dynptr 객체 | 23 |
| 모든 경로에 바운드가 없는 패킷 접근 | 22 |
| 널 검사 누락 | 19 |
| 포인터 타입 또는 출처(provenance) 불일치 | 16 |
| 검증되지 않은 주소의 역참조 | 16 |
| 인덱스가 객체 용량을 초과 | 15 |
| 컨텍스트 또는 계약(contract) 오용 | 15 |
| 짝이 맞지 않는 리소스 참조 | 15 |
| 인터럽트 플래그를 순서대로 복원하지 않음 | 11 |
| 프로브 시그니처가 ABI와 불일치 | 9 |
| 크기 초과이거나 초기화되지 않은 스택 버퍼 | 6 |
| 합계 | 191 |
논문의 지적은 이 12종 중 10종이 eBPF 고유라는 것입니다. 패킷 바운드를 증명하는 일이나 dynptr을 그 수명과 짝지어 다루는 일은 일반적인 C 프로그래밍 지식으로는 나오지 않습니다. 그래서 수정하려면 일반 프로그래밍 실력이 아니라 도메인 지식이 필요합니다. 널 검사 누락 정도가 예외적으로 일반적인 축에 듭니다.
이건 왜 eBPF 학습 곡선이 그렇게 가파른지에 대한 정량적 설명이기도 합니다. 문제의 대부분이 "C를 잘 짜면 되는" 종류가 아닙니다.
발견 3 — 에러 문자열 하나가 원인 9가지를 가리킨다
가장 아픈 수치가 여기 있습니다.
첫째, errno는 거의 아무 정보도 주지 않습니다. 전체 거부의 47%가 EINVAL 하나만 돌려줍니다. 절반 가까이가 "뭔가 잘못됨"이라는 뜻의 에러 코드로 뭉뚱그려지는 셈입니다.
둘째, 에러 문자열도 너무 거칩니다. 저자들은 각 에러 문자열에서 레지스터 번호와 오프셋을 마스킹해 정규화한 뒤 235건을 묶어 봤습니다. 서로 다른 문자열 167개가 템플릿 82개로 줄어듭니다. 그리고 82개 템플릿 중 15개가 각각 둘 이상의 근본 원인에 대응합니다.
가장 심한 것이 아래 표의 첫 줄입니다.
| 터미널 메시지 템플릿 | 사례 수 | 대응하는 원인 범주 수 |
|---|---|---|
R# invalid mem access 'scalar' | 28 | 9 |
invalid access to packet | 26 | 5 |
invalid access to map value | 18 | 4 |
R# !read_ok | 13 | 4 |
invalid mem access 'scalar' 하나가 28건에 걸쳐 서로 다른 근본 원인 9가지를 가립니다. 이 메시지를 보고 원인을 좁힐 수 있다고 믿는 건 통계적으로 근거가 없습니다. 논문의 Takeaway를 그대로 옮기면 — 터미널 에러는 수정을 안내하기에 너무 거칠다.
기존 도구들이 이 격차를 못 메우는 이유
이미 도구가 있지 않냐고 물을 수 있습니다. 논문은 두 가지를 짚습니다.
PrettyVerifier(Rizza 등, 2025 IEEE CSR)는 정규표현식으로 터미널 에러를 소스 줄과 힌트에 매핑합니다. BPF Verifier Visualizer(bpfvv)는 명령어별 상태를 인터랙티브하게 렌더링하는 프런트엔드입니다. bpfvv 저장소 자신도 스스로를 원시적인 디버거 UI라고 설명합니다 — 런타임 상태가 아니라 로그를 해석하는.
논문의 평가는 이렇습니다 — 둘 다 개발자가 에러를 읽는 걸 도와주지만, 증명을 어디서 잃었는지 짚어 주지는 않는다.
그리고 왜 이게 어려운지에 대한 설명이 날카롭습니다. Rust 같은 언어의 에러 설명 도구는 검사기가 만들어 둔 구조물(AST, 타이핑 제약)을 따라 걸을 수 있습니다. 그런데 eBPF verifier는 그런 아티팩트를 노출하지 않습니다. 남는 건 로그뿐입니다.
bpfix — 로그에서 증명의 생애를 복원한다
그래서 저자들이 만든 것이 bpfix입니다. 핵심 통찰은 이겁니다 — 로그 레벨 2에서 verifier는 명령어마다 추상 상태를 전부 찍습니다. 즉 잃어버린 증명은 이미 로그 안에 살아 있습니다. 아무도 그걸 읽어 주지 않을 뿐입니다.
bpfix는 Rust로 약 23,000줄이고, 유일한 필수 입력이 기존 verifier 로그입니다. 소스나 오브젝트 메타데이터는 선택 사항이고, 있으면 명령어에서 소스로의 매핑이 좋아집니다. 파이프라인은 세 단계입니다.
- 로그 분석 — 터미널 에러와 명령어별 추상 상태를 정규화된 증거 스트림으로 뽑는다.
- 증명 복원 — 터미널 에러를 증명 계열(proof family)에 매핑한다. 포인터 출처, 패킷 바운드, 스칼라 범위 같은 것들이다. 그리고 그 증명에 대한 증거가 언제 성립하고, 유지되고, 사라지는지를 추적한다.
- 진단 생성 — 스팬을 고르고, 증명을 다시 세우기 위한 지침을 만든다.
출력은 Rust 스타일 평문 진단입니다. 안정적인 에러 식별자, 필요한 증명, 관련 스팬과 증거, 관측 가능한 경우의 loss point, 그리고 help: 지침으로 구성됩니다. "관측 가능한 경우"라는 단서는 저자들이 직접 단 것입니다 — 언제나 잡아내는 게 아닙니다.
또 하나 실용적인 기능은 책임 층 분리입니다. bpfix는 거부를 source_bug(소스 층)와 lowering_artifact(컴파일러 층)로 라벨링합니다. 앞서 본 "19%는 소스가 멀쩡하다" 문제에 직접 대응하는 기능입니다. 다만 논문은 이걸 최선 노력 기반 귀속(best-effort attribution)이라고 명시합니다.
논문 Figure 5가 이 기능의 요점을 보여 줍니다. 두 거부가 똑같은 터미널 메시지 invalid mem access 'scalar'로 끝나는데, 하나는 소스 버그(Stack Overflow 56965789 — 패킷 베이스 없이 정수 오프셋을 포인터로 캐스팅)이고 다른 하나는 컴파일러 lowering 아티팩트(Stack Overflow 53136145 — 분기 병합이 패킷 포인터 타입을 가림)입니다. 수정이 착지해야 하는 층이 완전히 다른데 verifier 메시지는 동일합니다.
실제 사례 — aya 이슈 1002
논문이 든 구체적인 예 하나를 보겠습니다. 맵 객체 &globals의 주소를 __u64 *로 캐스팅해서 직접 읽고 쓰는 프로그램입니다.
// 거부되는 프로그램
__u64 *raw_map = (__u64 *)&globals; // 맵 객체 포인터
__u64 v = *raw_map;
*raw_map = v + 1; // 여기서 거부
// verifier: only read from bpf_array is supported
verifier 메시지는 틀리지 않았습니다. 다만 증상만 말합니다 — 맵 객체를 통한 쓰기를 봤다는 것. 왜 안 되는지, 뭘 해야 하는지는 말하지 않습니다.
bpfix의 진단은 이렇습니다 — 맵 포인터가 일반 메모리처럼 접근되고 있다(source_bug), 필요한 증명은 맵 내용을 읽거나 쓰기 전에 적절한 맵 헬퍼로 맵-값 포인터를 유도하는 것이다, 다음 행동은 원소를 룩업한 뒤 그 포인터를 통해 쓰는 것이다.
그리고 실제 개발자의 수정이 정확히 그 진단을 따라갑니다.
// 개발자의 실제 수정
__u32 key = 0;
__u64 *v = bpf_map_lookup_elem(&globals, &key);
if (!v) return 0;
*v += 1;
LLM은 verifier 에러를 고칠 수 있나 — bpfix-bench
논문의 마지막 축은 요즘 사람들이 실제로 궁금해하는 질문입니다. 어차피 에러 로그를 LLM한테 던질 텐데, 그게 되긴 하나?
저자들은 bpfix-bench라는 벤치마크를 만들었습니다. 소스 수준 수리 과제 75개이고, 40개는 특정 verifier 증명을 중심으로 구성했으며 35개는 Cilium, xdp-tools, bpftrace 같은 오픈소스 프로젝트에서 최소화해 뽑았습니다.
채점 방식이 정직합니다. 각 과제는 bpfix와 독립적인 실행 가능한 테스트 스위트를 함께 싣습니다. 수정이 인정받으려면 (1) 실제 커널 verifier를 통과해 로드되고, (2) 기능 테스트를 통과하고, (3) 소스 시맨틱 검사를 통과해야 합니다. 즉 "그럴듯한 텍스트"로는 점수를 못 받습니다. verifier를 우회하려고 로직을 망가뜨리는 흔한 꼼수도 소스 시맨틱 검사에서 걸립니다.
모델은 Qwen3.6 27B(주 모델), GLM 5.2(호스팅), Qwen2.5 3B 세 가지이고 temperature 0입니다. 3B를 넣은 이유는 이득이 작은 모델에서도 살아남는지 보려는 것입니다. 결과는 아래와 같습니다(전부 75문제 기준, 저자 자체 측정).
| 모델 | 원본 로그 1회 | bpfix 1회 | 원본 로그 + 재시도 1회 | bpfix + 재시도 1회 |
|---|---|---|---|---|
| GLM 5.2 | 28 (37.3%) | 38 (50.7%) | 47 (62.7%) | 52 (69.3%) |
| Qwen3.6 27B | 22 (29.3%) | 38 (50.7%) | 30 (40.0%) | 44 (58.7%) |
| Qwen2.5 3B | 0 (0%) | 8 (10.7%) | 0 (0%) | 10 (13.3%) |
두 가지를 읽을 수 있습니다.
첫째, 원본 verifier 로그만 주면 현재 모델들은 잘 못 고칩니다. 원샷 성공률이 0~37%입니다. 논문 표현으로는, 3B에서 27B까지 세 모델에 걸쳐 verifier 거부는 유능한 모델에게도 여전히 어렵습니다.
둘째, 로그를 bpfix 진단으로 바꾸면 개선됩니다. 논문이 초록에서 주장하는 폭은 11~21%p입니다. 원샷 기준으로 보면 Qwen3.6이 21.4%p로 가장 크고, Qwen2.5 3B가 10.7%p, GLM 5.2가 13.4%p입니다. 다만 GLM 5.2의 재시도 모드 이득은 6.6%p로 훨씬 작다는 점은 표에서 직접 확인해야 합니다 — 이 초록의 범위는 원샷 기준으로 읽는 게 맞습니다.
실패가 어느 단계에서 사라지는지도 저자들이 분해해 놨는데, 이게 오히려 더 설득력 있습니다. Qwen3.6 27B에서 verifier 로드 실패가 19건에서 10건으로, 소스 시맨틱 실패가 22건에서 16건으로 줄었습니다. GLM 5.2도 같은 방향입니다(10 → 5, 25 → 22). 컴파일 실패는 양쪽 모두 낮게 유지됐습니다.
작은 모델 이야기는 따로 볼 만합니다. Qwen2.5 3B는 원본 로그로 75문제 중 하나도 못 고쳤습니다. 원샷 후보 62건이 verifier 로드에서 실패했고, 프롬프트 3건은 아예 컨텍스트 윈도를 넘겨 프로그램을 반환하지 못했습니다. 더 짧은 bpfix 진단을 주면 모델 호출 실패는 사라지고 로드 실패는 39건으로 줄지만, 컴파일 실패는 7건에서 14건으로 늘어납니다. 저자들의 해석이 정직합니다 — bpfix는 작은 모델에게도 더 의미 있는 수리를 시도할 만큼의 verifier 관련 정보를 주지만, 평범한 코드 생성 오류는 그대로 남는다.
이 수치를 어디까지 믿을 것인가
여기서부터는 논문이 스스로 말한 한계와, 제가 읽으며 붙인 유보입니다. 이 글의 숫자 대부분이 저자 자체 측정이라는 점을 먼저 분명히 해 둡니다. bpfix도, 벤치마크도 같은 팀이 만들었습니다.
선택 효과가 큽니다. 936건 중 235건만 남았습니다. 남은 이유는 저자들의 고정 툴체인에서 재현됐기 때문이고, 탈락한 701건 중 상당수는 환경 의존적이거나 소스가 없는 것들입니다. 그리고 애초에 데이터 출처가 Stack Overflow와 GitHub 이슈입니다 — 사람들이 질문을 올릴 만큼 헷갈렸던 거부들이 과대표집됩니다. "진단이 불충분하다"는 결론을 내기에 유리한 쪽으로 표본이 기울어 있을 수 있습니다. 물론 커널 셀프테스트와 수정 커밋이 섞여 있어 순수 편향은 아니고, 실무에서 사람을 실제로 막아 세우는 게 그 헷갈리는 거부라는 반론도 성립합니다.
툴체인이 하나로 고정돼 있습니다. 커널 6.15.11, clang 18입니다. verifier는 릴리스마다 바뀌고, 특히 에러 메시지와 pruning 동작은 계속 손질됩니다. 6.15에서 측정한 47%가 최신 커널에서 그대로일 거라고 가정하면 안 됩니다.
모델 선택이 제한적입니다. Qwen3.6 27B, GLM 5.2, Qwen2.5 3B입니다. 프런티어급 모델은 평가에 없습니다. 그러니 "LLM은 verifier 에러를 못 고친다"가 아니라 "이 세 모델은 원본 로그로 0~37%를 고쳤다"가 정확한 진술입니다.
그리고 bpfix를 써도 천장이 낮습니다. 최고 성적이 원샷 50.7%, 재시도까지 붙여 69.3%입니다. 진단이 좋아지면 수리가 나아진다는 방향성은 세 모델에서 일관되게 나왔지만, "이제 LLM에게 맡기면 된다" 수준과는 거리가 있습니다.
도구 성숙도도 감안해야 합니다. bpfix 저장소는 MIT 라이선스로 공개돼 있고 v0.1.0이 2026년 7월 11일에 나왔습니다. 닷새 된 0.1.0입니다. README 자신이 스스로를 커널 패치도, verifier 대체물도, 자동 소스 수리 도구도 아니라고 못 박습니다. 진단 설명 도구입니다. 프로덕션 파이프라인에 넣기 전에 그 정도 기대치로 접근하는 게 맞습니다.
한편, verifier 자체도 바뀌고 있다
이 논문이 "로그를 더 잘 읽자"는 접근이라면, 반대편에는 "verifier가 애초에 덜 거부하게 만들자"는 흐름이 있습니다.
LWN의 BPF loop verification with scalar evolution(Daroc Alden, 2026년 6월 9일)이 다룬 Eduard Zingerman의 작업이 그 예입니다. 2026년 LSFMM+BPF 서밋에서 발표된 진행 중인 작업으로, 목표는 Zingerman의 말 그대로 verifier가 전형적인 for/while 루프를 루프를 돌지 않고 단일 패스로 처리하게 하는 것입니다.
현재 verifier는 루프를 만나면 종료 조건에 도달할 때까지 반복마다 평가하는데, 이 과정에서 더 나은 구현이라면 걸리지 않았을 명령어 수 제한에 잘못 걸릴 수 있습니다. 스칼라 진화(scalar evolution)는 루프 안에서 변수가 가질 수 있는 값의 범위를 계산해 이걸 피하려는 기법입니다.
다만 이건 아직 프로토타입이고 머지되지 않았습니다. 스택에 스필/복원되는 레지스터를 다루지 못하고, 모든 루프 변수를 균일하게 분석하지도 못합니다. Zingerman은 스택 조작, 부호 있는 정수 연산, 더 복잡한 루프를 지원한 뒤에 커널 제출을 제안할 계획이라고 밝혔습니다. 로드 시간에 대해서는 엄밀한 측정을 하지 않았다고 본인이 말했습니다. (Starovoitov는 과거 유사한 verifier 변경들이 처음엔 우려를 샀지만 결국 로드 시간이 빨라지고 메모리 사용이 줄었다고 언급했습니다.)
두 흐름이 같은 문제의 양면입니다 — 하나는 거부를 줄이고, 하나는 거부됐을 때 이해 가능하게 만듭니다. 그리고 후자가 사라질 일은 없습니다. verifier가 아무리 똑똑해져도 거부는 남고, 남는 거부는 설명돼야 합니다.
그래서 실무자는 무엇을 할 것인가
이 논문에서 도구를 도입하지 않아도 가져갈 수 있는 것들이 있습니다.
로그 레벨 2를 기본으로 쓰십시오. bpfix가 존재할 수 있는 이유 자체가 "필요한 정보는 이미 로그에 있다"는 것입니다. 레벨 2에서 verifier는 명령어마다 추상 상태를 찍습니다. 그걸 안 켜고 있다면 진단의 원재료를 버리고 있는 겁니다.
터미널 에러 줄을 믿지 마십시오. 그건 검증이 멈춘 곳입니다. 버그는 증명이 사라진 곳에 있습니다. 로그를 거슬러 올라가며 문제의 레지스터가 언제 마지막으로 제대로 된 타입이었는지를 찾는 게 실제로 해야 하는 일입니다. pkt(off=..,r=..)가 scalar로 바뀐 지점 같은 것 말이죠.
거부를 무조건 자기 탓으로 돌리지 마십시오. 다섯 중 하나는 소스가 멀쩡합니다. 특히 -O0이나 특이한 최적화 레벨로 빌드하고 있다면 컴파일러 lowering을 의심할 이유가 통계적으로 있습니다.
에러 문자열로 검색해서 나온 답을 그대로 믿지 마십시오. invalid mem access 'scalar'는 원인 9가지를 가립니다. Stack Overflow에서 같은 메시지에 달린 답이 당신 경우에 맞을 확률은 생각보다 낮습니다.
LLM에게 던질 거면 로그만 던지지 마십시오. 이 논문이 보여 준 건 진단의 질이 수리 성공률을 좌우한다는 것입니다. 어떤 증명이 필요했고 어디서 사라졌는지에 대한 맥락을 사람이 먼저 좁혀 주면 결과가 달라집니다.
마치며
이 논문이 가치 있는 이유는 새로운 사실을 알려줘서가 아닙니다. eBPF 개발자들이 이미 몸으로 알던 걸 숫자로 만들었기 때문입니다. verifier 에러가 도움이 안 된다는 건 모두가 알았지만, 47%가 EINVAL이고 하나의 에러 문자열이 원인 9가지를 가린다는 건 아무도 몰랐습니다.
그리고 진단이 이렇게 어려운 근본 이유가 구조적이라는 점이 흥미롭습니다. Rust 컴파일러는 자기가 만든 타이핑 제약을 따라 걸으며 에러를 설명할 수 있습니다. eBPF verifier는 그런 아티팩트를 남기지 않습니다. 로그만 남깁니다. bpfix가 하는 일은 결국 그 로그를 아티팩트로 되돌리는 역공학입니다. 근사한 해법이지만, 애초에 verifier가 증명 구조를 내보내 줬다면 필요 없었을 일이기도 합니다. 장기적으로 이 격차가 진짜로 닫히는 길은 커널 쪽에 있을 겁니다.
당장은 bpfix가 닷새 된 0.1.0이고, 개선 폭은 저자 자체 측정이고, 툴체인 하나에서 잰 수치입니다. 그러니 이 글의 결론은 "bpfix를 쓰세요"가 아닙니다. verifier가 멈춘 지점과 당신 버그가 있는 지점은 다르다 — 이걸 명시적으로 알고 로그를 읽기 시작하는 것만으로도, 다음번 invalid mem access 'scalar' 앞에서 보내는 시간이 줄어들 겁니다.
참고 자료
- Characterizing and Bridging the Diagnostic Gap in eBPF Verifier Rejections (Zheng 등, arXiv:2607.02748, 2026년 7월 2일)
- eunomia-bpf/bpfix — 논문의 도구 구현 (MIT, v0.1.0 / 2026년 7월 11일)
- BPF loop verification with scalar evolution (Daroc Alden, LWN, 2026년 6월 9일)
- libbpf/bpfvv — BPF Verifier Visualizer
- eBPF verifier — 커널 공식 문서
- eBPF 기초 완전 정복 — 프로그램, 맵, 그리고 Verifier의 세계(관련 글)