diff --git a/src/.gitignore b/src/.gitignore index 638593578c707a28512260f59a2cc5bb6ce1a40e..403f0b1d3f4a419b73c238a20f81fe1d42b7842f 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,4 @@ /.output /bootstrap /minimal +/uprobe diff --git a/src/Makefile b/src/Makefile index deb899c062bca116a891ac878058daa952d2a221..8e575729b272702e75814968cef32563efce60de 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,7 +9,7 @@ INCLUDES := -I$(OUTPUT) CFLAGS := -g -Wall ARCH := $(shell uname -m | sed 's/x86_64/x86/') -APPS = minimal bootstrap +APPS = minimal bootstrap uprobe # Get Clang's default includes on this system. We'll explicitly add these dirs # to the includes list when compiling with `-target bpf` because otherwise some diff --git a/src/uprobe.bpf.c b/src/uprobe.bpf.c new file mode 100644 index 0000000000000000000000000000000000000000..b41afa89ceaf946a95c1cd9d82ab49ff8681008f --- /dev/null +++ b/src/uprobe.bpf.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright (c) 2020 Facebook */ +#include <linux/bpf.h> +#include <linux/ptrace.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +SEC("uprobe/func") +int BPF_KPROBE(uprobe, int a, int b) +{ + bpf_printk("UPROBE ENTRY: a = %d, b = %d\n", a, b); + return 0; +} + +SEC("uretprobe/func") +int BPF_KRETPROBE(uretprobe, int ret) +{ + bpf_printk("UPROBE EXIT: return = %d\n", ret); + return 0; +} diff --git a/src/uprobe.c b/src/uprobe.c new file mode 100644 index 0000000000000000000000000000000000000000..ff804ad224c18779261a1ece1c952c4235f1310a --- /dev/null +++ b/src/uprobe.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/resource.h> +#include <bpf/libbpf.h> +#include "uprobe.skel.h" + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + return vfprintf(stderr, format, args); +} + +static void bump_memlock_rlimit(void) +{ + struct rlimit rlim_new = { + .rlim_cur = RLIM_INFINITY, + .rlim_max = RLIM_INFINITY, + }; + + if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) { + fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n"); + exit(1); + } +} + +/* Find process's base load address. We use /proc/self/maps for that, + * searching for the first executable (r-xp) memory mapping: + * + * 5574fd254000-5574fd258000 r-xp 00002000 fd:01 668759 /usr/bin/cat + * ^^^^^^^^^^^^ ^^^^^^^^ + * + * Subtracting that region's offset (4th column) from its absolute start + * memory address (1st column) gives us the process's base load address. + */ +static long get_base_addr() { + size_t start, offset; + char buf[256]; + FILE *f; + + f = fopen("/proc/self/maps", "r"); + if (!f) + return -errno; + + while (fscanf(f, "%zx-%*x %s %zx %*[^\n]\n", &start, buf, &offset) == 3) { + if (strcmp(buf, "r-xp") == 0) { + fclose(f); + return start - offset; + } + } + + fclose(f); + return -1; +} + +/* It's a global function to make sure compiler doesn't inline it. */ +int uprobed_function(int a, int b) +{ + return a + b; +} + +int main(int argc, char **argv) +{ + struct uprobe_bpf *skel; + long base_addr, uprobe_offset; + int err, i; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything */ + bump_memlock_rlimit(); + + /* Load and verify BPF application */ + skel = uprobe_bpf__open_and_load(); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + base_addr = get_base_addr(); + if (base_addr < 0) { + fprintf(stderr, "Failed to determine process's load address\n"); + err = base_addr; + goto cleanup; + } + + /* uprobe/uretprobe expects relative offset of the function to attach + * to. This offset is relateve to the process's base load address. So + * easy way to do this is to take an absolute address of the desired + * function and substract base load address from it. If we were to + * parse ELF to calculate this function, we'd need to add .text + * section offset and function's offset within .text ELF section. + */ + uprobe_offset = (long)&uprobed_function - base_addr; + + /* Attach tracepoint handler */ + skel->links.uprobe = bpf_program__attach_uprobe(skel->progs.uprobe, + false /* not uretprobe */, + 0 /* self pid */, + "/proc/self/exe", + uprobe_offset); + err = libbpf_get_error(skel->links.uprobe); + if (err) { + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + + /* we can also attach uprobe/uretprobe to any existing or future + * processes that use the same binary executable; to do that we need + * to specify -1 as PID, as we do here + */ + skel->links.uretprobe = bpf_program__attach_uprobe(skel->progs.uretprobe, + true /* uretprobe */, + -1 /* any pid */, + "/proc/self/exe", + uprobe_offset); + err = libbpf_get_error(skel->links.uretprobe); + if (err) { + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + + printf("Successfully started!\n"); + + for (i = 0; ; i++) { + /* trigger our BPF programs */ + fprintf(stderr, "."); + uprobed_function(i, i + 1); + sleep(1); + } + +cleanup: + uprobe_bpf__destroy(skel); + return -err; +}