Microsoft MVP성태의 닷넷 이야기
Linux: 86. Golang + bpf2go를 사용한 eBPF 기본 예제 [링크 복사], [링크+제목 복사],
조회: 6199
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 14개 있습니다.)
Linux: 86. Golang + bpf2go를 사용한 eBPF 기본 예제
; https://www.sysnet.pe.kr/2/0/13769

Linux: 94. eBPF - vmlinux.h 헤더 포함하는 방법 (bpf2go에서 사용)
; https://www.sysnet.pe.kr/2/0/13783

Linux: 95. eBPF - kprobe를 이용한 트레이스
; https://www.sysnet.pe.kr/2/0/13784

Linux: 96. eBPF (bpf2go) - fentry, fexit를 이용한 트레이스
; https://www.sysnet.pe.kr/2/0/13788

Linux: 100.  eBPF의 2가지 방식 - libbcc와 libbpf(CO-RE)
; https://www.sysnet.pe.kr/2/0/13801

Linux: 103. eBPF (bpf2go) - Tracepoint를 이용한 트레이스 (BPF_PROG_TYPE_TRACEPOINT)
; https://www.sysnet.pe.kr/2/0/13810

Linux: 105. eBPF - bpf2go에서 전역 변수 설정 방법
; https://www.sysnet.pe.kr/2/0/13815

Linux: 106. eBPF / bpf2go - (BPF_MAP_TYPE_HASH) Map을 이용한 전역 변수 구현
; https://www.sysnet.pe.kr/2/0/13817

Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
; https://www.sysnet.pe.kr/2/0/13819

Linux: 109. eBPF / bpf2go - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
; https://www.sysnet.pe.kr/2/0/13824

Linux: 110. eBPF / bpf2go - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
; https://www.sysnet.pe.kr/2/0/13825

Linux: 115. eBPF (bpf2go) - ARRAY / HASH map 기본 사용법
; https://www.sysnet.pe.kr/2/0/13893

Linux: 116. eBPF / bpf2go - BTF Style Maps 정의 구문과 데이터 정렬 문제
; https://www.sysnet.pe.kr/2/0/13894

Linux: 117. eBPF / bpf2go - Map에 추가된 요소의 개수를 확인하는 방법
; https://www.sysnet.pe.kr/2/0/13895




Golang + bpf2go를 사용한 eBPF 기본 예제

Golang의 경우 cilium repo에서 bpf2go라는 프로그램이 제공돼,

cilium/ebpf - ebpf-go is a pure-Go library to read, modify and load eBPF programs and attach them to various hooks in the Linux kernel
; https://github.com/cilium/ebpf

꽤나 간편하게 eBPF 프로그램을 작성할 수 있는데요, 이번 글에서는 위의 문서에 나온 "Getting Started" 내용을 간단하게 실습해 보겠습니다.




Go 프로젝트를 하나 만들고, 다음의 eBPF C 코드 파일을 하나 추가해줍니다.

//go:build ignore

typedef unsigned long long u64;
typedef unsigned int u32;

#include <linux/bpf.h>
#include <linux/types.h>
#include <bpf/bpf_helpers.h>

SEC("kprobe/sys_clone")
int kprobe_sys_clone(void *ctx)
{
    u64 bpf_id = bpf_get_current_pid_tgid();
    u32 tgid = bpf_id >> 32;
    u32 pid = bpf_id;

    bpf_printk("pid == %d, thread_id == %d\n", tgid, pid);
    return -1;
}

char __license[] SEC("license") = "GPL";

보는 바와 같이, 그냥 "Hello World" 성격의 eBPF 예제에 불과합니다. 위의 코드를 bpf2go를 이용하면 GoLang에서 바로 사용할 수 있는 object 파일과 소스코드가 생성되는데요, 문서에 따라 다음과 같이 실행해 볼 수 있습니다.

// Go 예제 패키지의 이름이 ebpf_sample이라고 가정
// 위에서 소개한 eBPF C 코드 파일명은 basic.c, 그것을 bpf2go로 컴파일해 생성할 Go 파일명을 ebpf_basic으로 지정

