├── .gitignore ├── .gitmodules ├── LICENSE ├── examples ├── Makefile ├── minimal.c ├── profile.c ├── profile.h └── uprobe.c └── src └── DotnetEbpf ├── Directory.Build.props ├── DotnetEbpf.Core ├── BpfArrayAttribute.cs ├── BpfFlags.cs ├── BpfLicenseAttribute.cs ├── BpfRingBuffer.cs ├── BpfSectionAttribute.cs ├── BpfUtils.cs ├── CTypes.cs ├── CUtils.cs ├── DotnetEbpf.Core.csproj ├── Maps │ ├── BpfMap.cs │ ├── BpfMapCustomDefinedType.cs │ ├── MapType.cs │ ├── MapTypeAttribute.cs │ ├── MapTypeUtils.cs │ └── MaxEntriesAttribute.cs └── Plugin.cs ├── DotnetEbpf.Examples ├── DotnetEbpf.Examples.csproj ├── Minimal.cs ├── Profile.cs ├── Uprobe.cs ├── minimal.manifest.json ├── profile.manifest.json └── uprobe.manifest.json └── DotnetEbpf.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | [Bb]in/ 3 | [Oo]bj/ 4 | *.suo 5 | *.user 6 | *.vs 7 | artifacts/ 8 | generated/ 9 | .output/ 10 | examples/* 11 | !examples/*.c 12 | !examples/*.h 13 | 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dntc"] 2 | path = dntc 3 | url = git@github.com:KallDrexx/dntc.git 4 | [submodule "libbpf"] 5 | path = libbpf 6 | url = git@github.com:libbpf/libbpf.git 7 | [submodule "blazesym"] 8 | path = blazesym 9 | url = git@github.com:libbpf/blazesym.git 10 | [submodule "vmlinux.h"] 11 | path = vmlinux.h 12 | url = git@github.com:libbpf/vmlinux.h.git 13 | [submodule "bpftool"] 14 | path = bpftool 15 | url = https://github.com/libbpf/bpftool.git 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Matthew Shapiro 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 | # Heavily based on libbpf-bootstrap Makefile 3 | 4 | .SUFFIXES: 5 | 6 | DOTNET ?= dotnet 7 | OUTPUT := .output 8 | 9 | BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) 10 | BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool 11 | BPFTOOL_SRC := $(abspath ../bpftool/src) 12 | 13 | CARGO ?= $(shell which cargo) 14 | 15 | # List of different application targets that can be invoked. 16 | APPS = minimal uprobe 17 | 18 | BZS_APPS := profile # Apps requiring blazesym 19 | APPS += $(BZS_APPS) 20 | 21 | # Dotnet project directories to check for changes of 22 | DNTC_TOOL_DIR := ../dntc 23 | SRC_ROOT = ../src/DotnetEbpf 24 | EXAMPLES_DIR = $(SRC_ROOT)/DotnetEbpf.Examples 25 | CORE_DIR = $(SRC_ROOT)/DotnetEbpf.Core 26 | 27 | DNTC_TOOL_CSPROJ = $(DNTC_TOOL_DIR)/Dntc.Cli/Dntc.Cli.csproj 28 | MANIFEST_DIR = $(EXAMPLES_DIR) 29 | 30 | EXAMPLE_ARTIFACTS_DIR = $(SRC_ROOT)/artifacts/bin/DotnetEbpf.Examples/release 31 | EXAMPLE_DLL = $(EXAMPLE_ARTIFACTS_DIR)/DotnetEbpf.Examples.dll 32 | EXAMPLE_CSPROJ = $(EXAMPLES_DIR)/DotnetEbpf.Examples.csproj 33 | CORE_DLL = $(EXAMPLE_ARTIFACTS_DIR)/DotnetEbpf.Core.dll 34 | 35 | # libbpf vars 36 | CLANG ?= clang 37 | LIBBPF_SRC := $(abspath ../libbpf/src) 38 | LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) 39 | ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ 40 | | sed 's/arm.*/arm/' \ 41 | | sed 's/aarch64/arm64/' \ 42 | | sed 's/ppc64le/powerpc/' \ 43 | | sed 's/mips.*/mips/' \ 44 | | sed 's/riscv64/riscv/' \ 45 | | sed 's/loongarch64/loongarch/') 46 | VMLINUX := ../vmlinux.h/include/$(ARCH)/vmlinux.h 47 | LIBBLAZESYM_SRC := $(abspath ../blazesym/) 48 | LIBBLAZESYM_INC := $(abspath $(LIBBLAZESYM_SRC)/capi/include) 49 | LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym_c.a) 50 | # Use our own libbpf API headers and Linux UAPI headers distributed with 51 | # libbpf to avoid dependency on system-wide headers, which could be missing or 52 | # outdated 53 | INCLUDES := -I$(OUTPUT) -I../libbpf/include/uapi -I$(dir $(VMLINUX)) -I$(LIBBLAZESYM_INC) 54 | CFLAGS := -g -Wall 55 | ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) 56 | 57 | # Required by libblazesym 58 | ALL_LDFLAGS += -lrt -ldl -lpthread -lm 59 | 60 | # Get Clang's default includes on this system. We'll explicitly add these dirs 61 | # to the includes list when compiling with `-target bpf` because otherwise some 62 | # architecture-specific dirs will be "missing" on some architectures/distros - 63 | # headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, 64 | # sys/cdefs.h etc. might be missing. 65 | # 66 | # Use '-idirafter': Don't interfere with include mechanics except where the 67 | # build would have failed anyways. 68 | CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ 69 | | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') 70 | 71 | # Build the specified application 72 | $(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) 73 | $(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ 74 | 75 | # Build user-space code 76 | $(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h 77 | 78 | $(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) 79 | $(call msg,CC,$@) 80 | $(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ 81 | 82 | $(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_OBJ) 83 | 84 | $(BZS_APPS): $(LIBBLAZESYM_OBJ) 85 | 86 | # Build the DotnetEbpf.Examples project 87 | $(EXAMPLE_DLL): $(shell find $(EXAMPLES_DIR) $(DNTC_TOOL_DIR) $(CORE_DIR)) 88 | $(DOTNET) build -c Release $(EXAMPLE_CSPROJ) 89 | 90 | # Transpile via a manifest with the same name as the target. It's expected that the manifests 91 | # will produce a single file output with the name of the target plus ".bpf.c" 92 | $(OUTPUT)/%.bpf.c: $(EXAMPLE_DLL) $(EXAMPLES_DIR)/%.manifest.json %.c | $(OUTPUT) 93 | $(DOTNET) run --project $(DNTC_TOOL_CSPROJ) -- $(word 2, $^) 94 | 95 | # Build the BPF code 96 | $(OUTPUT)/%.bpf.o: $(OUTPUT)/%.bpf.c $(LIBBPF_OBJ) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) 97 | $(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ 98 | $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ 99 | -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) 100 | $(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) 101 | 102 | # Build libbpf 103 | $(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf 104 | $(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ 105 | OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ 106 | INCLUDEDIR= LIBDIR= UAPIDIR= \ 107 | install 108 | 109 | # Generate BPF skeleton 110 | $(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) 111 | $(call msg,GEN-SKEL,$@) 112 | $(Q)$(BPFTOOL) gen skeleton $< > $@ 113 | 114 | # Build bpftool 115 | $(BPFTOOL): | $(BPFTOOL_OUTPUT) 116 | $(call msg,BPFTOOL,$@) 117 | $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap 118 | 119 | 120 | $(LIBBLAZESYM_SRC)/target/release/libblazesym_c.a:: 121 | $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --package=blazesym-c --release 122 | 123 | $(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym_c.a | $(OUTPUT) 124 | $(call msg,LIB, $@) 125 | $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym_c.a $@ 126 | 127 | # Required directory creation 128 | $(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): 129 | mkdir -p $@ 130 | 131 | .phony: clean 132 | clean: 133 | $(DOTNET) clean -c Release $(SRC_ROOT)/*.sln 134 | $(DOTNET) clean -c Debug $(SRC_ROOT)/*.sln 135 | rm -rf $(OUTPUT) 136 | 137 | # delete failed targets 138 | .DELETE_ON_ERROR: 139 | 140 | # keep intermediate (.skel.h, .bpf.o, etc) targets 141 | .SECONDARY: -------------------------------------------------------------------------------- /examples/minimal.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 | /* Copyright (c) 2020 Facebook */ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "minimal.skel.h" 8 | 9 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 10 | { 11 | return vfprintf(stderr, format, args); 12 | } 13 | 14 | int main(int argc, char **argv) 15 | { 16 | struct minimal_bpf *skel; 17 | int err; 18 | 19 | /* Set up libbpf errors and debug info callback */ 20 | libbpf_set_print(libbpf_print_fn); 21 | 22 | /* Open BPF application */ 23 | skel = minimal_bpf__open(); 24 | if (!skel) { 25 | fprintf(stderr, "Failed to open BPF skeleton\n"); 26 | return 1; 27 | } 28 | 29 | /* ensure BPF program only handles write() syscalls from our process */ 30 | skel->bss->my_pid = getpid(); 31 | 32 | /* Load & verify BPF programs */ 33 | err = minimal_bpf__load(skel); 34 | if (err) { 35 | fprintf(stderr, "Failed to load and verify BPF skeleton\n"); 36 | goto cleanup; 37 | } 38 | 39 | /* Attach tracepoint handler */ 40 | err = minimal_bpf__attach(skel); 41 | if (err) { 42 | fprintf(stderr, "Failed to attach BPF skeleton\n"); 43 | goto cleanup; 44 | } 45 | 46 | printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` " 47 | "to see output of the BPF programs.\n"); 48 | 49 | for (;;) { 50 | /* trigger our BPF program */ 51 | fprintf(stderr, "."); 52 | sleep(1); 53 | } 54 | 55 | cleanup: 56 | minimal_bpf__destroy(skel); 57 | return -err; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /examples/profile.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 | /* Copyright (c) 2022 Facebook */ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "profile.skel.h" 17 | #include "profile.h" 18 | #include "blazesym.h" 19 | 20 | /* 21 | * This function is from libbpf, but it is not a public API and can only be 22 | * used for demonstration. We can use this here because we statically link 23 | * against the libbpf built from submodule during build. 24 | */ 25 | extern int parse_cpu_mask_file(const char *fcpu, bool **mask, int *mask_sz); 26 | 27 | static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, 28 | unsigned long flags) 29 | { 30 | int ret; 31 | 32 | ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags); 33 | return ret; 34 | } 35 | 36 | static struct blaze_symbolizer *symbolizer; 37 | 38 | static void print_frame(const char *name, uintptr_t input_addr, uintptr_t addr, uint64_t offset, const blaze_symbolize_code_info* code_info) 39 | { 40 | // If we have an input address we have a new symbol. 41 | if (input_addr != 0) { 42 | printf("%016lx: %s @ 0x%lx+0x%lx", input_addr, name, addr, offset); 43 | if (code_info != NULL && code_info->dir != NULL && code_info->file != NULL) { 44 | printf(" %s/%s:%u\n", code_info->dir, code_info->file, code_info->line); 45 | } else if (code_info != NULL && code_info->file != NULL) { 46 | printf(" %s:%u\n", code_info->file, code_info->line); 47 | } else { 48 | printf("\n"); 49 | } 50 | } else { 51 | printf("%16s %s", "", name); 52 | if (code_info != NULL && code_info->dir != NULL && code_info->file != NULL) { 53 | printf("@ %s/%s:%u [inlined]\n", code_info->dir, code_info->file, code_info->line); 54 | } else if (code_info != NULL && code_info->file != NULL) { 55 | printf("@ %s:%u [inlined]\n", code_info->file, code_info->line); 56 | } else { 57 | printf("[inlined]\n"); 58 | } 59 | } 60 | } 61 | 62 | static void show_stack_trace(__u64 *stack, int stack_sz, pid_t pid) 63 | { 64 | const struct blaze_symbolize_inlined_fn* inlined; 65 | const struct blaze_syms *syms; 66 | const struct blaze_sym *sym; 67 | int i, j; 68 | 69 | assert(sizeof(uintptr_t) == sizeof(uint64_t)); 70 | 71 | if (pid) { 72 | struct blaze_symbolize_src_process src = { 73 | .type_size = sizeof(src), 74 | .pid = pid, 75 | }; 76 | syms = blaze_symbolize_process_abs_addrs(symbolizer, &src, (const uintptr_t *)stack, stack_sz); 77 | } else { 78 | struct blaze_symbolize_src_kernel src = { 79 | .type_size = sizeof(src), 80 | }; 81 | syms = blaze_symbolize_kernel_abs_addrs(symbolizer, &src, (const uintptr_t *)stack, stack_sz); 82 | } 83 | 84 | if (syms == NULL) { 85 | printf(" failed to symbolize addresses: %s\n", blaze_err_str(blaze_err_last())); 86 | return; 87 | } 88 | 89 | for (i = 0; i < stack_sz; i++) { 90 | if (!syms || syms->cnt <= i || syms->syms[i].name == NULL) { 91 | printf("%016llx: \n", stack[i]); 92 | continue; 93 | } 94 | 95 | sym = &syms->syms[i]; 96 | print_frame(sym->name, stack[i], sym->addr, sym->offset, &sym->code_info); 97 | 98 | for (j = 0; j < sym->inlined_cnt; j++) { 99 | inlined = &sym->inlined[j]; 100 | print_frame(sym->name, 0, 0, 0, &inlined->code_info); 101 | } 102 | } 103 | 104 | blaze_syms_free(syms); 105 | } 106 | 107 | /* Receive events from the ring buffer. */ 108 | static int event_handler(void *_ctx, void *data, size_t size) 109 | { 110 | struct stacktrace_event *event = data; 111 | 112 | if (event->kstack_sz <= 0 && event->ustack_sz <= 0) 113 | return 1; 114 | 115 | printf("COMM: %s (pid=%d) @ CPU %d\n", event->comm, event->pid, event->cpu_id); 116 | 117 | if (event->kstack_sz > 0) { 118 | printf("Kernel:\n"); 119 | show_stack_trace(event->kstack, event->kstack_sz / sizeof(__u64), 0); 120 | } else { 121 | printf("No Kernel Stack\n"); 122 | } 123 | 124 | if (event->ustack_sz > 0) { 125 | printf("Userspace:\n"); 126 | show_stack_trace(event->ustack, event->ustack_sz / sizeof(__u64), event->pid); 127 | } else { 128 | printf("No Userspace Stack\n"); 129 | } 130 | 131 | printf("\n"); 132 | return 0; 133 | } 134 | 135 | static void show_help(const char *progname) 136 | { 137 | printf("Usage: %s [-f ] [--sw-event] [-h]\n", progname); 138 | printf("Options:\n"); 139 | printf(" -f Sampling frequency [default: 1]\n"); 140 | printf(" --sw-event Use software event for triggering stack trace capture\n"); 141 | printf(" -h Print help\n"); 142 | } 143 | 144 | int main(int argc, char *const argv[]) 145 | { 146 | const char *online_cpus_file = "/sys/devices/system/cpu/online"; 147 | int freq = 1, sw_event = 0, pid = -1, cpu; 148 | struct profile_bpf *skel = NULL; 149 | struct perf_event_attr attr; 150 | struct bpf_link **links = NULL; 151 | struct ring_buffer *ring_buf = NULL; 152 | int num_cpus, num_online_cpus; 153 | int *pefds = NULL, pefd; 154 | int argp, i, err = 0; 155 | bool *online_mask = NULL; 156 | 157 | static struct option long_options[] = { 158 | {"sw-event", no_argument, 0, 's'}, 159 | {0, 0, 0, 0} 160 | }; 161 | 162 | while ((argp = getopt_long(argc, argv, "hf:", long_options, NULL)) != -1) { 163 | switch (argp) { 164 | case 'f': 165 | freq = atoi(optarg); 166 | if (freq < 1) 167 | freq = 1; 168 | break; 169 | case 's': 170 | sw_event = 1; 171 | break; 172 | 173 | case 'h': 174 | default: 175 | show_help(argv[0]); 176 | return 1; 177 | } 178 | } 179 | 180 | err = parse_cpu_mask_file(online_cpus_file, &online_mask, &num_online_cpus); 181 | if (err) { 182 | fprintf(stderr, "Fail to get online CPU numbers: %d\n", err); 183 | goto cleanup; 184 | } 185 | 186 | num_cpus = libbpf_num_possible_cpus(); 187 | if (num_cpus <= 0) { 188 | fprintf(stderr, "Fail to get the number of processors\n"); 189 | err = -1; 190 | goto cleanup; 191 | } 192 | 193 | skel = profile_bpf__open_and_load(); 194 | if (!skel) { 195 | fprintf(stderr, "Fail to open and load BPF skeleton\n"); 196 | err = -1; 197 | goto cleanup; 198 | } 199 | 200 | symbolizer = blaze_symbolizer_new(); 201 | if (!symbolizer) { 202 | fprintf(stderr, "Fail to create a symbolizer\n"); 203 | err = -1; 204 | goto cleanup; 205 | } 206 | 207 | /* Prepare ring buffer to receive events from the BPF program. */ 208 | ring_buf = ring_buffer__new(bpf_map__fd(skel->maps.events), event_handler, NULL, NULL); 209 | if (!ring_buf) { 210 | err = -1; 211 | goto cleanup; 212 | } 213 | 214 | pefds = malloc(num_cpus * sizeof(int)); 215 | for (i = 0; i < num_cpus; i++) { 216 | pefds[i] = -1; 217 | } 218 | 219 | links = calloc(num_cpus, sizeof(struct bpf_link *)); 220 | 221 | memset(&attr, 0, sizeof(attr)); 222 | attr.type = sw_event ? PERF_TYPE_SOFTWARE : PERF_TYPE_HARDWARE; 223 | attr.size = sizeof(attr); 224 | attr.config = sw_event ? PERF_COUNT_SW_CPU_CLOCK : PERF_COUNT_HW_CPU_CYCLES; 225 | attr.sample_freq = freq; 226 | attr.freq = 1; 227 | 228 | for (cpu = 0; cpu < num_cpus; cpu++) { 229 | /* skip offline/not present CPUs */ 230 | if (cpu >= num_online_cpus || !online_mask[cpu]) 231 | continue; 232 | 233 | /* Set up performance monitoring on a CPU/Core */ 234 | pefd = perf_event_open(&attr, pid, cpu, -1, PERF_FLAG_FD_CLOEXEC); 235 | if (pefd < 0) { 236 | if (!sw_event && errno == ENOENT) { 237 | fprintf(stderr, 238 | "Fail to set up performance monitor on a CPU/Core.\n" 239 | "Try running the profile example with the `--sw-event` option.\n"); 240 | } else { 241 | fprintf(stderr, "Fail to set up performance monitor on a CPU/Core.\n"); 242 | } 243 | err = -1; 244 | goto cleanup; 245 | } 246 | pefds[cpu] = pefd; 247 | 248 | /* Attach a BPF program on a CPU */ 249 | links[cpu] = bpf_program__attach_perf_event(skel->progs.profile, pefd); 250 | if (!links[cpu]) { 251 | err = -1; 252 | goto cleanup; 253 | } 254 | } 255 | 256 | /* Wait and receive stack traces */ 257 | while (ring_buffer__poll(ring_buf, -1) >= 0) { 258 | } 259 | 260 | cleanup: 261 | if (links) { 262 | for (cpu = 0; cpu < num_cpus; cpu++) 263 | bpf_link__destroy(links[cpu]); 264 | free(links); 265 | } 266 | if (pefds) { 267 | for (i = 0; i < num_cpus; i++) { 268 | if (pefds[i] >= 0) 269 | close(pefds[i]); 270 | } 271 | free(pefds); 272 | } 273 | ring_buffer__free(ring_buf); 274 | profile_bpf__destroy(skel); 275 | blaze_symbolizer_free(symbolizer); 276 | free(online_mask); 277 | return -err; 278 | } 279 | 280 | -------------------------------------------------------------------------------- /examples/profile.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ 2 | /* Copyright (c) 2022 Meta Platforms, Inc. */ 3 | #ifndef __PROFILE_H_ 4 | #define __PROFILE_H_ 5 | 6 | #ifndef TASK_COMM_LEN 7 | #define TASK_COMM_LEN 16 8 | #endif 9 | 10 | #ifndef MAX_STACK_DEPTH 11 | #define MAX_STACK_DEPTH 128 12 | #endif 13 | 14 | typedef __u64 stack_trace_t[MAX_STACK_DEPTH]; 15 | 16 | struct stacktrace_event { 17 | __u32 pid; 18 | __u32 cpu_id; 19 | char comm[TASK_COMM_LEN]; 20 | __s32 kstack_sz; 21 | __s32 ustack_sz; 22 | stack_trace_t kstack; 23 | stack_trace_t ustack; 24 | }; 25 | 26 | #endif /* __PROFILE_H_ */ 27 | 28 | -------------------------------------------------------------------------------- /examples/uprobe.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 | /* Copyright (c) 2020 Facebook */ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "uprobe.skel.h" 9 | 10 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 11 | { 12 | return vfprintf(stderr, format, args); 13 | } 14 | 15 | /* It's a global function to make sure compiler doesn't inline it. To be extra 16 | * sure we also use "asm volatile" and noinline attributes to prevent 17 | * compiler from local inlining. 18 | */ 19 | __attribute__((noinline)) int uprobed_add(int a, int b) 20 | { 21 | asm volatile (""); 22 | return a + b; 23 | } 24 | 25 | __attribute__((noinline)) int uprobed_sub(int a, int b) 26 | { 27 | asm volatile (""); 28 | return a - b; 29 | } 30 | 31 | int main(int argc, char **argv) 32 | { 33 | struct uprobe_bpf *skel; 34 | int err, i; 35 | LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); 36 | 37 | /* Set up libbpf errors and debug info callback */ 38 | libbpf_set_print(libbpf_print_fn); 39 | 40 | /* Load and verify BPF application */ 41 | skel = uprobe_bpf__open_and_load(); 42 | if (!skel) { 43 | fprintf(stderr, "Failed to open and load BPF skeleton\n"); 44 | return 1; 45 | } 46 | 47 | /* Attach tracepoint handler */ 48 | uprobe_opts.func_name = "uprobed_add"; 49 | uprobe_opts.retprobe = false; 50 | /* uprobe/uretprobe expects relative offset of the function to attach 51 | * to. libbpf will automatically find the offset for us if we provide the 52 | * function name. If the function name is not specified, libbpf will try 53 | * to use the function offset instead. 54 | */ 55 | skel->links.uprobe_add = bpf_program__attach_uprobe_opts(skel->progs.uprobe_add, 56 | 0 /* self pid */, "/proc/self/exe", 57 | 0 /* offset for function */, 58 | &uprobe_opts /* opts */); 59 | if (!skel->links.uprobe_add) { 60 | err = -errno; 61 | fprintf(stderr, "Failed to attach uprobe: %d\n", err); 62 | goto cleanup; 63 | } 64 | 65 | /* we can also attach uprobe/uretprobe to any existing or future 66 | * processes that use the same binary executable; to do that we need 67 | * to specify -1 as PID, as we do here 68 | */ 69 | uprobe_opts.func_name = "uprobed_add"; 70 | uprobe_opts.retprobe = true; 71 | skel->links.uretprobe_add = bpf_program__attach_uprobe_opts( 72 | skel->progs.uretprobe_add, -1 /* self pid */, "/proc/self/exe", 73 | 0 /* offset for function */, &uprobe_opts /* opts */); 74 | if (!skel->links.uretprobe_add) { 75 | err = -errno; 76 | fprintf(stderr, "Failed to attach uprobe: %d\n", err); 77 | goto cleanup; 78 | } 79 | 80 | /* Let libbpf perform auto-attach for uprobe_sub/uretprobe_sub 81 | * NOTICE: we provide path and symbol info in SEC for BPF programs 82 | */ 83 | err = uprobe_bpf__attach(skel); 84 | if (err) { 85 | fprintf(stderr, "Failed to auto-attach BPF skeleton: %d\n", err); 86 | goto cleanup; 87 | } 88 | 89 | printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` " 90 | "to see output of the BPF programs.\n"); 91 | 92 | for (i = 0;; i++) { 93 | /* trigger our BPF programs */ 94 | fprintf(stderr, "."); 95 | uprobed_add(i, i + 1); 96 | uprobed_sub(i * i, i); 97 | sleep(1); 98 | } 99 | 100 | cleanup: 101 | uprobe_bpf__destroy(skel); 102 | return -err; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/DotnetEbpf/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(MSBuildThisFileDirectory)artifacts 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/BpfArrayAttribute.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Common; 2 | using Dntc.Common.Conversion; 3 | using Dntc.Common.Conversion.Mutators; 4 | using Dntc.Common.Definitions.CustomDefinedTypes; 5 | using Dntc.Common.Syntax.Expressions; 6 | using Dntc.Common.Syntax.Statements; 7 | using Mono.Cecil; 8 | 9 | namespace DotnetEbpf.Core; 10 | 11 | /// 12 | /// Specifies that the field is a statically sized array with a size known at compile time. 13 | /// 14 | /// 15 | [AttributeUsage(AttributeTargets.Field)] 16 | public class BpfArrayAttribute(int numberOfElements) : Attribute 17 | { 18 | public class Mutator : IFieldConversionMutator 19 | { 20 | private readonly ConversionCatalog _conversionCatalog; 21 | 22 | public Mutator(ConversionCatalog conversionCatalog) 23 | { 24 | _conversionCatalog = conversionCatalog; 25 | } 26 | 27 | public IReadOnlySet RequiredTypes => new HashSet(); 28 | 29 | public void Mutate(FieldConversionInfo conversionInfo, FieldDefinition field) 30 | { 31 | var attribute = field 32 | .CustomAttributes 33 | .SingleOrDefault(x => x.AttributeType.FullName == typeof(BpfArrayAttribute).FullName); 34 | 35 | if (attribute == null) 36 | { 37 | return; 38 | } 39 | 40 | var fieldType = field.FieldType; 41 | if (!fieldType.IsArray) 42 | { 43 | var message = $"BpfArrayAttribute was attached to the field {field.FullName} but " + 44 | $"its field type is not an array type"; 45 | 46 | throw new InvalidOperationException(message); 47 | } 48 | 49 | if (!attribute.HasConstructorArguments || attribute.ConstructorArguments[0].Value is not int size) 50 | { 51 | var message = $"Field {field.FullName}'s BpfArrayAttribute constructor did not have a " + 52 | $"single integer size value"; 53 | 54 | throw new InvalidOperationException(message); 55 | } 56 | 57 | // We need to get the derived type info of the array's element type, so we can 58 | // accurately tell the array's type conversion info what it's native name is. This 59 | // *should* not be a race condition because the dependency graph should have ensured 60 | // that this field's type info is added to the conversion catalog before the field itself. 61 | var elementType = new IlTypeName(fieldType.GetElementType().FullName); 62 | var elementTypeInfo = _conversionCatalog.Find(elementType); 63 | 64 | var sizeType = new IlTypeName(typeof(int).FullName!); // TODO: Figure out a way to make this configurable. 65 | var definedType = new DefinedType(fieldType, elementTypeInfo, size, sizeType); 66 | var fieldTypeInfo = new TypeConversionInfo(definedType, false); 67 | 68 | // Replace the field's current conversion info with one based on a statically sized defined type 69 | conversionInfo.FieldTypeConversionInfo = fieldTypeInfo; 70 | conversionInfo.StaticItemSize = size; 71 | } 72 | } 73 | 74 | private class DefinedType : StaticallySizedArrayDefinedType 75 | { 76 | public DefinedType( 77 | TypeReference arrayType, 78 | TypeConversionInfo elementTypeInfo, 79 | int size, 80 | IlTypeName sizeType) 81 | : base(arrayType, elementTypeInfo, size, sizeType) 82 | { 83 | } 84 | 85 | public override CStatementSet GetLengthCheckExpression( 86 | CBaseExpression arrayLengthField, 87 | CBaseExpression arrayInstance, 88 | DereferencedValueExpression index) 89 | { 90 | // Return no code for the length check, as ebpf programs can't abort, 91 | // and array checks are statically checked by the compiler. 92 | return new CustomCodeStatementSet(""); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/BpfFlags.cs: -------------------------------------------------------------------------------- 1 | namespace DotnetEbpf.Core; 2 | 3 | public enum BpfFlags 4 | { 5 | UserStack = 1 << 8, 6 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/BpfLicenseAttribute.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Common; 2 | using Dntc.Common.Conversion; 3 | using Dntc.Common.Conversion.Mutators; 4 | using Dntc.Common.Definitions; 5 | using Dntc.Common.Syntax.Expressions; 6 | using Mono.Cecil; 7 | 8 | namespace DotnetEbpf.Core; 9 | 10 | [AttributeUsage(AttributeTargets.Field)] 11 | public class BpfLicenseAttribute(string license) : Attribute 12 | { 13 | public const string DualBsdGpl = "Dual BSD/GPL"; 14 | 15 | public class Mutator : IFieldConversionMutator 16 | { 17 | private readonly ConversionCatalog _conversionCatalog; 18 | 19 | public Mutator(ConversionCatalog conversionCatalog) 20 | { 21 | _conversionCatalog = conversionCatalog; 22 | } 23 | 24 | public IReadOnlySet RequiredTypes => new HashSet([ 25 | new IlTypeName(typeof(char).FullName!), 26 | ]); 27 | 28 | public void Mutate(FieldConversionInfo conversionInfo, FieldDefinition field) 29 | { 30 | var attribute = field 31 | .CustomAttributes 32 | .FirstOrDefault(x => x.AttributeType.FullName == typeof(BpfLicenseAttribute).FullName); 33 | 34 | if (attribute == null) 35 | { 36 | return; 37 | } 38 | 39 | var license = attribute.ConstructorArguments[0].Value.ToString(); 40 | conversionInfo.AttributeText = "SEC(\"license\")"; 41 | conversionInfo.StaticItemSize = license!.Length + 1; // +1 for null terminator 42 | 43 | var returnType = _conversionCatalog.Find(new IlTypeName(typeof(char).FullName!)); 44 | conversionInfo.FieldTypeConversionInfo = returnType; 45 | 46 | var expression = new LiteralValueExpression($"\"{license}\"", returnType); 47 | conversionInfo.InitialValue = expression; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/BpfRingBuffer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using Dntc.Attributes; 3 | 4 | namespace DotnetEbpf.Core; 5 | 6 | #pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type 7 | public static class BpfRingBuffer 8 | { 9 | [NativeFunctionCall("bpf_ringbuf_reserve", "")] 10 | public static unsafe TItemType* Reserve(ref TMapType map, int size, uint flags) 11 | { 12 | return (TItemType*)Marshal.AllocHGlobal(sizeof(TItemType)); 13 | } 14 | 15 | [NativeFunctionCall("bpf_ringbuf_submit", "")] 16 | public static unsafe void Submit(T* data, uint flags) 17 | { 18 | 19 | } 20 | } 21 | #pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type 22 | -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/BpfSectionAttribute.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Common; 2 | using Dntc.Common.Conversion; 3 | using Dntc.Common.Conversion.Mutators; 4 | using Dntc.Common.Definitions; 5 | using Mono.Cecil; 6 | 7 | namespace DotnetEbpf.Core; 8 | 9 | /// 10 | /// Specifies that the method it is attached to should use the 11 | /// `SEC` macro to define what section the method should reside in. 12 | /// 13 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] 14 | public class BpfSectionAttribute(string section) : Attribute 15 | { 16 | public class Mutator : IMethodConversionMutator, IFieldConversionMutator 17 | { 18 | public void Mutate(MethodConversionInfo conversionInfo, DotNetDefinedMethod method) 19 | { 20 | var attribute = method.Definition 21 | .CustomAttributes 22 | .FirstOrDefault(x => x.AttributeType.FullName == typeof(BpfSectionAttribute).FullName); 23 | 24 | if (attribute == null) 25 | { 26 | return; 27 | } 28 | 29 | var name = attribute.ConstructorArguments[0].Value.ToString(); 30 | 31 | conversionInfo.AttributeText = $"SEC(\"{name}\")"; 32 | } 33 | 34 | public IReadOnlySet RequiredTypes => new HashSet(); 35 | 36 | public void Mutate(FieldConversionInfo conversionInfo, FieldDefinition field) 37 | { 38 | var attribute = field 39 | .CustomAttributes 40 | .FirstOrDefault(x => x.AttributeType.FullName == typeof(BpfSectionAttribute).FullName); 41 | 42 | if (attribute == null) 43 | { 44 | return; 45 | } 46 | 47 | var name = attribute.ConstructorArguments[0].Value.ToString(); 48 | 49 | conversionInfo.AttributeText = $"SEC(\"{name}\")"; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/BpfUtils.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Attributes; 2 | 3 | namespace DotnetEbpf.Core; 4 | 5 | public static class BpfUtils 6 | { 7 | private const string HelperHeaders = "vmlinux.h,"; 8 | 9 | /// 10 | /// Allows for calling `bpf_printk` with a single templated argument 11 | /// 12 | [NativeFunctionCall("bpf_printk", "vmlinux.h")] 13 | public static void Printk(string message, T param1) 14 | { 15 | } 16 | 17 | /// 18 | /// Allows for calling `bpf_printk` with two templated arguments 19 | /// 20 | [NativeFunctionCall("bpf_printk", "vmlinux.h")] 21 | public static void Printk(string message, T param1, U param2) 22 | { 23 | } 24 | 25 | /// 26 | /// Returns a 64-bit number where the high 32 bits are the bpf tgid and the low 32 bits 27 | /// are the bpf pid. 28 | /// 29 | [NativeFunctionCall("bpf_get_current_pid_tgid", HelperHeaders)] 30 | public static long GetCurrentPidTgid() 31 | { 32 | return 0; 33 | } 34 | 35 | [NativeFunctionCall("bpf_get_smp_processor_id", HelperHeaders)] 36 | public static UInt32 GetSmpProcessorId() 37 | { 38 | return 0; 39 | } 40 | 41 | [NativeFunctionCall("bpf_get_current_comm", HelperHeaders)] 42 | public static long GetCurrentComm(ref T buffer, uint bufferSize) 43 | { 44 | return 0; 45 | } 46 | 47 | [NativeFunctionCall("bpf_get_stack", HelperHeaders)] 48 | public static long GetStack(ref TContext context, ref TBuffer buffer, uint size, ulong flags) 49 | { 50 | return 0; 51 | } 52 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/CTypes.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Attributes; 2 | 3 | namespace DotnetEbpf.Core; 4 | 5 | public static class CTypes 6 | { 7 | /// 8 | /// Represents a void type in C. This is required because a function might take 9 | /// a void pointer, and this can't be represented with a c# `void`. 10 | /// 11 | [NativeType("void", null)] 12 | public struct CVoid { } 13 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/CUtils.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Attributes; 2 | 3 | namespace DotnetEbpf.Core; 4 | 5 | public static class CUtils 6 | { 7 | [NativeFunctionCall("sizeof", null)] 8 | public static int SizeOf(Type type) 9 | { 10 | return 0; 11 | } 12 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/DotnetEbpf.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 9113 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/Maps/BpfMap.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Attributes; 2 | using Dntc.Common; 3 | using Dntc.Common.Conversion; 4 | using Dntc.Common.Conversion.Mutators; 5 | using Dntc.Common.Definitions; 6 | using Dntc.Common.Definitions.Definers; 7 | using Dntc.Common.Syntax.Statements; 8 | using Mono.Cecil; 9 | 10 | namespace DotnetEbpf.Core.Maps; 11 | 12 | public struct BpfMap 13 | { 14 | public class FieldDefiner : IDotNetFieldDefiner 15 | { 16 | private readonly DefinitionCatalog _definitionCatalog; 17 | 18 | public FieldDefiner(DefinitionCatalog definitionCatalog) 19 | { 20 | _definitionCatalog = definitionCatalog; 21 | } 22 | 23 | public DefinedField? Define(FieldDefinition fieldDefinition) 24 | { 25 | if (fieldDefinition.FieldType.FullName != typeof(BpfMap).FullName) 26 | { 27 | return null; 28 | } 29 | 30 | var fieldType = BpfMapCustomDefinedType.CreateForField(fieldDefinition); 31 | if (fieldType == null) 32 | { 33 | return null; 34 | } 35 | 36 | var fieldName = fieldDefinition.Name; 37 | var customNameAttribute = fieldDefinition.CustomAttributes 38 | .SingleOrDefault(x => x.AttributeType.FullName == typeof(CustomFieldNameAttribute).FullName); 39 | 40 | if (customNameAttribute != null) 41 | { 42 | fieldName = customNameAttribute.ConstructorArguments[0].Value.ToString()!; 43 | } 44 | 45 | _definitionCatalog.Add([fieldType]); 46 | 47 | return new BpfMapField( 48 | fieldDefinition, 49 | fieldType.HeaderName, 50 | fieldType.SourceFileName, 51 | new CFieldName(Utils.MakeValidCName(fieldName)), 52 | new IlFieldId(fieldDefinition.FullName), 53 | fieldType, 54 | true, 55 | []); 56 | } 57 | 58 | private class BpfMapField : CustomDefinedField 59 | { 60 | private readonly BpfMapCustomDefinedType _fieldType; 61 | 62 | public BpfMapField( 63 | FieldDefinition? originalField, 64 | HeaderName? declaredInHeader, 65 | CSourceFileName? declaredInSourceFileName, 66 | CFieldName nativeName, 67 | IlFieldId name, 68 | BpfMapCustomDefinedType fieldType, 69 | bool isGlobal, 70 | IReadOnlyList? referencedHeaders = null) 71 | : base(originalField, declaredInHeader, declaredInSourceFileName, nativeName, name, fieldType.IlName, 72 | isGlobal, referencedHeaders) 73 | { 74 | _fieldType = fieldType; 75 | 76 | 77 | } 78 | 79 | public override CustomCodeStatementSet GetCustomDeclaration() 80 | { 81 | return new CustomCodeStatementSet($"{_fieldType.NativeName} {NativeName}"); 82 | } 83 | } 84 | } 85 | 86 | public class FieldConversionMutator : IFieldConversionMutator 87 | { 88 | public IReadOnlySet RequiredTypes => new HashSet(); 89 | 90 | public void Mutate(FieldConversionInfo conversionInfo, FieldDefinition fieldDefinition) 91 | { 92 | if (!fieldDefinition.IsStatic || fieldDefinition.FieldType.FullName != typeof(BpfMap).FullName) 93 | { 94 | return; 95 | } 96 | 97 | conversionInfo.AttributeText = "SEC(\".maps\")"; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/Maps/BpfMapCustomDefinedType.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Dntc.Common; 3 | using Dntc.Common.Conversion; 4 | using Dntc.Common.Definitions; 5 | using Dntc.Common.Syntax.Statements; 6 | using Mono.Cecil; 7 | 8 | namespace DotnetEbpf.Core.Maps; 9 | 10 | public class BpfMapCustomDefinedType : CustomDefinedType 11 | { 12 | private static readonly Dictionary BpfMaps = new(); 13 | private MapType? _mapType; 14 | private int? _maxEntries; 15 | 16 | private BpfMapCustomDefinedType( 17 | IlTypeName ilTypeName, 18 | HeaderName? headerName, 19 | CSourceFileName? sourceFileName, 20 | CTypeName nativeName, 21 | IReadOnlyList otherReferencedTypes, 22 | IReadOnlyList referencedHeaders) 23 | : base(ilTypeName, headerName, sourceFileName, nativeName, otherReferencedTypes, referencedHeaders) 24 | { 25 | } 26 | 27 | public static BpfMapCustomDefinedType? CreateForField(FieldDefinition field) 28 | { 29 | if (!field.IsStatic || field.FieldType.FullName != typeof(BpfMap).FullName) 30 | { 31 | // Only global that are of type `BpfMap` are valid 32 | return null; 33 | } 34 | 35 | // Every global that's declared as a map needs its own unique type id 36 | var typeId = new IlTypeName($"{field.DeclaringType.FullName}_{field.Name}"); 37 | var mapType = BpfMaps.GetValueOrDefault(typeId); 38 | if (mapType != null) 39 | { 40 | return mapType; 41 | } 42 | 43 | var fieldNamespace = Utils.GetNamespace(field.DeclaringType); 44 | mapType = new BpfMapCustomDefinedType( 45 | typeId, 46 | Utils.GetHeaderName(fieldNamespace), 47 | Utils.GetSourceFileName(fieldNamespace), 48 | new CTypeName(Utils.MakeValidCName(typeId.Value)), 49 | [], 50 | [new HeaderName("vmlinux.h"), new HeaderName("")]); 51 | 52 | mapType.AssignTypeAttribute(field); 53 | mapType.AssignMaxEntriesAttribute(field); 54 | BpfMaps.Add(typeId, mapType); 55 | 56 | return mapType; 57 | } 58 | 59 | public override CustomCodeStatementSet? GetCustomTypeDeclaration(ConversionCatalog catalog) 60 | { 61 | var content = new StringBuilder(); 62 | content.AppendLine("typedef struct {"); 63 | 64 | if (_mapType != null) 65 | { 66 | content.AppendLine($"\t__uint(type, {_mapType.Value.ToAttributeString()});"); 67 | } 68 | 69 | if (_maxEntries != null) 70 | { 71 | content.AppendLine($"\t__uint(max_entries, {_maxEntries.Value});"); 72 | } 73 | 74 | content.AppendLine($"}} {NativeName};"); 75 | 76 | return new CustomCodeStatementSet(content.ToString()); 77 | } 78 | 79 | private void AssignTypeAttribute(FieldDefinition fieldDefinition) 80 | { 81 | var attribute = fieldDefinition.CustomAttributes 82 | .SingleOrDefault(x => x.AttributeType.FullName == typeof(MapTypeAttribute).FullName); 83 | 84 | if (attribute == null) 85 | { 86 | return; 87 | } 88 | 89 | var type = (MapType)attribute.ConstructorArguments[0].Value; 90 | _mapType = type; 91 | } 92 | 93 | private void AssignMaxEntriesAttribute(FieldDefinition fieldDefinition) 94 | { 95 | var attribute = fieldDefinition.CustomAttributes 96 | .SingleOrDefault(x => x.AttributeType.FullName == typeof(MaxEntriesAttribute).FullName); 97 | 98 | if (attribute == null) 99 | { 100 | return; 101 | } 102 | 103 | var count = (int)attribute.ConstructorArguments[0].Value; 104 | _maxEntries = count; 105 | } 106 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/Maps/MapType.cs: -------------------------------------------------------------------------------- 1 | namespace DotnetEbpf.Core.Maps; 2 | 3 | public enum MapType 4 | { 5 | RingBuffer, 6 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/Maps/MapTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace DotnetEbpf.Core.Maps; 2 | 3 | /// 4 | /// Defines the type of map that should be created 5 | /// 6 | [AttributeUsage(AttributeTargets.Field)] 7 | public class MapTypeAttribute(MapType type) : Attribute 8 | { 9 | 10 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/Maps/MapTypeUtils.cs: -------------------------------------------------------------------------------- 1 | namespace DotnetEbpf.Core.Maps; 2 | 3 | public static class MapTypeUtils 4 | { 5 | /// 6 | /// Converts the map type enumeration value into the eBPF value 7 | /// 8 | public static string ToAttributeString(this MapType type) 9 | { 10 | switch (type) 11 | { 12 | case MapType.RingBuffer: 13 | return "BPF_MAP_TYPE_RINGBUF"; 14 | 15 | default: 16 | throw new NotSupportedException(type.ToString()); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/Maps/MaxEntriesAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace DotnetEbpf.Core.Maps; 2 | 3 | /// 4 | /// Specifies the number of entries a map can hold 5 | /// 6 | [AttributeUsage(AttributeTargets.Field)] 7 | public class MaxEntriesAttribute(int count) : Attribute 8 | { 9 | 10 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Core/Plugin.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Common; 2 | using Dntc.Common.Definitions; 3 | using DotnetEbpf.Core.Maps; 4 | 5 | namespace DotnetEbpf.Core; 6 | 7 | public class Plugin : ITranspilerPlugin 8 | { 9 | public bool BypassBuiltInNativeDefinitions => true; 10 | 11 | public void Customize(TranspilerContext context) 12 | { 13 | AddNativeTypes(context); 14 | context.Definers.Add(new BpfMap.FieldDefiner(context.DefinitionCatalog)); 15 | context.ConversionInfoCreator.AddMethodMutator(new BpfSectionAttribute.Mutator()); 16 | context.ConversionInfoCreator.AddFieldMutator(new BpfSectionAttribute.Mutator()); 17 | context.ConversionInfoCreator.AddFieldMutator(new BpfLicenseAttribute.Mutator(context.ConversionCatalog)); 18 | context.ConversionInfoCreator.AddFieldMutator(new BpfMap.FieldConversionMutator()); 19 | context.ConversionInfoCreator.AddFieldMutator(new BpfArrayAttribute.Mutator(context.ConversionCatalog)); 20 | } 21 | 22 | private static void AddNativeTypes(TranspilerContext context) 23 | { 24 | context.DefinitionCatalog.Add([ 25 | new NativeDefinedType( 26 | new IlTypeName(typeof(int).FullName!), 27 | new HeaderName("vmlinux.h"), 28 | new CTypeName("__s32"), 29 | []), 30 | 31 | new NativeDefinedType( 32 | new IlTypeName(typeof(uint).FullName!), 33 | new HeaderName("vmlinux.h"), 34 | new CTypeName("__u32"), 35 | []), 36 | 37 | new NativeDefinedType( 38 | new IlTypeName(typeof(long).FullName!), 39 | new HeaderName("vmlinux.h"), 40 | new CTypeName("__s64"), 41 | []), 42 | 43 | new NativeDefinedType( 44 | new IlTypeName(typeof(char).FullName!), 45 | null, 46 | new CTypeName("char"), 47 | []), 48 | 49 | new NativeDefinedType( 50 | new IlTypeName(typeof(string).FullName!), 51 | null, 52 | new CTypeName("char*"), 53 | []), 54 | 55 | new NativeDefinedType( 56 | new IlTypeName(typeof(void).FullName!), 57 | null, 58 | new CTypeName("void"), 59 | []), 60 | 61 | new NativeDefinedType( 62 | new IlTypeName(typeof(sbyte).FullName!), 63 | null, 64 | new CTypeName("__s8"), 65 | []), 66 | 67 | new NativeDefinedType( 68 | new IlTypeName(typeof(byte).FullName!), 69 | null, 70 | new CTypeName("__u8"), 71 | []), 72 | 73 | new NativeDefinedType( 74 | new IlTypeName(typeof(bool).FullName!), 75 | null, 76 | new CTypeName("bool"), 77 | []), 78 | 79 | new NativeDefinedType( 80 | new IlTypeName(typeof(ulong).FullName!), 81 | null, 82 | new CTypeName("__u64"), 83 | []), 84 | 85 | new NativeDefinedType( 86 | new IlTypeName(typeof(Type).FullName!), 87 | null, 88 | new CTypeName(""), // just needs to exist for typeof() support 89 | []), 90 | ]); 91 | } 92 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Examples/DotnetEbpf.Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Makefile 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Examples/Minimal.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Attributes; 2 | using DotnetEbpf.Core; 3 | 4 | namespace DotnetEbpf.Examples; 5 | 6 | public static class Minimal 7 | { 8 | [BpfLicense(BpfLicenseAttribute.DualBsdGpl)] 9 | public static string? License; 10 | 11 | [CustomFieldName("my_pid")] 12 | public static int MyPid; 13 | 14 | /// 15 | /// Prints the process that triggered the BPF trace point 16 | /// 17 | [CustomFunctionName("handle_tp")] 18 | [BpfSection("tp/syscalls/sys_enter_write")] 19 | public static int HandleTracePoint(ref CTypes.CVoid ctx) 20 | { 21 | var pid = (int)(BpfUtils.GetCurrentPidTgid() >> 32); 22 | if (pid == MyPid) 23 | { 24 | // only activate for the process that opened the bpf application 25 | BpfUtils.Printk("BPF triggered from PID %d.\n", pid); 26 | } 27 | 28 | return 0; 29 | } 30 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Examples/Profile.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Attributes; 2 | using DotnetEbpf.Core; 3 | using DotnetEbpf.Core.Maps; 4 | 5 | namespace DotnetEbpf.Examples; 6 | 7 | public static class Profile 8 | { 9 | private const int TaskCommLen = 16; 10 | private const int MaxStackDepth = 128; 11 | 12 | [BpfLicense(BpfLicenseAttribute.DualBsdGpl)] 13 | public static string? License; 14 | 15 | [NativeType("struct stacktrace_event", "../profile.h")] 16 | public struct StackTraceEvent 17 | { 18 | [CustomFieldName("pid")] 19 | public uint Pid; 20 | 21 | [CustomFieldName("cpu_id")] 22 | public uint CpuId; 23 | 24 | [CustomFieldName("comm")] 25 | [BpfArray(TaskCommLen)] 26 | public byte[] Comm; 27 | 28 | [CustomFieldName("kstack_sz")] 29 | public int KStackSize; 30 | 31 | [CustomFieldName("ustack_sz")] 32 | public int UStackSize; 33 | 34 | [CustomFieldName("kstack")] 35 | [BpfArray(MaxStackDepth)] 36 | public ulong[] KStack; 37 | 38 | [CustomFieldName("ustack")] 39 | [BpfArray(MaxStackDepth)] 40 | public ulong[] UStack; 41 | } 42 | 43 | [MapType(MapType.RingBuffer)] 44 | [MaxEntries(256 * 1024)] 45 | [CustomFieldName("events")] 46 | public static BpfMap Events; 47 | 48 | [BpfSection("perf_event")] 49 | [CustomFunctionName("profile")] 50 | public static unsafe int RunProfile(ref CTypes.CVoid context) 51 | { 52 | var pid = (uint)(BpfUtils.GetCurrentPidTgid() >> 32); 53 | var cpuId = BpfUtils.GetSmpProcessorId(); 54 | 55 | var profileEvent = BpfRingBuffer.Reserve( 56 | ref Events, 57 | CUtils.SizeOf(typeof(StackTraceEvent)), 58 | 0); 59 | 60 | if (profileEvent == null) 61 | { 62 | return 1; 63 | } 64 | 65 | profileEvent->Pid = pid; 66 | profileEvent->CpuId = cpuId; 67 | 68 | var code = BpfUtils.GetCurrentComm(ref profileEvent->Comm, TaskCommLen); 69 | if (code != 0) 70 | { 71 | profileEvent->Comm[0] = 0; 72 | } 73 | 74 | profileEvent->KStackSize = (int)BpfUtils.GetStack(ref context, ref profileEvent->KStack, MaxStackDepth, 0); 75 | profileEvent->UStackSize = (int)BpfUtils.GetStack( 76 | ref context, 77 | ref profileEvent->UStack, 78 | MaxStackDepth, 79 | (int)BpfFlags.UserStack); 80 | 81 | BpfRingBuffer.Submit(profileEvent, 0); 82 | 83 | return 0; 84 | } 85 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Examples/Uprobe.cs: -------------------------------------------------------------------------------- 1 | using Dntc.Attributes; 2 | using DotnetEbpf.Core; 3 | 4 | namespace DotnetEbpf.Examples; 5 | 6 | public static class Uprobe 7 | { 8 | private const string TracingHeaders = "vmlinux.h,"; 9 | 10 | [BpfLicense(BpfLicenseAttribute.DualBsdGpl)] 11 | public static string? License; 12 | 13 | [BpfSection("uprobe")] 14 | [CustomDeclaration("int BPF_KPROBE(uprobe_add, int a, int b)", null, TracingHeaders)] 15 | public static int UProbeAdd(int a, int b) 16 | { 17 | BpfUtils.Printk("uprobed_add ENTRY: a = %d, b = %d", a, b); 18 | return 0; 19 | } 20 | 21 | [BpfSection("uretprobe")] 22 | [CustomDeclaration("int BPF_KRETPROBE(uretprobe_add, int ret)", null, TracingHeaders)] 23 | public static int URetProbeAdd(int ret) 24 | { 25 | BpfUtils.Printk("uprobed_add EXIT: return = %d", ret); 26 | return 0; 27 | } 28 | 29 | [BpfSection("uprobe//proc/self/exe:uprobed_sub")] 30 | [CustomDeclaration("int BPF_KPROBE(uprobe_sub, int a, int b)", null, TracingHeaders)] 31 | public static int UProbeSub(int a, int b) 32 | { 33 | BpfUtils.Printk("uprobed_sub ENTRY: a = %d, b = %d", a, b); 34 | return 0; 35 | } 36 | 37 | [BpfSection("uretprobe//proc/self/exe:uprobed_sub")] 38 | [CustomDeclaration("int BPF_KRETPROBE(uretprobe_sub, int ret)", null, TracingHeaders)] 39 | public static int URetProbeSub(int ret) 40 | { 41 | BpfUtils.Printk("uprobed_sub EXIT: return = %d", ret); 42 | return 0; 43 | } 44 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Examples/minimal.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "AssemblyDirectory": "../artifacts/bin/DotnetEbpf.Examples/release", 3 | "AssembliesToLoad": [ 4 | "DotnetEbpf.Core.dll", 5 | "DotnetEbpf.Examples.dll" 6 | ], 7 | "PluginAssembly": "DotnetEbpf.Core.dll", 8 | "OutputDirectory": "../../../examples/.output", 9 | "SingleGeneratedSourceFileName": "minimal.bpf.c", 10 | "MethodsToTranspile": [ 11 | "System.Int32 DotnetEbpf.Examples.Minimal::HandleTracePoint(DotnetEbpf.Core.CTypes/CVoid&)" 12 | ], 13 | "GlobalsToTranspile": [ 14 | "System.String DotnetEbpf.Examples.Minimal::License" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Examples/profile.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "AssemblyDirectory": "../artifacts/bin/DotnetEbpf.Examples/release", 3 | "AssembliesToLoad": [ 4 | "DotnetEbpf.Core.dll", 5 | "DotnetEbpf.Examples.dll" 6 | ], 7 | "PluginAssembly": "DotnetEbpf.Core.dll", 8 | "OutputDirectory": "../../../examples/.output", 9 | "SingleGeneratedSourceFileName": "profile.bpf.c", 10 | "MethodsToTranspile": [ 11 | "System.Int32 DotnetEbpf.Examples.Profile::RunProfile(DotnetEbpf.Core.CTypes/CVoid&)" 12 | ], 13 | "GlobalsToTranspile": [ 14 | "System.String DotnetEbpf.Examples.Profile::License", 15 | "DotnetEbpf.Core.Maps.BpfMap DotnetEbpf.Examples.Profile::Events" 16 | ] 17 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.Examples/uprobe.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "AssemblyDirectory": "../artifacts/bin/DotnetEbpf.Examples/release", 3 | "AssembliesToLoad": [ 4 | "DotnetEbpf.Core.dll", 5 | "DotnetEbpf.Examples.dll" 6 | ], 7 | "PluginAssembly": "DotnetEbpf.Core.dll", 8 | "OutputDirectory": "../../../examples/.output", 9 | "SingleGeneratedSourceFileName": "uprobe.bpf.c", 10 | "MethodsToTranspile": [ 11 | "System.Int32 DotnetEbpf.Examples.Uprobe::UProbeAdd(System.Int32,System.Int32)", 12 | "System.Int32 DotnetEbpf.Examples.Uprobe::UProbeSub(System.Int32,System.Int32)", 13 | "System.Int32 DotnetEbpf.Examples.Uprobe::URetProbeAdd(System.Int32)", 14 | "System.Int32 DotnetEbpf.Examples.Uprobe::URetProbeSub(System.Int32)" 15 | ], 16 | "GlobalsToTranspile": [ 17 | "System.String DotnetEbpf.Examples.Uprobe::License" 18 | ] 19 | } -------------------------------------------------------------------------------- /src/DotnetEbpf/DotnetEbpf.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotnetEbpf.Core", "DotnetEbpf.Core\DotnetEbpf.Core.csproj", "{B666BD32-E8D4-4758-9FC8-4CF89ACBD18A}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotnetEbpf.Examples", "DotnetEbpf.Examples\DotnetEbpf.Examples.csproj", "{AD0CE8F0-983F-4890-BC60-0E0FA792BBAF}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dntc", "Dntc", "{4CE12458-09BD-47E2-BF3A-5F453EC3899A}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dntc.Attributes", "..\..\dntc\Dntc.Attributes\Dntc.Attributes.csproj", "{6F0A2FFA-4F0E-4D5A-8EDF-51C297625331}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dntc.Common", "..\..\dntc\Dntc.Common\Dntc.Common.csproj", "{F52017BE-C392-4915-866B-C0045DD6DEC7}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dntc.Cli", "..\..\dntc\Dntc.Cli\Dntc.Cli.csproj", "{0215AB53-7C38-4064-8326-A1008630A36D}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {B666BD32-E8D4-4758-9FC8-4CF89ACBD18A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {B666BD32-E8D4-4758-9FC8-4CF89ACBD18A}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {B666BD32-E8D4-4758-9FC8-4CF89ACBD18A}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {B666BD32-E8D4-4758-9FC8-4CF89ACBD18A}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {AD0CE8F0-983F-4890-BC60-0E0FA792BBAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {AD0CE8F0-983F-4890-BC60-0E0FA792BBAF}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {AD0CE8F0-983F-4890-BC60-0E0FA792BBAF}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {AD0CE8F0-983F-4890-BC60-0E0FA792BBAF}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {6F0A2FFA-4F0E-4D5A-8EDF-51C297625331}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {6F0A2FFA-4F0E-4D5A-8EDF-51C297625331}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {6F0A2FFA-4F0E-4D5A-8EDF-51C297625331}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {6F0A2FFA-4F0E-4D5A-8EDF-51C297625331}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {F52017BE-C392-4915-866B-C0045DD6DEC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {F52017BE-C392-4915-866B-C0045DD6DEC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {F52017BE-C392-4915-866B-C0045DD6DEC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {F52017BE-C392-4915-866B-C0045DD6DEC7}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {0215AB53-7C38-4064-8326-A1008630A36D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {0215AB53-7C38-4064-8326-A1008630A36D}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {0215AB53-7C38-4064-8326-A1008630A36D}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {0215AB53-7C38-4064-8326-A1008630A36D}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {6F0A2FFA-4F0E-4D5A-8EDF-51C297625331} = {4CE12458-09BD-47E2-BF3A-5F453EC3899A} 44 | {F52017BE-C392-4915-866B-C0045DD6DEC7} = {4CE12458-09BD-47E2-BF3A-5F453EC3899A} 45 | {0215AB53-7C38-4064-8326-A1008630A36D} = {4CE12458-09BD-47E2-BF3A-5F453EC3899A} 46 | EndGlobalSection 47 | EndGlobal 48 | --------------------------------------------------------------------------------