├── .gitignore ├── ebpf ├── assets │ ├── bin │ │ └── probe.o │ └── assets.go ├── include │ └── all.h ├── main.c └── etrace │ ├── const.h │ ├── syscall_cache.h │ ├── event.h │ ├── etrace.h │ ├── syscall_args.h │ └── process.h ├── internal ├── btf │ ├── testdata │ │ ├── relocs-eb.elf │ │ ├── relocs-el.elf │ │ ├── vmlinux-btf.gz │ │ └── relocs.c │ ├── doc.go │ ├── ext_info_test.go │ ├── fuzz.go │ ├── strings_test.go │ ├── btf_types_string.go │ ├── core_reloc_test.go │ ├── strings.go │ ├── btf_test.go │ ├── types_test.go │ ├── btf_types.go │ └── ext_info.go ├── ptr_32_be.go ├── ptr_32_le.go ├── ptr_64.go ├── io.go ├── io_test.go ├── testutils │ ├── bpffs.go │ ├── rlimit.go │ ├── glob.go │ ├── cgroup.go │ └── feature.go ├── cpu_test.go ├── endian.go ├── ptr.go ├── pinning.go ├── errors.go ├── fd.go ├── syscall_test.go ├── feature_test.go ├── elf.go ├── cpu.go ├── syscall_string.go ├── feature.go ├── version_test.go ├── version.go ├── syscall.go └── unix │ ├── types_linux.go │ └── types_other.go ├── Makefile ├── tools.go ├── cmd ├── etrace │ ├── main.go │ └── run │ │ ├── etrace.go │ │ ├── cmd.go │ │ └── options.go └── custom_handler │ └── main.go ├── pkg ├── etrace │ ├── utils.go │ ├── byteorder.go │ ├── model.go │ ├── time_resolver.go │ ├── syscall_args.go │ ├── manager.go │ ├── etrace.go │ └── syscall_event.go └── ringbuf │ ├── ring.go │ └── reader.go ├── go.mod ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | /bin/ 4 | vendor/ 5 | graphs/ 6 | TODO 7 | -------------------------------------------------------------------------------- /ebpf/assets/bin/probe.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/etrace/HEAD/ebpf/assets/bin/probe.o -------------------------------------------------------------------------------- /internal/btf/testdata/relocs-eb.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/etrace/HEAD/internal/btf/testdata/relocs-eb.elf -------------------------------------------------------------------------------- /internal/btf/testdata/relocs-el.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/etrace/HEAD/internal/btf/testdata/relocs-el.elf -------------------------------------------------------------------------------- /internal/btf/testdata/vmlinux-btf.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/etrace/HEAD/internal/btf/testdata/vmlinux-btf.gz -------------------------------------------------------------------------------- /internal/ptr_32_be.go: -------------------------------------------------------------------------------- 1 | // +build armbe mips mips64p32 2 | 3 | package internal 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // Pointer wraps an unsafe.Pointer to be 64bit to 10 | // conform to the syscall specification. 11 | type Pointer struct { 12 | pad uint32 13 | ptr unsafe.Pointer 14 | } 15 | -------------------------------------------------------------------------------- /internal/ptr_32_le.go: -------------------------------------------------------------------------------- 1 | // +build 386 amd64p32 arm mipsle mips64p32le 2 | 3 | package internal 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // Pointer wraps an unsafe.Pointer to be 64bit to 10 | // conform to the syscall specification. 11 | type Pointer struct { 12 | ptr unsafe.Pointer 13 | pad uint32 14 | } 15 | -------------------------------------------------------------------------------- /internal/ptr_64.go: -------------------------------------------------------------------------------- 1 | // +build !386,!amd64p32,!arm,!mipsle,!mips64p32le 2 | // +build !armbe,!mips,!mips64p32 3 | 4 | package internal 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // Pointer wraps an unsafe.Pointer to be 64bit to 11 | // conform to the syscall specification. 12 | type Pointer struct { 13 | ptr unsafe.Pointer 14 | } 15 | -------------------------------------------------------------------------------- /internal/btf/doc.go: -------------------------------------------------------------------------------- 1 | // Package btf handles data encoded according to the BPF Type Format. 2 | // 3 | // The canonical documentation lives in the Linux kernel repository and is 4 | // available at https://www.kernel.org/doc/html/latest/bpf/btf.html 5 | // 6 | // The API is very much unstable. You should only use this via the main 7 | // ebpf library. 8 | package btf 9 | -------------------------------------------------------------------------------- /internal/io.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "errors" 4 | 5 | // DiscardZeroes makes sure that all written bytes are zero 6 | // before discarding them. 7 | type DiscardZeroes struct{} 8 | 9 | func (DiscardZeroes) Write(p []byte) (int, error) { 10 | for _, b := range p { 11 | if b != 0 { 12 | return 0, errors.New("encountered non-zero byte") 13 | } 14 | } 15 | return len(p), nil 16 | } 17 | -------------------------------------------------------------------------------- /internal/io_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | ) 8 | 9 | func TestDiscardZero(t *testing.T) { 10 | _, err := io.Copy(DiscardZeroes{}, bytes.NewReader([]byte{0, 0, 0})) 11 | if err != nil { 12 | t.Error("Returned an error even though input was zero:", err) 13 | } 14 | 15 | _, err = io.Copy(DiscardZeroes{}, bytes.NewReader([]byte{1})) 16 | if err == nil { 17 | t.Error("No error even though input is non-zero") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/btf/ext_info_test.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/Gui774ume/etrace/internal" 8 | ) 9 | 10 | func TestParseExtInfoBigRecordSize(t *testing.T) { 11 | rd := strings.NewReader("\xff\xff\xff\xff\x00\x00\x00\x000709171295166016") 12 | table := stringTable("\x00") 13 | _, err := parseExtInfo(rd, internal.NativeEndian, table) 14 | if err == nil { 15 | t.Error("Parsing ext info with large record size doesn't return an error") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/testutils/bpffs.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | // TempBPFFS creates a temporary directory on a BPF FS. 10 | // 11 | // The directory is automatically cleaned up at the end of the test run. 12 | func TempBPFFS(tb testing.TB) string { 13 | tb.Helper() 14 | 15 | tmp, err := ioutil.TempDir("/sys/fs/bpf", "ebpf-test") 16 | if err != nil { 17 | tb.Fatal("Create temporary directory on BPFFS:", err) 18 | } 19 | tb.Cleanup(func() { os.RemoveAll(tmp) }) 20 | 21 | return tmp 22 | } 23 | -------------------------------------------------------------------------------- /internal/testutils/rlimit.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Gui774ume/etrace/internal/unix" 8 | ) 9 | 10 | func init() { 11 | // Increase the memlock for all tests unconditionally. It's a great source of 12 | // weird bugs, since different distros have different default limits. 13 | err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{ 14 | Cur: unix.RLIM_INFINITY, 15 | Max: unix.RLIM_INFINITY, 16 | }) 17 | if err != nil { 18 | fmt.Fprintln(os.Stderr, "WARNING: Failed to adjust rlimit, tests may fail") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ebpf/include/all.h: -------------------------------------------------------------------------------- 1 | #ifndef _ALL_H__ 2 | #define _ALL_H__ 3 | 4 | #pragma clang diagnostic push 5 | #pragma clang diagnostic ignored "-Waddress-of-packed-member" 6 | #pragma clang diagnostic ignored "-Warray-bounds" 7 | #pragma clang diagnostic ignored "-Wunused-label" 8 | #pragma clang diagnostic ignored "-Wunknown-attributes" 9 | #pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end" 10 | #pragma clang diagnostic ignored "-Wframe-address" 11 | 12 | #include "vmlinux.h" 13 | #include 14 | #include 15 | #include 16 | 17 | #pragma clang diagnostic pop 18 | 19 | #include "bpf_core_read.h" 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /internal/cpu_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParseCPUs(t *testing.T) { 8 | for str, result := range map[string]int{ 9 | "0-1": 2, 10 | "0-2\n": 3, 11 | "0": 1, 12 | } { 13 | n, err := parseCPUs(str) 14 | if err != nil { 15 | t.Errorf("Can't parse `%s`: %v", str, err) 16 | } else if n != result { 17 | t.Error("Parsing", str, "returns", n, "instead of", result) 18 | } 19 | } 20 | 21 | for _, str := range []string{ 22 | "0,3-4", 23 | "0-", 24 | "1,", 25 | "", 26 | } { 27 | _, err := parseCPUs(str) 28 | if err == nil { 29 | t.Error("Parsed invalid format:", str) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ebpf/main.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2020 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | 9 | // Custom eBPF helpers 10 | #include "include/all.h" 11 | 12 | // etrace probes 13 | #include "etrace/const.h" 14 | #include "etrace/process.h" 15 | #include "etrace/syscall_cache.h" 16 | #include "etrace/event.h" 17 | #include "etrace/syscall_args.h" 18 | #include "etrace/etrace.h" 19 | 20 | char _license[] SEC("license") = "GPL"; 21 | __u32 _version SEC("version") = 0xFFFFFFFE; 22 | -------------------------------------------------------------------------------- /internal/endian.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/binary" 5 | "unsafe" 6 | ) 7 | 8 | // NativeEndian is set to either binary.BigEndian or binary.LittleEndian, 9 | // depending on the host's endianness. 10 | var NativeEndian binary.ByteOrder 11 | 12 | // Clang is set to either "el" or "eb" depending on the host's endianness. 13 | var ClangEndian string 14 | 15 | func init() { 16 | if isBigEndian() { 17 | NativeEndian = binary.BigEndian 18 | ClangEndian = "eb" 19 | } else { 20 | NativeEndian = binary.LittleEndian 21 | ClangEndian = "el" 22 | } 23 | } 24 | 25 | func isBigEndian() (ret bool) { 26 | i := int(0x1) 27 | bs := (*[int(unsafe.Sizeof(i))]byte)(unsafe.Pointer(&i)) 28 | return bs[0] == 0 29 | } 30 | -------------------------------------------------------------------------------- /ebpf/assets/assets.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package assets 18 | 19 | import _ "embed" 20 | 21 | //go:embed bin/probe.o 22 | var Probe []byte 23 | -------------------------------------------------------------------------------- /internal/ptr.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/Gui774ume/etrace/internal/unix" 7 | ) 8 | 9 | // NewPointer creates a 64-bit pointer from an unsafe Pointer. 10 | func NewPointer(ptr unsafe.Pointer) Pointer { 11 | return Pointer{ptr: ptr} 12 | } 13 | 14 | // NewSlicePointer creates a 64-bit pointer from a byte slice. 15 | func NewSlicePointer(buf []byte) Pointer { 16 | if len(buf) == 0 { 17 | return Pointer{} 18 | } 19 | 20 | return Pointer{ptr: unsafe.Pointer(&buf[0])} 21 | } 22 | 23 | // NewStringPointer creates a 64-bit pointer from a string. 24 | func NewStringPointer(str string) Pointer { 25 | p, err := unix.BytePtrFromString(str) 26 | if err != nil { 27 | return Pointer{} 28 | } 29 | 30 | return Pointer{ptr: unsafe.Pointer(p)} 31 | } 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ARCH ?= x86 2 | 3 | all: build 4 | 5 | build-ebpf: 6 | mkdir -p ebpf/assets/bin 7 | clang -D__KERNEL__ \ 8 | -Wno-unused-value \ 9 | -Wno-pointer-sign \ 10 | -Wno-compare-distinct-pointer-types \ 11 | -Wunused \ 12 | -Wall \ 13 | -Werror \ 14 | -I/lib/modules/$$(uname -r)/build/include \ 15 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 16 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/$(ARCH)/include \ 18 | -I/lib/modules/$$(uname -r)/build/arch/$(ARCH)/include/uapi \ 19 | -I/lib/modules/$$(uname -r)/build/arch/$(ARCH)/include/generated \ 20 | -c -O2 -g -target bpf \ 21 | ebpf/main.c \ 22 | -o ebpf/assets/bin/probe.o 23 | 24 | build: 25 | mkdir -p bin/ 26 | go build -o bin/ ./cmd/... 27 | 28 | run: 29 | sudo ./bin/etrace --log-level debug 30 | 31 | install: 32 | sudo cp ./bin/etrace /usr/bin/ 33 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +build tools 18 | 19 | package tools 20 | 21 | // Those imports are used to track tool dependencies. 22 | // This is the currently recommended approach: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 23 | 24 | import ( 25 | _ "github.com/shuLhan/go-bindata/cmd/go-bindata" 26 | _ "golang.org/x/tools/cmd/stringer" 27 | ) 28 | -------------------------------------------------------------------------------- /cmd/etrace/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/sirupsen/logrus" 21 | 22 | "github.com/Gui774ume/etrace/cmd/etrace/run" 23 | ) 24 | 25 | func main() { 26 | logrus.SetFormatter(&logrus.TextFormatter{ 27 | FullTimestamp: true, 28 | TimestampFormat: "2006-01-02T15:04:05Z", 29 | DisableLevelTruncation: true, 30 | }) 31 | run.Etrace.Execute() 32 | } 33 | -------------------------------------------------------------------------------- /pkg/etrace/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import "github.com/DataDog/gopsutil/cpu" 20 | 21 | // NumCPU returns the count of CPUs in the CPU affinity mask of the pid 1 process 22 | func NumCPU() (int, error) { 23 | cpuInfos, err := cpu.Info() 24 | if err != nil { 25 | return 0, err 26 | } 27 | var count int32 28 | for _, inf := range cpuInfos { 29 | count += inf.Cores 30 | } 31 | return int(count), nil 32 | } 33 | -------------------------------------------------------------------------------- /internal/btf/fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | // Use with https://github.com/dvyukov/go-fuzz 4 | 5 | package btf 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | 11 | "github.com/Gui774ume/etrace/internal" 12 | ) 13 | 14 | func FuzzSpec(data []byte) int { 15 | if len(data) < binary.Size(btfHeader{}) { 16 | return -1 17 | } 18 | 19 | spec, err := loadNakedSpec(bytes.NewReader(data), internal.NativeEndian, nil, nil) 20 | if err != nil { 21 | if spec != nil { 22 | panic("spec is not nil") 23 | } 24 | return 0 25 | } 26 | if spec == nil { 27 | panic("spec is nil") 28 | } 29 | return 1 30 | } 31 | 32 | func FuzzExtInfo(data []byte) int { 33 | if len(data) < binary.Size(btfExtHeader{}) { 34 | return -1 35 | } 36 | 37 | table := stringTable("\x00foo\x00barfoo\x00") 38 | info, err := parseExtInfo(bytes.NewReader(data), internal.NativeEndian, table) 39 | if err != nil { 40 | if info != nil { 41 | panic("info is not nil") 42 | } 43 | return 0 44 | } 45 | if info == nil { 46 | panic("info is nil") 47 | } 48 | return 1 49 | } 50 | -------------------------------------------------------------------------------- /pkg/etrace/byteorder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import ( 20 | "encoding/binary" 21 | "unsafe" 22 | ) 23 | 24 | // GetHostByteOrder guesses the hosts byte order 25 | func GetHostByteOrder() binary.ByteOrder { 26 | var i int32 = 0x01020304 27 | u := unsafe.Pointer(&i) 28 | pb := (*byte)(u) 29 | b := *pb 30 | if b == 0x04 { 31 | return binary.LittleEndian 32 | } 33 | 34 | return binary.BigEndian 35 | } 36 | 37 | // ByteOrder holds the hosts byte order 38 | var ByteOrder binary.ByteOrder 39 | 40 | func init() { 41 | ByteOrder = GetHostByteOrder() 42 | } 43 | -------------------------------------------------------------------------------- /internal/pinning.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/Gui774ume/etrace/internal/unix" 9 | ) 10 | 11 | func Pin(currentPath, newPath string, fd *FD) error { 12 | if newPath == "" { 13 | return errors.New("given pinning path cannot be empty") 14 | } 15 | if currentPath == newPath { 16 | return nil 17 | } 18 | if currentPath == "" { 19 | return BPFObjPin(newPath, fd) 20 | } 21 | var err error 22 | // Renameat2 is used instead of os.Rename to disallow the new path replacing 23 | // an existing path. 24 | if err = unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE); err == nil { 25 | // Object is now moved to the new pinning path. 26 | return nil 27 | } 28 | if !os.IsNotExist(err) { 29 | return fmt.Errorf("unable to move pinned object to new path %v: %w", newPath, err) 30 | } 31 | // Internal state not in sync with the file system so let's fix it. 32 | return BPFObjPin(newPath, fd) 33 | } 34 | 35 | func Unpin(pinnedPath string) error { 36 | if pinnedPath == "" { 37 | return nil 38 | } 39 | err := os.Remove(pinnedPath) 40 | if err == nil || os.IsNotExist(err) { 41 | return nil 42 | } 43 | return err 44 | } 45 | -------------------------------------------------------------------------------- /ebpf/etrace/const.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2020 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _CONST_H_ 9 | #define _CONST_H_ 10 | 11 | #define SYSCALL_EXECVE 59 12 | 13 | #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 14 | 15 | __attribute__((always_inline)) static u64 load_etrace_tgid() { 16 | u64 etrace_tgid = 0; 17 | LOAD_CONSTANT("etrace_tgid", etrace_tgid); 18 | return etrace_tgid; 19 | } 20 | 21 | __attribute__((always_inline)) static u64 load_syscall_filter() { 22 | u64 syscall_filter = 0; 23 | LOAD_CONSTANT("syscall_filter", syscall_filter); 24 | return syscall_filter; 25 | } 26 | 27 | __attribute__((always_inline)) static u64 load_comm_filter() { 28 | u64 comm_filter = 0; 29 | LOAD_CONSTANT("comm_filter", comm_filter); 30 | return comm_filter; 31 | } 32 | 33 | __attribute__((always_inline)) static u64 load_follow_children() { 34 | u64 follow_children = 0; 35 | LOAD_CONSTANT("follow_children", follow_children); 36 | return follow_children; 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /internal/errors.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/Gui774ume/etrace/internal/unix" 10 | ) 11 | 12 | // ErrorWithLog returns an error that includes logs from the 13 | // kernel verifier. 14 | // 15 | // logErr should be the error returned by the syscall that generated 16 | // the log. It is used to check for truncation of the output. 17 | func ErrorWithLog(err error, log []byte, logErr error) error { 18 | logStr := strings.Trim(CString(log), "\t\r\n ") 19 | if errors.Is(logErr, unix.ENOSPC) { 20 | logStr += " (truncated...)" 21 | } 22 | 23 | return &VerifierError{err, logStr} 24 | } 25 | 26 | // VerifierError includes information from the eBPF verifier. 27 | type VerifierError struct { 28 | cause error 29 | log string 30 | } 31 | 32 | func (le *VerifierError) Unwrap() error { 33 | return le.cause 34 | } 35 | 36 | func (le *VerifierError) Error() string { 37 | if le.log == "" { 38 | return le.cause.Error() 39 | } 40 | 41 | return fmt.Sprintf("%s: %s", le.cause, le.log) 42 | } 43 | 44 | // CString turns a NUL / zero terminated byte buffer into a string. 45 | func CString(in []byte) string { 46 | inLen := bytes.IndexByte(in, 0) 47 | if inLen == -1 { 48 | return "" 49 | } 50 | return string(in[:inLen]) 51 | } 52 | -------------------------------------------------------------------------------- /internal/btf/strings_test.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestStringTable(t *testing.T) { 10 | const in = "\x00one\x00two\x00" 11 | 12 | st, err := readStringTable(strings.NewReader(in)) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | if !bytes.Equal([]byte(in), []byte(st)) { 18 | t.Error("String table doesn't match input") 19 | } 20 | 21 | testcases := []struct { 22 | offset uint32 23 | want string 24 | }{ 25 | {0, ""}, 26 | {1, "one"}, 27 | {5, "two"}, 28 | } 29 | 30 | for _, tc := range testcases { 31 | have, err := st.Lookup(tc.offset) 32 | if err != nil { 33 | t.Errorf("Offset %d: %s", tc.offset, err) 34 | continue 35 | } 36 | 37 | if have != tc.want { 38 | t.Errorf("Offset %d: want %s but have %s", tc.offset, tc.want, have) 39 | } 40 | } 41 | 42 | if _, err := st.Lookup(2); err == nil { 43 | t.Error("No error when using offset pointing into middle of string") 44 | } 45 | 46 | // Make sure we reject bogus tables 47 | _, err = readStringTable(strings.NewReader("\x00one")) 48 | if err == nil { 49 | t.Fatal("Accepted non-terminated string") 50 | } 51 | 52 | _, err = readStringTable(strings.NewReader("one\x00")) 53 | if err == nil { 54 | t.Fatal("Accepted non-empty first item") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/fd.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strconv" 9 | 10 | "github.com/Gui774ume/etrace/internal/unix" 11 | ) 12 | 13 | var ErrClosedFd = errors.New("use of closed file descriptor") 14 | 15 | type FD struct { 16 | raw int64 17 | } 18 | 19 | func NewFD(value uint32) *FD { 20 | fd := &FD{int64(value)} 21 | runtime.SetFinalizer(fd, (*FD).Close) 22 | return fd 23 | } 24 | 25 | func (fd *FD) String() string { 26 | return strconv.FormatInt(fd.raw, 10) 27 | } 28 | 29 | func (fd *FD) Value() (uint32, error) { 30 | if fd.raw < 0 { 31 | return 0, ErrClosedFd 32 | } 33 | 34 | return uint32(fd.raw), nil 35 | } 36 | 37 | func (fd *FD) Close() error { 38 | if fd.raw < 0 { 39 | return nil 40 | } 41 | 42 | value := int(fd.raw) 43 | fd.raw = -1 44 | 45 | fd.Forget() 46 | return unix.Close(value) 47 | } 48 | 49 | func (fd *FD) Forget() { 50 | runtime.SetFinalizer(fd, nil) 51 | } 52 | 53 | func (fd *FD) Dup() (*FD, error) { 54 | if fd.raw < 0 { 55 | return nil, ErrClosedFd 56 | } 57 | 58 | dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0) 59 | if err != nil { 60 | return nil, fmt.Errorf("can't dup fd: %v", err) 61 | } 62 | 63 | return NewFD(uint32(dup)), nil 64 | } 65 | 66 | func (fd *FD) File(name string) *os.File { 67 | fd.Forget() 68 | return os.NewFile(uintptr(fd.raw), name) 69 | } 70 | -------------------------------------------------------------------------------- /internal/syscall_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/Gui774ume/etrace/internal/unix" 8 | ) 9 | 10 | func TestObjName(t *testing.T) { 11 | name := NewBPFObjName("more_than_16_characters_long") 12 | if name[len(name)-1] != 0 { 13 | t.Error("NewBPFObjName doesn't null terminate") 14 | } 15 | if len(name) != unix.BPF_OBJ_NAME_LEN { 16 | t.Errorf("Name is %d instead of %d bytes long", len(name), unix.BPF_OBJ_NAME_LEN) 17 | } 18 | } 19 | 20 | func TestWrappedErrno(t *testing.T) { 21 | a := error(wrappedErrno{unix.EINVAL}) 22 | b := error(unix.EINVAL) 23 | 24 | if a == b { 25 | t.Error("wrappedErrno is comparable to plain errno") 26 | } 27 | 28 | if !errors.Is(a, b) { 29 | t.Error("errors.Is(wrappedErrno, errno) returns false") 30 | } 31 | 32 | if errors.Is(a, unix.EAGAIN) { 33 | t.Error("errors.Is(wrappedErrno, EAGAIN) returns true") 34 | } 35 | } 36 | 37 | func TestSyscallError(t *testing.T) { 38 | err := errors.New("foo") 39 | foo := SyscallError(err, unix.EINVAL) 40 | 41 | if !errors.Is(foo, unix.EINVAL) { 42 | t.Error("SyscallError is not the wrapped errno") 43 | } 44 | 45 | if !errors.Is(foo, err) { 46 | t.Error("SyscallError is not the wrapped error") 47 | } 48 | 49 | if errors.Is(unix.EINVAL, foo) { 50 | t.Error("Errno is the SyscallError") 51 | } 52 | 53 | if errors.Is(err, foo) { 54 | t.Error("Error is the SyscallError") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/feature_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestFeatureTest(t *testing.T) { 10 | var called bool 11 | 12 | fn := FeatureTest("foo", "1.0", func() error { 13 | called = true 14 | return nil 15 | }) 16 | 17 | if called { 18 | t.Error("Function was called too early") 19 | } 20 | 21 | err := fn() 22 | if !called { 23 | t.Error("Function wasn't called") 24 | } 25 | 26 | if err != nil { 27 | t.Error("Unexpected negative result:", err) 28 | } 29 | 30 | fn = FeatureTest("bar", "2.1.1", func() error { 31 | return ErrNotSupported 32 | }) 33 | 34 | err = fn() 35 | if err == nil { 36 | t.Fatal("Unexpected positive result") 37 | } 38 | 39 | fte, ok := err.(*UnsupportedFeatureError) 40 | if !ok { 41 | t.Fatal("Result is not a *UnsupportedFeatureError") 42 | } 43 | 44 | if !strings.Contains(fte.Error(), "2.1.1") { 45 | t.Error("UnsupportedFeatureError.Error doesn't contain version") 46 | } 47 | 48 | if !errors.Is(err, ErrNotSupported) { 49 | t.Error("UnsupportedFeatureError is not ErrNotSupported") 50 | } 51 | 52 | err2 := fn() 53 | if err != err2 { 54 | t.Error("Didn't cache an error wrapping ErrNotSupported") 55 | } 56 | 57 | fn = FeatureTest("bar", "2.1.1", func() error { 58 | return errors.New("foo") 59 | }) 60 | 61 | err1, err2 := fn(), fn() 62 | if err1 == err2 { 63 | t.Error("Cached result of unsuccessful execution") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/testutils/glob.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | // Files calls fn for each given file. 9 | // 10 | // The function errors out if the pattern matches no files. 11 | func Files(t *testing.T, files []string, fn func(*testing.T, string)) { 12 | t.Helper() 13 | 14 | if len(files) == 0 { 15 | t.Fatalf("No files given") 16 | } 17 | 18 | for _, f := range files { 19 | file := f // force copy 20 | name := filepath.Base(file) 21 | t.Run(name, func(t *testing.T) { 22 | fn(t, file) 23 | }) 24 | } 25 | } 26 | 27 | // Glob finds files matching a pattern. 28 | // 29 | // The pattern should may include full path. Excludes use the same syntax as 30 | // pattern, but are only applied to the basename instead of the full path. 31 | func Glob(tb testing.TB, pattern string, excludes ...string) []string { 32 | tb.Helper() 33 | 34 | files, err := filepath.Glob(pattern) 35 | if err != nil { 36 | tb.Fatal("Can't glob files:", err) 37 | } 38 | 39 | if len(excludes) == 0 { 40 | return files 41 | } 42 | 43 | var filtered []string 44 | nextFile: 45 | for _, file := range files { 46 | base := filepath.Base(file) 47 | for _, exclude := range excludes { 48 | if matched, err := filepath.Match(exclude, base); err != nil { 49 | tb.Fatal(err) 50 | } else if matched { 51 | continue nextFile 52 | } 53 | } 54 | filtered = append(filtered, file) 55 | } 56 | 57 | return filtered 58 | } 59 | -------------------------------------------------------------------------------- /internal/btf/btf_types_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage"; DO NOT EDIT. 2 | 3 | package btf 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[StaticFunc-0] 12 | _ = x[GlobalFunc-1] 13 | _ = x[ExternFunc-2] 14 | } 15 | 16 | const _FuncLinkage_name = "staticglobalextern" 17 | 18 | var _FuncLinkage_index = [...]uint8{0, 6, 12, 18} 19 | 20 | func (i FuncLinkage) String() string { 21 | if i < 0 || i >= FuncLinkage(len(_FuncLinkage_index)-1) { 22 | return "FuncLinkage(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | return _FuncLinkage_name[_FuncLinkage_index[i]:_FuncLinkage_index[i+1]] 25 | } 26 | func _() { 27 | // An "invalid array index" compiler error signifies that the constant values have changed. 28 | // Re-run the stringer command to generate them again. 29 | var x [1]struct{} 30 | _ = x[StaticVar-0] 31 | _ = x[GlobalVar-1] 32 | _ = x[ExternVar-2] 33 | } 34 | 35 | const _VarLinkage_name = "staticglobalextern" 36 | 37 | var _VarLinkage_index = [...]uint8{0, 6, 12, 18} 38 | 39 | func (i VarLinkage) String() string { 40 | if i < 0 || i >= VarLinkage(len(_VarLinkage_index)-1) { 41 | return "VarLinkage(" + strconv.FormatInt(int64(i), 10) + ")" 42 | } 43 | return _VarLinkage_name[_VarLinkage_index[i]:_VarLinkage_index[i+1]] 44 | } 45 | -------------------------------------------------------------------------------- /internal/btf/core_reloc_test.go: -------------------------------------------------------------------------------- 1 | package btf_test 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/Gui774ume/etrace/internal/testutils" 10 | "github.com/cilium/ebpf" 11 | ) 12 | 13 | func TestCoreRelocationLoad(t *testing.T) { 14 | testutils.Files(t, testutils.Glob(t, "testdata/*-el.elf"), func(t *testing.T, file string) { 15 | fh, err := os.Open(file) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | defer fh.Close() 20 | 21 | spec, err := ebpf.LoadCollectionSpecFromReader(fh) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | for _, progSpec := range spec.Programs { 27 | t.Run(progSpec.Name, func(t *testing.T) { 28 | if _, err := fh.Seek(0, io.SeekStart); err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{ 33 | TargetBTF: fh, 34 | }) 35 | 36 | if strings.HasPrefix(progSpec.Name, "err_") { 37 | if err == nil { 38 | prog.Close() 39 | t.Fatal("Expected an error") 40 | } 41 | t.Log("Got expected error:", err) 42 | return 43 | } 44 | 45 | if err != nil { 46 | t.Fatal("Load program:", err) 47 | } 48 | defer prog.Close() 49 | 50 | ret, _, err := prog.Test(make([]byte, 14)) 51 | testutils.SkipIfNotSupported(t, err) 52 | if err != nil { 53 | t.Fatal("Error when running:", err) 54 | } 55 | 56 | if ret != 0 { 57 | t.Error("Assertion failed on line", ret) 58 | } 59 | }) 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /internal/btf/strings.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | ) 10 | 11 | type stringTable []byte 12 | 13 | func readStringTable(r io.Reader) (stringTable, error) { 14 | contents, err := ioutil.ReadAll(r) 15 | if err != nil { 16 | return nil, fmt.Errorf("can't read string table: %v", err) 17 | } 18 | 19 | if len(contents) < 1 { 20 | return nil, errors.New("string table is empty") 21 | } 22 | 23 | if contents[0] != '\x00' { 24 | return nil, errors.New("first item in string table is non-empty") 25 | } 26 | 27 | if contents[len(contents)-1] != '\x00' { 28 | return nil, errors.New("string table isn't null terminated") 29 | } 30 | 31 | return stringTable(contents), nil 32 | } 33 | 34 | func (st stringTable) Lookup(offset uint32) (string, error) { 35 | if int64(offset) > int64(^uint(0)>>1) { 36 | return "", fmt.Errorf("offset %d overflows int", offset) 37 | } 38 | 39 | pos := int(offset) 40 | if pos >= len(st) { 41 | return "", fmt.Errorf("offset %d is out of bounds", offset) 42 | } 43 | 44 | if pos > 0 && st[pos-1] != '\x00' { 45 | return "", fmt.Errorf("offset %d isn't start of a string", offset) 46 | } 47 | 48 | str := st[pos:] 49 | end := bytes.IndexByte(str, '\x00') 50 | if end == -1 { 51 | return "", fmt.Errorf("offset %d isn't null terminated", offset) 52 | } 53 | 54 | return string(str[:end]), nil 55 | } 56 | 57 | func (st stringTable) LookupName(offset uint32) (Name, error) { 58 | str, err := st.Lookup(offset) 59 | return Name(str), err 60 | } 61 | -------------------------------------------------------------------------------- /internal/elf.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "debug/elf" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | type SafeELFFile struct { 10 | *elf.File 11 | } 12 | 13 | // NewSafeELFFile reads an ELF safely. 14 | // 15 | // Any panic during parsing is turned into an error. This is necessary since 16 | // there are a bunch of unfixed bugs in debug/elf. 17 | // 18 | // https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+debug%2Felf+in%3Atitle 19 | func NewSafeELFFile(r io.ReaderAt) (safe *SafeELFFile, err error) { 20 | defer func() { 21 | r := recover() 22 | if r == nil { 23 | return 24 | } 25 | 26 | safe = nil 27 | err = fmt.Errorf("reading ELF file panicked: %s", r) 28 | }() 29 | 30 | file, err := elf.NewFile(r) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | return &SafeELFFile{file}, nil 36 | } 37 | 38 | // Symbols is the safe version of elf.File.Symbols. 39 | func (se *SafeELFFile) Symbols() (syms []elf.Symbol, err error) { 40 | defer func() { 41 | r := recover() 42 | if r == nil { 43 | return 44 | } 45 | 46 | syms = nil 47 | err = fmt.Errorf("reading ELF symbols panicked: %s", r) 48 | }() 49 | 50 | syms, err = se.File.Symbols() 51 | return 52 | } 53 | 54 | // DynamicSymbols is the safe version of elf.File.DynamicSymbols. 55 | func (se *SafeELFFile) DynamicSymbols() (syms []elf.Symbol, err error) { 56 | defer func() { 57 | r := recover() 58 | if r == nil { 59 | return 60 | } 61 | 62 | syms = nil 63 | err = fmt.Errorf("reading ELF dynamic symbols panicked: %s", r) 64 | }() 65 | 66 | syms, err = se.File.DynamicSymbols() 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /internal/cpu.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | var sysCPU struct { 11 | once sync.Once 12 | err error 13 | num int 14 | } 15 | 16 | // PossibleCPUs returns the max number of CPUs a system may possibly have 17 | // Logical CPU numbers must be of the form 0-n 18 | func PossibleCPUs() (int, error) { 19 | sysCPU.once.Do(func() { 20 | sysCPU.num, sysCPU.err = parseCPUsFromFile("/sys/devices/system/cpu/possible") 21 | }) 22 | 23 | return sysCPU.num, sysCPU.err 24 | } 25 | 26 | func parseCPUsFromFile(path string) (int, error) { 27 | spec, err := ioutil.ReadFile(path) 28 | if err != nil { 29 | return 0, err 30 | } 31 | 32 | n, err := parseCPUs(string(spec)) 33 | if err != nil { 34 | return 0, fmt.Errorf("can't parse %s: %v", path, err) 35 | } 36 | 37 | return n, nil 38 | } 39 | 40 | // parseCPUs parses the number of cpus from a string produced 41 | // by bitmap_list_string() in the Linux kernel. 42 | // Multiple ranges are rejected, since they can't be unified 43 | // into a single number. 44 | // This is the format of /sys/devices/system/cpu/possible, it 45 | // is not suitable for /sys/devices/system/cpu/online, etc. 46 | func parseCPUs(spec string) (int, error) { 47 | if strings.Trim(spec, "\n") == "0" { 48 | return 1, nil 49 | } 50 | 51 | var low, high int 52 | n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high) 53 | if n != 2 || err != nil { 54 | return 0, fmt.Errorf("invalid format: %s", spec) 55 | } 56 | if low != 0 { 57 | return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec) 58 | } 59 | 60 | // cpus is 0 indexed 61 | return high + 1, nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/testutils/cgroup.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | "sync" 9 | "testing" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | var cgroup2 = struct { 15 | once sync.Once 16 | path string 17 | err error 18 | }{} 19 | 20 | func cgroup2Path() (string, error) { 21 | cgroup2.once.Do(func() { 22 | mounts, err := ioutil.ReadFile("/proc/mounts") 23 | if err != nil { 24 | cgroup2.err = err 25 | return 26 | } 27 | 28 | for _, line := range strings.Split(string(mounts), "\n") { 29 | mount := strings.SplitN(line, " ", 3) 30 | if mount[0] == "cgroup2" { 31 | cgroup2.path = mount[1] 32 | return 33 | } 34 | 35 | continue 36 | } 37 | 38 | cgroup2.err = errors.New("cgroup2 not mounted") 39 | }) 40 | 41 | if cgroup2.err != nil { 42 | return "", cgroup2.err 43 | } 44 | 45 | return cgroup2.path, nil 46 | } 47 | 48 | func CreateCgroup(tb testing.TB) *os.File { 49 | tb.Helper() 50 | 51 | cg2, err := cgroup2Path() 52 | if err != nil { 53 | tb.Fatal("Can't locate cgroup2 mount:", err) 54 | } 55 | 56 | cgdir, err := ioutil.TempDir(cg2, "ebpf-link") 57 | if err != nil { 58 | tb.Fatal("Can't create cgroupv2:", err) 59 | } 60 | 61 | cgroup, err := os.Open(cgdir) 62 | if err != nil { 63 | os.Remove(cgdir) 64 | tb.Fatal(err) 65 | } 66 | tb.Cleanup(func() { 67 | cgroup.Close() 68 | os.Remove(cgdir) 69 | }) 70 | 71 | return cgroup 72 | } 73 | 74 | func GetCgroupIno(t *testing.T, cgroup *os.File) uint64 { 75 | cgroupStat := unix.Stat_t{} 76 | err := unix.Fstat(int(cgroup.Fd()), &cgroupStat) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | return cgroupStat.Ino 82 | } 83 | -------------------------------------------------------------------------------- /internal/testutils/feature.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/Gui774ume/etrace/internal" 8 | ) 9 | 10 | func mustKernelVersion() internal.Version { 11 | v, err := internal.KernelVersion() 12 | if err != nil { 13 | panic(err) 14 | } 15 | return v 16 | } 17 | 18 | func CheckFeatureTest(t *testing.T, fn func() error) { 19 | t.Helper() 20 | 21 | err := fn() 22 | if err == nil { 23 | return 24 | } 25 | 26 | var ufe *internal.UnsupportedFeatureError 27 | if errors.As(err, &ufe) { 28 | checkKernelVersion(t, ufe) 29 | } else { 30 | t.Error("Feature test failed:", err) 31 | } 32 | } 33 | 34 | func SkipIfNotSupported(tb testing.TB, err error) { 35 | var ufe *internal.UnsupportedFeatureError 36 | if errors.As(err, &ufe) { 37 | checkKernelVersion(tb, ufe) 38 | tb.Skip(ufe.Error()) 39 | } 40 | if errors.Is(err, internal.ErrNotSupported) { 41 | tb.Skip(err.Error()) 42 | } 43 | } 44 | 45 | func checkKernelVersion(tb testing.TB, ufe *internal.UnsupportedFeatureError) { 46 | if ufe.MinimumVersion.Unspecified() { 47 | return 48 | } 49 | 50 | kernelVersion := mustKernelVersion() 51 | if ufe.MinimumVersion.Less(kernelVersion) { 52 | tb.Helper() 53 | tb.Fatalf("Feature '%s' isn't supported even though kernel %s is newer than %s", 54 | ufe.Name, kernelVersion, ufe.MinimumVersion) 55 | } 56 | } 57 | 58 | func SkipOnOldKernel(tb testing.TB, minVersion, feature string) { 59 | tb.Helper() 60 | 61 | minv, err := internal.NewVersion(minVersion) 62 | if err != nil { 63 | tb.Fatalf("Invalid version %s: %s", minVersion, err) 64 | } 65 | 66 | if mustKernelVersion().Less(minv) { 67 | tb.Skipf("Test requires at least kernel %s (due to missing %s)", minv, feature) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pkg/etrace/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import ( 20 | "errors" 21 | ) 22 | 23 | const ( 24 | // MaxDataPerArg is the maximum data length collected per argument 25 | MaxDataPerArg = 1024 26 | ) 27 | 28 | // Options contains the parameters of ETrace 29 | type Options struct { 30 | RawDump bool 31 | JSONDump bool 32 | Stdout bool 33 | BytesShown int 34 | Stats bool 35 | EventHandler func(data []byte) 36 | hasCustomEventHandler bool 37 | SyscallFilters []Syscall 38 | CommFilters []string 39 | Follow bool 40 | } 41 | 42 | func (o Options) ShouldActivateProbes() bool { 43 | return o.JSONDump || o.RawDump || o.Stats || o.Stdout || o.hasCustomEventHandler 44 | } 45 | 46 | func (o Options) SendEventsToUserSpace() bool { 47 | return o.JSONDump || o.RawDump || o.Stdout 48 | } 49 | 50 | func (o Options) IsValid() error { 51 | if o.JSONDump == true && o.RawDump == true { 52 | return errors.New("you can't activate both --json and --raw") 53 | } 54 | return nil 55 | } 56 | 57 | func (o Options) check() error { 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Gui774ume/etrace 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/DataDog/ebpf-manager v0.0.0-20210922150435-a5331deb80cd 7 | github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321 8 | github.com/cilium/ebpf v0.6.3-0.20210917122031-fc2955d2ecee 9 | github.com/frankban/quicktest v1.13.1 10 | github.com/google/go-cmp v0.5.6 11 | github.com/pkg/errors v0.9.1 12 | github.com/shuLhan/go-bindata v4.0.0+incompatible 13 | github.com/sirupsen/logrus v1.8.1 14 | github.com/spf13/cobra v1.1.1 15 | golang.org/x/sys v0.5.0 16 | golang.org/x/tools v0.1.5 17 | ) 18 | 19 | require ( 20 | github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 // indirect 21 | github.com/avast/retry-go v3.0.0+incompatible // indirect 22 | github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect 23 | github.com/florianl/go-tc v0.3.0 // indirect 24 | github.com/go-ole/go-ole v1.2.4 // indirect 25 | github.com/hashicorp/errwrap v1.0.0 // indirect 26 | github.com/hashicorp/go-multierror v1.1.1 // indirect 27 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 28 | github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect 29 | github.com/kr/pretty v0.3.0 // indirect 30 | github.com/kr/text v0.2.0 // indirect 31 | github.com/mdlayher/netlink v1.4.0 // indirect 32 | github.com/rogpeppe/go-internal v1.6.1 // indirect 33 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect 34 | github.com/spf13/pflag v1.0.5 // indirect 35 | github.com/vishvananda/netlink v1.1.0 // indirect 36 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect 37 | golang.org/x/mod v0.5.0 // indirect 38 | golang.org/x/net v0.7.0 // indirect 39 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /pkg/etrace/time_resolver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import ( 20 | "time" 21 | 22 | "github.com/DataDog/gopsutil/host" 23 | ) 24 | 25 | // TimeResolver converts kernel monotonic timestamps to absolute times 26 | type TimeResolver struct { 27 | bootTime time.Time 28 | } 29 | 30 | // NewTimeResolver returns a new time resolver 31 | func NewTimeResolver() (*TimeResolver, error) { 32 | bt, err := host.BootTime() 33 | if err != nil { 34 | return nil, err 35 | } 36 | tr := TimeResolver{ 37 | bootTime: time.Unix(int64(bt), 0), 38 | } 39 | return &tr, nil 40 | } 41 | 42 | // ResolveMonotonicTimestamp converts a kernel monotonic timestamp to an absolute time 43 | func (tr *TimeResolver) ResolveMonotonicTimestamp(timestamp uint64) time.Time { 44 | if timestamp > 0 { 45 | return tr.bootTime.Add(time.Duration(timestamp)) 46 | } 47 | return time.Time{} 48 | } 49 | 50 | // ApplyBootTime return the time re-aligned from the boot time 51 | func (tr *TimeResolver) ApplyBootTime(timestamp time.Time) time.Time { 52 | if !timestamp.IsZero() { 53 | return timestamp.Add(time.Duration(tr.bootTime.UnixNano())) 54 | } 55 | return time.Time{} 56 | } 57 | 58 | // ComputeMonotonicTimestamp converts an absolute time to a kernel monotonic timestamp 59 | func (tr *TimeResolver) ComputeMonotonicTimestamp(timestamp time.Time) int64 { 60 | if !timestamp.IsZero() { 61 | return timestamp.Sub(tr.bootTime).Nanoseconds() 62 | } 63 | return 0 64 | } 65 | -------------------------------------------------------------------------------- /internal/syscall_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output syscall_string.go -type=BPFCmd"; DO NOT EDIT. 2 | 3 | package internal 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[BPF_MAP_CREATE-0] 12 | _ = x[BPF_MAP_LOOKUP_ELEM-1] 13 | _ = x[BPF_MAP_UPDATE_ELEM-2] 14 | _ = x[BPF_MAP_DELETE_ELEM-3] 15 | _ = x[BPF_MAP_GET_NEXT_KEY-4] 16 | _ = x[BPF_PROG_LOAD-5] 17 | _ = x[BPF_OBJ_PIN-6] 18 | _ = x[BPF_OBJ_GET-7] 19 | _ = x[BPF_PROG_ATTACH-8] 20 | _ = x[BPF_PROG_DETACH-9] 21 | _ = x[BPF_PROG_TEST_RUN-10] 22 | _ = x[BPF_PROG_GET_NEXT_ID-11] 23 | _ = x[BPF_MAP_GET_NEXT_ID-12] 24 | _ = x[BPF_PROG_GET_FD_BY_ID-13] 25 | _ = x[BPF_MAP_GET_FD_BY_ID-14] 26 | _ = x[BPF_OBJ_GET_INFO_BY_FD-15] 27 | _ = x[BPF_PROG_QUERY-16] 28 | _ = x[BPF_RAW_TRACEPOINT_OPEN-17] 29 | _ = x[BPF_BTF_LOAD-18] 30 | _ = x[BPF_BTF_GET_FD_BY_ID-19] 31 | _ = x[BPF_TASK_FD_QUERY-20] 32 | _ = x[BPF_MAP_LOOKUP_AND_DELETE_ELEM-21] 33 | _ = x[BPF_MAP_FREEZE-22] 34 | _ = x[BPF_BTF_GET_NEXT_ID-23] 35 | _ = x[BPF_MAP_LOOKUP_BATCH-24] 36 | _ = x[BPF_MAP_LOOKUP_AND_DELETE_BATCH-25] 37 | _ = x[BPF_MAP_UPDATE_BATCH-26] 38 | _ = x[BPF_MAP_DELETE_BATCH-27] 39 | _ = x[BPF_LINK_CREATE-28] 40 | _ = x[BPF_LINK_UPDATE-29] 41 | _ = x[BPF_LINK_GET_FD_BY_ID-30] 42 | _ = x[BPF_LINK_GET_NEXT_ID-31] 43 | _ = x[BPF_ENABLE_STATS-32] 44 | _ = x[BPF_ITER_CREATE-33] 45 | } 46 | 47 | const _BPFCmd_name = "BPF_MAP_CREATEBPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEYBPF_PROG_LOADBPF_OBJ_PINBPF_OBJ_GETBPF_PROG_ATTACHBPF_PROG_DETACHBPF_PROG_TEST_RUNBPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_PROG_GET_FD_BY_IDBPF_MAP_GET_FD_BY_IDBPF_OBJ_GET_INFO_BY_FDBPF_PROG_QUERYBPF_RAW_TRACEPOINT_OPENBPF_BTF_LOADBPF_BTF_GET_FD_BY_IDBPF_TASK_FD_QUERYBPF_MAP_LOOKUP_AND_DELETE_ELEMBPF_MAP_FREEZEBPF_BTF_GET_NEXT_IDBPF_MAP_LOOKUP_BATCHBPF_MAP_LOOKUP_AND_DELETE_BATCHBPF_MAP_UPDATE_BATCHBPF_MAP_DELETE_BATCHBPF_LINK_CREATEBPF_LINK_UPDATEBPF_LINK_GET_FD_BY_IDBPF_LINK_GET_NEXT_IDBPF_ENABLE_STATSBPF_ITER_CREATE" 48 | 49 | var _BPFCmd_index = [...]uint16{0, 14, 33, 52, 71, 91, 104, 115, 126, 141, 156, 173, 193, 212, 233, 253, 275, 289, 312, 324, 344, 361, 391, 405, 424, 444, 475, 495, 515, 530, 545, 566, 586, 602, 617} 50 | 51 | func (i BPFCmd) String() string { 52 | if i < 0 || i >= BPFCmd(len(_BPFCmd_index)-1) { 53 | return "BPFCmd(" + strconv.FormatInt(int64(i), 10) + ")" 54 | } 55 | return _BPFCmd_name[_BPFCmd_index[i]:_BPFCmd_index[i+1]] 56 | } 57 | -------------------------------------------------------------------------------- /cmd/etrace/run/etrace.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "os/signal" 23 | 24 | "github.com/pkg/errors" 25 | "github.com/sirupsen/logrus" 26 | "github.com/spf13/cobra" 27 | 28 | "github.com/Gui774ume/etrace/pkg/etrace" 29 | ) 30 | 31 | func etraceCmd(cmd *cobra.Command, args []string) error { 32 | // Set log level 33 | logrus.SetLevel(options.LogLevel) 34 | 35 | if len(options.SyscallFilters) > 0 { 36 | for _, s := range options.SyscallFilters { 37 | newSyscall := etrace.ParseSyscallName(s) 38 | if newSyscall == -1 { 39 | return errors.Errorf("unknown syscall: %s", s) 40 | } 41 | options.ETraceOptions.SyscallFilters = append(options.ETraceOptions.SyscallFilters, newSyscall) 42 | } 43 | } 44 | 45 | if len(options.InputFile) > 0 { 46 | options.ETraceOptions.RawDump = false 47 | options.ETraceOptions.JSONDump = false 48 | options.ETraceOptions.Stdout = false 49 | options.ETraceOptions.Stats = false 50 | } 51 | 52 | // create a new ETrace instance 53 | trace, err := etrace.NewETrace(options.ETraceOptions) 54 | if err != nil { 55 | return errors.Wrap(err, "couldn't create a new ETracer") 56 | } 57 | 58 | // start ETrace 59 | if options.ETraceOptions.ShouldActivateProbes() { 60 | if err := trace.Start(); err != nil { 61 | return errors.Wrap(err, "couldn't start") 62 | } 63 | logrus.Infoln("Tracing started ... (Ctrl + C to stop)\n") 64 | if len(trace.DumpFile) > 0 { 65 | logrus.Infof("output file: %s", trace.DumpFile) 66 | } 67 | 68 | wait() 69 | _ = trace.Stop() 70 | 71 | } else if len(options.InputFile) > 0 { 72 | logrus.Infof("Parsing %s ...", options.InputFile) 73 | output, err := trace.ParseInputFile(options.InputFile) 74 | if err != nil { 75 | logrus.Errorf("couldn't parse input file: %v", err) 76 | } else { 77 | logrus.Infof("done ! Output file: %s", output) 78 | } 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // wait stops the main goroutine until an interrupt or kill signal is sent 85 | func wait() { 86 | sig := make(chan os.Signal, 1) 87 | signal.Notify(sig, os.Interrupt, os.Kill) 88 | <-sig 89 | fmt.Println() 90 | } 91 | -------------------------------------------------------------------------------- /cmd/etrace/run/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // Etrace represents the base command of etrace 24 | var Etrace = &cobra.Command{ 25 | Use: "etrace", 26 | RunE: etraceCmd, 27 | } 28 | 29 | var options CLIOptions 30 | 31 | func init() { 32 | Etrace.Flags().VarP( 33 | NewLogLevelSanitizer(&options.LogLevel), 34 | "log-level", 35 | "l", 36 | "log level, options: panic, fatal, error, warn, info, debug or trace") 37 | Etrace.Flags().BoolVar( 38 | &options.ETraceOptions.RawDump, 39 | "raw", 40 | false, 41 | "dump the data retrieved from kernel space without parsing it, use this option instead of the --json option to reduce the amount of lost events. You can ask ETrace to parse a raw dump using the --input option") 42 | Etrace.Flags().BoolVar( 43 | &options.ETraceOptions.JSONDump, 44 | "json", 45 | false, 46 | "parse and dump the data retrieved from kernel space in the JSON format. This option might lead to more lost events than the --raw option and more CPU usage") 47 | Etrace.Flags().BoolVar( 48 | &options.ETraceOptions.Stdout, 49 | "stdout", 50 | false, 51 | "parse and dump the data retrieved from kernel space to the console. This option might lead to more lost events than the --raw option and more CPU usage.") 52 | Etrace.Flags().IntVar( 53 | &options.ETraceOptions.BytesShown, 54 | "bytes", 55 | 8, 56 | "amount of bytes shown to the screen when --stdout is provided") 57 | Etrace.Flags().BoolVar( 58 | &options.ETraceOptions.Stats, 59 | "stats", 60 | true, 61 | "show syscall statistics") 62 | Etrace.Flags().BoolVar( 63 | &options.ETraceOptions.Follow, 64 | "follow", 65 | true, 66 | "defines if etrace should trace the children of the processes that match the provided comm (works only for newly created children)") 67 | Etrace.Flags().StringArrayVarP( 68 | &options.ETraceOptions.CommFilters, 69 | "comm", 70 | "c", 71 | []string{}, 72 | "list of process comms to filter, leave empty to capture everything") 73 | Etrace.Flags().StringArrayVarP( 74 | &options.SyscallFilters, 75 | "syscall", 76 | "s", 77 | []string{}, 78 | "list of syscalls to filter, leave empty to capture everything") 79 | Etrace.Flags().StringVar( 80 | &options.InputFile, 81 | "input", 82 | "", 83 | "input file to parse data from") 84 | } 85 | -------------------------------------------------------------------------------- /internal/feature.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | // ErrNotSupported indicates that a feature is not supported by the current kernel. 10 | var ErrNotSupported = errors.New("not supported") 11 | 12 | // UnsupportedFeatureError is returned by FeatureTest() functions. 13 | type UnsupportedFeatureError struct { 14 | // The minimum Linux mainline version required for this feature. 15 | // Used for the error string, and for sanity checking during testing. 16 | MinimumVersion Version 17 | 18 | // The name of the feature that isn't supported. 19 | Name string 20 | } 21 | 22 | func (ufe *UnsupportedFeatureError) Error() string { 23 | if ufe.MinimumVersion.Unspecified() { 24 | return fmt.Sprintf("%s not supported", ufe.Name) 25 | } 26 | return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion) 27 | } 28 | 29 | // Is indicates that UnsupportedFeatureError is ErrNotSupported. 30 | func (ufe *UnsupportedFeatureError) Is(target error) bool { 31 | return target == ErrNotSupported 32 | } 33 | 34 | type featureTest struct { 35 | sync.RWMutex 36 | successful bool 37 | result error 38 | } 39 | 40 | // FeatureTestFn is used to determine whether the kernel supports 41 | // a certain feature. 42 | // 43 | // The return values have the following semantics: 44 | // 45 | // err == ErrNotSupported: the feature is not available 46 | // err == nil: the feature is available 47 | // err != nil: the test couldn't be executed 48 | type FeatureTestFn func() error 49 | 50 | // FeatureTest wraps a function so that it is run at most once. 51 | // 52 | // name should identify the tested feature, while version must be in the 53 | // form Major.Minor[.Patch]. 54 | // 55 | // Returns an error wrapping ErrNotSupported if the feature is not supported. 56 | func FeatureTest(name, version string, fn FeatureTestFn) func() error { 57 | v, err := NewVersion(version) 58 | if err != nil { 59 | return func() error { return err } 60 | } 61 | 62 | ft := new(featureTest) 63 | return func() error { 64 | ft.RLock() 65 | if ft.successful { 66 | defer ft.RUnlock() 67 | return ft.result 68 | } 69 | ft.RUnlock() 70 | ft.Lock() 71 | defer ft.Unlock() 72 | // check one more time on the off 73 | // chance that two go routines 74 | // were able to call into the write 75 | // lock 76 | if ft.successful { 77 | return ft.result 78 | } 79 | err := fn() 80 | switch { 81 | case errors.Is(err, ErrNotSupported): 82 | ft.result = &UnsupportedFeatureError{ 83 | MinimumVersion: v, 84 | Name: name, 85 | } 86 | fallthrough 87 | 88 | case err == nil: 89 | ft.successful = true 90 | 91 | default: 92 | // We couldn't execute the feature test to a point 93 | // where it could make a determination. 94 | // Don't cache the result, just return it. 95 | return fmt.Errorf("detect support for %s: %w", name, err) 96 | } 97 | 98 | return ft.result 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /cmd/custom_handler/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "os/signal" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/Gui774ume/etrace/pkg/etrace" 27 | ) 28 | 29 | // et is the global etrace tracer 30 | var et *etrace.ETrace 31 | 32 | // eventZero is used to reset the event used for parsing in a memory efficient way 33 | var eventZero = etrace.NewSyscallEvent() 34 | 35 | // event is used to parse events 36 | var event = etrace.NewSyscallEvent() 37 | 38 | // zeroEvent provides an empty event 39 | func zeroEvent() { 40 | *event = *eventZero 41 | } 42 | 43 | func main() { 44 | // Set log level 45 | logrus.SetLevel(logrus.TraceLevel) 46 | 47 | // create a new ETrace instance 48 | var err error 49 | et, err = etrace.NewETrace(etrace.Options{ 50 | EventHandler: myCustomEventHandler, 51 | }) 52 | if err != nil { 53 | logrus.Errorf("couldn't instantiate etrace: %v\n", err) 54 | return 55 | } 56 | 57 | // start ETrace 58 | if err = et.Start(); err != nil { 59 | logrus.Errorf("couldn't start etrace: %v\n", err) 60 | return 61 | } 62 | logrus.Infoln("Tracing started ... (Ctrl + C to stop)\n") 63 | 64 | wait() 65 | 66 | if err = et.Stop(); err != nil { 67 | logrus.Errorf("couldn't stop etrace: %v\n", err) 68 | } 69 | } 70 | 71 | // wait stops the main goroutine until an interrupt or kill signal is sent 72 | func wait() { 73 | sig := make(chan os.Signal, 1) 74 | signal.Notify(sig, os.Interrupt, os.Kill) 75 | <-sig 76 | fmt.Println() 77 | } 78 | 79 | func myCustomEventHandler(data []byte) { 80 | // reset event 81 | zeroEvent() 82 | 83 | // parse syscall type 84 | read, err := event.Syscall.UnmarshalSyscall(data) 85 | if err != nil { 86 | logrus.Errorf("failed to decode syscall type: %v", err) 87 | return 88 | } 89 | 90 | // find arguments definition 91 | syscallDefinition, ok := et.SyscallDefinitions[event.Syscall] 92 | if ok { 93 | for i := range syscallDefinition.Arguments { 94 | event.Args[i] = etrace.SyscallArgumentValue{ 95 | Argument: &syscallDefinition.Arguments[i], 96 | } 97 | } 98 | } else { 99 | logrus.Errorf("couldn't find the syscall definition of %s", event.Syscall) 100 | return 101 | } 102 | 103 | // parse the binary data according to the syscall definition 104 | err = event.UnmarshalBinary(data, read, et) 105 | if err != nil { 106 | logrus.Errorf("failed to decode event: %v", err) 107 | return 108 | } 109 | 110 | // print the output to the screen 111 | fmt.Printf("%s\n", event.String(50)) 112 | } 113 | -------------------------------------------------------------------------------- /internal/version_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVersion(t *testing.T) { 8 | a, err := NewVersion("1.2") 9 | if err != nil { 10 | t.Fatal(err) 11 | } 12 | 13 | b, err := NewVersion("2.2.1") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | if !a.Less(b) { 19 | t.Error("A should be less than B") 20 | } 21 | 22 | if b.Less(a) { 23 | t.Error("B shouldn't be less than A") 24 | } 25 | 26 | v200 := Version{2, 0, 0} 27 | if !a.Less(v200) { 28 | t.Error("1.2.1 should not be less than 2.0.0") 29 | } 30 | 31 | if v200.Less(a) { 32 | t.Error("2.0.0 should not be less than 1.2.1") 33 | } 34 | } 35 | 36 | func TestKernelVersion(t *testing.T) { 37 | // Kernels 4.4 and 4.9 have a SUBLEVEL of over 255 and clamp it to 255. 38 | // In our implementation, the other version segments are truncated. 39 | if v, want := (Version{256, 256, 256}), uint32(255); v.Kernel() != want { 40 | t.Errorf("256.256.256 should result in a kernel version of %d, got: %d", want, v.Kernel()) 41 | } 42 | 43 | // Known good version. 44 | if v, want := (Version{4, 9, 128}), uint32(264576); v.Kernel() != want { 45 | t.Errorf("4.9.1 should result in a kernel version of %d, got: %d", want, v.Kernel()) 46 | } 47 | } 48 | 49 | func TestVersionDetection(t *testing.T) { 50 | var tests = []struct { 51 | name string 52 | s string 53 | v Version 54 | err bool 55 | }{ 56 | {"ubuntu version_signature", "Ubuntu 4.15.0-91.92-generic 4.15.18", Version{4, 15, 18}, false}, 57 | {"debian uname version", "#1 SMP Debian 4.19.37-5+deb10u2 (2019-08-08)", Version{4, 19, 37}, false}, 58 | {"debian uname release (missing patch)", "4.19.0-5-amd64", Version{4, 19, 0}, false}, 59 | {"debian uname all", "Linux foo 5.6.0-0.bpo.2-amd64 #1 SMP Debian 5.6.14-2~bpo10+1 (2020-06-09) x86_64 GNU/Linux", Version{5, 6, 14}, false}, 60 | {"debian custom uname version", "#1577309 SMP Thu Dec 31 08:32:02 UTC 2020", Version{}, true}, 61 | {"debian custom uname release (missing patch)", "4.19-ovh-xxxx-std-ipv6-64", Version{4, 19, 0}, false}, 62 | {"arch uname version", "#1 SMP PREEMPT Thu, 11 Mar 2021 21:27:06 +0000", Version{}, true}, 63 | {"arch uname release", "5.5.10-arch1-1", Version{5, 5, 10}, false}, 64 | {"alpine uname version", "#1-Alpine SMP Thu Jan 23 10:58:18 UTC 2020", Version{}, true}, 65 | {"alpine uname release", "4.14.167-0-virt", Version{4, 14, 167}, false}, 66 | {"fedora uname version", "#1 SMP Tue May 14 18:22:28 UTC 2019", Version{}, true}, 67 | {"fedora uname release", "5.0.16-100.fc28.x86_64", Version{5, 0, 16}, false}, 68 | {"centos8 uname version", "#1 SMP Mon Mar 1 17:16:16 UTC 2021", Version{}, true}, 69 | {"centos8 uname release", "4.18.0-240.15.1.el8_3.x86_64", Version{4, 18, 0}, false}, 70 | {"devuan uname version", "#1 SMP Debian 4.19.181-1 (2021-03-19)", Version{4, 19, 181}, false}, 71 | {"devuan uname release", "4.19.0-16-amd64", Version{4, 19, 0}, false}, 72 | } 73 | 74 | for _, tt := range tests { 75 | t.Run(tt.name, func(t *testing.T) { 76 | v, err := findKernelVersion(tt.s) 77 | if err != nil { 78 | if !tt.err { 79 | t.Error("unexpected error:", err) 80 | } 81 | return 82 | } 83 | 84 | if tt.err { 85 | t.Error("expected error, but got none") 86 | } 87 | 88 | if v != tt.v { 89 | t.Errorf("unexpected version for string '%s'. got: %v, want: %v", tt.s, v, tt.v) 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pkg/ringbuf/ring.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ringbuf 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "os" 23 | "runtime" 24 | "sync/atomic" 25 | "unsafe" 26 | 27 | "github.com/Gui774ume/etrace/internal/unix" 28 | ) 29 | 30 | type ringbufEventRing struct { 31 | prod []byte 32 | cons []byte 33 | *ringReader 34 | } 35 | 36 | func newRingBufEventRing(mapFD, size int) (*ringbufEventRing, error) { 37 | cons, err := unix.Mmap(mapFD, 0, os.Getpagesize(), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) 38 | if err != nil { 39 | return nil, fmt.Errorf("can't mmap consumer page: %w", err) 40 | } 41 | 42 | prod, err := unix.Mmap(mapFD, (int64)(os.Getpagesize()), os.Getpagesize()+2*size, unix.PROT_READ, unix.MAP_SHARED) 43 | if err != nil { 44 | _ = unix.Munmap(cons) 45 | return nil, fmt.Errorf("can't mmap data pages: %w", err) 46 | } 47 | 48 | cons_pos := (*uint64)(unsafe.Pointer(&cons[0])) 49 | prod_pos := (*uint64)(unsafe.Pointer(&prod[0])) 50 | 51 | ring := &ringbufEventRing{ 52 | prod: prod, 53 | cons: cons, 54 | ringReader: newRingReader(cons_pos, prod_pos, prod[os.Getpagesize():]), 55 | } 56 | runtime.SetFinalizer(ring, (*ringbufEventRing).Close) 57 | 58 | return ring, nil 59 | } 60 | 61 | func (ring *ringbufEventRing) Close() { 62 | runtime.SetFinalizer(ring, nil) 63 | 64 | _ = unix.Munmap(ring.prod) 65 | _ = unix.Munmap(ring.cons) 66 | 67 | ring.prod = nil 68 | ring.cons = nil 69 | } 70 | 71 | type ringReader struct { 72 | prod_pos, cons_pos *uint64 73 | cons uint64 74 | mask uint64 75 | ring []byte 76 | } 77 | 78 | func newRingReader(cons_ptr, prod_ptr *uint64, ring []byte) *ringReader { 79 | return &ringReader{ 80 | prod_pos: prod_ptr, 81 | cons_pos: cons_ptr, 82 | cons: atomic.LoadUint64(cons_ptr), 83 | // cap is always a power of two 84 | mask: uint64(cap(ring)/2 - 1), 85 | ring: ring, 86 | } 87 | } 88 | 89 | func (rr *ringReader) loadConsumer() { 90 | rr.cons = atomic.LoadUint64(rr.cons_pos) 91 | } 92 | 93 | func (rr *ringReader) storeConsumer() { 94 | atomic.StoreUint64(rr.cons_pos, rr.cons) 95 | } 96 | 97 | // clamp delta to 'end' if 'start+delta' is beyond 'end' 98 | func clamp(start, end, delta uint64) uint64 { 99 | if remainder := end - start; delta > remainder { 100 | return remainder 101 | } 102 | return delta 103 | } 104 | 105 | func (rr *ringReader) skipRead(skipBytes uint64) { 106 | rr.cons += clamp(rr.cons, atomic.LoadUint64(rr.prod_pos), skipBytes) 107 | } 108 | 109 | func (rr *ringReader) Read(p []byte) (int, error) { 110 | prod := atomic.LoadUint64(rr.prod_pos) 111 | 112 | n := clamp(rr.cons, prod, uint64(len(p))) 113 | 114 | start := rr.cons & rr.mask 115 | 116 | copy(p, rr.ring[start:start+n]) 117 | rr.cons += n 118 | 119 | if prod == rr.cons { 120 | return int(n), io.EOF 121 | } 122 | 123 | return int(n), nil 124 | } 125 | -------------------------------------------------------------------------------- /ebpf/etrace/syscall_cache.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2020 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _SYSCALL_CACHE_H_ 9 | #define _SYSCALL_CACHE_H_ 10 | 11 | struct { 12 | __uint(type, BPF_MAP_TYPE_ARRAY); 13 | __type(key, u32); 14 | __type(value, u32); 15 | __uint(max_entries, 600); 16 | } syscall_filters SEC(".maps"); 17 | 18 | struct { 19 | __uint(type, BPF_MAP_TYPE_HASH); 20 | __type(key, char[TASK_COMM_LEN]); 21 | __type(value, u32); 22 | __uint(max_entries, 512); 23 | } comm_filters SEC(".maps"); 24 | 25 | __attribute__((always_inline)) int is_syscall_type_ignored(u32 nr) { 26 | // check if syscalls are filtered 27 | if (load_syscall_filter() == 1) { 28 | u32 *filter = bpf_map_lookup_elem(&syscall_filters, &nr); 29 | if (filter == 0 || (filter != 0 && *filter != 1)) { 30 | // filter out syscall 31 | return 1; 32 | } 33 | } 34 | return 0; 35 | } 36 | 37 | __attribute__((always_inline)) int is_syscall_ignored(u32 nr) { 38 | // check if the pid is traced 39 | if (load_follow_children() == 1) { 40 | u32 key = bpf_get_current_pid_tgid(); 41 | u32 *is_traced = bpf_map_lookup_elem(&traced_pids, &key); 42 | if (is_traced) { 43 | return is_syscall_type_ignored(nr); 44 | } 45 | key = bpf_get_current_pid_tgid() >> 32; 46 | is_traced = bpf_map_lookup_elem(&traced_pids, &key); 47 | if (is_traced) { 48 | return is_syscall_type_ignored(nr); 49 | } 50 | } 51 | 52 | // check if comms are filtered 53 | if (load_comm_filter() == 1) { 54 | char comm[TASK_COMM_LEN] = {}; 55 | bpf_get_current_comm(&comm[0], TASK_COMM_LEN); 56 | u32 *filter = bpf_map_lookup_elem(&comm_filters, comm); 57 | if (filter == 0 || (filter != 0 && *filter != 1)) { 58 | // filter out syscall 59 | return 1; 60 | } 61 | } 62 | 63 | // register this process as traced 64 | u32 key = bpf_get_current_pid_tgid(); 65 | bpf_map_update_elem(&traced_pids, &key, &key, BPF_ANY); 66 | key = bpf_get_current_pid_tgid() >> 32; 67 | bpf_map_update_elem(&traced_pids, &key, &key, BPF_ANY); 68 | return is_syscall_type_ignored(nr); 69 | } 70 | 71 | struct syscall_cache { 72 | struct process_context entry_process_context; 73 | u64 args[6]; 74 | u32 nr; 75 | }; 76 | 77 | struct { 78 | __uint(type, BPF_MAP_TYPE_LRU_HASH); 79 | __type(key, u64); 80 | __type(value, struct syscall_cache); 81 | __uint(max_entries, 4096); 82 | } syscall_cache SEC(".maps"); 83 | 84 | struct syscall_cache syscall_cache_zero = {}; 85 | 86 | __attribute__((always_inline)) struct syscall_cache *reset_syscall_cache(u64 id) { 87 | int ret = bpf_map_update_elem(&syscall_cache, &id, &syscall_cache_zero, BPF_ANY); 88 | if (ret < 0) { 89 | // should never happen 90 | return 0; 91 | } 92 | return bpf_map_lookup_elem(&syscall_cache, &id); 93 | } 94 | 95 | __attribute__((always_inline)) struct syscall_cache *get_syscall_cache(u64 id) { 96 | return bpf_map_lookup_elem(&syscall_cache, &id); 97 | } 98 | 99 | __attribute__((always_inline)) int delete_syscall_cache(u64 id) { 100 | return bpf_map_delete_elem(&syscall_cache, &id); 101 | } 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /ebpf/etrace/event.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2020 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _EVENT_H_ 9 | #define _EVENT_H_ 10 | 11 | #define MAX_DATA_PER_SYSCALL 9216 12 | #define MAX_DATA_PER_ARG 1024 13 | 14 | struct syscall_event { 15 | u32 nr; 16 | u32 tgid; 17 | u32 pid; 18 | u32 padding; 19 | u64 ret; 20 | u64 ts; 21 | struct process_context entry_process_context; 22 | struct process_context exit_process_context; 23 | }; 24 | 25 | struct syscall_buffer { 26 | struct syscall_event evt; 27 | char args[MAX_DATA_PER_SYSCALL]; 28 | u16 cursor; 29 | }; 30 | 31 | struct syscall_buffer syscall_buffer_zero = {}; 32 | 33 | struct { 34 | __uint(type, BPF_MAP_TYPE_HASH); 35 | __type(key, u64); 36 | __type(value, struct syscall_buffer); 37 | __uint(max_entries, 512); 38 | } syscall_buffer_cache SEC(".maps"); 39 | 40 | __attribute__((always_inline)) struct syscall_buffer *reset_syscall_buffer_cache(u64 id) { 41 | int ret = bpf_map_update_elem(&syscall_buffer_cache, &id, &syscall_cache_zero, BPF_ANY); 42 | if (ret < 0) { 43 | // should never happen 44 | return 0; 45 | } 46 | return bpf_map_lookup_elem(&syscall_buffer_cache, &id); 47 | } 48 | 49 | __attribute__((always_inline)) struct syscall_buffer *get_syscall_buffer_cache(u64 id) { 50 | return bpf_map_lookup_elem(&syscall_buffer_cache, &id); 51 | } 52 | 53 | __attribute__((always_inline)) int delete_syscall_buffer_cache(u64 id) { 54 | return bpf_map_delete_elem(&syscall_buffer_cache, &id); 55 | } 56 | 57 | struct { 58 | __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 59 | __type(key, u32); 60 | __type(value, struct syscall_buffer); 61 | __uint(max_entries, 1); 62 | } syscall_buffer_gen SEC(".maps"); 63 | 64 | __attribute__((always_inline)) struct syscall_buffer *new_syscall_buffer() { 65 | u32 key = 0; 66 | int ret = bpf_map_update_elem(&syscall_buffer_gen, &key, &syscall_buffer_zero, BPF_ANY); 67 | if (ret < 0) { 68 | // should never happen 69 | return 0; 70 | } 71 | return bpf_map_lookup_elem(&syscall_buffer_gen, &key); 72 | } 73 | 74 | struct { 75 | __uint(type, BPF_MAP_TYPE_RINGBUF); 76 | __uint(max_entries, 16384 * 1024 /* 16 MB */); 77 | } events SEC(".maps"); 78 | 79 | struct events_stats_counter { 80 | u64 lost; 81 | u64 sent; 82 | }; 83 | 84 | struct { 85 | __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 86 | __type(key, u32); 87 | __type(value, struct events_stats_counter); 88 | __uint(max_entries, 600); 89 | } events_stats SEC(".maps"); 90 | 91 | struct { 92 | __uint(type, BPF_MAP_TYPE_ARRAY); 93 | __type(key, u32); 94 | __type(value, u32); 95 | __uint(max_entries, 1); 96 | } events_sync SEC(".maps"); 97 | 98 | __attribute__((always_inline)) int send_syscall_buffer(struct syscall_buffer *buf) { 99 | u32 sync_key = 0; 100 | u32 *sync_value = bpf_map_lookup_elem(&events_sync, &sync_key); 101 | if (sync_value == 0 || (sync_value != 0 && *sync_value == 1)) { 102 | return 0; 103 | } 104 | int ret = 0; 105 | if (*sync_value == 0) { 106 | ret = bpf_ringbuf_output(&events, buf, sizeof(buf->evt) + (buf->cursor & (MAX_DATA_PER_SYSCALL - MAX_DATA_PER_ARG - 1)), BPF_RB_FORCE_WAKEUP); 107 | } 108 | 109 | // record statistics 110 | struct events_stats_counter *stats = bpf_map_lookup_elem(&events_stats, &buf->evt.nr); 111 | if (stats != 0) { 112 | if (ret < 0) { 113 | __sync_fetch_and_add(&stats->lost, 1); 114 | } else { 115 | __sync_fetch_and_add(&stats->sent, 1); 116 | } 117 | } 118 | return ret; 119 | } 120 | 121 | #endif -------------------------------------------------------------------------------- /cmd/etrace/run/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "regexp" 23 | 24 | "github.com/Gui774ume/etrace/pkg/etrace" 25 | 26 | "github.com/pkg/errors" 27 | "github.com/sirupsen/logrus" 28 | ) 29 | 30 | // CLIOptions are the command line options of ssh-probe 31 | type CLIOptions struct { 32 | LogLevel logrus.Level 33 | SyscallFilters []string 34 | InputFile string 35 | ETraceOptions etrace.Options 36 | } 37 | 38 | // LogLevelSanitizer is a log level sanitizer that ensures that the provided log level exists 39 | type LogLevelSanitizer struct { 40 | logLevel *logrus.Level 41 | } 42 | 43 | // NewLogLevelSanitizer creates a new instance of LogLevelSanitizer. The sanitized level will be written in the provided 44 | // logrus level 45 | func NewLogLevelSanitizer(sanitizedLevel *logrus.Level) *LogLevelSanitizer { 46 | *sanitizedLevel = logrus.InfoLevel 47 | return &LogLevelSanitizer{ 48 | logLevel: sanitizedLevel, 49 | } 50 | } 51 | 52 | func (lls *LogLevelSanitizer) String() string { 53 | return fmt.Sprintf("%v", *lls.logLevel) 54 | } 55 | 56 | func (lls *LogLevelSanitizer) Set(val string) error { 57 | sanitized, err := logrus.ParseLevel(val) 58 | if err != nil { 59 | return err 60 | } 61 | *lls.logLevel = sanitized 62 | return nil 63 | } 64 | 65 | func (lls *LogLevelSanitizer) Type() string { 66 | return "string" 67 | } 68 | 69 | // PathSanitizer is a path sanitizer that ensures that the provided path exists 70 | type PathSanitizer struct { 71 | path *string 72 | } 73 | 74 | // NewPathSanitizer creates a new instance of PathSanitizer. The sanitized path will be written in the provided string 75 | func NewPathSanitizer(sanitizedPath *string) *PathSanitizer { 76 | return &PathSanitizer{ 77 | path: sanitizedPath, 78 | } 79 | } 80 | 81 | func (ps *PathSanitizer) String() string { 82 | return fmt.Sprintf("%v", *ps.path) 83 | } 84 | 85 | func (ps *PathSanitizer) Set(val string) error { 86 | if len(val) == 0 { 87 | return nil 88 | } 89 | if _, err := os.Stat(val); err != nil { 90 | return err 91 | } 92 | *ps.path = val 93 | return nil 94 | } 95 | 96 | func (ps *PathSanitizer) Type() string { 97 | return "string" 98 | } 99 | 100 | // RegexpSanitizer is a regexp sanitizer that ensures that the provided regexp is valid 101 | type RegexpSanitizer struct { 102 | pattern **regexp.Regexp 103 | } 104 | 105 | // NewRegexpSanitizerWithDefault creates a new instance of RegexpSanitizer. The sanitized regexp will be written in the provided 106 | // regexp pointer 107 | func NewRegexpSanitizerWithDefault(sanitizedPattern **regexp.Regexp, defaultPattern *regexp.Regexp) *RegexpSanitizer { 108 | *sanitizedPattern = defaultPattern 109 | return &RegexpSanitizer{ 110 | pattern: sanitizedPattern, 111 | } 112 | } 113 | 114 | func (rs *RegexpSanitizer) String() string { 115 | return "*" 116 | } 117 | 118 | func (rs *RegexpSanitizer) Set(val string) error { 119 | if len(val) == 0 { 120 | return errors.New("empty pattern") 121 | } 122 | pattern, err := regexp.Compile(val) 123 | if err != nil { 124 | return errors.Wrap(err, "invalid pattern") 125 | } 126 | *rs.pattern = pattern 127 | return nil 128 | } 129 | 130 | func (rs *RegexpSanitizer) Type() string { 131 | return "regexp" 132 | } 133 | -------------------------------------------------------------------------------- /pkg/etrace/syscall_args.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "os" 23 | "regexp" 24 | "strconv" 25 | "strings" 26 | 27 | "github.com/sirupsen/logrus" 28 | 29 | "github.com/Gui774ume/etrace/internal/btf" 30 | ) 31 | 32 | var ( 33 | tracepointSyscallsPath = "/sys/kernel/debug/tracing/events/syscalls/" 34 | tracepointSyscallFormatPattern = tracepointSyscallsPath + "sys_enter_%s/format" 35 | formatRegex = regexp.MustCompile(".*field:(.*);\toffset:(.*);\tsize:(.*);\tsigned:(.*);") 36 | ) 37 | 38 | func (e *ETrace) prepareSyscallArgs() error { 39 | // load kernel BTF 40 | var err error 41 | e.kernelSpec, err = btf.LoadKernelSpec() 42 | if err != nil { 43 | return err 44 | } 45 | 46 | // list available syscall trace points 47 | syscalls, err := ioutil.ReadDir(tracepointSyscallsPath) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | var fmtFile *os.File 53 | var syscallName string 54 | var fmtData []byte 55 | var splittedArgs []string 56 | 57 | for _, syscall := range syscalls { 58 | if !strings.HasPrefix(syscall.Name(), "sys_enter_") { 59 | continue 60 | } 61 | 62 | // read format file to retrieve arguments definition 63 | syscallName = strings.TrimPrefix(syscall.Name(), "sys_enter_") 64 | fmtFile, err = os.Open(fmt.Sprintf(tracepointSyscallFormatPattern, syscallName)) 65 | if err != nil { 66 | logrus.Debugf("couldn't open format file for %s: %v", syscallName, err) 67 | continue 68 | } 69 | 70 | fmtData, err = ioutil.ReadAll(fmtFile) 71 | if err != nil { 72 | logrus.Debugf("couldn't read format file for %s: %v", syscallName, err) 73 | continue 74 | } 75 | 76 | // extract syscall arguments 77 | syscallFuncProto := SyscallDefinition{ 78 | NR: ParseSyscallName(syscallName), 79 | } 80 | args := formatRegex.FindAllSubmatch(fmtData, -1) 81 | 82 | for i := 5; i < len(args); i++ { 83 | splittedArgs = strings.Split(string(args[i][1]), " ") 84 | arg := SyscallArgument{ 85 | Name: splittedArgs[len(splittedArgs)-1], 86 | Definition: strings.Join(splittedArgs[:len(splittedArgs)-1], " "), 87 | } 88 | arg.Size, err = strconv.Atoi(string(args[i][3])) 89 | if err != nil { 90 | arg.Size = 0 91 | } 92 | arg.ParseType() 93 | 94 | // try to resolve size using the kernel BTF 95 | if err = e.kernelSpec.FindType(arg.TypeName, arg.BTFType); err == nil { 96 | // update argument size 97 | arg.ResolveSizeFromBTFType() 98 | } else { 99 | // override the length of buffer with variable length 100 | if arg.TypeName == "void" || arg.TypeName == "char" { 101 | arg.Size = 0 102 | arg.BTFType = &btf.Int{ 103 | Size: MaxDataPerArg, 104 | Encoding: btf.Char, 105 | } 106 | 107 | // the size of the buffer can be determined by: 108 | // - a trailing \x00 109 | // - another syscall argument 110 | // - the return value 111 | arg.ResolveDynamicSizeResolutionType(syscallFuncProto.NR, i-5, e.kernelSpec) 112 | } else { 113 | // the remaining unresolved types are integers. The value parsed in the trace point format is enough. 114 | intType := &btf.Int{ 115 | Size: uint32(arg.Size), 116 | } 117 | if arg.TypeName == "int" { 118 | intType.Encoding = btf.Signed 119 | } 120 | arg.BTFType = intType 121 | } 122 | } 123 | syscallFuncProto.Arguments = append(syscallFuncProto.Arguments, arg) 124 | } 125 | 126 | e.SyscallDefinitions[syscallFuncProto.NR] = syscallFuncProto 127 | } 128 | return nil 129 | } 130 | 131 | func (e *ETrace) pushSyscallDefinitions() error { 132 | var err error 133 | for nr, def := range e.SyscallDefinitions { 134 | if err = e.syscallDefinitionsMap.Put(nr, def); err != nil { 135 | return err 136 | } 137 | } 138 | return nil 139 | } 140 | -------------------------------------------------------------------------------- /internal/btf/btf_test.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "os" 11 | "testing" 12 | 13 | "github.com/Gui774ume/etrace/internal" 14 | "github.com/Gui774ume/etrace/internal/testutils" 15 | ) 16 | 17 | func TestParseVmlinux(t *testing.T) { 18 | fh, err := os.Open("testdata/vmlinux-btf.gz") 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | defer fh.Close() 23 | 24 | rd, err := gzip.NewReader(fh) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | buf, err := ioutil.ReadAll(rd) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | spec, err := loadNakedSpec(bytes.NewReader(buf), binary.LittleEndian, nil, nil) 35 | if err != nil { 36 | t.Fatal("Can't load BTF:", err) 37 | } 38 | 39 | var iphdr Struct 40 | err = spec.FindType("iphdr", &iphdr) 41 | if err != nil { 42 | t.Fatalf("unable to find `iphdr` struct: %s", err) 43 | } 44 | for _, m := range iphdr.Members { 45 | if m.Name == "version" { 46 | // __u8 is a typedef 47 | td, ok := m.Type.(*Typedef) 48 | if !ok { 49 | t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type) 50 | } 51 | u8int, ok := td.Type.(*Int) 52 | if !ok { 53 | t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type) 54 | } 55 | if u8int.Bits != 8 { 56 | t.Fatalf("incorrect bit size of an __u8 int: expected: 8 actual: %d", u8int.Bits) 57 | } 58 | if u8int.Encoding != 0 { 59 | t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8int.Encoding) 60 | } 61 | if u8int.Offset != 0 { 62 | t.Fatalf("incorrect int offset of an __u8 int: expected: 0 actual: %d", u8int.Offset) 63 | } 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestParseCurrentKernelBTF(t *testing.T) { 70 | spec, err := loadKernelSpec() 71 | testutils.SkipIfNotSupported(t, err) 72 | if err != nil { 73 | t.Fatal("Can't load BTF:", err) 74 | } 75 | 76 | if len(spec.namedTypes) == 0 { 77 | t.Fatal("Empty kernel BTF") 78 | } 79 | } 80 | 81 | func TestLoadSpecFromElf(t *testing.T) { 82 | testutils.Files(t, testutils.Glob(t, "../../testdata/loader-e*.elf"), func(t *testing.T, file string) { 83 | fh, err := os.Open(file) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | defer fh.Close() 88 | 89 | spec, err := LoadSpecFromReader(fh) 90 | if err != nil { 91 | t.Fatal("Can't load BTF:", err) 92 | } 93 | 94 | if spec == nil { 95 | t.Error("No BTF found in ELF") 96 | } 97 | 98 | if sec, err := spec.Program("xdp", 1); err != nil { 99 | t.Error("Can't get BTF for the xdp section:", err) 100 | } else if sec == nil { 101 | t.Error("Missing BTF for the xdp section") 102 | } 103 | 104 | if sec, err := spec.Program("socket", 1); err != nil { 105 | t.Error("Can't get BTF for the socket section:", err) 106 | } else if sec == nil { 107 | t.Error("Missing BTF for the socket section") 108 | } 109 | 110 | var bpfMapDef Struct 111 | if err := spec.FindType("bpf_map_def", &bpfMapDef); err != nil { 112 | t.Error("Can't find bpf_map_def:", err) 113 | } 114 | 115 | var tmp Void 116 | if err := spec.FindType("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) { 117 | t.Error("FindType doesn't return ErrNotFound:", err) 118 | } 119 | 120 | var fn Func 121 | if err := spec.FindType("global_fn", &fn); err != nil { 122 | t.Error("Can't find global_fn():", err) 123 | } else { 124 | if fn.Linkage != GlobalFunc { 125 | t.Error("Expected global linkage:", fn) 126 | } 127 | } 128 | 129 | var v Var 130 | if err := spec.FindType("key3", &v); err != nil { 131 | t.Error("Cant find key3:", err) 132 | } else { 133 | if v.Linkage != GlobalVar { 134 | t.Error("Expected global linkage:", v) 135 | } 136 | } 137 | 138 | if spec.byteOrder != internal.NativeEndian { 139 | return 140 | } 141 | 142 | t.Run("Handle", func(t *testing.T) { 143 | btf, err := NewHandle(spec) 144 | testutils.SkipIfNotSupported(t, err) 145 | if err != nil { 146 | t.Fatal("Can't load BTF:", err) 147 | } 148 | defer btf.Close() 149 | }) 150 | }) 151 | } 152 | 153 | func TestLoadKernelSpec(t *testing.T) { 154 | if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) { 155 | t.Skip("/sys/kernel/btf/vmlinux not present") 156 | } 157 | 158 | _, err := LoadKernelSpec() 159 | if err != nil { 160 | t.Fatal("Can't load kernel spec:", err) 161 | } 162 | } 163 | 164 | func TestHaveBTF(t *testing.T) { 165 | testutils.CheckFeatureTest(t, haveBTF) 166 | } 167 | 168 | func TestHaveFuncLinkage(t *testing.T) { 169 | testutils.CheckFeatureTest(t, haveFuncLinkage) 170 | } 171 | 172 | func ExampleSpec_FindType() { 173 | // Acquire a Spec via one of its constructors. 174 | spec := new(Spec) 175 | 176 | // Declare a variable of the desired type 177 | var foo Struct 178 | 179 | if err := spec.FindType("foo", &foo); err != nil { 180 | // There is no struct with name foo, or there 181 | // are multiple possibilities. 182 | } 183 | 184 | // We've found struct foo 185 | fmt.Println(foo.Name) 186 | } 187 | -------------------------------------------------------------------------------- /ebpf/etrace/etrace.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2020 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _ETRACE_H_ 9 | #define _ETRACE_H_ 10 | 11 | struct tracepoint_raw_syscalls_sys_enter { 12 | unsigned short common_type; 13 | unsigned char common_flags; 14 | unsigned char common_preempt_count; 15 | int common_pid; 16 | long id; 17 | unsigned long args[6]; 18 | }; 19 | 20 | SEC("tracepoint/raw_syscalls/sys_enter") 21 | int sys_enter(struct tracepoint_raw_syscalls_sys_enter *args) { 22 | // ignore syscalls from etrace 23 | u64 id = bpf_get_current_pid_tgid(); 24 | if ((id >> 32) == load_etrace_tgid()) { 25 | return 0; 26 | } 27 | 28 | // create new syscall cache entry 29 | u32 nr = 0; 30 | bpf_probe_read(&nr, sizeof(nr), &args->id); 31 | 32 | // filter all events except the execve one 33 | if (nr != SYSCALL_EXECVE) { 34 | if (is_syscall_ignored(nr)) { 35 | return 0; 36 | } 37 | } 38 | 39 | struct syscall_cache *cache = reset_syscall_cache(id); 40 | if (cache == 0) { 41 | // this syscall is ignored 42 | return 0; 43 | } 44 | 45 | // save syscall nr and input registers 46 | bpf_probe_read(&cache->nr, sizeof(cache->nr), &args->id); 47 | bpf_probe_read(&cache->args[0], sizeof(cache->args), &args->args[0]); 48 | 49 | // save entry process context 50 | fill_process_context(&cache->entry_process_context); 51 | return 0; 52 | } 53 | 54 | SEC("kprobe/security_bprm_check") 55 | int kprobe_security_bprm_check(struct pt_regs *ctx) { 56 | // ignore syscalls from etrace 57 | u64 id = bpf_get_current_pid_tgid(); 58 | if ((id >> 32) == load_etrace_tgid()) { 59 | return 0; 60 | } 61 | 62 | // fetch syscall cache 63 | struct syscall_cache *cache = get_syscall_cache(id); 64 | if (cache == 0) { 65 | return 0; 66 | } 67 | 68 | // lookup syscall definition 69 | struct syscall_definition *def = lookup_definition(cache->nr); 70 | if (def == 0) { 71 | return 0; 72 | } 73 | 74 | struct syscall_buffer *buf = reset_syscall_buffer_cache(id); 75 | if (buf == 0) { 76 | // should never happen 77 | return 0; 78 | } 79 | 80 | // resolve arguments 81 | int ret = resolve_args(cache, def, buf); 82 | if (ret < 0) { 83 | // couldn't resolve arguments, exit now and do not send event 84 | delete_syscall_buffer_cache(id); 85 | } 86 | return 0; 87 | } 88 | 89 | struct tracepoint_raw_syscalls_sys_exit { 90 | unsigned short common_type; 91 | unsigned char common_flags; 92 | unsigned char common_preempt_count; 93 | int common_pid; 94 | long id; 95 | long ret; 96 | }; 97 | 98 | SEC("tracepoint/raw_syscalls/sys_exit") 99 | int sys_exit(struct tracepoint_raw_syscalls_sys_exit *args) { 100 | // ignore syscalls from etrace 101 | u64 id = bpf_get_current_pid_tgid(); 102 | if ((id >> 32) == load_etrace_tgid()) { 103 | return 0; 104 | } 105 | 106 | // fetch syscall cache 107 | struct syscall_cache *cache = get_syscall_cache(id); 108 | if (cache == 0) { 109 | return 0; 110 | } 111 | 112 | // prepare event 113 | struct syscall_buffer *buf = new_syscall_buffer(); 114 | if (buf == 0) { 115 | // should never happen 116 | goto exit; 117 | } 118 | bpf_probe_read(&buf->evt.entry_process_context, sizeof(buf->evt.entry_process_context), &cache->entry_process_context); 119 | bpf_probe_read(&buf->evt.ret, sizeof(buf->evt.ret), &args->ret); 120 | fill_process_context(&buf->evt.exit_process_context); 121 | buf->evt.nr = cache->nr; 122 | buf->evt.ts = bpf_ktime_get_ns(); 123 | buf->evt.tgid = id >> 32; 124 | buf->evt.pid = id; 125 | 126 | // filter execve events 127 | if (cache->nr == SYSCALL_EXECVE) { 128 | if (is_syscall_ignored(cache->nr)) { 129 | goto exit; 130 | } 131 | 132 | // copy argument from cache 133 | struct syscall_buffer *cached_buf = get_syscall_buffer_cache(id); 134 | if (cached_buf == 0) { 135 | // shouldn't happen 136 | goto exit; 137 | } 138 | bpf_probe_read(buf->args, MAX_DATA_PER_SYSCALL, cached_buf->args); 139 | buf->cursor = cached_buf->cursor; 140 | 141 | } else { 142 | 143 | // lookup syscall definition 144 | struct syscall_definition *def = lookup_definition(cache->nr); 145 | if (def == 0) { 146 | goto exit; 147 | } 148 | 149 | // resolve arguments 150 | int ret = resolve_args(cache, def, buf); 151 | if (ret < 0) { 152 | // couldn't resolve arguments, exit now and do not send event 153 | goto exit; 154 | } 155 | } 156 | 157 | // send event 158 | send_syscall_buffer(buf); 159 | 160 | exit: 161 | delete_syscall_cache(id); 162 | delete_syscall_buffer_cache(id); 163 | return 0; 164 | } 165 | 166 | #endif 167 | -------------------------------------------------------------------------------- /internal/version.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "regexp" 7 | "sync" 8 | 9 | "github.com/Gui774ume/etrace/internal/unix" 10 | ) 11 | 12 | const ( 13 | // Version constant used in ELF binaries indicating that the loader needs to 14 | // substitute the eBPF program's version with the value of the kernel's 15 | // KERNEL_VERSION compile-time macro. Used for compatibility with BCC, gobpf 16 | // and RedSift. 17 | MagicKernelVersion = 0xFFFFFFFE 18 | ) 19 | 20 | var ( 21 | // Match between one and three decimals separated by dots, with the last 22 | // segment (patch level) being optional on some kernels. 23 | // The x.y.z string must appear at the start of a string or right after 24 | // whitespace to prevent sequences like 'x.y.z-a.b.c' from matching 'a.b.c'. 25 | rgxKernelVersion = regexp.MustCompile(`(?:\A|\s)\d{1,3}\.\d{1,3}(?:\.\d{1,3})?`) 26 | 27 | kernelVersion = struct { 28 | once sync.Once 29 | version Version 30 | err error 31 | }{} 32 | ) 33 | 34 | // A Version in the form Major.Minor.Patch. 35 | type Version [3]uint16 36 | 37 | // NewVersion creates a version from a string like "Major.Minor.Patch". 38 | // 39 | // Patch is optional. 40 | func NewVersion(ver string) (Version, error) { 41 | var major, minor, patch uint16 42 | n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch) 43 | if n < 2 { 44 | return Version{}, fmt.Errorf("invalid version: %s", ver) 45 | } 46 | return Version{major, minor, patch}, nil 47 | } 48 | 49 | func (v Version) String() string { 50 | if v[2] == 0 { 51 | return fmt.Sprintf("v%d.%d", v[0], v[1]) 52 | } 53 | return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2]) 54 | } 55 | 56 | // Less returns true if the version is less than another version. 57 | func (v Version) Less(other Version) bool { 58 | for i, a := range v { 59 | if a == other[i] { 60 | continue 61 | } 62 | return a < other[i] 63 | } 64 | return false 65 | } 66 | 67 | // Unspecified returns true if the version is all zero. 68 | func (v Version) Unspecified() bool { 69 | return v[0] == 0 && v[1] == 0 && v[2] == 0 70 | } 71 | 72 | // Kernel implements the kernel's KERNEL_VERSION macro from linux/version.h. 73 | // It represents the kernel version and patch level as a single value. 74 | func (v Version) Kernel() uint32 { 75 | 76 | // Kernels 4.4 and 4.9 have their SUBLEVEL clamped to 255 to avoid 77 | // overflowing into PATCHLEVEL. 78 | // See kernel commit 9b82f13e7ef3 ("kbuild: clamp SUBLEVEL to 255"). 79 | s := v[2] 80 | if s > 255 { 81 | s = 255 82 | } 83 | 84 | // Truncate members to uint8 to prevent them from spilling over into 85 | // each other when overflowing 8 bits. 86 | return uint32(uint8(v[0]))<<16 | uint32(uint8(v[1]))<<8 | uint32(uint8(s)) 87 | } 88 | 89 | // KernelVersion returns the version of the currently running kernel. 90 | func KernelVersion() (Version, error) { 91 | kernelVersion.once.Do(func() { 92 | kernelVersion.version, kernelVersion.err = detectKernelVersion() 93 | }) 94 | 95 | if kernelVersion.err != nil { 96 | return Version{}, kernelVersion.err 97 | } 98 | return kernelVersion.version, nil 99 | } 100 | 101 | // detectKernelVersion returns the version of the running kernel. It scans the 102 | // following sources in order: /proc/version_signature, uname -v, uname -r. 103 | // In each of those locations, the last-appearing x.y(.z) value is selected 104 | // for parsing. The first location that yields a usable version number is 105 | // returned. 106 | func detectKernelVersion() (Version, error) { 107 | 108 | // Try reading /proc/version_signature for Ubuntu compatibility. 109 | // Example format: Ubuntu 4.15.0-91.92-generic 4.15.18 110 | // This method exists in the kernel itself, see d18acd15c 111 | // ("perf tools: Fix kernel version error in ubuntu"). 112 | if pvs, err := ioutil.ReadFile("/proc/version_signature"); err == nil { 113 | // If /proc/version_signature exists, failing to parse it is an error. 114 | // It only exists on Ubuntu, where the real patch level is not obtainable 115 | // through any other method. 116 | v, err := findKernelVersion(string(pvs)) 117 | if err != nil { 118 | return Version{}, err 119 | } 120 | return v, nil 121 | } 122 | 123 | var uname unix.Utsname 124 | if err := unix.Uname(&uname); err != nil { 125 | return Version{}, fmt.Errorf("calling uname: %w", err) 126 | } 127 | 128 | // Debian puts the version including the patch level in uname.Version. 129 | // It is not an error if there's no version number in uname.Version, 130 | // as most distributions don't use it. Parsing can continue on uname.Release. 131 | // Example format: #1 SMP Debian 4.19.37-5+deb10u2 (2019-08-08) 132 | if v, err := findKernelVersion(unix.ByteSliceToString(uname.Version[:])); err == nil { 133 | return v, nil 134 | } 135 | 136 | // Most other distributions have the full kernel version including patch 137 | // level in uname.Release. 138 | // Example format: 4.19.0-5-amd64, 5.5.10-arch1-1 139 | v, err := findKernelVersion(unix.ByteSliceToString(uname.Release[:])) 140 | if err != nil { 141 | return Version{}, err 142 | } 143 | 144 | return v, nil 145 | } 146 | 147 | // findKernelVersion matches s against rgxKernelVersion and parses the result 148 | // into a Version. If s contains multiple matches, the last entry is selected. 149 | func findKernelVersion(s string) (Version, error) { 150 | m := rgxKernelVersion.FindAllString(s, -1) 151 | if m == nil { 152 | return Version{}, fmt.Errorf("no kernel version in string: %s", s) 153 | } 154 | // Pick the last match of the string in case there are multiple. 155 | s = m[len(m)-1] 156 | 157 | v, err := NewVersion(s) 158 | if err != nil { 159 | return Version{}, fmt.Errorf("parsing version string %s: %w", s, err) 160 | } 161 | 162 | return v, nil 163 | } 164 | -------------------------------------------------------------------------------- /internal/btf/types_test.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | qt "github.com/frankban/quicktest" 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestSizeof(t *testing.T) { 12 | testcases := []struct { 13 | size int 14 | typ Type 15 | }{ 16 | {0, (*Void)(nil)}, 17 | {1, &Int{Size: 1}}, 18 | {4, &Enum{}}, 19 | {0, &Array{Type: &Pointer{Target: (*Void)(nil)}, Nelems: 0}}, 20 | {12, &Array{Type: &Enum{}, Nelems: 3}}, 21 | } 22 | 23 | for _, tc := range testcases { 24 | name := fmt.Sprint(tc.typ) 25 | t.Run(name, func(t *testing.T) { 26 | have, err := Sizeof(tc.typ) 27 | if err != nil { 28 | t.Fatal("Can't calculate size:", err) 29 | } 30 | if have != tc.size { 31 | t.Errorf("Expected size %d, got %d", tc.size, have) 32 | } 33 | }) 34 | } 35 | } 36 | 37 | func TestCopyType(t *testing.T) { 38 | _, _ = copyType((*Void)(nil), nil) 39 | 40 | in := &Int{Size: 4} 41 | out, _ := copyType(in, nil) 42 | 43 | in.Size = 8 44 | if size := out.(*Int).Size; size != 4 { 45 | t.Error("Copy doesn't make a copy, expected size 4, got", size) 46 | } 47 | 48 | t.Run("cyclical", func(t *testing.T) { 49 | _, _ = copyType(newCyclicalType(2), nil) 50 | }) 51 | 52 | t.Run("identity", func(t *testing.T) { 53 | u16 := &Int{Size: 2} 54 | 55 | out, _ := copyType(&Struct{ 56 | Members: []Member{ 57 | {Name: "a", Type: u16}, 58 | {Name: "b", Type: u16}, 59 | }, 60 | }, nil) 61 | 62 | outStruct := out.(*Struct) 63 | qt.Assert(t, outStruct.Members[0].Type, qt.Equals, outStruct.Members[1].Type) 64 | }) 65 | } 66 | 67 | // The following are valid Types. 68 | // 69 | // There currently is no better way to document which 70 | // types implement an interface. 71 | func ExampleType_validTypes() { 72 | var _ Type = &Void{} 73 | var _ Type = &Int{} 74 | var _ Type = &Pointer{} 75 | var _ Type = &Array{} 76 | var _ Type = &Struct{} 77 | var _ Type = &Union{} 78 | var _ Type = &Enum{} 79 | var _ Type = &Fwd{} 80 | var _ Type = &Typedef{} 81 | var _ Type = &Volatile{} 82 | var _ Type = &Const{} 83 | var _ Type = &Restrict{} 84 | var _ Type = &Func{} 85 | var _ Type = &FuncProto{} 86 | var _ Type = &Var{} 87 | var _ Type = &Datasec{} 88 | } 89 | 90 | func TestType(t *testing.T) { 91 | types := []func() Type{ 92 | func() Type { return &Void{} }, 93 | func() Type { return &Int{Size: 2, Bits: 3} }, 94 | func() Type { return &Pointer{Target: &Void{}} }, 95 | func() Type { return &Array{Type: &Int{}} }, 96 | func() Type { 97 | return &Struct{ 98 | Members: []Member{{Type: &Void{}}}, 99 | } 100 | }, 101 | func() Type { 102 | return &Union{ 103 | Members: []Member{{Type: &Void{}}}, 104 | } 105 | }, 106 | func() Type { return &Enum{} }, 107 | func() Type { return &Fwd{Name: "thunk"} }, 108 | func() Type { return &Typedef{Type: &Void{}} }, 109 | func() Type { return &Volatile{Type: &Void{}} }, 110 | func() Type { return &Const{Type: &Void{}} }, 111 | func() Type { return &Restrict{Type: &Void{}} }, 112 | func() Type { return &Func{Name: "foo", Type: &Void{}} }, 113 | func() Type { 114 | return &FuncProto{ 115 | Params: []FuncParam{{Name: "bar", Type: &Void{}}}, 116 | Return: &Void{}, 117 | } 118 | }, 119 | func() Type { return &Var{Type: &Void{}} }, 120 | func() Type { 121 | return &Datasec{ 122 | Vars: []VarSecinfo{{Type: &Void{}}}, 123 | } 124 | }, 125 | } 126 | 127 | compareTypes := cmp.Comparer(func(a, b *Type) bool { 128 | return a == b 129 | }) 130 | 131 | for _, fn := range types { 132 | typ := fn() 133 | t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) { 134 | t.Logf("%v", typ) 135 | 136 | if typ == typ.copy() { 137 | t.Error("Copy doesn't copy") 138 | } 139 | 140 | var first, second typeDeque 141 | typ.walk(&first) 142 | typ.walk(&second) 143 | 144 | if diff := cmp.Diff(first.all(), second.all(), compareTypes); diff != "" { 145 | t.Errorf("Walk mismatch (-want +got):\n%s", diff) 146 | } 147 | }) 148 | } 149 | } 150 | 151 | func TestTypeDeque(t *testing.T) { 152 | a, b := new(Type), new(Type) 153 | 154 | t.Run("pop", func(t *testing.T) { 155 | var td typeDeque 156 | td.push(a) 157 | td.push(b) 158 | 159 | if td.pop() != b { 160 | t.Error("Didn't pop b first") 161 | } 162 | 163 | if td.pop() != a { 164 | t.Error("Didn't pop a second") 165 | } 166 | 167 | if td.pop() != nil { 168 | t.Error("Didn't pop nil") 169 | } 170 | }) 171 | 172 | t.Run("shift", func(t *testing.T) { 173 | var td typeDeque 174 | td.push(a) 175 | td.push(b) 176 | 177 | if td.shift() != a { 178 | t.Error("Didn't shift a second") 179 | } 180 | 181 | if td.shift() != b { 182 | t.Error("Didn't shift b first") 183 | } 184 | 185 | if td.shift() != nil { 186 | t.Error("Didn't shift nil") 187 | } 188 | }) 189 | 190 | t.Run("push", func(t *testing.T) { 191 | var td typeDeque 192 | td.push(a) 193 | td.push(b) 194 | td.shift() 195 | 196 | ts := make([]Type, 12) 197 | for i := range ts { 198 | td.push(&ts[i]) 199 | } 200 | 201 | if td.shift() != b { 202 | t.Error("Didn't shift b first") 203 | } 204 | for i := range ts { 205 | if td.shift() != &ts[i] { 206 | t.Fatal("Shifted wrong Type at pos", i) 207 | } 208 | } 209 | }) 210 | 211 | t.Run("all", func(t *testing.T) { 212 | var td typeDeque 213 | td.push(a) 214 | td.push(b) 215 | 216 | all := td.all() 217 | if len(all) != 2 { 218 | t.Fatal("Expected 2 elements, got", len(all)) 219 | } 220 | 221 | if all[0] != a || all[1] != b { 222 | t.Fatal("Elements don't match") 223 | } 224 | }) 225 | } 226 | 227 | func newCyclicalType(n int) Type { 228 | ptr := &Pointer{} 229 | prev := Type(ptr) 230 | for i := 0; i < n; i++ { 231 | switch i % 5 { 232 | case 0: 233 | prev = &Struct{ 234 | Members: []Member{ 235 | {Type: prev}, 236 | }, 237 | } 238 | 239 | case 1: 240 | prev = &Const{Type: prev} 241 | case 2: 242 | prev = &Volatile{Type: prev} 243 | case 3: 244 | prev = &Typedef{Type: prev} 245 | case 4: 246 | prev = &Array{Type: prev} 247 | } 248 | } 249 | ptr.Target = prev 250 | return ptr 251 | } 252 | -------------------------------------------------------------------------------- /ebpf/etrace/syscall_args.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2020 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _SYSCALL_ARGS_H_ 9 | #define _SYSCALL_ARGS_H_ 10 | 11 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_UNKNOWN 0 12 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_ARG0 1 << 0 13 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_ARG1 1 << 1 14 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_ARG2 1 << 2 15 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_ARG3 1 << 3 16 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_ARG4 1 << 4 17 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_ARG5 1 << 5 18 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_RETURN_VALUE 1 << 6 19 | #define DYNAMIC_SIZE_RESOLUTION_TYPE_TRAILING_ZERO 1 << 7 20 | 21 | struct syscall_argument { 22 | u32 dereference_count; 23 | u32 size; 24 | u32 size_multiplier; 25 | u32 dynamic_size_resolution_type; 26 | }; 27 | 28 | struct syscall_definition { 29 | struct syscall_argument args[6]; 30 | }; 31 | 32 | struct { 33 | __uint(type, BPF_MAP_TYPE_HASH); 34 | __type(key, u32); 35 | __type(value, struct syscall_definition); 36 | __uint(max_entries, 600); 37 | } syscall_definitions SEC(".maps"); 38 | 39 | __attribute__((always_inline)) struct syscall_definition *lookup_definition(u32 syscall_nr) { 40 | return bpf_map_lookup_elem(&syscall_definitions, &syscall_nr); 41 | } 42 | 43 | __attribute__((always_inline)) int resolve_args(struct syscall_cache *cache, struct syscall_definition *def, struct syscall_buffer *buf) { 44 | u64 arg = 0; 45 | u64 *array_cursor = 0; 46 | u64 size = 0; 47 | u32 copy_ret = 0; 48 | int ret = 0; 49 | 50 | #pragma unroll 51 | for (int i = 0; i < 6; i++) { 52 | if (def->args[i].size == 0 && def->args[i].dynamic_size_resolution_type == DYNAMIC_SIZE_RESOLUTION_TYPE_UNKNOWN) { 53 | goto exit; 54 | } 55 | 56 | array_cursor = 0; 57 | arg = (u64) cache->args[i]; 58 | 59 | // handle dereferences 60 | if (def->args[i].dereference_count == 2) { 61 | if (arg == 0) { 62 | // null pointer can't be dereferenced, move on 63 | goto exit; 64 | } 65 | array_cursor = (u64 *)arg; 66 | bpf_probe_read_user(&arg, sizeof(arg), array_cursor); 67 | } 68 | // arg is now either a pointer to a value, or the value itself 69 | 70 | // resolve value size 71 | size = def->args[i].size; 72 | if (size == 0) { 73 | // the size is determined at runtime 74 | switch (def->args[i].dynamic_size_resolution_type) { 75 | case DYNAMIC_SIZE_RESOLUTION_TYPE_ARG0: 76 | size = cache->args[0]; 77 | break; 78 | case DYNAMIC_SIZE_RESOLUTION_TYPE_ARG1: 79 | size = cache->args[1]; 80 | break; 81 | case DYNAMIC_SIZE_RESOLUTION_TYPE_ARG2: 82 | size = cache->args[2]; 83 | break; 84 | case DYNAMIC_SIZE_RESOLUTION_TYPE_ARG3: 85 | size = cache->args[3]; 86 | break; 87 | case DYNAMIC_SIZE_RESOLUTION_TYPE_ARG4: 88 | size = cache->args[4]; 89 | break; 90 | case DYNAMIC_SIZE_RESOLUTION_TYPE_ARG5: 91 | size = cache->args[5]; 92 | break; 93 | case DYNAMIC_SIZE_RESOLUTION_TYPE_RETURN_VALUE: 94 | size = buf->evt.ret; 95 | break; 96 | } 97 | 98 | // use multiplier if applicable 99 | if (def->args[i].size_multiplier > 0) { 100 | size = size * def->args[i].size_multiplier; 101 | } 102 | } 103 | if (size > MAX_DATA_PER_ARG) { 104 | size = MAX_DATA_PER_ARG; 105 | } 106 | 107 | copy_ret = 0; 108 | ret = 0; 109 | 110 | for (int j = 0; j < 200; j++) { 111 | // copy value 112 | if (def->args[i].dereference_count == 1 || def->args[i].dereference_count == 2) { 113 | // arg is a pointer to the value 114 | if (def->args[i].dynamic_size_resolution_type == DYNAMIC_SIZE_RESOLUTION_TYPE_TRAILING_ZERO) { 115 | ret = bpf_probe_read_user_str(&buf->args[(buf->cursor + 4 + copy_ret) & (MAX_DATA_PER_SYSCALL - MAX_DATA_PER_ARG - 1)], MAX_DATA_PER_ARG, (void *)arg); 116 | if (ret > 0) { 117 | copy_ret += ret; 118 | } 119 | } else { 120 | ret = bpf_probe_read_user(&buf->args[(buf->cursor + 4 + copy_ret) & (MAX_DATA_PER_SYSCALL - MAX_DATA_PER_ARG - 1)], size, (void *)arg); 121 | if (ret == 0) { 122 | copy_ret += size; 123 | } 124 | } 125 | } else { 126 | // arg is the value itself 127 | ret = bpf_probe_read_kernel(&buf->args[(buf->cursor + 4 + copy_ret) & (MAX_DATA_PER_SYSCALL - MAX_DATA_PER_ARG - 1)], size, &arg); 128 | if (ret == 0) { 129 | copy_ret += size; 130 | } 131 | } 132 | 133 | if (copy_ret >= (MAX_DATA_PER_ARG - 2) || def->args[i].dereference_count != 2) { 134 | goto write_length; 135 | } 136 | 137 | array_cursor++; 138 | bpf_probe_read_user(&arg, sizeof(arg), array_cursor); 139 | if (arg == 0) { 140 | goto write_length; 141 | } 142 | } 143 | 144 | write_length: 145 | bpf_probe_read(&buf->args[buf->cursor & (MAX_DATA_PER_SYSCALL - MAX_DATA_PER_ARG - 1)], sizeof(u32), ©_ret); 146 | if (copy_ret > 0) { 147 | buf->cursor += copy_ret + 4; 148 | } else { 149 | buf->cursor += 4; 150 | } 151 | } 152 | 153 | exit: 154 | return 0; 155 | } 156 | 157 | #endif -------------------------------------------------------------------------------- /internal/btf/testdata/relocs.c: -------------------------------------------------------------------------------- 1 | #include "../../../testdata/common.h" 2 | #include "bpf_core_read.h" 3 | 4 | enum e { 5 | // clang-12 doesn't allow enum relocations with zero value. 6 | // See https://reviews.llvm.org/D97659 7 | ONE = 1, 8 | TWO, 9 | }; 10 | 11 | typedef enum e e_t; 12 | 13 | struct s { 14 | int _1; 15 | char _2; 16 | }; 17 | 18 | typedef struct s s_t; 19 | 20 | union u { 21 | int *_1; 22 | char *_2; 23 | }; 24 | 25 | typedef union u u_t; 26 | 27 | #define local_id_zero(expr) \ 28 | ({ \ 29 | if (bpf_core_type_id_local(expr) != 0) { \ 30 | return __LINE__; \ 31 | } \ 32 | }) 33 | 34 | #define local_id_not_zero(expr) \ 35 | ({ \ 36 | if (bpf_core_type_id_local(expr) == 0) { \ 37 | return __LINE__; \ 38 | } \ 39 | }) 40 | 41 | #define target_and_local_id_match(expr) \ 42 | ({ \ 43 | if (bpf_core_type_id_kernel(expr) != bpf_core_type_id_local(expr)) { \ 44 | return __LINE__; \ 45 | } \ 46 | }) 47 | 48 | __section("socket_filter/type_ids") int type_ids() { 49 | local_id_not_zero(int); 50 | local_id_not_zero(struct { int frob; }); 51 | local_id_not_zero(enum {FRAP}); 52 | local_id_not_zero(union { char bar; }); 53 | 54 | local_id_not_zero(struct s); 55 | local_id_not_zero(s_t); 56 | local_id_not_zero(const s_t); 57 | local_id_not_zero(volatile s_t); 58 | local_id_not_zero(enum e); 59 | local_id_not_zero(e_t); 60 | local_id_not_zero(const e_t); 61 | local_id_not_zero(volatile e_t); 62 | local_id_not_zero(union u); 63 | local_id_not_zero(u_t); 64 | local_id_not_zero(const u_t); 65 | local_id_not_zero(volatile u_t); 66 | 67 | // Qualifiers on types crash clang. 68 | target_and_local_id_match(struct s); 69 | target_and_local_id_match(s_t); 70 | // target_and_local_id_match(const s_t); 71 | // target_and_local_id_match(volatile s_t); 72 | target_and_local_id_match(enum e); 73 | target_and_local_id_match(e_t); 74 | // target_and_local_id_match(const e_t); 75 | // target_and_local_id_match(volatile e_t); 76 | target_and_local_id_match(union u); 77 | target_and_local_id_match(u_t); 78 | // target_and_local_id_match(const u_t); 79 | // target_and_local_id_match(volatile u_t); 80 | 81 | return 0; 82 | } 83 | 84 | #define type_exists(expr) \ 85 | ({ \ 86 | if (!bpf_core_type_exists(expr)) { \ 87 | return __LINE__; \ 88 | } \ 89 | }) 90 | 91 | #define type_size_matches(expr) \ 92 | ({ \ 93 | if (bpf_core_type_size(expr) != sizeof(expr)) { \ 94 | return __LINE__; \ 95 | } \ 96 | }) 97 | 98 | __section("socket_filter/types") int types() { 99 | type_exists(struct s); 100 | type_exists(s_t); 101 | type_exists(const s_t); 102 | type_exists(volatile s_t); 103 | type_exists(enum e); 104 | type_exists(e_t); 105 | type_exists(const e_t); 106 | type_exists(volatile e_t); 107 | type_exists(union u); 108 | type_exists(u_t); 109 | type_exists(const u_t); 110 | type_exists(volatile u_t); 111 | // TODO: Check non-existence. 112 | 113 | type_size_matches(struct s); 114 | type_size_matches(s_t); 115 | type_size_matches(const s_t); 116 | type_size_matches(volatile s_t); 117 | type_size_matches(enum e); 118 | type_size_matches(e_t); 119 | type_size_matches(const e_t); 120 | type_size_matches(volatile e_t); 121 | type_size_matches(union u); 122 | type_size_matches(u_t); 123 | type_size_matches(const u_t); 124 | type_size_matches(volatile u_t); 125 | 126 | return 0; 127 | } 128 | 129 | #define enum_value_exists(t, v) \ 130 | ({ \ 131 | if (!bpf_core_enum_value_exists(t, v)) { \ 132 | return __LINE__; \ 133 | } \ 134 | }) 135 | 136 | #define enum_value_matches(t, v) \ 137 | ({ \ 138 | if (v != bpf_core_enum_value(t, v)) { \ 139 | return __LINE__; \ 140 | } \ 141 | }) 142 | 143 | __section("socket_filter/enums") int enums() { 144 | enum_value_exists(enum e, ONE); 145 | enum_value_exists(volatile enum e, ONE); 146 | enum_value_exists(const enum e, ONE); 147 | enum_value_exists(e_t, TWO); 148 | // TODO: Check non-existence. 149 | 150 | enum_value_matches(enum e, TWO); 151 | enum_value_matches(e_t, ONE); 152 | enum_value_matches(volatile e_t, ONE); 153 | enum_value_matches(const e_t, ONE); 154 | 155 | return 0; 156 | } 157 | 158 | #define field_exists(f) \ 159 | ({ \ 160 | if (!bpf_core_field_exists(f)) { \ 161 | return __LINE__; \ 162 | } \ 163 | }) 164 | 165 | #define field_size_matches(f) \ 166 | ({ \ 167 | if (sizeof(f) != bpf_core_field_size(f)) { \ 168 | return __LINE__; \ 169 | } \ 170 | }) 171 | 172 | #define field_offset_matches(t, f) \ 173 | ({ \ 174 | if (__builtin_offsetof(t, f) != __builtin_preserve_field_info(((typeof(t) *)0)->f, BPF_FIELD_BYTE_OFFSET)) { \ 175 | return __LINE__; \ 176 | } \ 177 | }) 178 | 179 | __section("socket_filter/fields") int fields() { 180 | field_exists((struct s){}._1); 181 | field_exists((s_t){}._2); 182 | field_exists((union u){}._1); 183 | field_exists((u_t){}._2); 184 | 185 | field_size_matches((struct s){}._1); 186 | field_size_matches((s_t){}._2); 187 | field_size_matches((union u){}._1); 188 | field_size_matches((u_t){}._2); 189 | 190 | field_offset_matches(struct s, _1); 191 | field_offset_matches(s_t, _2); 192 | field_offset_matches(union u, _1); 193 | field_offset_matches(u_t, _2); 194 | 195 | struct t { 196 | union { 197 | s_t s[10]; 198 | }; 199 | struct { 200 | union u u; 201 | }; 202 | } bar, *barp = &bar; 203 | 204 | field_exists(bar.s[2]._1); 205 | field_exists(bar.s[1]._2); 206 | field_exists(bar.u._1); 207 | field_exists(bar.u._2); 208 | field_exists(barp[1].u._2); 209 | 210 | field_size_matches(bar.s[2]._1); 211 | field_size_matches(bar.s[1]._2); 212 | field_size_matches(bar.u._1); 213 | field_size_matches(bar.u._2); 214 | field_size_matches(barp[1].u._2); 215 | 216 | field_offset_matches(struct t, s[2]._1); 217 | field_offset_matches(struct t, s[1]._2); 218 | field_offset_matches(struct t, u._1); 219 | field_offset_matches(struct t, u._2); 220 | 221 | return 0; 222 | } 223 | 224 | struct ambiguous { 225 | int _1; 226 | char _2; 227 | }; 228 | 229 | struct ambiguous___flavour { 230 | char _1; 231 | int _2; 232 | }; 233 | 234 | __section("socket_filter/err_ambiguous") int err_ambiguous() { 235 | return bpf_core_type_id_kernel(struct ambiguous); 236 | } 237 | 238 | __section("socket_filter/err_ambiguous_flavour") int err_ambiguous_flavour() { 239 | return bpf_core_type_id_kernel(struct ambiguous___flavour); 240 | } 241 | -------------------------------------------------------------------------------- /ebpf/etrace/process.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2020 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _PROCESS_H_ 9 | #define _PROCESS_H_ 10 | 11 | #define CGROUP_MAX_LENGTH 72 12 | #define TASK_COMM_LEN 16 13 | 14 | struct cgroup_context { 15 | u32 subsystem_id; 16 | u32 state_id; 17 | char name[CGROUP_MAX_LENGTH]; 18 | }; 19 | 20 | struct credentials_context { 21 | kuid_t uid; /* real UID of the task */ 22 | kgid_t gid; /* real GID of the task */ 23 | kuid_t suid; /* saved UID of the task */ 24 | kgid_t sgid; /* saved GID of the task */ 25 | kuid_t euid; /* effective UID of the task */ 26 | kgid_t egid; /* effective GID of the task */ 27 | kuid_t fsuid; /* UID for VFS ops */ 28 | kgid_t fsgid; /* GID for VFS ops */ 29 | unsigned securebits; /* SUID-less security management */ 30 | u32 padding; 31 | kernel_cap_t cap_inheritable; /* caps our children can inherit */ 32 | kernel_cap_t cap_permitted; /* caps we're permitted */ 33 | kernel_cap_t cap_effective; /* caps we can actually use */ 34 | kernel_cap_t cap_bset; /* capability bounding set */ 35 | kernel_cap_t cap_ambient; /* Ambient capability set */ 36 | }; 37 | 38 | struct namespace_context { 39 | u32 cgroup_namespace; 40 | u32 ipc_namespace; 41 | u32 net_namespace; 42 | u32 mnt_namespace; 43 | u32 pid_namespace; 44 | u32 time_namespace; 45 | u32 user_namespace; 46 | u32 uts_namespace; 47 | }; 48 | 49 | struct process_context { 50 | struct namespace_context namespaces; 51 | struct credentials_context credentials; 52 | char comm[TASK_COMM_LEN]; 53 | struct cgroup_context cgroups[CGROUP_SUBSYS_COUNT]; 54 | }; 55 | 56 | __attribute__((always_inline)) int fill_process_context(struct process_context *ctx) { 57 | // fetch current task 58 | struct task_struct* task = (struct task_struct*)bpf_get_current_task(); 59 | 60 | // fetch process comm 61 | bpf_get_current_comm(ctx->comm, sizeof(ctx->comm)); 62 | 63 | // fetch cgroup data 64 | char *container_id; 65 | #pragma unroll 66 | for (u32 i = 0; i < CGROUP_SUBSYS_COUNT; i++) { 67 | ctx->cgroups[i].subsystem_id = i; 68 | BPF_CORE_READ_INTO(&ctx->cgroups[i].state_id, task, cgroups, subsys[i], id); 69 | BPF_CORE_READ_INTO(&container_id, task, cgroups, subsys[i], cgroup, kn, name); 70 | bpf_probe_read_str(ctx->cgroups[i].name, sizeof(ctx->cgroups[i].name), container_id); 71 | } 72 | 73 | // fetch process credentials 74 | BPF_CORE_READ_INTO(&ctx->credentials.uid, task, cred, uid); 75 | BPF_CORE_READ_INTO(&ctx->credentials.gid, task, cred, gid); 76 | BPF_CORE_READ_INTO(&ctx->credentials.suid, task, cred, suid); 77 | BPF_CORE_READ_INTO(&ctx->credentials.sgid, task, cred, sgid); 78 | BPF_CORE_READ_INTO(&ctx->credentials.euid, task, cred, euid); 79 | BPF_CORE_READ_INTO(&ctx->credentials.egid, task, cred, egid); 80 | BPF_CORE_READ_INTO(&ctx->credentials.fsuid, task, cred, fsuid); 81 | BPF_CORE_READ_INTO(&ctx->credentials.fsgid, task, cred, fsgid); 82 | BPF_CORE_READ_INTO(&ctx->credentials.securebits, task, cred, securebits); 83 | BPF_CORE_READ_INTO(&ctx->credentials.cap_inheritable, task, cred, cap_inheritable); 84 | BPF_CORE_READ_INTO(&ctx->credentials.cap_permitted, task, cred, cap_permitted); 85 | BPF_CORE_READ_INTO(&ctx->credentials.cap_effective, task, cred, cap_effective); 86 | BPF_CORE_READ_INTO(&ctx->credentials.cap_bset, task, cred, cap_bset); 87 | BPF_CORE_READ_INTO(&ctx->credentials.cap_ambient, task, cred, cap_ambient); 88 | 89 | // fetch process namespaces 90 | BPF_CORE_READ_INTO(&ctx->namespaces.cgroup_namespace, task, nsproxy, cgroup_ns, ns.inum); 91 | BPF_CORE_READ_INTO(&ctx->namespaces.ipc_namespace, task, nsproxy, ipc_ns, ns.inum); 92 | BPF_CORE_READ_INTO(&ctx->namespaces.net_namespace, task, nsproxy, net_ns, ns.inum); 93 | BPF_CORE_READ_INTO(&ctx->namespaces.mnt_namespace, task, nsproxy, mnt_ns, ns.inum); 94 | BPF_CORE_READ_INTO(&ctx->namespaces.pid_namespace, task, nsproxy, pid_ns_for_children, ns.inum); 95 | BPF_CORE_READ_INTO(&ctx->namespaces.time_namespace, task, nsproxy, time_ns, ns.inum); 96 | BPF_CORE_READ_INTO(&ctx->namespaces.user_namespace, task, cred, user_ns, ns.inum); 97 | BPF_CORE_READ_INTO(&ctx->namespaces.uts_namespace, task, nsproxy, uts_ns, ns.inum); 98 | return 0; 99 | } 100 | 101 | struct { 102 | __uint(type, BPF_MAP_TYPE_HASH); 103 | __type(key, u32); 104 | __type(value, u32); 105 | __uint(max_entries, 8192); 106 | } traced_pids SEC(".maps"); 107 | 108 | struct sched_process_fork_args 109 | { 110 | unsigned short common_type; 111 | unsigned char common_flags; 112 | unsigned char common_preempt_count; 113 | int common_pid; 114 | 115 | char parent_comm[16]; 116 | pid_t parent_pid; 117 | char child_comm[16]; 118 | pid_t child_pid; 119 | }; 120 | 121 | /* 122 | * tracepoint__sched__sched_process_fork is used to track child processes and inherit tracing state 123 | */ 124 | SEC("tracepoint/sched/sched_process_fork") 125 | int tracepoint__sched__sched_process_fork(struct sched_process_fork_args *ctx) 126 | { 127 | u32 key = bpf_get_current_pid_tgid(); 128 | u32 child_pid = (u32) ctx->child_pid; 129 | 130 | // check if the parent process is traced 131 | u32 *is_traced = bpf_map_lookup_elem(&traced_pids, &key); 132 | if (!is_traced) { 133 | key = bpf_get_current_pid_tgid() >> 32; 134 | is_traced = bpf_map_lookup_elem(&traced_pids, &key); 135 | if (!is_traced) { 136 | // the parent isn't traced 137 | return 0; 138 | } 139 | } 140 | 141 | // inherit traced state 142 | bpf_map_update_elem(&traced_pids, &child_pid, &child_pid, BPF_ANY); 143 | return 0; 144 | } 145 | 146 | SEC("kprobe/do_exit") 147 | int kprobe_do_exit(struct pt_regs *ctx) { 148 | u32 tid = bpf_get_current_pid_tgid(); 149 | // delete traced pids entry 150 | bpf_map_delete_elem(&traced_pids, &tid); 151 | return 0; 152 | } 153 | 154 | #endif -------------------------------------------------------------------------------- /pkg/etrace/manager.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import ( 20 | "bytes" 21 | "math" 22 | "os" 23 | "time" 24 | 25 | manager "github.com/DataDog/ebpf-manager" 26 | "github.com/cilium/ebpf" 27 | "github.com/pkg/errors" 28 | "golang.org/x/sys/unix" 29 | 30 | "github.com/Gui774ume/etrace/ebpf/assets" 31 | "github.com/Gui774ume/etrace/pkg/ringbuf" 32 | ) 33 | 34 | func (e *ETrace) prepareManager() { 35 | e.manager = &manager.Manager{ 36 | Probes: []*manager.Probe{ 37 | { 38 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 39 | EBPFSection: "tracepoint/raw_syscalls/sys_enter", 40 | EBPFFuncName: "sys_enter", 41 | }, 42 | }, 43 | { 44 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 45 | EBPFSection: "tracepoint/raw_syscalls/sys_exit", 46 | EBPFFuncName: "sys_exit", 47 | }, 48 | }, 49 | { 50 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 51 | EBPFSection: "tracepoint/sched/sched_process_fork", 52 | EBPFFuncName: "tracepoint__sched__sched_process_fork", 53 | }, 54 | }, 55 | { 56 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 57 | EBPFSection: "kprobe/do_exit", 58 | EBPFFuncName: "kprobe_do_exit", 59 | }, 60 | }, 61 | { 62 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 63 | EBPFSection: "kprobe/security_bprm_check", 64 | EBPFFuncName: "kprobe_security_bprm_check", 65 | }, 66 | }, 67 | }, 68 | } 69 | e.managerOptions = manager.Options{ 70 | // DefaultKProbeMaxActive is the maximum number of active kretprobe at a given time 71 | DefaultKProbeMaxActive: 512, 72 | 73 | VerifierOptions: ebpf.CollectionOptions{ 74 | Programs: ebpf.ProgramOptions{ 75 | // LogSize is the size of the log buffer given to the verifier. Give it a big enough (2 * 1024 * 1024) 76 | // value so that all our programs fit. If the verifier ever outputs a `no space left on device` error, 77 | // we'll need to increase this value. 78 | LogSize: 2097152, 79 | }, 80 | }, 81 | 82 | // Extend RLIMIT_MEMLOCK (8) size 83 | // On some systems, the default for RLIMIT_MEMLOCK may be as low as 64 bytes. 84 | // This will result in an EPERM (Operation not permitted) error, when trying to create an eBPF map 85 | // using bpf(2) with BPF_MAP_CREATE. 86 | // 87 | // We are setting the limit to infinity until we have a better handle on the true requirements. 88 | RLimit: &unix.Rlimit{ 89 | Cur: math.MaxUint64, 90 | Max: math.MaxUint64, 91 | }, 92 | 93 | ConstantEditors: []manager.ConstantEditor{ 94 | { 95 | Name: "etrace_tgid", 96 | Value: uint64(os.Getpid()), 97 | }, 98 | }, 99 | } 100 | 101 | if len(e.options.SyscallFilters) > 0 { 102 | e.managerOptions.ConstantEditors = append(e.managerOptions.ConstantEditors, manager.ConstantEditor{ 103 | Name: "syscall_filter", 104 | Value: uint64(1), 105 | }) 106 | } 107 | if len(e.options.CommFilters) > 0 { 108 | e.managerOptions.ConstantEditors = append(e.managerOptions.ConstantEditors, manager.ConstantEditor{ 109 | Name: "comm_filter", 110 | Value: uint64(1), 111 | }) 112 | } 113 | if e.options.Follow { 114 | e.managerOptions.ConstantEditors = append(e.managerOptions.ConstantEditors, manager.ConstantEditor{ 115 | Name: "follow_children", 116 | Value: uint64(1), 117 | }) 118 | } 119 | } 120 | 121 | func (e *ETrace) selectMaps() error { 122 | var err error 123 | e.syscallDefinitionsMap, _, err = e.manager.GetMap("syscall_definitions") 124 | if err != nil || e.syscallDefinitionsMap == nil { 125 | return errors.Errorf("couldn't find \"syscall_definitions\" map") 126 | } 127 | ring, _, err := e.manager.GetMap("events") 128 | if err != nil || ring == nil { 129 | return errors.Errorf("couldn't find \"events\" map") 130 | } 131 | e.reader, err = ringbuf.NewReader(ring) 132 | if err != nil { 133 | return errors.Errorf("couldn't instantiate a new ring buffer reader: %v", err) 134 | } 135 | e.eventsSyncMap, _, err = e.manager.GetMap("events_sync") 136 | if err != nil { 137 | return errors.Errorf("couldn't find \"events_sync\" map") 138 | } 139 | e.eventsStatsMap, _, err = e.manager.GetMap("events_stats") 140 | if err != nil { 141 | return errors.Errorf("couldn't find \"events_stats\" map") 142 | } 143 | e.syscallFilterMap, _, err = e.manager.GetMap("syscall_filters") 144 | if err != nil { 145 | return errors.Errorf("couldn't find \"syscall_filters\" map") 146 | } 147 | e.commFilterMap, _, err = e.manager.GetMap("comm_filters") 148 | if err != nil { 149 | return errors.Errorf("couldn't find \"comm_filters\" map") 150 | } 151 | return nil 152 | } 153 | 154 | func (e *ETrace) startManager() error { 155 | // setup a default manager 156 | e.prepareManager() 157 | 158 | // initialize the manager 159 | if err := e.manager.InitWithOptions(bytes.NewReader(assets.Probe), e.managerOptions); err != nil { 160 | return errors.Wrap(err, "couldn't init manager") 161 | } 162 | 163 | // select kernel space maps 164 | if err := e.selectMaps(); err != nil { 165 | return err 166 | } 167 | 168 | // start the manager 169 | if err := e.manager.Start(); err != nil { 170 | return errors.Wrap(err, "couldn't start manager") 171 | } 172 | 173 | e.startTime = time.Now() 174 | 175 | go func(e *ETrace) { 176 | if e == nil { 177 | return 178 | } 179 | e.wg.Add(1) 180 | defer e.wg.Done() 181 | 182 | var sample ringbuf.Record 183 | var err error 184 | 185 | for { 186 | sample, err = e.reader.Read() 187 | if err != nil { 188 | select { 189 | case <-e.ctx.Done(): 190 | return 191 | default: 192 | } 193 | continue 194 | } 195 | e.handleEvent(sample.RawSample) 196 | } 197 | }(e) 198 | return nil 199 | } 200 | -------------------------------------------------------------------------------- /internal/syscall.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "runtime" 7 | "syscall" 8 | "unsafe" 9 | 10 | "github.com/Gui774ume/etrace/internal/unix" 11 | ) 12 | 13 | //go:generate stringer -output syscall_string.go -type=BPFCmd 14 | 15 | // BPFCmd identifies a subcommand of the bpf syscall. 16 | type BPFCmd int 17 | 18 | // Well known BPF commands. 19 | const ( 20 | BPF_MAP_CREATE BPFCmd = iota 21 | BPF_MAP_LOOKUP_ELEM 22 | BPF_MAP_UPDATE_ELEM 23 | BPF_MAP_DELETE_ELEM 24 | BPF_MAP_GET_NEXT_KEY 25 | BPF_PROG_LOAD 26 | BPF_OBJ_PIN 27 | BPF_OBJ_GET 28 | BPF_PROG_ATTACH 29 | BPF_PROG_DETACH 30 | BPF_PROG_TEST_RUN 31 | BPF_PROG_GET_NEXT_ID 32 | BPF_MAP_GET_NEXT_ID 33 | BPF_PROG_GET_FD_BY_ID 34 | BPF_MAP_GET_FD_BY_ID 35 | BPF_OBJ_GET_INFO_BY_FD 36 | BPF_PROG_QUERY 37 | BPF_RAW_TRACEPOINT_OPEN 38 | BPF_BTF_LOAD 39 | BPF_BTF_GET_FD_BY_ID 40 | BPF_TASK_FD_QUERY 41 | BPF_MAP_LOOKUP_AND_DELETE_ELEM 42 | BPF_MAP_FREEZE 43 | BPF_BTF_GET_NEXT_ID 44 | BPF_MAP_LOOKUP_BATCH 45 | BPF_MAP_LOOKUP_AND_DELETE_BATCH 46 | BPF_MAP_UPDATE_BATCH 47 | BPF_MAP_DELETE_BATCH 48 | BPF_LINK_CREATE 49 | BPF_LINK_UPDATE 50 | BPF_LINK_GET_FD_BY_ID 51 | BPF_LINK_GET_NEXT_ID 52 | BPF_ENABLE_STATS 53 | BPF_ITER_CREATE 54 | ) 55 | 56 | // BPF wraps SYS_BPF. 57 | // 58 | // Any pointers contained in attr must use the Pointer type from this package. 59 | func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) { 60 | r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size) 61 | runtime.KeepAlive(attr) 62 | 63 | var err error 64 | if errNo != 0 { 65 | err = wrappedErrno{errNo} 66 | } 67 | 68 | return r1, err 69 | } 70 | 71 | type BPFProgAttachAttr struct { 72 | TargetFd uint32 73 | AttachBpfFd uint32 74 | AttachType uint32 75 | AttachFlags uint32 76 | ReplaceBpfFd uint32 77 | } 78 | 79 | func BPFProgAttach(attr *BPFProgAttachAttr) error { 80 | _, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 81 | return err 82 | } 83 | 84 | type BPFProgDetachAttr struct { 85 | TargetFd uint32 86 | AttachBpfFd uint32 87 | AttachType uint32 88 | } 89 | 90 | func BPFProgDetach(attr *BPFProgDetachAttr) error { 91 | _, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 92 | return err 93 | } 94 | 95 | type BPFEnableStatsAttr struct { 96 | StatsType uint32 97 | } 98 | 99 | func BPFEnableStats(attr *BPFEnableStatsAttr) (*FD, error) { 100 | ptr, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 101 | if err != nil { 102 | return nil, fmt.Errorf("enable stats: %w", err) 103 | } 104 | return NewFD(uint32(ptr)), nil 105 | 106 | } 107 | 108 | type bpfObjAttr struct { 109 | fileName Pointer 110 | fd uint32 111 | fileFlags uint32 112 | } 113 | 114 | const bpfFSType = 0xcafe4a11 115 | 116 | // BPFObjPin wraps BPF_OBJ_PIN. 117 | func BPFObjPin(fileName string, fd *FD) error { 118 | dirName := filepath.Dir(fileName) 119 | var statfs unix.Statfs_t 120 | if err := unix.Statfs(dirName, &statfs); err != nil { 121 | return err 122 | } 123 | if uint64(statfs.Type) != bpfFSType { 124 | return fmt.Errorf("%s is not on a bpf filesystem", fileName) 125 | } 126 | 127 | value, err := fd.Value() 128 | if err != nil { 129 | return err 130 | } 131 | 132 | attr := bpfObjAttr{ 133 | fileName: NewStringPointer(fileName), 134 | fd: value, 135 | } 136 | _, err = BPF(BPF_OBJ_PIN, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 137 | if err != nil { 138 | return fmt.Errorf("pin object %s: %w", fileName, err) 139 | } 140 | return nil 141 | } 142 | 143 | // BPFObjGet wraps BPF_OBJ_GET. 144 | func BPFObjGet(fileName string, flags uint32) (*FD, error) { 145 | attr := bpfObjAttr{ 146 | fileName: NewStringPointer(fileName), 147 | fileFlags: flags, 148 | } 149 | ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 150 | if err != nil { 151 | return nil, fmt.Errorf("get object %s: %w", fileName, err) 152 | } 153 | return NewFD(uint32(ptr)), nil 154 | } 155 | 156 | type bpfObjGetInfoByFDAttr struct { 157 | fd uint32 158 | infoLen uint32 159 | info Pointer 160 | } 161 | 162 | // BPFObjGetInfoByFD wraps BPF_OBJ_GET_INFO_BY_FD. 163 | // 164 | // Available from 4.13. 165 | func BPFObjGetInfoByFD(fd *FD, info unsafe.Pointer, size uintptr) error { 166 | value, err := fd.Value() 167 | if err != nil { 168 | return err 169 | } 170 | 171 | attr := bpfObjGetInfoByFDAttr{ 172 | fd: value, 173 | infoLen: uint32(size), 174 | info: NewPointer(info), 175 | } 176 | _, err = BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 177 | if err != nil { 178 | return fmt.Errorf("fd %v: %w", fd, err) 179 | } 180 | return nil 181 | } 182 | 183 | // BPFObjName is a null-terminated string made up of 184 | // 'A-Za-z0-9_' characters. 185 | type BPFObjName [unix.BPF_OBJ_NAME_LEN]byte 186 | 187 | // NewBPFObjName truncates the result if it is too long. 188 | func NewBPFObjName(name string) BPFObjName { 189 | var result BPFObjName 190 | copy(result[:unix.BPF_OBJ_NAME_LEN-1], name) 191 | return result 192 | } 193 | 194 | type BPFMapCreateAttr struct { 195 | MapType uint32 196 | KeySize uint32 197 | ValueSize uint32 198 | MaxEntries uint32 199 | Flags uint32 200 | InnerMapFd uint32 // since 4.12 56f668dfe00d 201 | NumaNode uint32 // since 4.14 96eabe7a40aa 202 | MapName BPFObjName // since 4.15 ad5b177bd73f 203 | MapIfIndex uint32 204 | BTFFd uint32 205 | BTFKeyTypeID uint32 206 | BTFValueTypeID uint32 207 | } 208 | 209 | func BPFMapCreate(attr *BPFMapCreateAttr) (*FD, error) { 210 | fd, err := BPF(BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 211 | if err != nil { 212 | return nil, err 213 | } 214 | 215 | return NewFD(uint32(fd)), nil 216 | } 217 | 218 | // wrappedErrno wraps syscall.Errno to prevent direct comparisons with 219 | // syscall.E* or unix.E* constants. 220 | // 221 | // You should never export an error of this type. 222 | type wrappedErrno struct { 223 | syscall.Errno 224 | } 225 | 226 | func (we wrappedErrno) Unwrap() error { 227 | return we.Errno 228 | } 229 | 230 | type syscallError struct { 231 | error 232 | errno syscall.Errno 233 | } 234 | 235 | func SyscallError(err error, errno syscall.Errno) error { 236 | return &syscallError{err, errno} 237 | } 238 | 239 | func (se *syscallError) Is(target error) bool { 240 | return target == se.error 241 | } 242 | 243 | func (se *syscallError) Unwrap() error { 244 | return se.errno 245 | } 246 | -------------------------------------------------------------------------------- /internal/unix/types_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package unix 4 | 5 | import ( 6 | "bytes" 7 | "syscall" 8 | 9 | linux "golang.org/x/sys/unix" 10 | ) 11 | 12 | const ( 13 | ENOENT = linux.ENOENT 14 | EEXIST = linux.EEXIST 15 | EAGAIN = linux.EAGAIN 16 | ENOSPC = linux.ENOSPC 17 | EINVAL = linux.EINVAL 18 | EPOLLIN = linux.EPOLLIN 19 | EINTR = linux.EINTR 20 | EPERM = linux.EPERM 21 | ESRCH = linux.ESRCH 22 | ENODEV = linux.ENODEV 23 | EBADF = linux.EBADF 24 | E2BIG = linux.E2BIG 25 | // ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP 26 | ENOTSUPP = syscall.Errno(0x20c) 27 | 28 | BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC 29 | BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE 30 | BPF_F_RDONLY = linux.BPF_F_RDONLY 31 | BPF_F_WRONLY = linux.BPF_F_WRONLY 32 | BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG 33 | BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG 34 | BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE 35 | BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE 36 | BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP 37 | BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN 38 | BPF_TAG_SIZE = linux.BPF_TAG_SIZE 39 | SYS_BPF = linux.SYS_BPF 40 | F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC 41 | EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD 42 | EPOLL_CLOEXEC = linux.EPOLL_CLOEXEC 43 | O_CLOEXEC = linux.O_CLOEXEC 44 | O_NONBLOCK = linux.O_NONBLOCK 45 | PROT_READ = linux.PROT_READ 46 | PROT_WRITE = linux.PROT_WRITE 47 | MAP_SHARED = linux.MAP_SHARED 48 | PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1 49 | PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE 50 | PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT 51 | PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT 52 | PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE 53 | PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE 54 | PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF 55 | PerfBitWatermark = linux.PerfBitWatermark 56 | PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW 57 | PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC 58 | RLIM_INFINITY = linux.RLIM_INFINITY 59 | RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK 60 | BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME 61 | PERF_RECORD_LOST = linux.PERF_RECORD_LOST 62 | PERF_RECORD_SAMPLE = linux.PERF_RECORD_SAMPLE 63 | AT_FDCWD = linux.AT_FDCWD 64 | RENAME_NOREPLACE = linux.RENAME_NOREPLACE 65 | ) 66 | 67 | // Statfs_t is a wrapper 68 | type Statfs_t = linux.Statfs_t 69 | 70 | // Rlimit is a wrapper 71 | type Rlimit = linux.Rlimit 72 | 73 | // Setrlimit is a wrapper 74 | func Setrlimit(resource int, rlim *Rlimit) (err error) { 75 | return linux.Setrlimit(resource, rlim) 76 | } 77 | 78 | // Syscall is a wrapper 79 | func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { 80 | return linux.Syscall(trap, a1, a2, a3) 81 | } 82 | 83 | // FcntlInt is a wrapper 84 | func FcntlInt(fd uintptr, cmd, arg int) (int, error) { 85 | return linux.FcntlInt(fd, cmd, arg) 86 | } 87 | 88 | // IoctlSetInt is a wrapper 89 | func IoctlSetInt(fd int, req uint, value int) error { 90 | return linux.IoctlSetInt(fd, req, value) 91 | } 92 | 93 | // Statfs is a wrapper 94 | func Statfs(path string, buf *Statfs_t) (err error) { 95 | return linux.Statfs(path, buf) 96 | } 97 | 98 | // Close is a wrapper 99 | func Close(fd int) (err error) { 100 | return linux.Close(fd) 101 | } 102 | 103 | // EpollEvent is a wrapper 104 | type EpollEvent = linux.EpollEvent 105 | 106 | // EpollWait is a wrapper 107 | func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { 108 | return linux.EpollWait(epfd, events, msec) 109 | } 110 | 111 | // EpollCtl is a wrapper 112 | func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) { 113 | return linux.EpollCtl(epfd, op, fd, event) 114 | } 115 | 116 | // Eventfd is a wrapper 117 | func Eventfd(initval uint, flags int) (fd int, err error) { 118 | return linux.Eventfd(initval, flags) 119 | } 120 | 121 | // Write is a wrapper 122 | func Write(fd int, p []byte) (n int, err error) { 123 | return linux.Write(fd, p) 124 | } 125 | 126 | // EpollCreate1 is a wrapper 127 | func EpollCreate1(flag int) (fd int, err error) { 128 | return linux.EpollCreate1(flag) 129 | } 130 | 131 | // PerfEventMmapPage is a wrapper 132 | type PerfEventMmapPage linux.PerfEventMmapPage 133 | 134 | // SetNonblock is a wrapper 135 | func SetNonblock(fd int, nonblocking bool) (err error) { 136 | return linux.SetNonblock(fd, nonblocking) 137 | } 138 | 139 | // Mmap is a wrapper 140 | func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { 141 | return linux.Mmap(fd, offset, length, prot, flags) 142 | } 143 | 144 | // Munmap is a wrapper 145 | func Munmap(b []byte) (err error) { 146 | return linux.Munmap(b) 147 | } 148 | 149 | // PerfEventAttr is a wrapper 150 | type PerfEventAttr = linux.PerfEventAttr 151 | 152 | // PerfEventOpen is a wrapper 153 | func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) { 154 | return linux.PerfEventOpen(attr, pid, cpu, groupFd, flags) 155 | } 156 | 157 | // Utsname is a wrapper 158 | type Utsname = linux.Utsname 159 | 160 | // Uname is a wrapper 161 | func Uname(buf *Utsname) (err error) { 162 | return linux.Uname(buf) 163 | } 164 | 165 | // Getpid is a wrapper 166 | func Getpid() int { 167 | return linux.Getpid() 168 | } 169 | 170 | // Gettid is a wrapper 171 | func Gettid() int { 172 | return linux.Gettid() 173 | } 174 | 175 | // Tgkill is a wrapper 176 | func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) { 177 | return linux.Tgkill(tgid, tid, sig) 178 | } 179 | 180 | // BytePtrFromString is a wrapper 181 | func BytePtrFromString(s string) (*byte, error) { 182 | return linux.BytePtrFromString(s) 183 | } 184 | 185 | // ByteSliceToString is a wrapper 186 | func ByteSliceToString(s []byte) string { 187 | return linux.ByteSliceToString(s) 188 | } 189 | 190 | // Renameat2 is a wrapper 191 | func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error { 192 | return linux.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags) 193 | } 194 | 195 | func KernelRelease() (string, error) { 196 | var uname Utsname 197 | err := Uname(&uname) 198 | if err != nil { 199 | return "", err 200 | } 201 | 202 | end := bytes.IndexByte(uname.Release[:], 0) 203 | release := string(uname.Release[:end]) 204 | return release, nil 205 | } 206 | -------------------------------------------------------------------------------- /internal/unix/types_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package unix 4 | 5 | import ( 6 | "fmt" 7 | "runtime" 8 | "syscall" 9 | ) 10 | 11 | var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) 12 | 13 | const ( 14 | ENOENT = syscall.ENOENT 15 | EEXIST = syscall.EEXIST 16 | EAGAIN = syscall.EAGAIN 17 | ENOSPC = syscall.ENOSPC 18 | EINVAL = syscall.EINVAL 19 | EINTR = syscall.EINTR 20 | EPERM = syscall.EPERM 21 | ESRCH = syscall.ESRCH 22 | ENODEV = syscall.ENODEV 23 | EBADF = syscall.Errno(0) 24 | E2BIG = syscall.Errno(0) 25 | // ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP 26 | ENOTSUPP = syscall.Errno(0x20c) 27 | 28 | BPF_F_NO_PREALLOC = 0 29 | BPF_F_NUMA_NODE = 0 30 | BPF_F_RDONLY = 0 31 | BPF_F_WRONLY = 0 32 | BPF_F_RDONLY_PROG = 0 33 | BPF_F_WRONLY_PROG = 0 34 | BPF_F_SLEEPABLE = 0 35 | BPF_F_MMAPABLE = 0 36 | BPF_F_INNER_MAP = 0 37 | BPF_OBJ_NAME_LEN = 0x10 38 | BPF_TAG_SIZE = 0x8 39 | SYS_BPF = 321 40 | F_DUPFD_CLOEXEC = 0x406 41 | EPOLLIN = 0x1 42 | EPOLL_CTL_ADD = 0x1 43 | EPOLL_CLOEXEC = 0x80000 44 | O_CLOEXEC = 0x80000 45 | O_NONBLOCK = 0x800 46 | PROT_READ = 0x1 47 | PROT_WRITE = 0x2 48 | MAP_SHARED = 0x1 49 | PERF_ATTR_SIZE_VER1 = 0 50 | PERF_TYPE_SOFTWARE = 0x1 51 | PERF_TYPE_TRACEPOINT = 0 52 | PERF_COUNT_SW_BPF_OUTPUT = 0xa 53 | PERF_EVENT_IOC_DISABLE = 0 54 | PERF_EVENT_IOC_ENABLE = 0 55 | PERF_EVENT_IOC_SET_BPF = 0 56 | PerfBitWatermark = 0x4000 57 | PERF_SAMPLE_RAW = 0x400 58 | PERF_FLAG_FD_CLOEXEC = 0x8 59 | RLIM_INFINITY = 0x7fffffffffffffff 60 | RLIMIT_MEMLOCK = 8 61 | BPF_STATS_RUN_TIME = 0 62 | PERF_RECORD_LOST = 2 63 | PERF_RECORD_SAMPLE = 9 64 | AT_FDCWD = -0x2 65 | RENAME_NOREPLACE = 0x1 66 | ) 67 | 68 | // Statfs_t is a wrapper 69 | type Statfs_t struct { 70 | Type int64 71 | Bsize int64 72 | Blocks uint64 73 | Bfree uint64 74 | Bavail uint64 75 | Files uint64 76 | Ffree uint64 77 | Fsid [2]int32 78 | Namelen int64 79 | Frsize int64 80 | Flags int64 81 | Spare [4]int64 82 | } 83 | 84 | // Rlimit is a wrapper 85 | type Rlimit struct { 86 | Cur uint64 87 | Max uint64 88 | } 89 | 90 | // Setrlimit is a wrapper 91 | func Setrlimit(resource int, rlim *Rlimit) (err error) { 92 | return errNonLinux 93 | } 94 | 95 | // Syscall is a wrapper 96 | func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { 97 | return 0, 0, syscall.Errno(1) 98 | } 99 | 100 | // FcntlInt is a wrapper 101 | func FcntlInt(fd uintptr, cmd, arg int) (int, error) { 102 | return -1, errNonLinux 103 | } 104 | 105 | // IoctlSetInt is a wrapper 106 | func IoctlSetInt(fd int, req uint, value int) error { 107 | return errNonLinux 108 | } 109 | 110 | // Statfs is a wrapper 111 | func Statfs(path string, buf *Statfs_t) error { 112 | return errNonLinux 113 | } 114 | 115 | // Close is a wrapper 116 | func Close(fd int) (err error) { 117 | return errNonLinux 118 | } 119 | 120 | // EpollEvent is a wrapper 121 | type EpollEvent struct { 122 | Events uint32 123 | Fd int32 124 | Pad int32 125 | } 126 | 127 | // EpollWait is a wrapper 128 | func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { 129 | return 0, errNonLinux 130 | } 131 | 132 | // EpollCtl is a wrapper 133 | func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) { 134 | return errNonLinux 135 | } 136 | 137 | // Eventfd is a wrapper 138 | func Eventfd(initval uint, flags int) (fd int, err error) { 139 | return 0, errNonLinux 140 | } 141 | 142 | // Write is a wrapper 143 | func Write(fd int, p []byte) (n int, err error) { 144 | return 0, errNonLinux 145 | } 146 | 147 | // EpollCreate1 is a wrapper 148 | func EpollCreate1(flag int) (fd int, err error) { 149 | return 0, errNonLinux 150 | } 151 | 152 | // PerfEventMmapPage is a wrapper 153 | type PerfEventMmapPage struct { 154 | Version uint32 155 | Compat_version uint32 156 | Lock uint32 157 | Index uint32 158 | Offset int64 159 | Time_enabled uint64 160 | Time_running uint64 161 | Capabilities uint64 162 | Pmc_width uint16 163 | Time_shift uint16 164 | Time_mult uint32 165 | Time_offset uint64 166 | Time_zero uint64 167 | Size uint32 168 | 169 | Data_head uint64 170 | Data_tail uint64 171 | Data_offset uint64 172 | Data_size uint64 173 | Aux_head uint64 174 | Aux_tail uint64 175 | Aux_offset uint64 176 | Aux_size uint64 177 | } 178 | 179 | // SetNonblock is a wrapper 180 | func SetNonblock(fd int, nonblocking bool) (err error) { 181 | return errNonLinux 182 | } 183 | 184 | // Mmap is a wrapper 185 | func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { 186 | return []byte{}, errNonLinux 187 | } 188 | 189 | // Munmap is a wrapper 190 | func Munmap(b []byte) (err error) { 191 | return errNonLinux 192 | } 193 | 194 | // PerfEventAttr is a wrapper 195 | type PerfEventAttr struct { 196 | Type uint32 197 | Size uint32 198 | Config uint64 199 | Sample uint64 200 | Sample_type uint64 201 | Read_format uint64 202 | Bits uint64 203 | Wakeup uint32 204 | Bp_type uint32 205 | Ext1 uint64 206 | Ext2 uint64 207 | Branch_sample_type uint64 208 | Sample_regs_user uint64 209 | Sample_stack_user uint32 210 | Clockid int32 211 | Sample_regs_intr uint64 212 | Aux_watermark uint32 213 | Sample_max_stack uint16 214 | } 215 | 216 | // PerfEventOpen is a wrapper 217 | func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) { 218 | return 0, errNonLinux 219 | } 220 | 221 | // Utsname is a wrapper 222 | type Utsname struct { 223 | Release [65]byte 224 | Version [65]byte 225 | } 226 | 227 | // Uname is a wrapper 228 | func Uname(buf *Utsname) (err error) { 229 | return errNonLinux 230 | } 231 | 232 | // Getpid is a wrapper 233 | func Getpid() int { 234 | return -1 235 | } 236 | 237 | // Gettid is a wrapper 238 | func Gettid() int { 239 | return -1 240 | } 241 | 242 | // Tgkill is a wrapper 243 | func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) { 244 | return errNonLinux 245 | } 246 | 247 | // BytePtrFromString is a wrapper 248 | func BytePtrFromString(s string) (*byte, error) { 249 | return nil, errNonLinux 250 | } 251 | 252 | // ByteSliceToString is a wrapper 253 | func ByteSliceToString(s []byte) string { 254 | return "" 255 | } 256 | 257 | // Renameat2 is a wrapper 258 | func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error { 259 | return errNonLinux 260 | } 261 | 262 | func KernelRelease() (string, error) { 263 | return "", errNonLinux 264 | } 265 | -------------------------------------------------------------------------------- /internal/btf/btf_types.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | //go:generate stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage 10 | 11 | // btfKind describes a Type. 12 | type btfKind uint8 13 | 14 | // Equivalents of the BTF_KIND_* constants. 15 | const ( 16 | kindUnknown btfKind = iota 17 | kindInt 18 | kindPointer 19 | kindArray 20 | kindStruct 21 | kindUnion 22 | kindEnum 23 | kindForward 24 | kindTypedef 25 | kindVolatile 26 | kindConst 27 | kindRestrict 28 | // Added ~4.20 29 | kindFunc 30 | kindFuncProto 31 | // Added ~5.1 32 | kindVar 33 | kindDatasec 34 | // Added ~5.13 35 | kindFloat 36 | ) 37 | 38 | // FuncLinkage describes BTF function linkage metadata. 39 | type FuncLinkage int 40 | 41 | // Equivalent of enum btf_func_linkage. 42 | const ( 43 | StaticFunc FuncLinkage = iota // static 44 | GlobalFunc // global 45 | ExternFunc // extern 46 | ) 47 | 48 | // VarLinkage describes BTF variable linkage metadata. 49 | type VarLinkage int 50 | 51 | const ( 52 | StaticVar VarLinkage = iota // static 53 | GlobalVar // global 54 | ExternVar // extern 55 | ) 56 | 57 | const ( 58 | btfTypeKindShift = 24 59 | btfTypeKindLen = 5 60 | btfTypeVlenShift = 0 61 | btfTypeVlenMask = 16 62 | btfTypeKindFlagShift = 31 63 | btfTypeKindFlagMask = 1 64 | ) 65 | 66 | // btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst. 67 | type btfType struct { 68 | NameOff uint32 69 | /* "info" bits arrangement 70 | * bits 0-15: vlen (e.g. # of struct's members), linkage 71 | * bits 16-23: unused 72 | * bits 24-28: kind (e.g. int, ptr, array...etc) 73 | * bits 29-30: unused 74 | * bit 31: kind_flag, currently used by 75 | * struct, union and fwd 76 | */ 77 | Info uint32 78 | /* "size" is used by INT, ENUM, STRUCT and UNION. 79 | * "size" tells the size of the type it is describing. 80 | * 81 | * "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT, 82 | * FUNC and FUNC_PROTO. 83 | * "type" is a type_id referring to another type. 84 | */ 85 | SizeType uint32 86 | } 87 | 88 | func (k btfKind) String() string { 89 | switch k { 90 | case kindUnknown: 91 | return "Unknown" 92 | case kindInt: 93 | return "Integer" 94 | case kindPointer: 95 | return "Pointer" 96 | case kindArray: 97 | return "Array" 98 | case kindStruct: 99 | return "Struct" 100 | case kindUnion: 101 | return "Union" 102 | case kindEnum: 103 | return "Enumeration" 104 | case kindForward: 105 | return "Forward" 106 | case kindTypedef: 107 | return "Typedef" 108 | case kindVolatile: 109 | return "Volatile" 110 | case kindConst: 111 | return "Const" 112 | case kindRestrict: 113 | return "Restrict" 114 | case kindFunc: 115 | return "Function" 116 | case kindFuncProto: 117 | return "Function Proto" 118 | case kindVar: 119 | return "Variable" 120 | case kindDatasec: 121 | return "Section" 122 | case kindFloat: 123 | return "Float" 124 | default: 125 | return fmt.Sprintf("Unknown (%d)", k) 126 | } 127 | } 128 | 129 | func mask(len uint32) uint32 { 130 | return (1 << len) - 1 131 | } 132 | 133 | func (bt *btfType) info(len, shift uint32) uint32 { 134 | return (bt.Info >> shift) & mask(len) 135 | } 136 | 137 | func (bt *btfType) setInfo(value, len, shift uint32) { 138 | bt.Info &^= mask(len) << shift 139 | bt.Info |= (value & mask(len)) << shift 140 | } 141 | 142 | func (bt *btfType) Kind() btfKind { 143 | return btfKind(bt.info(btfTypeKindLen, btfTypeKindShift)) 144 | } 145 | 146 | func (bt *btfType) SetKind(kind btfKind) { 147 | bt.setInfo(uint32(kind), btfTypeKindLen, btfTypeKindShift) 148 | } 149 | 150 | func (bt *btfType) Vlen() int { 151 | return int(bt.info(btfTypeVlenMask, btfTypeVlenShift)) 152 | } 153 | 154 | func (bt *btfType) SetVlen(vlen int) { 155 | bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift) 156 | } 157 | 158 | func (bt *btfType) KindFlag() bool { 159 | return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1 160 | } 161 | 162 | func (bt *btfType) Linkage() FuncLinkage { 163 | return FuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift)) 164 | } 165 | 166 | func (bt *btfType) SetLinkage(linkage FuncLinkage) { 167 | bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift) 168 | } 169 | 170 | func (bt *btfType) Type() TypeID { 171 | // TODO: Panic here if wrong kind? 172 | return TypeID(bt.SizeType) 173 | } 174 | 175 | func (bt *btfType) Size() uint32 { 176 | // TODO: Panic here if wrong kind? 177 | return bt.SizeType 178 | } 179 | 180 | type rawType struct { 181 | btfType 182 | data interface{} 183 | } 184 | 185 | func (rt *rawType) Marshal(w io.Writer, bo binary.ByteOrder) error { 186 | if err := binary.Write(w, bo, &rt.btfType); err != nil { 187 | return err 188 | } 189 | 190 | if rt.data == nil { 191 | return nil 192 | } 193 | 194 | return binary.Write(w, bo, rt.data) 195 | } 196 | 197 | type btfArray struct { 198 | Type TypeID 199 | IndexType TypeID 200 | Nelems uint32 201 | } 202 | 203 | type btfMember struct { 204 | NameOff uint32 205 | Type TypeID 206 | Offset uint32 207 | } 208 | 209 | type btfVarSecinfo struct { 210 | Type TypeID 211 | Offset uint32 212 | Size uint32 213 | } 214 | 215 | type btfVariable struct { 216 | Linkage uint32 217 | } 218 | 219 | type btfEnum struct { 220 | NameOff uint32 221 | Val int32 222 | } 223 | 224 | type btfParam struct { 225 | NameOff uint32 226 | Type TypeID 227 | } 228 | 229 | func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) { 230 | var ( 231 | header btfType 232 | types []rawType 233 | ) 234 | 235 | for id := TypeID(1); ; id++ { 236 | if err := binary.Read(r, bo, &header); err == io.EOF { 237 | return types, nil 238 | } else if err != nil { 239 | return nil, fmt.Errorf("can't read type info for id %v: %v", id, err) 240 | } 241 | 242 | var data interface{} 243 | switch header.Kind() { 244 | case kindInt: 245 | data = new(uint32) 246 | case kindPointer: 247 | case kindArray: 248 | data = new(btfArray) 249 | case kindStruct: 250 | fallthrough 251 | case kindUnion: 252 | data = make([]btfMember, header.Vlen()) 253 | case kindEnum: 254 | data = make([]btfEnum, header.Vlen()) 255 | case kindForward: 256 | case kindTypedef: 257 | case kindVolatile: 258 | case kindConst: 259 | case kindRestrict: 260 | case kindFunc: 261 | case kindFuncProto: 262 | data = make([]btfParam, header.Vlen()) 263 | case kindVar: 264 | data = new(btfVariable) 265 | case kindDatasec: 266 | data = make([]btfVarSecinfo, header.Vlen()) 267 | case kindFloat: 268 | default: 269 | return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind()) 270 | } 271 | 272 | if data == nil { 273 | types = append(types, rawType{header, nil}) 274 | continue 275 | } 276 | 277 | if err := binary.Read(r, bo, data); err != nil { 278 | return nil, fmt.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err) 279 | } 280 | 281 | types = append(types, rawType{header, data}) 282 | } 283 | } 284 | 285 | func intEncoding(raw uint32) (IntEncoding, uint32, byte) { 286 | return IntEncoding((raw & 0x0f000000) >> 24), (raw & 0x00ff0000) >> 16, byte(raw & 0x000000ff) 287 | } 288 | -------------------------------------------------------------------------------- /pkg/ringbuf/reader.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ringbuf 18 | 19 | import ( 20 | "encoding/binary" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "runtime" 25 | "sync" 26 | 27 | "github.com/cilium/ebpf" 28 | "golang.org/x/sys/unix" 29 | 30 | "github.com/Gui774ume/etrace/internal" 31 | ) 32 | 33 | var ( 34 | ErrClosed = errors.New("ringbuf reader was closed") 35 | errDiscard = errors.New("sample discarded") 36 | errBusy = errors.New("sample not committed yet") 37 | ) 38 | 39 | func addToEpoll(epollfd, fd int) error { 40 | 41 | event := unix.EpollEvent{ 42 | Events: unix.EPOLLIN, 43 | Fd: int32(fd), 44 | } 45 | 46 | if err := unix.EpollCtl(epollfd, unix.EPOLL_CTL_ADD, fd, &event); err != nil { 47 | return fmt.Errorf("can't add fd to epoll: %w", err) 48 | } 49 | return nil 50 | } 51 | 52 | // ringbufHeader from 'struct bpf_ringbuf_hdr' in kernel/bpf/ringbuf.c 53 | type ringbufHeader struct { 54 | Len uint32 55 | PgOff uint32 56 | } 57 | 58 | func (rh *ringbufHeader) isBusy() bool { 59 | return rh.Len&unix.BPF_RINGBUF_BUSY_BIT != 0 60 | } 61 | 62 | func (rh *ringbufHeader) isDiscard() bool { 63 | return rh.Len&unix.BPF_RINGBUF_DISCARD_BIT != 0 64 | } 65 | 66 | func (rh *ringbufHeader) dataLen() int { 67 | return int(rh.Len & ^uint32(unix.BPF_RINGBUF_BUSY_BIT|unix.BPF_RINGBUF_DISCARD_BIT)) 68 | } 69 | 70 | // Align returns 'n' updated to 'alignment' boundary. 71 | func Align(n, alignment int) int { 72 | return (int(n) + alignment - 1) / alignment * alignment 73 | } 74 | 75 | type Record struct { 76 | RawSample []byte 77 | } 78 | 79 | func readRecord(rd *ringbufEventRing) (r Record, err error) { 80 | rd.loadConsumer() 81 | var header ringbufHeader 82 | err = binary.Read(rd, internal.NativeEndian, &header) 83 | if err == io.EOF { 84 | return Record{}, err 85 | } 86 | 87 | if err != nil { 88 | return Record{}, fmt.Errorf("can't read event header: %w", err) 89 | } 90 | 91 | if header.isBusy() { 92 | // the next sample in the ring is not committed yet so we 93 | // exit without storing the reader/consumer position 94 | // and start again from the same position. 95 | return Record{}, fmt.Errorf("%w", errBusy) 96 | } 97 | 98 | /* read up to 8 byte alignment */ 99 | dataLenAligned := uint64(Align(header.dataLen(), 8)) 100 | 101 | if header.isDiscard() { 102 | // when the record header indicates that the data should be 103 | // discarded, we skip it by just updating the consumer position 104 | // to the next record instead of normal Read() to avoid allocating data 105 | // and reading/copying from the ring (which normally keeps track of the 106 | // consumer position). 107 | rd.skipRead(dataLenAligned) 108 | rd.storeConsumer() 109 | 110 | return Record{}, fmt.Errorf("%w", errDiscard) 111 | } 112 | 113 | data := make([]byte, dataLenAligned) 114 | 115 | if _, err := io.ReadFull(rd, data); err != nil { 116 | return Record{}, fmt.Errorf("can't read sample: %w", err) 117 | } 118 | 119 | rd.storeConsumer() 120 | 121 | return Record{RawSample: data[:header.dataLen()]}, nil 122 | } 123 | 124 | // Reader allows reading bpf_ringbuf_output 125 | // from user space. 126 | type Reader struct { 127 | // mu protects read/write access to the Reader structure 128 | mu sync.Mutex 129 | 130 | ring *ringbufEventRing 131 | 132 | epollFd int 133 | epollEvents []unix.EpollEvent 134 | 135 | closeFd int 136 | // Ensure we only close once 137 | closeOnce sync.Once 138 | } 139 | 140 | // NewReader creates a new BPF ringbuf reader. 141 | func NewReader(ringbufMap *ebpf.Map) (r *Reader, err error) { 142 | if ringbufMap.Type() != ebpf.RingBuf { 143 | return nil, fmt.Errorf("invalid Map type: %s", ringbufMap.Type()) 144 | } 145 | 146 | epollFd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) 147 | if err != nil { 148 | return nil, fmt.Errorf("can't create epoll fd: %w", err) 149 | } 150 | 151 | var ( 152 | fds = []int{epollFd} 153 | maxEntries = int(ringbufMap.MaxEntries()) 154 | ring *ringbufEventRing 155 | ) 156 | 157 | if maxEntries == 0 || (maxEntries&(maxEntries-1)) != 0 { 158 | return nil, fmt.Errorf("Ringbuffer map size %d is zero or not a power of two", maxEntries) 159 | } 160 | defer func() { 161 | if err != nil { 162 | // close epollFd and closeFd 163 | for _, fd := range fds { 164 | unix.Close(fd) 165 | } 166 | if ring != nil { 167 | ring.Close() 168 | } 169 | } 170 | }() 171 | 172 | ring, err = newRingBufEventRing(ringbufMap.FD(), maxEntries) 173 | if err != nil { 174 | return nil, fmt.Errorf("failed to create ringbuf ring: %w", err) 175 | } 176 | 177 | if err := addToEpoll(epollFd, ringbufMap.FD()); err != nil { 178 | return nil, err 179 | } 180 | 181 | closeFd, err := unix.Eventfd(0, unix.O_CLOEXEC|unix.O_NONBLOCK) 182 | if err != nil { 183 | return nil, err 184 | } 185 | fds = append(fds, closeFd) 186 | 187 | if err := addToEpoll(epollFd, closeFd); err != nil { 188 | return nil, err 189 | } 190 | 191 | r = &Reader{ 192 | ring: ring, 193 | epollFd: epollFd, 194 | // Allocate extra event for closeFd 195 | epollEvents: make([]unix.EpollEvent, 2), 196 | closeFd: closeFd, 197 | } 198 | runtime.SetFinalizer(r, (*Reader).Close) 199 | return r, nil 200 | } 201 | 202 | // Close frees resources used by the reader. 203 | // 204 | // It interrupts calls to Read. 205 | func (r *Reader) Close() error { 206 | var err error 207 | r.closeOnce.Do(func() { 208 | runtime.SetFinalizer(r, nil) 209 | 210 | // Interrupt Read() via the closeFd event fd. 211 | var value [8]byte 212 | internal.NativeEndian.PutUint64(value[:], 1) 213 | 214 | if _, err = unix.Write(r.closeFd, value[:]); err != nil { 215 | err = fmt.Errorf("can't write event fd: %w", err) 216 | return 217 | } 218 | 219 | // Acquire the lock. This ensures that Read isn't running. 220 | r.mu.Lock() 221 | defer r.mu.Unlock() 222 | 223 | unix.Close(r.epollFd) 224 | unix.Close(r.closeFd) 225 | r.epollFd, r.closeFd = -1, -1 226 | 227 | if r.ring != nil { 228 | r.ring.Close() 229 | } 230 | r.ring = nil 231 | 232 | }) 233 | if err != nil { 234 | return fmt.Errorf("close Reader: %w", err) 235 | } 236 | return nil 237 | } 238 | 239 | // Read the next record from the BPF ringbuf. 240 | // 241 | // Calling Close interrupts the function. 242 | func (r *Reader) Read() (Record, error) { 243 | r.mu.Lock() 244 | defer r.mu.Unlock() 245 | 246 | if r.epollFd == -1 { 247 | return Record{}, fmt.Errorf("%w", ErrClosed) 248 | } 249 | 250 | for { 251 | nEvents, err := unix.EpollWait(r.epollFd, r.epollEvents, -1) 252 | if temp, ok := err.(temporaryError); ok && temp.Temporary() { 253 | // Retry the syscall if we were interrupted, see https://github.com/golang/go/issues/20400 254 | continue 255 | } 256 | 257 | if err != nil { 258 | return Record{}, err 259 | } 260 | 261 | for _, event := range r.epollEvents[:nEvents] { 262 | if int(event.Fd) == r.closeFd { 263 | return Record{}, fmt.Errorf("%w", ErrClosed) 264 | } 265 | } 266 | 267 | record, err := readRecord(r.ring) 268 | 269 | if errors.Is(err, errBusy) || errors.Is(err, errDiscard) { 270 | continue 271 | } 272 | 273 | return record, err 274 | } 275 | } 276 | 277 | type temporaryError interface { 278 | Temporary() bool 279 | } 280 | -------------------------------------------------------------------------------- /internal/btf/ext_info.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | 12 | "github.com/Gui774ume/etrace/internal" 13 | "github.com/cilium/ebpf/asm" 14 | ) 15 | 16 | type btfExtHeader struct { 17 | Magic uint16 18 | Version uint8 19 | Flags uint8 20 | HdrLen uint32 21 | 22 | FuncInfoOff uint32 23 | FuncInfoLen uint32 24 | LineInfoOff uint32 25 | LineInfoLen uint32 26 | } 27 | 28 | type btfExtCoreHeader struct { 29 | CoreReloOff uint32 30 | CoreReloLen uint32 31 | } 32 | 33 | func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, relos map[string]coreRelos, err error) { 34 | var header btfExtHeader 35 | var coreHeader btfExtCoreHeader 36 | if err := binary.Read(r, bo, &header); err != nil { 37 | return nil, nil, nil, fmt.Errorf("can't read header: %v", err) 38 | } 39 | 40 | if header.Magic != btfMagic { 41 | return nil, nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic) 42 | } 43 | 44 | if header.Version != 1 { 45 | return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version) 46 | } 47 | 48 | if header.Flags != 0 { 49 | return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags) 50 | } 51 | 52 | remainder := int64(header.HdrLen) - int64(binary.Size(&header)) 53 | if remainder < 0 { 54 | return nil, nil, nil, errors.New("header is too short") 55 | } 56 | 57 | coreHdrSize := int64(binary.Size(&coreHeader)) 58 | if remainder >= coreHdrSize { 59 | if err := binary.Read(r, bo, &coreHeader); err != nil { 60 | return nil, nil, nil, fmt.Errorf("can't read CO-RE relocation header: %v", err) 61 | } 62 | remainder -= coreHdrSize 63 | } 64 | 65 | // Of course, the .BTF.ext header has different semantics than the 66 | // .BTF ext header. We need to ignore non-null values. 67 | _, err = io.CopyN(ioutil.Discard, r, remainder) 68 | if err != nil { 69 | return nil, nil, nil, fmt.Errorf("header padding: %v", err) 70 | } 71 | 72 | if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil { 73 | return nil, nil, nil, fmt.Errorf("can't seek to function info section: %v", err) 74 | } 75 | 76 | buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen))) 77 | funcInfo, err = parseExtInfo(buf, bo, strings) 78 | if err != nil { 79 | return nil, nil, nil, fmt.Errorf("function info: %w", err) 80 | } 81 | 82 | if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil { 83 | return nil, nil, nil, fmt.Errorf("can't seek to line info section: %v", err) 84 | } 85 | 86 | buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen))) 87 | lineInfo, err = parseExtInfo(buf, bo, strings) 88 | if err != nil { 89 | return nil, nil, nil, fmt.Errorf("line info: %w", err) 90 | } 91 | 92 | if coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 { 93 | if _, err := r.Seek(int64(header.HdrLen+coreHeader.CoreReloOff), io.SeekStart); err != nil { 94 | return nil, nil, nil, fmt.Errorf("can't seek to CO-RE relocation section: %v", err) 95 | } 96 | 97 | relos, err = parseExtInfoRelos(io.LimitReader(r, int64(coreHeader.CoreReloLen)), bo, strings) 98 | if err != nil { 99 | return nil, nil, nil, fmt.Errorf("CO-RE relocation info: %w", err) 100 | } 101 | } 102 | 103 | return funcInfo, lineInfo, relos, nil 104 | } 105 | 106 | type btfExtInfoSec struct { 107 | SecNameOff uint32 108 | NumInfo uint32 109 | } 110 | 111 | type extInfoRecord struct { 112 | InsnOff uint64 113 | Opaque []byte 114 | } 115 | 116 | type extInfo struct { 117 | recordSize uint32 118 | records []extInfoRecord 119 | } 120 | 121 | func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) { 122 | if other.recordSize != ei.recordSize { 123 | return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize) 124 | } 125 | 126 | records := make([]extInfoRecord, 0, len(ei.records)+len(other.records)) 127 | records = append(records, ei.records...) 128 | for _, info := range other.records { 129 | records = append(records, extInfoRecord{ 130 | InsnOff: info.InsnOff + offset, 131 | Opaque: info.Opaque, 132 | }) 133 | } 134 | return extInfo{ei.recordSize, records}, nil 135 | } 136 | 137 | func (ei extInfo) MarshalBinary() ([]byte, error) { 138 | if len(ei.records) == 0 { 139 | return nil, nil 140 | } 141 | 142 | buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records))) 143 | for _, info := range ei.records { 144 | // The kernel expects offsets in number of raw bpf instructions, 145 | // while the ELF tracks it in bytes. 146 | insnOff := uint32(info.InsnOff / asm.InstructionSize) 147 | if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil { 148 | return nil, fmt.Errorf("can't write instruction offset: %v", err) 149 | } 150 | 151 | buf.Write(info.Opaque) 152 | } 153 | 154 | return buf.Bytes(), nil 155 | } 156 | 157 | func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) { 158 | const maxRecordSize = 256 159 | 160 | var recordSize uint32 161 | if err := binary.Read(r, bo, &recordSize); err != nil { 162 | return nil, fmt.Errorf("can't read record size: %v", err) 163 | } 164 | 165 | if recordSize < 4 { 166 | // Need at least insnOff 167 | return nil, errors.New("record size too short") 168 | } 169 | if recordSize > maxRecordSize { 170 | return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize) 171 | } 172 | 173 | result := make(map[string]extInfo) 174 | for { 175 | secName, infoHeader, err := parseExtInfoHeader(r, bo, strings) 176 | if errors.Is(err, io.EOF) { 177 | return result, nil 178 | } 179 | 180 | var records []extInfoRecord 181 | for i := uint32(0); i < infoHeader.NumInfo; i++ { 182 | var byteOff uint32 183 | if err := binary.Read(r, bo, &byteOff); err != nil { 184 | return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err) 185 | } 186 | 187 | buf := make([]byte, int(recordSize-4)) 188 | if _, err := io.ReadFull(r, buf); err != nil { 189 | return nil, fmt.Errorf("section %v: can't read record: %v", secName, err) 190 | } 191 | 192 | if byteOff%asm.InstructionSize != 0 { 193 | return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff) 194 | } 195 | 196 | records = append(records, extInfoRecord{uint64(byteOff), buf}) 197 | } 198 | 199 | result[secName] = extInfo{ 200 | recordSize, 201 | records, 202 | } 203 | } 204 | } 205 | 206 | // bpfCoreRelo matches `struct bpf_core_relo` from the kernel 207 | type bpfCoreRelo struct { 208 | InsnOff uint32 209 | TypeID TypeID 210 | AccessStrOff uint32 211 | Kind COREKind 212 | } 213 | 214 | type coreRelo struct { 215 | insnOff uint32 216 | typeID TypeID 217 | accessor coreAccessor 218 | kind COREKind 219 | } 220 | 221 | type coreRelos []coreRelo 222 | 223 | // append two slices of extInfoRelo to each other. The InsnOff of b are adjusted 224 | // by offset. 225 | func (r coreRelos) append(other coreRelos, offset uint64) coreRelos { 226 | result := make([]coreRelo, 0, len(r)+len(other)) 227 | result = append(result, r...) 228 | for _, relo := range other { 229 | relo.insnOff += uint32(offset) 230 | result = append(result, relo) 231 | } 232 | return result 233 | } 234 | 235 | var extInfoReloSize = binary.Size(bpfCoreRelo{}) 236 | 237 | func parseExtInfoRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]coreRelos, error) { 238 | var recordSize uint32 239 | if err := binary.Read(r, bo, &recordSize); err != nil { 240 | return nil, fmt.Errorf("read record size: %v", err) 241 | } 242 | 243 | if recordSize != uint32(extInfoReloSize) { 244 | return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize) 245 | } 246 | 247 | result := make(map[string]coreRelos) 248 | for { 249 | secName, infoHeader, err := parseExtInfoHeader(r, bo, strings) 250 | if errors.Is(err, io.EOF) { 251 | return result, nil 252 | } 253 | 254 | var relos coreRelos 255 | for i := uint32(0); i < infoHeader.NumInfo; i++ { 256 | var relo bpfCoreRelo 257 | if err := binary.Read(r, bo, &relo); err != nil { 258 | return nil, fmt.Errorf("section %v: read record: %v", secName, err) 259 | } 260 | 261 | if relo.InsnOff%asm.InstructionSize != 0 { 262 | return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, relo.InsnOff) 263 | } 264 | 265 | accessorStr, err := strings.Lookup(relo.AccessStrOff) 266 | if err != nil { 267 | return nil, err 268 | } 269 | 270 | accessor, err := parseCoreAccessor(accessorStr) 271 | if err != nil { 272 | return nil, fmt.Errorf("accessor %q: %s", accessorStr, err) 273 | } 274 | 275 | relos = append(relos, coreRelo{ 276 | relo.InsnOff, 277 | relo.TypeID, 278 | accessor, 279 | relo.Kind, 280 | }) 281 | } 282 | 283 | result[secName] = relos 284 | } 285 | } 286 | 287 | func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) { 288 | var infoHeader btfExtInfoSec 289 | if err := binary.Read(r, bo, &infoHeader); err != nil { 290 | return "", nil, fmt.Errorf("read ext info header: %w", err) 291 | } 292 | 293 | secName, err := strings.Lookup(infoHeader.SecNameOff) 294 | if err != nil { 295 | return "", nil, fmt.Errorf("get section name: %w", err) 296 | } 297 | 298 | if infoHeader.NumInfo == 0 { 299 | return "", nil, fmt.Errorf("section %s has zero records", secName) 300 | } 301 | 302 | return secName, &infoHeader, nil 303 | } 304 | -------------------------------------------------------------------------------- /pkg/etrace/etrace.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "io/ioutil" 24 | "os" 25 | "sync" 26 | "time" 27 | 28 | manager "github.com/DataDog/ebpf-manager" 29 | "github.com/cilium/ebpf" 30 | "github.com/pkg/errors" 31 | "github.com/sirupsen/logrus" 32 | 33 | "github.com/Gui774ume/etrace/internal/btf" 34 | "github.com/Gui774ume/etrace/pkg/ringbuf" 35 | ) 36 | 37 | var eventZero = NewSyscallEvent() 38 | 39 | // ETrace is the main ETrace structure 40 | type ETrace struct { 41 | handleEvent func(data []byte) 42 | options Options 43 | inputFile *os.File 44 | dumpFile *os.File 45 | jsonEncoder *json.Encoder 46 | 47 | ctx context.Context 48 | cancelFunc context.CancelFunc 49 | wg *sync.WaitGroup 50 | 51 | manager *manager.Manager 52 | managerOptions manager.Options 53 | startTime time.Time 54 | 55 | kernelSpec *btf.Spec 56 | SyscallDefinitions map[Syscall]SyscallDefinition 57 | syscallDefinitionsMap *ebpf.Map 58 | eventsSyncMap *ebpf.Map 59 | eventsStatsMap *ebpf.Map 60 | syscallFilterMap *ebpf.Map 61 | commFilterMap *ebpf.Map 62 | 63 | reader *ringbuf.Reader 64 | event *SyscallEvent 65 | timeResolver *TimeResolver 66 | numCPU int 67 | 68 | // DumpFile is the output file 69 | DumpFile string 70 | } 71 | 72 | // NewETrace creates a new ETrace instance 73 | func NewETrace(options Options) (*ETrace, error) { 74 | var err error 75 | 76 | if err = options.IsValid(); err != nil { 77 | return nil, err 78 | } 79 | 80 | e := &ETrace{ 81 | wg: &sync.WaitGroup{}, 82 | options: options, 83 | handleEvent: options.EventHandler, 84 | SyscallDefinitions: make(map[Syscall]SyscallDefinition), 85 | event: NewSyscallEvent(), 86 | } 87 | if e.handleEvent == nil { 88 | e.handleEvent = e.defaultEventHandler 89 | } else { 90 | e.options.hasCustomEventHandler = true 91 | } 92 | 93 | var pattern string 94 | if options.RawDump { 95 | pattern = "etrace-*.raw" 96 | } else if options.JSONDump { 97 | pattern = "etrace-*.json" 98 | } 99 | if len(pattern) > 0 { 100 | e.dumpFile, err = ioutil.TempFile("/tmp", pattern) 101 | if err != nil { 102 | return nil, err 103 | } 104 | e.DumpFile = e.dumpFile.Name() 105 | if err = os.Chmod(e.DumpFile, 0777); err != nil { 106 | return nil, err 107 | } 108 | 109 | if options.JSONDump { 110 | e.jsonEncoder = json.NewEncoder(e.dumpFile) 111 | } 112 | } 113 | 114 | e.timeResolver, err = NewTimeResolver() 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | e.numCPU, err = NumCPU() 120 | if err != nil { 121 | return nil, err 122 | } 123 | e.ctx, e.cancelFunc = context.WithCancel(context.Background()) 124 | 125 | if err := e.prepareSyscallArgs(); err != nil { 126 | return nil, errors.Wrap(err, "couldn't prepare syscall arguments") 127 | } 128 | return e, nil 129 | } 130 | 131 | // Start hooks on the requested symbols and begins tracing 132 | func (e *ETrace) Start() error { 133 | if e.options.ShouldActivateProbes() { 134 | if err := e.startManager(); err != nil { 135 | return err 136 | } 137 | 138 | if err := e.pushSyscallDefinitions(); err != nil { 139 | return errors.Wrap(err, "couldn't push syscall definitions to the kernel") 140 | } 141 | 142 | if err := e.pushFilters(); err != nil { 143 | return errors.Wrap(err, "couldn't push filters to the kernel") 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | // ParseInputFile parses a raw input file into a JSON output file 150 | func (e *ETrace) ParseInputFile(inputFile string) (string, error) { 151 | var err error 152 | e.dumpFile, err = ioutil.TempFile("/tmp", "etrace-*.json") 153 | if err != nil { 154 | return "", err 155 | } 156 | e.DumpFile = e.dumpFile.Name() 157 | if err = os.Chmod(e.DumpFile, 0777); err != nil { 158 | return "", err 159 | } 160 | e.jsonEncoder = json.NewEncoder(e.dumpFile) 161 | 162 | // read input file 163 | e.inputFile, err = os.Open(inputFile) 164 | if err != nil { 165 | return "", fmt.Errorf("couldn't open input file %s: %w", inputFile, err) 166 | } 167 | 168 | var sizeB [2]byte 169 | var done bool 170 | var data []byte 171 | for !done { 172 | if _, err = e.inputFile.Read(sizeB[:]); err != nil { 173 | break 174 | } 175 | 176 | data = make([]byte, ByteOrder.Uint16(sizeB[:])) 177 | if _, err = e.inputFile.Read(data); err != nil { 178 | break 179 | } 180 | 181 | event = e.zeroEvent() 182 | if err = e.ParseData(data, event); err != nil { 183 | logrus.Debugf("failed to parse event: %s", err) 184 | continue 185 | } 186 | 187 | if err = e.jsonEncoder.Encode(event); err != nil { 188 | logrus.Debugf("failed to encode event: %s", err) 189 | continue 190 | } 191 | } 192 | 193 | return e.DumpFile, nil 194 | } 195 | 196 | // Stop shuts down ETrace 197 | func (e *ETrace) Stop() error { 198 | if e.options.ShouldActivateProbes() { 199 | if e.manager == nil { 200 | // nothing to stop, return 201 | return nil 202 | } 203 | 204 | // stop writting to the ring buffer 205 | syncKey := uint32(0) 206 | stop := uint32(1) 207 | _ = e.eventsSyncMap.Put(syncKey, stop) 208 | 209 | } 210 | 211 | e.cancelFunc() 212 | 213 | if e.options.ShouldActivateProbes() { 214 | if e.reader != nil { 215 | _ = e.reader.Close() 216 | } 217 | e.wg.Wait() 218 | if e.dumpFile != nil { 219 | _ = e.dumpFile.Close() 220 | } 221 | 222 | // dump events stats 223 | if e.options.Stats { 224 | if err := e.dumpEventsStats(); err != nil { 225 | return err 226 | } 227 | } 228 | 229 | if err := e.manager.Stop(manager.CleanAll); err != nil { 230 | return fmt.Errorf("couldn't stop manager: %w", err) 231 | } 232 | } 233 | return nil 234 | } 235 | 236 | type EventStats struct { 237 | Lost uint64 238 | Sent uint64 239 | } 240 | 241 | func (e *ETrace) dumpEventsStats() error { 242 | stats := make([]EventStats, e.numCPU) 243 | var syscall Syscall 244 | var iterSent uint64 245 | var iterLost uint64 246 | var totalSent uint64 247 | var totalLoast uint64 248 | 249 | // loop through all the values of the stats map 250 | logrus.Infoln() 251 | logrus.Infof("%24s\t\t|\t\tSent\t\t|\t\tLost", "Syscall Name") 252 | iterator := e.eventsStatsMap.Iterate() 253 | for iterator.Next(&syscall, &stats) { 254 | if syscall == SysLastSyscall { 255 | break 256 | } 257 | iterSent = 0 258 | iterLost = 0 259 | for _, counters := range stats { 260 | iterSent += counters.Sent 261 | iterLost += counters.Lost 262 | } 263 | 264 | if iterSent > 0 || iterLost > 0 { 265 | logrus.Infof("%24s\t\t|\t\t%d\t\t|\t\t%d", syscall, iterSent, iterLost) 266 | totalSent += iterSent 267 | totalLoast += iterLost 268 | } 269 | } 270 | if err := iterator.Err(); err != nil { 271 | logrus.Warnf("couldn't dump events stats map: %v", err) 272 | } 273 | logrus.Infoln() 274 | logrus.Infof("Total events: %d", totalSent) 275 | logrus.Infof("Total lost: %d", totalLoast) 276 | return nil 277 | } 278 | 279 | func (e *ETrace) pushFilters() error { 280 | var err error 281 | filter := uint32(1) 282 | 283 | if len(e.options.CommFilters) > 0 { 284 | for _, comm := range e.options.CommFilters { 285 | commB := make([]byte, 16) 286 | copy(commB[:], comm) 287 | err = e.commFilterMap.Put(commB, filter) 288 | if err != nil { 289 | return errors.Wrapf(err, "couldn't push comm filter for \"%s\"", comm) 290 | } 291 | } 292 | } 293 | 294 | if len(e.options.SyscallFilters) > 0 { 295 | for _, s := range e.options.SyscallFilters { 296 | err = e.syscallFilterMap.Put(s, filter) 297 | if err != nil { 298 | return errors.Wrapf(err, "couldn't push syscall filter for \"%s\"", s) 299 | } 300 | } 301 | } 302 | 303 | if e.options.Stats && !e.options.SendEventsToUserSpace() { 304 | syncKey := uint32(0) 305 | ignore := uint32(2) 306 | _ = e.eventsSyncMap.Put(syncKey, ignore) 307 | } 308 | 309 | return nil 310 | } 311 | 312 | func (e *ETrace) zeroEvent() *SyscallEvent { 313 | *e.event = *eventZero 314 | return e.event 315 | } 316 | 317 | // sizeB is used to store and write the size of the bytes buffer received from the ring buffer 318 | var sizeB [2]byte 319 | var jsonData []byte 320 | var newLine = []byte("\n") 321 | var event *SyscallEvent 322 | 323 | func (e *ETrace) defaultEventHandler(data []byte) { 324 | var err error 325 | jsonData = nil 326 | 327 | if e.options.RawDump { 328 | ByteOrder.PutUint16(sizeB[:], uint16(len(data))) 329 | _, err = e.dumpFile.Write(sizeB[:]) 330 | if err != nil { 331 | logrus.Errorf("failed to write buffer size: %s", err) 332 | return 333 | } 334 | _, err = e.dumpFile.Write(data) 335 | if err != nil { 336 | logrus.Errorf("failed to write buffer: %s", err) 337 | return 338 | } 339 | return 340 | } 341 | 342 | if e.options.JSONDump || e.options.Stdout { 343 | event = e.zeroEvent() 344 | 345 | if err = e.ParseData(data, event); err != nil { 346 | logrus.Errorln(err) 347 | } 348 | 349 | if e.options.JSONDump { 350 | if err = e.jsonEncoder.Encode(event); err != nil { 351 | logrus.Errorln(err) 352 | } 353 | } else if e.options.Stdout { 354 | fmt.Printf("%s\n", event.String(e.options.BytesShown)) 355 | } 356 | } 357 | } 358 | 359 | // ParseData parses a SyscallEvent from a raw bytes array 360 | func (e *ETrace) ParseData(data []byte, event *SyscallEvent) error { 361 | // parse syscall type 362 | read, err := event.Syscall.UnmarshalSyscall(data) 363 | if err != nil { 364 | return fmt.Errorf("failed to decode syscall type: %w", err) 365 | } 366 | 367 | // resolve arguments definition 368 | syscallDefinition, ok := e.SyscallDefinitions[event.Syscall] 369 | if ok { 370 | for i := range syscallDefinition.Arguments { 371 | event.Args[i] = SyscallArgumentValue{ 372 | Argument: &syscallDefinition.Arguments[i], 373 | } 374 | } 375 | } else { 376 | return fmt.Errorf("couldn't find the syscall definition of %s", event.Syscall) 377 | } 378 | 379 | // unmarshal 380 | err = event.UnmarshalBinary(data, read, e) 381 | if err != nil { 382 | return fmt.Errorf("failed to decode event: %w", err) 383 | } 384 | 385 | return nil 386 | } 387 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ETrace 2 | 3 | [![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 5 | 6 | ETrace is a syscall tracing utility powered by eBPF. 7 | 8 | This is (yet another) `strace` implementation with a twist: 9 | - ETrace figures out dynamically the parameters of each syscall by parsing the tracepoint format files in the `/sys/kernel/debug/tracing/events/syscalls/*` directories 10 | - ETrace fetches dynamically the size of each structure of each syscall parameter by parsing the BTF information of the kernel. Once the data is retrieved from kernel space, it uses the same BTF information to parse and format the captured data. 11 | 12 | In addition to normal syscall parameters, ETrace also collects process context data on each syscall entry and exit. This context includes: 13 | - The process cgroups 14 | - The process namespaces 15 | - The process credentials 16 | - The process comm 17 | 18 | ### System requirements 19 | 20 | This project was developed on a Ubuntu Hirsute machine (Linux Kernel 5.11). 21 | 22 | - golang 1.16+ 23 | - (optional) Kernel headers are expected to be installed in `lib/modules/$(uname -r)`, update the `Makefile` with their location otherwise. 24 | - (optional) clang & llvm 11.0.1+ 25 | - (optional) libbpf-dev 26 | 27 | Optional fields are required to recompile the eBPF programs. 28 | 29 | ### Build 30 | 31 | 1) Since ETrace was built using CORE, you shouldn't need to rebuild the eBPF programs. That said, if you want to rebuild the eBPF programs, you can use the following command: 32 | 33 | ```shell script 34 | # ~ make build-ebpf 35 | ``` 36 | 37 | 2) To build ETrace and the custom handler demo, run: 38 | 39 | ```shell script 40 | # ~ make build 41 | ``` 42 | 43 | 3) To install ETrace (copy to /usr/bin/etrace) run: 44 | ```shell script 45 | # ~ make install 46 | ``` 47 | 48 | ### Getting started 49 | 50 | ETrace needs to run as root. Run `sudo etrace -h` to get help. 51 | 52 | ```shell script 53 | # ~ etrace -h 54 | Usage: 55 | etrace [flags] 56 | 57 | Flags: 58 | --bytes int amount of bytes shown to the screen when --stdout is provided (default 8) 59 | -c, --comm stringArray list of process comms to filter, leave empty to capture everything 60 | -h, --help help for etrace 61 | --input string input file to parse data from 62 | --json parse and dump the data retrieved from kernel space in the JSON format. This option might lead to more lost events than the --raw option and more CPU usage 63 | -l, --log-level string log level, options: panic, fatal, error, warn, info, debug or trace (default "info") 64 | --raw dump the data retrieved from kernel space without parsing it, use this option instead of the --json option to reduce the amount of lost events, and reduce the CPU usage of ETrace. You can ask ETrace to parse a --raw dump using the --input option 65 | --stats show syscall statistics (default true) 66 | --stdout parse and dump the data retrieved from kernel space to the console. This option might lead to more lost events than the --raw option and more CPU usage. 67 | -s, --syscall stringArray list of syscalls to filter, leave empty to capture everything 68 | ``` 69 | 70 | ### Example 71 | 72 | #### Dump all the syscalls of the command `cat /etc/hosts` 73 | 74 | ```shell script 75 | # ~ sudo etrace --comm cat --stdout 76 | INFO[2021-09-25T21:54:11Z] Tracing started ... (Ctrl + C to stop) 77 | cat(10609) | SysBrk(unsigned long brk: 0) = 93940710199296 78 | cat(10609) | SysArchPrctl(int option: 12289, unsigned long arg2: 140727494090368) = -22 79 | cat(10609) | SysAccess(const char * filename: /etc/ld.so.preload, int mode: 4) = -2 80 | cat(10609) | SysOpenat(int dfd: 4294967196, const char * filename: /etc/ld.so.cache, int flags: 524288, umode_t mode: 0) = 3 81 | cat(10609) | SysNewfstatat(int dfd: 3, const char * filename: NULL, struct stat * statbuf: {uint st_dev: 2049, uint st_ino: 1988, uint st_nlink: 1, uint st_mode: 33188, uint st_uid: 0, uint st_gid: 0, uint __pad0: 0, uint st_rdev: 0, int st_size: 34082, int st_blksize: 4096, int st_blocks: 72, uint st_atime: 1632575756, uint st_atime_nsec: 340000086, uint st_mtime: 1632478351, uint st_mtime_nsec: 721952010, uint st_ctime: 1632478351, uint st_ctime_nsec: 721952010, array __unused: {int 0: 0, int 1: 0, int 2: 0}}, int flag: 4096) = 0 82 | cat(10609) | SysMmap(unsigned long addr: 0, unsigned long len: 34082, unsigned long prot: 1, unsigned long flags: 2, unsigned long fd: 3, unsigned long off: 0) = 140402711691264 83 | cat(10609) | SysClose(unsigned int fd: 3) = 0 84 | cat(10609) | SysOpenat(int dfd: 4294967196, const char * filename: /lib/x86_64-linux-gnu/libc.so.6, int flags: 524288, umode_t mode: 0) = 3 85 | cat(10609) | SysRead(unsigned int fd: 3, char * buf: 0x7f454c4602010103..., size_t count: 832) = 832 86 | cat(10609) | SysPread64(unsigned int fd: 3, char * buf: 0x0600000004000000..., size_t count: 784, loff_t pos: 64) = 784 87 | cat(10609) | SysPread64(unsigned int fd: 3, char * buf: 0x0400000020000000..., size_t count: 48, loff_t pos: 848) = 48 88 | cat(10609) | SysPread64(unsigned int fd: 3, char * buf: 0x0400000014000000..., size_t count: 68, loff_t pos: 896) = 68 89 | [...] 90 | cat(10609) | SysOpenat(int dfd: 4294967196, const char * filename: /etc/hosts, int flags: 0, umode_t mode: 0) = 3 91 | cat(10609) | SysFstat(unsigned int fd: 3, struct stat * statbuf: {uint st_dev: 2049, uint st_ino: 44, uint st_nlink: 1, uint st_mode: 33188, uint st_uid: 0, uint st_gid: 0, uint __pad0: 0, uint st_rdev: 0, int st_size: 262, int st_blksize: 4096, int st_blocks: 8, uint st_atime: 1632575760, uint st_atime_nsec: 904000225, uint st_mtime: 1627389290, uint st_mtime_nsec: 284000207, uint st_ctime: 1627389290, uint st_ctime_nsec: 284000207, array __unused: {int 0: 0, int 1: 0, int 2: 0}}) = 0 92 | cat(10609) | SysFadvise64(int fd: 3, loff_t offset: 0, size_t len: 0, int advice: 2) = 0 93 | cat(10609) | SysMmap(unsigned long addr: 0, unsigned long len: 139264, unsigned long prot: 3, unsigned long flags: 34, unsigned long fd: 4294967295, unsigned long off: 0) = 140402704576512 94 | cat(10609) | SysRead(unsigned int fd: 3, char * buf: 127.0.0.1 localhost 95 | 96 | # The following lines are desirable for IPv6 capable hosts 97 | ::1 ip6-localhost ip6-loopback 98 | fe00::0 ip6-localnet 99 | ff00::0 ip6-mcastprefix 100 | ff02::1 ip6-allnodes 101 | ff02::2 ip6-allrouters 102 | ff02::3 ip6-allhosts 103 | 127.0.1.1 ubuntu-hirsute ubuntu-hirsute 104 | 105 | , size_t count: 131072) = 262 106 | cat(10609) | SysWrite(unsigned int fd: 1, const char * buf: 127.0.0.1 localhost 107 | 108 | # The following lines are desirable for IPv6 capable hosts 109 | ::1 ip6-localhost ip6-loopback 110 | fe00::0 ip6-localnet 111 | ff00::0 ip6-mcastprefix 112 | ff02::1 ip6-allnodes 113 | ff02::2 ip6-allrouters 114 | ff02::3 ip6-allhosts 115 | 127.0.1.1 ubuntu-hirsute ubuntu-hirsute 116 | 117 | , size_t count: 262) = 262 118 | cat(10609) | SysRead(unsigned int fd: 3, char * buf: NULL, size_t count: 131072) = 0 119 | cat(10609) | SysMunmap(unsigned long addr: 140402704576512, size_t len: 139264) = 0 120 | cat(10609) | SysClose(unsigned int fd: 3) = 0 121 | cat(10609) | SysClose(unsigned int fd: 1) = 0 122 | cat(10609) | SysClose(unsigned int fd: 2) = 0 123 | ``` 124 | 125 | #### Dump all the syscalls of the postgres process to a JSON file 126 | 127 | ```shell script 128 | # ~ sudo etrace --comm postgres --json 129 | INFO[2021-09-25T21:54:11Z] Tracing started ... (Ctrl + C to stop) 130 | INFO[2023-02-25T19:59:32Z] output file: /tmp/etrace-225206860.json 131 | ^C 132 | INFO[2023-02-25T19:59:38Z] 133 | INFO[2023-02-25T19:59:38Z] Syscall Name | Sent | Lost 134 | INFO[2023-02-25T19:59:38Z] SysClose | 10 | 5 135 | INFO[2023-02-25T19:59:38Z] SysEpollCtl | 20 | 5 136 | INFO[2023-02-25T19:59:38Z] SysEpollCreate1 | 1 | 0 137 | INFO[2023-02-25T19:59:38Z] 138 | INFO[2023-02-25T19:59:38Z] Total events: 31 139 | INFO[2023-02-25T19:59:38Z] Total lost: 10 140 | ``` 141 | 142 | If you see too many lost events, you can switch to the `raw` output format and then ask `etrace` to deserialize the `raw` file to `json`. 143 | See the command below for an example. 144 | 145 | ```shell script 146 | # ~ sudo etrace --comm postgres --raw 147 | INFO[2021-09-25T21:54:11Z] Tracing started ... (Ctrl + C to stop) 148 | INFO[2023-02-25T19:59:32Z] output file: /tmp/etrace-950342369.raw 149 | ^C 150 | INFO[2023-02-25T19:59:38Z] 151 | INFO[2023-02-25T19:59:38Z] Syscall Name | Sent | Lost 152 | INFO[2023-02-25T19:59:38Z] SysClose | 11 | 0 153 | INFO[2023-02-25T19:59:38Z] SysEpollCtl | 22 | 0 154 | INFO[2023-02-25T19:59:38Z] SysEpollCreate1 | 11 | 0 155 | INFO[2023-02-25T19:59:38Z] 156 | INFO[2023-02-25T19:59:38Z] Total events: 44 157 | INFO[2023-02-25T19:59:38Z] Total lost: 0 158 | # ~ sudo etrace --input /tmp/etrace-950342369.raw --json 159 | INFO[2023-02-25T20:02:35Z] Parsing /tmp/etrace-950342369.raw ... 160 | INFO[2023-02-25T20:02:46Z] done ! Output file: /tmp/etrace-801484115.json 161 | ``` 162 | 163 | ### Integrate ETrace into your projects by leveraging the custom handler option 164 | 165 | The script below showcases how you can import ETrace into your projects and stream the kernel events directly to your own handler. 166 | 167 | ```go 168 | package main 169 | 170 | import ( 171 | "fmt" 172 | "os" 173 | "os/signal" 174 | 175 | "github.com/sirupsen/logrus" 176 | 177 | "github.com/Gui774ume/etrace/pkg/etrace" 178 | ) 179 | 180 | // et is the global etrace tracer 181 | var et *etrace.ETrace 182 | 183 | // eventZero is used to reset the event used for parsing in a memory efficient way 184 | var eventZero = etrace.NewSyscallEvent() 185 | 186 | // event is used to parse events 187 | var event = etrace.NewSyscallEvent() 188 | 189 | // zeroEvent provides an empty event 190 | func zeroEvent() { 191 | *event = *eventZero 192 | } 193 | 194 | func main() { 195 | // Set log level 196 | logrus.SetLevel(logrus.TraceLevel) 197 | 198 | // create a new ETrace instance 199 | var err error 200 | et, err = etrace.NewETrace(etrace.Options{ 201 | EventHandler: myCustomEventHandler, 202 | }) 203 | if err != nil { 204 | logrus.Errorf("couldn't instantiate etrace: %v\n", err) 205 | return 206 | } 207 | 208 | // start ETrace 209 | if err = et.Start(); err != nil { 210 | logrus.Errorf("couldn't start etrace: %v\n", err) 211 | return 212 | } 213 | logrus.Infoln("Tracing started ... (Ctrl + C to stop)\n") 214 | 215 | wait() 216 | 217 | if err = et.Stop(); err != nil { 218 | logrus.Errorf("couldn't stop etrace: %v\n", err) 219 | } 220 | } 221 | 222 | // wait stops the main goroutine until an interrupt or kill signal is sent 223 | func wait() { 224 | sig := make(chan os.Signal, 1) 225 | signal.Notify(sig, os.Interrupt, os.Kill) 226 | <-sig 227 | fmt.Println() 228 | } 229 | 230 | func myCustomEventHandler(data []byte) { 231 | // reset event 232 | zeroEvent() 233 | 234 | // parse syscall type 235 | read, err := event.Syscall.UnmarshalSyscall(data) 236 | if err != nil { 237 | logrus.Errorf("failed to decode syscall type: %v", err) 238 | return 239 | } 240 | 241 | // find arguments definition 242 | syscallDefinition, ok := et.SyscallDefinitions[event.Syscall] 243 | if ok { 244 | for i := range syscallDefinition.Arguments { 245 | event.Args[i] = etrace.SyscallArgumentValue{ 246 | Argument: &syscallDefinition.Arguments[i], 247 | } 248 | } 249 | } else { 250 | logrus.Errorf("couldn't find the syscall definition of %s", event.Syscall) 251 | return 252 | } 253 | 254 | // parse the binary data according to the syscall definition 255 | err = event.UnmarshalBinary(data, read, et) 256 | if err != nil { 257 | logrus.Errorf("failed to decode event: %v", err) 258 | return 259 | } 260 | 261 | // print the output to the screen 262 | fmt.Printf("%s\n", event.String(50)) 263 | } 264 | ``` 265 | 266 | ## License 267 | 268 | - The golang code is under Apache 2.0 License. 269 | - The eBPF programs are under the GPL v2 License. -------------------------------------------------------------------------------- /pkg/etrace/syscall_event.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package etrace 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "fmt" 23 | "reflect" 24 | "time" 25 | 26 | "github.com/Gui774ume/etrace/internal/btf" 27 | ) 28 | 29 | const ( 30 | CgroupNameLength = 72 31 | TaskCommLength = 16 32 | CgroupSubSysCount = 13 33 | ) 34 | 35 | type CgroupContext struct { 36 | SubsystemID uint32 `json:"subsystem_id"` 37 | StateID uint32 `json:"state_id"` 38 | Name string `json:"name"` 39 | } 40 | 41 | func (cc *CgroupContext) UnmarshalBinary(data []byte) (int, error) { 42 | if len(data) < 8+CgroupNameLength { 43 | return 0, fmt.Errorf("parsing CgroupContext, got len %d, needed %d: %w", len(data), 8+CgroupNameLength, ErrNotEnoughData) 44 | } 45 | cc.SubsystemID = ByteOrder.Uint32(data[0:4]) 46 | cc.StateID = ByteOrder.Uint32(data[4:8]) 47 | cc.Name = string(bytes.Trim(data[8:8+CgroupNameLength], "\x00")) 48 | return 8 + CgroupNameLength, nil 49 | } 50 | 51 | type CredentialsContext struct { 52 | UID uint32 `json:"uid"` 53 | GID uint32 `json:"gid"` 54 | SUID uint32 `json:"suid"` 55 | SGID uint32 `json:"sgid"` 56 | EUID uint32 `json:"euid"` 57 | EGID uint32 `json:"egid"` 58 | FSUID uint32 `json:"fsuid"` 59 | FSGID uint32 `json:"fsgid"` 60 | SecureBits uint32 `json:"secure_bits"` 61 | CapInheritable uint64 `json:"cap_inheritable"` 62 | CapPermitted uint64 `json:"cap_permitted"` 63 | CapEffective uint64 `json:"cap_effective"` 64 | CapBSET uint64 `json:"cap_bset"` 65 | CapAmbiant uint64 `json:"cap_ambiant"` 66 | } 67 | 68 | func (cc *CredentialsContext) UnmarshalBinary(data []byte) (int, error) { 69 | if len(data) < 80 { 70 | return 0, fmt.Errorf("parsing CredentialsContext, got len %d, needed 80: %w", len(data), ErrNotEnoughData) 71 | } 72 | cc.UID = ByteOrder.Uint32(data[:4]) 73 | cc.GID = ByteOrder.Uint32(data[4:8]) 74 | cc.SUID = ByteOrder.Uint32(data[8:12]) 75 | cc.SGID = ByteOrder.Uint32(data[12:16]) 76 | cc.EUID = ByteOrder.Uint32(data[16:20]) 77 | cc.EGID = ByteOrder.Uint32(data[20:24]) 78 | cc.FSUID = ByteOrder.Uint32(data[24:28]) 79 | cc.FSGID = ByteOrder.Uint32(data[28:32]) 80 | cc.SecureBits = ByteOrder.Uint32(data[32:36]) 81 | // padding 82 | cc.CapInheritable = ByteOrder.Uint64(data[40:48]) 83 | cc.CapPermitted = ByteOrder.Uint64(data[48:56]) 84 | cc.CapEffective = ByteOrder.Uint64(data[56:64]) 85 | cc.CapBSET = ByteOrder.Uint64(data[64:72]) 86 | cc.CapAmbiant = ByteOrder.Uint64(data[72:80]) 87 | return 80, nil 88 | } 89 | 90 | type NamespaceContext struct { 91 | CgroupNamespace uint32 `json:"cgroup_namespace"` 92 | IPCNamespace uint32 `json:"ipc_namespace"` 93 | NetNamespace uint32 `json:"net_namespace"` 94 | MntNamespace uint32 `json:"mnt_namespace"` 95 | PIDNamespace uint32 `json:"pid_namespace"` 96 | TimeNamespace uint32 `json:"time_namespace"` 97 | UserNamespace uint32 `json:"user_namespace"` 98 | UTSNamespace uint32 `json:"uts_namespace"` 99 | } 100 | 101 | func (nc *NamespaceContext) UnmarshalBinary(data []byte) (int, error) { 102 | if len(data) < 32 { 103 | return 0, fmt.Errorf("parsing NamespaceContext, got len %d, needed 32: %w", len(data), ErrNotEnoughData) 104 | } 105 | nc.CgroupNamespace = ByteOrder.Uint32(data[:4]) 106 | nc.IPCNamespace = ByteOrder.Uint32(data[4:8]) 107 | nc.NetNamespace = ByteOrder.Uint32(data[8:12]) 108 | nc.MntNamespace = ByteOrder.Uint32(data[12:16]) 109 | nc.PIDNamespace = ByteOrder.Uint32(data[16:20]) 110 | nc.TimeNamespace = ByteOrder.Uint32(data[20:24]) 111 | nc.UserNamespace = ByteOrder.Uint32(data[24:28]) 112 | nc.UTSNamespace = ByteOrder.Uint32(data[28:32]) 113 | return 32, nil 114 | } 115 | 116 | type ProcessContext struct { 117 | Cgroups [CgroupSubSysCount]CgroupContext `json:"cgroups"` 118 | NamespaceContext NamespaceContext `json:"namespace_context"` 119 | Credentials CredentialsContext `json:"credentials"` 120 | Comm string `json:"comm"` 121 | } 122 | 123 | func (pc *ProcessContext) UnmarshalBinary(data []byte) (int, error) { 124 | var cursor, read int 125 | var err error 126 | 127 | read, err = pc.NamespaceContext.UnmarshalBinary(data[cursor:]) 128 | if err != nil { 129 | return 0, err 130 | } 131 | cursor += read 132 | 133 | read, err = pc.Credentials.UnmarshalBinary(data[cursor:]) 134 | if err != nil { 135 | return 0, err 136 | } 137 | cursor += read 138 | 139 | if len(data[cursor:]) < TaskCommLength { 140 | return 0, fmt.Errorf("parsing ProcessContext.Comm: got len %d, needed %d: %w", len(data[cursor:]), TaskCommLength, ErrNotEnoughData) 141 | } 142 | pc.Comm = string(bytes.Trim(data[cursor:cursor+TaskCommLength], "\x00")) 143 | cursor += TaskCommLength 144 | 145 | for i := 0; i < CgroupSubSysCount; i++ { 146 | read, err = pc.Cgroups[i].UnmarshalBinary(data[cursor:]) 147 | if err != nil { 148 | return 0, err 149 | } 150 | cursor += read 151 | } 152 | 153 | return cursor, nil 154 | } 155 | 156 | type ParsedSyscallArgumentValue struct { 157 | Type string `json:"type"` 158 | Size int `json:"size"` 159 | Data []byte `json:"data,omitempty"` 160 | ParsedData interface{} `json:"parsed_data,omitempty"` 161 | Members []map[string]ParsedSyscallArgumentValue `json:"members,omitempty"` 162 | } 163 | 164 | func (psav ParsedSyscallArgumentValue) String(name string, sav *SyscallArgumentValue, bytesShown int) string { 165 | t := psav.Type 166 | if sav != nil { 167 | t = sav.Argument.Definition 168 | } 169 | output := fmt.Sprintf("%s %s: ", t, name) 170 | if psav.ParsedData != nil { 171 | output += fmt.Sprint(psav.ParsedData) 172 | } else if len(psav.Members) > 0 { 173 | output += "{" 174 | for i, arg := range psav.Members { 175 | for name, val := range arg { 176 | if i > 0 { 177 | output += ", " 178 | } 179 | output += val.String(name, nil, bytesShown) 180 | } 181 | } 182 | output += "}" 183 | } else { 184 | end := bytesShown 185 | if end > len(psav.Data) { 186 | end = len(psav.Data) 187 | } 188 | if len(psav.Data) > 0 { 189 | output += fmt.Sprintf("0x%x...", psav.Data[:end]) 190 | } else { 191 | output += "NULL" 192 | } 193 | } 194 | return output 195 | } 196 | 197 | type SyscallArgumentValue struct { 198 | Argument *SyscallArgument 199 | ParsedData *ParsedSyscallArgumentValue 200 | Data []byte 201 | } 202 | 203 | func parseInt(data []byte, isSigned bool) interface{} { 204 | if isSigned { 205 | switch len(data) { 206 | case 1: 207 | return int8(data[0]) 208 | case 2: 209 | val := ByteOrder.Uint16(data[0:2]) 210 | return int16(val) 211 | case 4: 212 | val := ByteOrder.Uint32(data[0:4]) 213 | return int32(val) 214 | case 8: 215 | val := ByteOrder.Uint64(data[0:8]) 216 | return int64(val) 217 | } 218 | } else { 219 | switch len(data) { 220 | case 1: 221 | return data[0] 222 | case 2: 223 | return ByteOrder.Uint16(data[0:2]) 224 | case 4: 225 | return ByteOrder.Uint32(data[0:4]) 226 | case 8: 227 | return ByteOrder.Uint64(data[0:8]) 228 | } 229 | } 230 | return nil 231 | } 232 | 233 | func newParsedSyscallArgumentValue(t btf.Type, data []byte, sav *SyscallArgumentValue) ParsedSyscallArgumentValue { 234 | var output ParsedSyscallArgumentValue 235 | switch reflect.TypeOf(t) { 236 | case reflect.TypeOf(&btf.Void{}): 237 | output.Type = "void" 238 | output.Size = len(data) 239 | if output.Size > 0 { 240 | output.Data = data 241 | } 242 | case reflect.TypeOf(&btf.Int{}): 243 | i := t.(*btf.Int) 244 | if i.Encoding&btf.Signed == btf.Signed { 245 | output.Type = "int" 246 | } else if i.Encoding&btf.Char == btf.Char { 247 | output.Type = "char" 248 | } else { 249 | output.Type = "uint" 250 | } 251 | var resizedData []byte 252 | output.Size = int(i.Size) 253 | if len(data) >= output.Size { 254 | resizedData = data[0:output.Size] 255 | } else { 256 | output.Size = len(data) 257 | resizedData = data 258 | } 259 | if output.Size > 0 { 260 | output.Data = resizedData 261 | if i.Encoding&btf.Char == btf.Char { 262 | dataStr := bytes.NewBuffer(bytes.Trim(resizedData, "\x00")).String() 263 | // actual strings should have the same size in bytes and in their ASCII representation 264 | // (the -1 accounts for the trailing \x00) 265 | if len(dataStr) == len(resizedData) || len(dataStr) == len(resizedData)-1 { 266 | output.ParsedData = dataStr 267 | } 268 | } else { 269 | output.ParsedData = parseInt(resizedData, i.Encoding&btf.Signed == btf.Signed) 270 | } 271 | } 272 | case reflect.TypeOf(&btf.Struct{}): 273 | s := t.(*btf.Struct) 274 | output.Type = fmt.Sprintf("struct %s", string(s.Name)) 275 | output.Size = int(s.Size) 276 | if len(data) >= output.Size { 277 | if output.Size > 0 { 278 | output.Data = data[0:output.Size] 279 | } 280 | } else { 281 | output.Size = len(data) 282 | if output.Size > 0 { 283 | output.Data = data 284 | } 285 | } 286 | 287 | var end int 288 | dataLen := len(data) 289 | for i, member := range s.Members { 290 | if i+1 < len(s.Members) { 291 | end = int(s.Members[i+1].Offset / 8) 292 | } else { 293 | end = dataLen 294 | } 295 | // make sure the entry is complete before parsing it (it could have been truncated) 296 | if dataLen >= int(member.Offset/8) && dataLen >= end { 297 | output.Members = append(output.Members, map[string]ParsedSyscallArgumentValue{ 298 | string(member.Name): newParsedSyscallArgumentValue(member.Type, data[member.Offset/8:end], nil), 299 | }) 300 | } else { 301 | // ignore truncated entries 302 | break 303 | } 304 | } 305 | case reflect.TypeOf(&btf.Pointer{}): 306 | p := t.(*btf.Pointer) 307 | // do not follow the pointer if the data left is simply a pointer 308 | if len(data) <= 8 { 309 | output.Type = "void *" 310 | output.Size = len(data) 311 | output.Data = data 312 | } else { 313 | output = newParsedSyscallArgumentValue(p.Target, data, nil) 314 | } 315 | case reflect.TypeOf(&btf.Array{}): 316 | a := t.(*btf.Array) 317 | output.Type = "array" 318 | output.Size = len(data) 319 | if output.Size > 0 { 320 | output.Data = data 321 | } 322 | 323 | if a.Nelems > 0 { 324 | elemSize := output.Size / int(a.Nelems) 325 | dataLen := len(data) 326 | for i := 0; i < int(a.Nelems); i++ { 327 | if dataLen >= (i+1)*elemSize { 328 | output.Members = append(output.Members, map[string]ParsedSyscallArgumentValue{ 329 | fmt.Sprintf("%d", i): newParsedSyscallArgumentValue(a.Type, data[i*elemSize:(i+1)*elemSize], nil), 330 | }) 331 | } else { 332 | // ignore truncated entries 333 | break 334 | } 335 | } 336 | } 337 | case reflect.TypeOf(&btf.Typedef{}): 338 | td := t.(*btf.Typedef) 339 | output = newParsedSyscallArgumentValue(td.Type, data, nil) 340 | case reflect.TypeOf(&btf.Volatile{}): 341 | v := t.(*btf.Volatile) 342 | output = newParsedSyscallArgumentValue(v.Type, data, nil) 343 | case reflect.TypeOf(&btf.Const{}): 344 | c := t.(*btf.Const) 345 | output = newParsedSyscallArgumentValue(c.Type, data, nil) 346 | case reflect.TypeOf(&btf.Restrict{}): 347 | r := t.(*btf.Restrict) 348 | output = newParsedSyscallArgumentValue(r.Type, data, nil) 349 | default: 350 | output.Size = len(data) 351 | if output.Size > 0 { 352 | output.Data = data 353 | } 354 | } 355 | 356 | // override type and typename if this is the top level entry 357 | if sav != nil { 358 | output.Type = sav.Argument.Definition 359 | } 360 | 361 | return output 362 | } 363 | 364 | func (sav SyscallArgumentValue) Parse() *ParsedSyscallArgumentValue { 365 | if sav.ParsedData == nil { 366 | parsedValue := newParsedSyscallArgumentValue(sav.Argument.BTFType, sav.Data, &sav) 367 | sav.ParsedData = &parsedValue 368 | } 369 | return sav.ParsedData 370 | } 371 | 372 | func (sav SyscallArgumentValue) MarshalJSON() ([]byte, error) { 373 | return json.Marshal(sav.Parse()) 374 | } 375 | 376 | type SyscallArgumentsList [6]SyscallArgumentValue 377 | 378 | func (sal SyscallArgumentsList) MarshalJSON() ([]byte, error) { 379 | var output []map[string]SyscallArgumentValue 380 | for _, arg := range sal { 381 | if arg.Argument != nil { 382 | output = append(output, map[string]SyscallArgumentValue{ 383 | arg.Argument.Name: arg, 384 | }) 385 | } 386 | } 387 | return json.Marshal(output) 388 | } 389 | 390 | func (sal SyscallArgumentsList) String(bytesShown int) string { 391 | var output string 392 | for i, arg := range sal { 393 | if arg.Argument != nil { 394 | if i >= 1 { 395 | output += ", " 396 | } 397 | output += arg.Parse().String(arg.Argument.Name, &arg, bytesShown) 398 | } 399 | } 400 | return output 401 | } 402 | 403 | type SyscallEvent struct { 404 | Syscall Syscall `json:"syscall"` 405 | Ret uint64 `json:"ret"` 406 | Timestamp time.Time `json:"timestamp"` 407 | PID uint32 `json:"pid"` 408 | TID uint32 `json:"tid"` 409 | EntryProcessContext ProcessContext `json:"entry_process_context"` 410 | ExitProcessContext ProcessContext `json:"exit_process_context"` 411 | 412 | Args SyscallArgumentsList `json:"args"` 413 | ArgsRaw []byte `json:"args_raw"` 414 | } 415 | 416 | // NewSyscallEvent returns a pointer to an initialized SyscallEvent 417 | func NewSyscallEvent() *SyscallEvent { 418 | return &SyscallEvent{ 419 | Args: SyscallArgumentsList{}, 420 | } 421 | } 422 | 423 | func (se *SyscallEvent) String(bytesShown int) string { 424 | return fmt.Sprintf("%s(%d) | %s(%s) = %d", se.EntryProcessContext.Comm, se.TID, se.Syscall, se.Args.String(bytesShown), int64(se.Ret)) 425 | } 426 | 427 | // var brokenSyscalls = make(map[Syscall]int) 428 | 429 | // UnmarshalBinary unmarshals the binary representation of SyscallEvent 430 | func (se *SyscallEvent) UnmarshalBinary(data []byte, read int, e *ETrace) error { 431 | var err error 432 | cursor := read 433 | 434 | if len(data[cursor:]) < 28 { 435 | return fmt.Errorf("parsing Ret, Timestamp, PID and TID: got len %d, needed 28: %w", len(data[cursor:]), err) 436 | } 437 | se.PID = ByteOrder.Uint32(data[cursor : cursor+4]) 438 | se.TID = ByteOrder.Uint32(data[cursor+4 : cursor+8]) 439 | // padding 440 | se.Ret = ByteOrder.Uint64(data[cursor+12 : cursor+20]) 441 | se.Timestamp = e.timeResolver.ResolveMonotonicTimestamp(ByteOrder.Uint64(data[cursor+20 : cursor+28])) 442 | cursor += 28 443 | 444 | read, err = se.EntryProcessContext.UnmarshalBinary(data[cursor:]) 445 | if err != nil { 446 | return err 447 | } 448 | cursor += read 449 | 450 | read, err = se.ExitProcessContext.UnmarshalBinary(data[cursor:]) 451 | if err != nil { 452 | return err 453 | } 454 | cursor += read 455 | 456 | // parse arguments 457 | se.ArgsRaw = data[cursor:] 458 | 459 | var size int 460 | for i := 0; i < 6; i++ { 461 | if len(data[cursor:]) < 4 { 462 | break 463 | } 464 | size = int(int32(ByteOrder.Uint32(data[cursor : cursor+4]))) 465 | cursor += 4 466 | if cursor+size > len(data) { 467 | // This shouldn't happen, if it does, this is a bug: the size of the argument as parsed by the kernel doesn't 468 | // match what we retrieved from kernel space. Last time this happened it was because MAX_DATA_PER_SYSCALL and 469 | // MAX_DATA_PER_ARG weren't properly set so that MAX_DATA_PER_SYSCALL - MAX_DATA_PER_ARG - 1 could be written 470 | // as (2^k - 1). 471 | // Stop parsing, the argument is incomplete 472 | return fmt.Errorf("couldn't parse argument %d of syscall %s: invalid payload length, expected at least %d bytes left, got %d left", i, se.Syscall.String(), cursor+size, len(data)) 473 | } 474 | if size > 0 { 475 | se.Args[i].Data = make([]byte, size) 476 | copy(se.Args[i].Data[:], data[cursor:cursor+size]) 477 | cursor += size 478 | _ = se.Args[i].Parse() 479 | } else { 480 | // Two options: 481 | // - the syscall definition is broken and there is something wrong with the computed BTF data 482 | // - the parameter was not provided to the syscall (this is an optional parameter) 483 | // 484 | // brokenSyscalls[se.Syscall] += 1 485 | } 486 | } 487 | return nil 488 | } 489 | --------------------------------------------------------------------------------