$ GOPACKAGE=ebpf_sample go run github.com/cilium/ebpf/cmd/bpf2go ebpf_basic basic.c
Compiled /mnt/c/temp/ebpf_sample/ebpf_basic_bpfel.o
Stripped /mnt/c/temp/ebpf_sample/ebpf_basic_bpfel.o
Wrote /mnt/c/temp/ebpf_sample/ebpf_basic_bpfel.go
Compiled /mnt/c/temp/ebpf_sample/ebpf_basic_bpfeb.o
Stripped /mnt/c/temp/ebpf_sample/ebpf_basic_bpfeb.o
Wrote /mnt/c/temp/ebpf_sample/ebpf_basic_bpfeb.go

...el, ...eb 파일이 있는데, 각각의 .go 파일을 열어보면 상단에 다음과 같은 내용이 나옵니다.

// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64
...[생략]...

// Code generated by bpf2go; DO NOT EDIT.
//go:build mips || mips64 || ppc64 || s390x
...[생략]...

이에 대해서는 아래의 문서에 나오는데요,

Shipping Portable eBPF-powered Applications
; https://ebpf-go.dev/guides/portable-ebpf/

  • _bpfel.o and *_bpfel.go for little-endian architectures like amd64, arm64, riscv64 and loong64
  • _bpfeb.o and *_bpfeb.go for big-endian architectures like s390(x), mips and sparc

따라서 대개의 경우 (amd64, arm64를 모두 커버하는) ...el 파일만 있어도 충분할 것입니다. 자, 그럼 이것을 가지고 테스트를 해볼까요?

package main

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go ebpf_basic basic.c

import (
	"fmt"
)

func main() {
	var bpfObj ebpf_basicObjects // bpf2go로 자동 생성된 타입
	err := loadEbpf_basicObjects(&bpfObj, nil) // bpf2go로 자동 생성된 함수
	if err != nil {
		fmt.Printf("objs == null, %v\n", err)
		return
	}
    defer func(bpfObj *ebpf_basicObjects) {
        _ = bpfObj.Close()
    }(&bpfObj)

	fmt.Printf("%v\n", bpfObj)
}

빌드 후 그냥 실행해 보면 이런 출력이 나옵니다.

objs == null, field ClsMain: program cls_main: load program: operation not permitted (MEMLOCK may be too low, consider rlimit.RemoveMemlock)

오류 메시지의 권한 문제뿐만 아니라, 함께 출력된 MEMLOCK 관련 제한을 해제하기 위해 다음과 같이 코드를 추가할 수 있는데요,

// import (
//  ...[생략]...
// 	"github.com/cilium/ebpf/rlimit"
// )

if err := rlimit.RemoveMemlock(); err != nil {
    log.Fatal("Removing memlock:", err)
}

// 실행 결과:
// Removing memlock:failed to set memlock rlimit: operation not permitted

저것 자체도 권한 문제로 실패합니다. eBPF는 나름 커널을 건드리는 만큼 권한이 필요하다는 것은 어쩔 수 없으니 sudo를 이용해 실행해야 합니다.

