Microsoft MVP성태의 닷넷 이야기
Linux: 86. Golang + bpf2go를 사용한 eBPF 기본 예제 [링크 복사], [링크+제목 복사],
조회: 6153
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12211정성태4/27/202019261개발 환경 구성: 486. WSL에서 Makefile로 공개된 리눅스 환경의 C/C++ 소스 코드 빌드
12210정성태4/20/202020691.NET Framework: 903. .NET Framework의 Strong-named 어셈블리 바인딩 (1) - app.config을 이용한 바인딩 리디렉션 [1]파일 다운로드1
12209정성태4/13/202017405오류 유형: 614. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우 (2)
12208정성태4/12/202015965Linux: 29. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우
12207정성태4/2/202015802스크립트: 19. Windows PowerShell의 NonInteractive 모드
12206정성태4/2/202018426오류 유형: 613. 파일 잠금이 바로 안 풀린다면? - The process cannot access the file '...' because it is being used by another process.
12205정성태4/2/202015094스크립트: 18. Powershell에서는 cmd.exe의 명령어를 지원하진 않습니다.
12204정성태4/1/202015091스크립트: 17. Powershell 명령어에 ';' (semi-colon) 문자가 포함된 경우
12203정성태3/18/202017938오류 유형: 612. warning: 'C:\ProgramData/Git/config' has a dubious owner: '...'.
12202정성태3/18/202021200개발 환경 구성: 486. .NET Framework 프로젝트를 위한 GitLab CI/CD Runner 구성
12201정성태3/18/202018434오류 유형: 611. git-credential-manager.exe: Using credentials for username "Personal Access Token". [1]
12200정성태3/18/202018524VS.NET IDE: 145. NuGet + Github 라이브러리 디버깅 관련 옵션 3가지 - "Enable Just My Code" / "Enable Source Link support" / "Suppress JIT optimization on module load (Managed only)"
12199정성태3/17/202016167오류 유형: 610. C# - CodeDomProvider 사용 시 Unhandled Exception: System.IO.DirectoryNotFoundException: Could not find a part of the path '...\f2_6uod0.tmp'.
12198정성태3/17/202019528오류 유형: 609. SQL 서버 접속 시 "Cannot open user default database. Login failed."
12197정성태3/17/202018811VS.NET IDE: 144. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 - 두 번째 이야기 [1]
12196정성태3/17/202015943오류 유형: 608. The ServicedComponent being invoked is not correctly configured (Use regsvcs to re-register).
12195정성태3/16/202018262.NET Framework: 902. C# - 프로세스의 모든 핸들을 열람 - 세 번째 이야기
12194정성태3/16/202020993오류 유형: 607. PostgreSQL - Npgsql.NpgsqlException: sorry, too many clients already
12193정성태3/16/202017903개발 환경 구성: 485. docker - SAP Adaptive Server Enterprise 컨테이너 실행 [1]
12192정성태3/14/202019928개발 환경 구성: 484. docker - Sybase Anywhere 16 컨테이너 실행
12191정성태3/14/202021042개발 환경 구성: 483. docker - OracleXE 컨테이너 실행 [1]
12190정성태3/14/202015623오류 유형: 606. Docker Desktop 업그레이드 시 "The process cannot access the file 'C:\Program Files\Docker\Docker\resources\dockerd.exe' because it is being used by another process."
12189정성태3/13/202021227개발 환경 구성: 482. Facebook OAuth 처리 시 상태 정보 전달 방법과 "유효한 OAuth 리디렉션 URI" 설정 규칙
12188정성태3/13/202026020Windows: 169. 부팅 시점에 실행되는 chkdsk 결과를 확인하는 방법
12187정성태3/12/202015579오류 유형: 605. NtpClient was unable to set a manual peer to use as a time source because of duplicate error on '...'.
12186정성태3/12/202017399오류 유형: 604. The SysVol Permissions for one or more GPOs on this domain controller and not in sync with the permissions for the GPOs on the Baseline domain controller.
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...