$ sudo ./ebpf_sample
loaded: {{Kprobe(kprobe_sys_clone)#7} {}}

(또한, SELinux가 설치된 환경이라면 데몬으로 실행하는 경우에는 별도 보안 설정이 필요합니다.)





이제 남은 작업은, 리눅스 커널에 해당 eBPF 코드를 연결하는 것인데요, 이것은 link ("github.com/cilium/ebpf/link") 패키지를 이용하면 됩니다.

이번 글에서 만든 eBPF 코드는 kprobe를 사용하므로, 이렇게 완성할 수 있습니다.

const (
	kprobeFunc = "sys_clone"
)

// ...[생략]...

// kprobe 연결
kp, err := link.Kprobe(kprobeFunc, bpfObj.KprobeSysClone, nil)
if err != nil {
    fmt.Printf("kp == null, %v\n", err)
}
defer func(kp link.Link) {
    _ = kp.Close()
}(kp)

fmt.Printf("link.Kprobe: %v\n", kp)

// 종료하지 못하도록 대기
fmt.Println("Press any key to exit...")
input := bufio.NewScanner(os.Stdin)
input.Scan()

위의 코드를 실행하면 이전에 작성했던 basic.c 파일의 eBPF 코드가 동작하게 됩니다.

$ sudo ./ebpf_sample
loaded: {{Kprobe(kprobe_sys_clone)#7} {}}
link.Kprobe: &{{0xc0001860a0 } 0xc000014030}
Press any key to exit...

따라서, 이제 해당 리눅스 시스템에서 sys_clone 커널 함수가 호출될 때마다 kprobe로 연결한 eBPF 코드가 실행될 텐데요, 이에 대한 확인을 bpf_printk 코드로 심었던 출력으로 알 수 있습니다.

bpf_printk가 보낸 출력은 /sys/kernel/debug/tracing/trace_pipe 파일에 전달되므로 다른 터미널을 하나 띄워 cat 명령어를 수행하면,

$ sudo cat /sys/kernel/debug/tracing/trace_pipe
bash-2178834 [010] ....1 351198.765874: bpf_trace_printk: pid == 2178834, thread_id == 2178834
bash-2178834 [010] ....1 351198.766143: bpf_trace_printk: pid == 2178834, thread_id == 2178834
...clone이 수행될 때마다 메시지 출력...

위와 같은 식의 메시지를 확인할 수 있습니다. 실제로 pid, tid가 정상적으로 출력되는지 테스트를 해볼까요? ^^

이를 위해 우선 bash shell을 하나 더 띄우고, 그것의 pid를 알아낸 다음,

// 터미널 A: bash

$ echo $$
596613

다른 터미널에서 저 pid로 trace_pipe 파일을 확인하는 명령어를 수행해 둡니다.

// 터미널 B: trace_pipe 확인

$ sudo cat /sys/kernel/debug/tracing/trace_pipe | grep 596613

이 상태에서 "터미널 A"로 돌아가 아무 명령어나 실행하면,

$ ls

"터미널 B"에서는 pid == 596613에 속한 메시지가 뜨는 것을 확인할 수 있습니다.

bash-596613  [003] ...21 411689.876366: bpf_trace_printk: pid == 596613, thread_id == 596613
bash-596613  [003] ...21 411689.878053: bpf_trace_printk: pid == 596613, thread_id == 596613

(위의 실습을 WSL 환경에서 해보면 pid가 존재하지 않습니다.)



참고로, 자주 eBPF 코드를 수정하는 경우에는 이런 식으로 명령어를 만들어 두면 편리할 것입니다.

$ go generate && go build && sudo ./ebpf_sample

go generate는 현재 go 패키지에 포함된 소스코드 내에 명시한 "//go:generate ..."를 모두 실행해 주는 역할을 합니다.

What is go:generate and how to use it ?
; https://www.practical-go-lessons.com/post/what-is-go-generate-and-how-to-use-it-ccava8v5toqc70ipi1jg

제가 작성한 go 소스코드를 보면,

package main

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go ebpf_basic basic.c

import (
	"fmt"
)

func main() {
	// ...[생략]...
}

"go run github.com/cilium/ebpf/cmd/bpf2go ebpf_basic basic.c" 명령을 수행하라고 지시하는데요, 결국 명령행에서 다음과 같은 식으로 수행하는 것과 다를 바가 없습니다.

// bpf2go 유틸리티 사용법
// 첫 번째 인자(출력): bpf2go가 생성할 파일명(임의의 파일명)
// 두 번째 인자(입력): eBPF 소스코드 파일명
$ bpf2go ebpf_basic basic.c

그다음 수행하는 "go build"의 경우 "basic.c" 파일이 패키지에 포함된 상태에서는 자칫 빌드를 할 수 없다는 오류가 발생할 수 있습니다.

package ebpf_sample: C source files not allowed when not using cgo or SWIG: basic.c

위와 같은 오류를 없애기 위해, basic.c 파일은 go 빌드에서 제외하라고 해당 파일 내에 다음과 같은 지시자를 포함하는 것입니다.

//go:build ignore

typedef unsigned long long u64;
typedef unsigned int u32;

#include <linux/bpf.h>
// ...[생략]...

대충 여기까지 잘 이해하셨다면 이제 다음의 글을 쉽게 따라할 수 있을 것입니다. ^^

Getting Started with eBPF in Go
; https://ebpf-go.dev/guides/getting-started/

An Applied Introduction to eBPF with Go
; https://edgedelta.com/company/blog/applied-introduction-ebpf-go




만약 이런 오류가 발생한다면?

$ go run github.com/cilium/ebpf/cmd/bpf2go -target amd64 ebpf_basic basic.c
Error: missing package, you should either set the go-package flag or the GOPACKAGE env
exit status 1

오류 메시지 그대로 "GOPACKAGE" 환경 변수로 빌드 대상이 되는 Go 프로젝트의 패키지 이름을 지정하면 됩니다.

$ GOPACKAGE=ebpf_sample go run github.com/cilium/ebpf/cmd/bpf2go -target amd64 ebpf_basic basic.c

// 또는 bpf2go의 -go-package 인자로 설정
$ go run github.com/cilium/ebpf/cmd/bpf2go -go-package ebpf_sample -target amd64 ebpf_basic basic.c

그래도 이런 오류가 발생한다면?

$ GOPACKAGE=ebpf_sample go run github.com/cilium/ebpf/cmd/bpf2go ebpf_basic basic.c
In file included from /home/testusr/ebpf_sample/basic.c:4:
In file included from /usr/include/linux/bpf.h:11:
/usr/include/linux/types.h:5:10: fatal error: 'asm/types.h' file not found
#include 
         ^~~~~~~~~~~~~
1 error generated.
Error: compile: exit status 1
exit status 1
main.go:3: running "go": exit status 1

다음의 글을 보시면 됩니다.

WSL / Ubuntu - /usr/include/linux/types.h:5:10: fatal error: 'asm/types.h' file not found
; https://www.sysnet.pe.kr/2/0/13761




이런 오류가 발생한다면?

$ go generate
/home/testusr/ebpf_sample/basic.c:5:10: fatal error: 'bpf/bpf_helpers.h' file not found
#include 
         ^~~~~~~~~~~~~~~~~~~
1 error generated.
Error: compile: exit status 1
exit status 1
main.go:3: running "go": exit status 1

"libbpf-dev" 패키지를 설치합니다.

$ sudo apt install libbpf-dev

$ ll /usr/include/bpf/bpf_helpers.h
-rw-r--r-- 1 root root 7679 Aug 19  2022 /usr/include/bpf/bpf_helpers.h

역시나 아래와 같은 오류도,

$ go generate
Error: exec: "llvm-strip": executable file not found in $PATH
exit status 1
main.go:3: running "go": exit status 1

관련된 패키지를 설치해 줍니다.

$ sudo apt install clang llvm

$ apt list -a llvm
Listing... Done
llvm/jammy,now 1:14.0-55~exp2 amd64 [installed]




제가 아직 리알못이라 이해가 안 되는 점이 하나 있는데요, bpf_printk로 출력한 메시지를 확인할 떄 tail -f를 썼더니 메시지가 아무 것도 안 나옵니다.

// 아래의 명령으로는 bpf_printk 출력이 안 뜸
$ sudo tail -f /sys/kernel/debug/tracing/trace_pipe

// 아래의 명령으로는 확인 가능
$ sudo cat /sys/kernel/debug/tracing/trace_pipe

혹시 이유를 아시는 분이 계실까요? ^^

참고로, 관련 traace 경로는 각각 tracefs와 debugfs가 마운트된 경로라고 합니다.

$ mount -t tracefs
tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec,relatime)
tracefs on /sys/kernel/debug/tracing type tracefs (rw,nosuid,nodev,noexec,relatime)

$ mount -t debugfs
debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime)

따라서 해당 경로에 대한 마운트가 돼 있지 않다면,

$ mount -t tracefs
$ mount -t debugfs
$

이런 식으로 직접 마운트를 할 수 있습니다.

$ sudo mount -t tracefs tracefs /sys/kernel/tracing
$ sudo mount -t debugfs debugfs /sys/kernel/debug
$ sudo mount -t tracefs tracefs /sys/kernel/debug/tracing

$ mount -t tracefs
tracefs on /sys/kernel/tracing type tracefs (rw,relatime)
tracefs on /sys/kernel/debug/tracing type tracefs (rw,relatime)

$ mount -t debugfs
debugfs on /sys/kernel/debug type debugfs (rw,relatime)

하지만, 저 설정은 재부팅후 사라지므로, fstab 파일에 영구적으로 추가해 두는 것이 좋습니다.

# cat /etc/fstab
LABEL=cloudimg-rootfs   /        ext4   defaults        0 1

debugfs /sys/kernel/debug debugfs defaults
tracefs /sys/kernel/tracing tracefs defaults
tracefs /sys/kernel/debug/tracing tracefs defaults

(잘 동작합니다. 저렇게 해서 잘 동작하는지는 확인하지 못했습니다. ^^)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/15/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 166  167  168  [169]  170  171  172  173  174  175  176  177  178  179  180  ...
NoWriterDateCnt.TitleFile(s)
796정성태11/2/200928337오류 유형: 89. Windows 7 백업 오류 - 0x80070057
795정성태11/2/200925720오류 유형: 88. TFS 2010 (beat2) 설치 오류 -TF255272
793정성태10/19/200927705.NET Framework: 166. WPF - XAML 요소의 네임스페이스와 CLR 타입 매핑
792정성태10/17/200927972웹: 13. IIS 7.5 에서 SQL Express 연결 시 오류
791정성태10/17/200931734웹: 12. 요청 페이지에 대해 빈 화면만 보이는 경우 [1]
789정성태10/13/200927412COM 개체 관련: 22. BB FlashBack SDK와 ActiveX 버전 관리 [7]
786정성태10/9/200923675개발 환경 구성: 52. 테스트를 위한 평가판 운영체제 구하기
785정성태10/8/200930305.NET Framework: 165. WPF - UI 업데이트를 바로 반영하고 싶다면? (2)파일 다운로드1
783정성태10/7/200928045.NET Framework: 164. WPF - 데이터 바인딩된 트리에서 부모 노드 찾는 방법 [1]파일 다운로드1
782정성태10/6/200929918개발 환경 구성: 51. Windows 7 - 다중 원격 접속(Remote Desktop) 허용
781정성태9/30/200926926.NET Framework: 163. WPF - TreeView 자동 스크롤 기능 해지 [2]파일 다운로드1
780정성태9/28/200931025Windows: 48. Windows 7/2008에서 ping을 위한 echo 요청 열기 [2]
779정성태9/24/200922365.NET Framework: 162. WPF - 중첩된 ScrollViewer의 크기 제어 - 두 번째 이야기파일 다운로드1
778정성태9/23/200923817오류 유형: 87. 시스템 시간 변경 후 Session이 맺어진 WCF 클라이언트의 예외 발생파일 다운로드1
776정성태9/17/200923027개발 환경 구성: 50. Reference assembly
775정성태9/13/200939744VC++: 37. XmlCodeGenerator를 C/C++ 코드 생성에 적용 [2]파일 다운로드1
773정성태9/5/200930292오류 유형 : 85. DEP 비호환 ActiveX 오류
772정성태9/2/200926889.NET Framework: 161. WPF - 윈도우 이벤트 가로채기 [1]파일 다운로드1
771정성태8/28/200920927.NET Framework: 160. WPF - 입력 포커스 외곽선 없애는 방법
770정성태8/26/200923243.NET Framework: 159. WCF - 같은 컴퓨터에서만 WCF 요청을 서비스하도록 설정
769정성태8/25/200926287개발 환경 구성: 49. GAC와 같은 Namespace Extension에 의해서 보여지는 폴더의 원본 확인 방법
768정성태8/24/200925718오류 유형: 85. WCF 연결 오류: MessageSecurityException
767정성태8/23/200933970.NET Framework: 158. 닷넷 프로파일러 - IL 코드 재작성 [14]
766정성태8/23/200934840.NET Framework: 157. C# 4.0 - dynamic 키워드 [4]파일 다운로드1
765정성태8/22/200928574.NET Framework: 156. XamDataGrid의 UnboundField 사용파일 다운로드1
764정성태8/21/200922943Windows: 47. Windows Virtual PC에 설치된 Windows 7 VPC에서 Aero 효과 사용 [3]
... 166  167  168  [169]  170  171  172  173  174  175  176  177  178  179  180  ...