From eeda781e4f4ae7c475eb5168886ab44453fae09a Mon Sep 17 00:00:00 2001 From: yunwei37 Date: Wed, 4 Dec 2024 16:05:39 +0000 Subject: [PATCH] add verify userspace program --- src/38-btf-uprobe/.gitignore | 1 + src/38-btf-uprobe/Makefile | 9 +- src/38-btf-uprobe/README.zh.md | 23 +- src/38-btf-uprobe/examples/.gitignore | 1 + src/38-btf-uprobe/examples/Makefile | 14 +- .../examples/btf-base-complete.c | 54 +++ src/38-btf-uprobe/test-verify/README.md | 451 ++++++++++++++++++ .../test-verify/minimal/.gitignore | 14 + .../test-verify/minimal/Makefile | 144 ++++++ .../test-verify/minimal/README.md | 125 +++++ .../test-verify/minimal/uprobe.bpf.c | 13 + .../test-verify/minimal/uprobe.c | 69 +++ .../test-verify/minimal/victim.c | 19 + .../test-verify/module/.gitignore | 23 + src/38-btf-uprobe/test-verify/module/Makefile | 11 + .../test-verify/module/README.md | 134 ++++++ .../test-verify/module/compact.h | 11 + src/38-btf-uprobe/test-verify/module/hello.c | 97 ++++ .../test-verify/verify-failed-no-btf/Makefile | 142 ++++++ .../verify-failed-no-btf/merge-btf.c | 63 +++ .../verify-failed-no-btf/uprobe.bpf.c | 32 ++ .../test-verify/verify-failed-no-btf/uprobe.c | 83 ++++ .../verify-failed-no-btf/uprobe_failed.bpf.c | 32 ++ .../verify-failed-no-btf/uprobe_failed.c | 83 ++++ .../test-verify/verify-memory-access/Makefile | 142 ++++++ .../verify-memory-access/merge-btf.c | 63 +++ .../verify-memory-access/uprobe.bpf.c | 47 ++ .../test-verify/verify-memory-access/uprobe.c | 83 ++++ .../verify-memory-access/uprobe_failed.bpf.c | 48 ++ .../verify-memory-access/uprobe_failed.c | 83 ++++ .../verify-resource-allocation/Makefile | 142 ++++++ .../verify-resource-allocation/merge-btf.c | 63 +++ .../verify-resource-allocation/ufunc.bpf.h | 98 ++++ .../verify-resource-allocation/uprobe.bpf.c | 57 +++ .../verify-resource-allocation/uprobe.c | 83 ++++ .../uprobe_failed.bpf.c | 51 ++ .../uprobe_failed.c | 83 ++++ 37 files changed, 2686 insertions(+), 5 deletions(-) create mode 100644 src/38-btf-uprobe/examples/btf-base-complete.c create mode 100644 src/38-btf-uprobe/test-verify/README.md create mode 100644 src/38-btf-uprobe/test-verify/minimal/.gitignore create mode 100644 src/38-btf-uprobe/test-verify/minimal/Makefile create mode 100644 src/38-btf-uprobe/test-verify/minimal/README.md create mode 100644 src/38-btf-uprobe/test-verify/minimal/uprobe.bpf.c create mode 100644 src/38-btf-uprobe/test-verify/minimal/uprobe.c create mode 100644 src/38-btf-uprobe/test-verify/minimal/victim.c create mode 100644 src/38-btf-uprobe/test-verify/module/.gitignore create mode 100644 src/38-btf-uprobe/test-verify/module/Makefile create mode 100644 src/38-btf-uprobe/test-verify/module/README.md create mode 100644 src/38-btf-uprobe/test-verify/module/compact.h create mode 100644 src/38-btf-uprobe/test-verify/module/hello.c create mode 100644 src/38-btf-uprobe/test-verify/verify-failed-no-btf/Makefile create mode 100644 src/38-btf-uprobe/test-verify/verify-failed-no-btf/merge-btf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.bpf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.c create mode 100644 src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.bpf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.c create mode 100644 src/38-btf-uprobe/test-verify/verify-memory-access/Makefile create mode 100644 src/38-btf-uprobe/test-verify/verify-memory-access/merge-btf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.bpf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.c create mode 100644 src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.bpf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.c create mode 100644 src/38-btf-uprobe/test-verify/verify-resource-allocation/Makefile create mode 100644 src/38-btf-uprobe/test-verify/verify-resource-allocation/merge-btf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-resource-allocation/ufunc.bpf.h create mode 100644 src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.bpf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.c create mode 100644 src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.bpf.c create mode 100644 src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.c diff --git a/src/38-btf-uprobe/.gitignore b/src/38-btf-uprobe/.gitignore index 743f134c..a01af291 100644 --- a/src/38-btf-uprobe/.gitignore +++ b/src/38-btf-uprobe/.gitignore @@ -2,3 +2,4 @@ uprobe merge-btf *.btf +uprobe_failed diff --git a/src/38-btf-uprobe/Makefile b/src/38-btf-uprobe/Makefile index dc1a02d6..0da8c3b0 100644 --- a/src/38-btf-uprobe/Makefile +++ b/src/38-btf-uprobe/Makefile @@ -66,7 +66,14 @@ $(call allow-override,CC,$(CROSS_COMPILE)cc) $(call allow-override,LD,$(CROSS_COMPILE)ld) .PHONY: all -all: $(APPS) merge-btf +all: $(APPS) merge-btf all-btf + +.PHONY: all-btf +all-btf: merge-btf + make -C examples + ./merge-btf /sys/kernel/btf/vmlinux examples/base-new.btf target-base-new.btf + ./merge-btf /sys/kernel/btf/vmlinux examples/base.btf target-base.btf + ./merge-btf /sys/kernel/btf/vmlinux examples/base-complete.btf target-base-complete.btf .PHONY: clean clean: diff --git a/src/38-btf-uprobe/README.zh.md b/src/38-btf-uprobe/README.zh.md index b45b35e1..359ee45d 100644 --- a/src/38-btf-uprobe/README.zh.md +++ b/src/38-btf-uprobe/README.zh.md @@ -112,7 +112,7 @@ int main(int argc, char **argv) { 我们可以使用pahole和clang来生成每个版本的btf。制作示例并生成btf: ```sh -make -C example # it's like: pahole --btf_encode_detached base.btf btf-base.o +make -C examples # it's like: pahole --btf_encode_detached base.btf btf-base.o ``` 然后我们执行eBPF程序和用户空间程序。 对于 `btf-base`: @@ -197,6 +197,27 @@ char LICENSE[] SEC("license") = "Dual BSD/GPL"; `struct data`的记录在eBPF程序中被保留下来。然后,我们可以使用 `btf-base.btf`来编译eBPF程序。 +这时,如果未提供用户态的 BTF 信息,会导致验证失败: + +```console +# ./uprobe examples/btf-base +..... +; int BPF_UPROBE(add_test, struct data *d) @ uprobe.bpf.c:23 +0: (79) r6 = *(u64 *)(r1 +112) ; R1=ctx() R6_w=scalar() +1: (b7) r7 = 0 ; R7_w=0 +; int a = 0, c = 0; @ uprobe.bpf.c:25 +2: (63) *(u32 *)(r10 -4) = r7 ; R7_w=0 R10=fp0 fp-8=0000???? +3: (63) *(u32 *)(r10 -8) = r7 ; R7_w=0 R10=fp0 fp-8=00000 +4: +failed to resolve CO-RE relocation [17] struct data.a (0:0 @ offset 0) +processed 5 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 +-- END PROG LOAD LOG -- +libbpf: prog 'add_test': failed to load: -22 +libbpf: failed to load object 'uprobe_bpf' +libbpf: failed to load BPF skeleton 'uprobe_bpf': -22 +Failed to load and verify BPF skeleton +``` + 将用户btf与内核btf合并,这样我们就有了一个完整的内核和用户空间的btf: ```sh diff --git a/src/38-btf-uprobe/examples/.gitignore b/src/38-btf-uprobe/examples/.gitignore index d36e43a0..f4f43997 100644 --- a/src/38-btf-uprobe/examples/.gitignore +++ b/src/38-btf-uprobe/examples/.gitignore @@ -2,3 +2,4 @@ *.btf btf-base btf-base-new +btf-base-complete diff --git a/src/38-btf-uprobe/examples/Makefile b/src/38-btf-uprobe/examples/Makefile index d99c09df..4f404a8b 100644 --- a/src/38-btf-uprobe/examples/Makefile +++ b/src/38-btf-uprobe/examples/Makefile @@ -7,7 +7,7 @@ BPF_SRCS = $(wildcard *.bpf.c) # BPF object files BPF_OBJS = $(BPF_SRCS:.c=.o) -all: $(BPF_OBJS) base.btf btf-base btf-base-new base-new.btf +all: $(BPF_OBJS) base.btf btf-base btf-base-new btf-base-complete base-new.btf base-complete.btf %.bpf.o: %.bpf.c $(BPF_CC) $(BPF_CFLAGS) $< -o $@ @@ -18,9 +18,15 @@ btf-base.o: btf-base.c btf-base-new.o: btf-base-new.c clang -g -c btf-base-new.c -o btf-base-new.o +btf-base-complete.o: btf-base-complete.c + clang -g -c btf-base-complete.c -o btf-base-complete.o + base.btf: btf-base.o pahole --btf_encode_detached base.btf btf-base.o +base-complete.btf: btf-base-complete.o + pahole --btf_encode_detached base-complete.btf btf-base-complete.o + base-new.btf: btf-base-new.o pahole --btf_encode_detached base-new.btf btf-base-new.o @@ -29,7 +35,9 @@ btf-base: btf-base.o btf-base-new: btf-base-new.o clang -g btf-base-new.o -o btf-base-new - + +btf-base-complete: btf-base-complete.o + clang -g btf-base-complete.o -o btf-base-complete clean: - rm -f *.o *.btf btf-base btf-base-new + rm -f *.o *.btf btf-base btf-base-new btf-base-complete diff --git a/src/38-btf-uprobe/examples/btf-base-complete.c b/src/38-btf-uprobe/examples/btf-base-complete.c new file mode 100644 index 00000000..19cf5036 --- /dev/null +++ b/src/38-btf-uprobe/examples/btf-base-complete.c @@ -0,0 +1,54 @@ +#include +#include + +struct deep_memory_block { + int a; + char b[10]; +}; + +struct inner_memory_block { + int a; + char b[10]; + struct deep_memory_block *deep; +}; + +struct data { + int a; + int b; + int c; + int d; + // represent a pointer to a memory block + struct inner_memory_block *inner; +}; + +// hook function to be called by eBPF program +int add_test(struct data *d) { + return d->a + d->c; +} + +struct data* my_alloc_data() { + printf("my_alloc_data\n"); + struct data *d = (struct data*)calloc(1, sizeof(struct data)); + d->inner = (struct inner_memory_block*)calloc(1, sizeof(struct inner_memory_block)); + d->inner->deep = (struct deep_memory_block*)calloc(1, sizeof(struct deep_memory_block)); + return d; +} + +void my_free_data(struct data *d) { + printf("my_free_data\n"); + free(d->inner->deep); + free(d->inner); + free(d); +} + +int main(int argc, char **argv) { + struct data *d = my_alloc_data(); + d->a = 1; + d->c = 3; + d->b = 5; + d->d = 7; + printf("add_test(&d) = %d\n", add_test(d)); + my_free_data(d); + return 0; +} + diff --git a/src/38-btf-uprobe/test-verify/README.md b/src/38-btf-uprobe/test-verify/README.md new file mode 100644 index 00000000..80bd3344 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/README.md @@ -0,0 +1,451 @@ +# using BTF to verify userspace eBPF extensions + +Here we will show how to use the type information of userspace application to verify the eBPF program which will access the userspace memory(include valid or invalid data structure memory access, pointer access, etc), and how to verify the resource allocation and deallocation of a psudo ufuncs(Userspace Function) which can be accessed by eBPF program in bpftime. + +This is using kernel eBPF verifier to verify userspace eBPF extensions, the userspace eBPF extensions can be uprobe or other eBPF programs, it can be run in the kernel space or in bpftime. + +## Usage + +The application developer or the extension user who wants to use the eBPF program for userspace application needs to provide: + +1. The BTF information of the userspace application. It can be generated by the compiler or from the DWARF information. +2. The psudo ufunc information if the eBPF program will access the resource allocation and deallocation function of userspace application. + +By reusing the kernel verifier, we can provide: + +1. CO-RE relocation, if the userspace application has different version, and the struct/function type is different. +2. Type checking and variable access checking for the userspace extension. +3. Memory access checking for the userspace extension, so no need to copy the data. +4. Resource allocation and deallocation function access checking. + +## The BTF format + +The BTF format is a binary format that contains the type information of the progam. + +The based program can be found in the [examples](../examples) directory, they represent the different version of host userspace applications, include: + +- `btf-base`: the base version of `btf-base-complete`, which contain the type information of `struct data` the same as [verify-failed-no-btf/uprobe.bpf.c](verify-failed-no-btf/uprobe.bpf.c). +- `btf-base-new`: the new version of `btf-base`, which contains the type information of `struct data` different from `btf-base` and [verify-failed-btf/uprobe.bpf.c](verify-failed-btf/uprobe.bpf.c). We can use it to test CO-RE relocation. +- `btf-base-complete`: the complete version of `btf-base`, which contains all the types that are used in the eBPF program, and a resource allocation and deallocation function. + +run `make -C ../` in this directory to generate the BTF files. + +## Verify struct data access + +The userspace application has data struct like + +version 1: + +```c +struct data { + int a; + int c; + int d; +}; +``` + +or + +version 2: + +```c +struct data { + int a; + int b; + int c; + int d; +}; +``` + + +### 1.1 Verify failed without BTF for userspace application + +The eBPF program has + +```c +struct data { + int a; + int c; + int d; +}; + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int a = 0, c = 0; + bpf_probe_read_user(&a, sizeof(a), &d->a); + bpf_probe_read_user(&c, sizeof(c), &d->c); + bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c); + return a + c; +} +``` + +If no BTF for userspace application is provided, the verification will fail. + +```console +$ sudo ./verify-failed-no-btf/uprobe ../examples/btf-base + +libbpf: prog 'add_test': -- BEGIN PROG LOAD LOG -- +0: R1=ctx() R10=fp0 +; int BPF_UPROBE(add_test, struct data *d) @ uprobe.bpf.c:23 +0: (79) r6 = *(u64 *)(r1 +112) ; R1=ctx() R6_w=scalar() +1: (b7) r7 = 0 ; R7_w=0 +; int a = 0, c = 0; @ uprobe.bpf.c:25 +2: (63) *(u32 *)(r10 -4) = r7 ; R7_w=0 R10=fp0 fp-8=0000???? +3: (63) *(u32 *)(r10 -8) = r7 ; R7_w=0 R10=fp0 fp-8=00000 +4: +failed to resolve CO-RE relocation [17] struct data.a (0:0 @ offset 0) +processed 5 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 +-- END PROG LOAD LOG -- +libbpf: prog 'add_test': failed to load: -22 +libbpf: failed to load object 'uprobe_bpf' +libbpf: failed to load BPF skeleton 'uprobe_bpf': -22 +Failed to load and verify BPF skeleton +``` + +### 1.2 Verify success with BTF for struct access and relocation + +See [../README.md](../README.md) for more details. + +```console +$ sudo ./verify-failed-no-btf/uprobe ../examples/btf-base ../target-base.btf + +libbpf: prog 'add_test': relo #0: [2] struct pt_regs.di (0:14 @ offset 112) +libbpf: prog 'add_test': relo #0: matching candidate #0 [88] struct pt_regs.di (0:14 @ offset 112) +libbpf: prog 'add_test': relo #0: patched insn #0 (LDX/ST/STX) off 112 -> 112 +libbpf: CO-RE relocating [17] struct data: found target candidate [143229] struct data in [vmlinux] +libbpf: prog 'add_test': relo #1: [17] struct data.a (0:0 @ offset 0) +libbpf: prog 'add_test': relo #1: matching candidate #0 [143229] struct data.a (0:0 @ offset 0) +libbpf: prog 'add_test': relo #1: patched insn #4 (ALU/ALU64) imm 0 -> 0 +libbpf: prog 'add_test': relo #2: [17] struct data.c (0:1 @ offset 4) +libbpf: prog 'add_test': relo #2: matching candidate #0 [143229] struct data.c (0:1 @ offset 4) +libbpf: prog 'add_test': relo #2: patched insn #11 (ALU/ALU64) imm 4 -> 4 +libbpf: map '.rodata.str1.1': created successfully, fd=3 +libbpf: elf: symbol address match for 'add_test' in '../examples/btf-base': 0x1140 +Successfully started! Press Ctrl+C to stop. +``` + +It can successfully find the type information of `struct data` in the BTF file and perform the verification. + +### 1.3 Verify failed with invalid userspace struct var access + +The eBPF program is: + +```c +struct data { + int a; + int e; + int d; +}; + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int a = 0, e = 0; + bpf_probe_read_user(&a, sizeof(a), &d->a); + bpf_probe_read_user(&e, sizeof(e), &d->e); + bpf_printk("add_test(&d) %d + %d = %d\n", a, e, a + e); + return a + e; +} +``` + +If the extension (BPF program) access the invalid userspace struct variable, the verification will fail. + +## Verify memory pointer access + +See [verify-memory-access](verify-memory-access) directory. + +The data struct is: + +```c +struct deep_memory_block { + int a; + char b[10]; +}; + +struct inner_memory_block { + int a; + char b[10]; + struct deep_memory_block *deep; +}; + +struct data { + int a; + int b; + int c; + int d; + // represent a pointer to a memory block + struct inner_memory_block *inner; +}; +``` + +This include a deep memory access through pointers. + +### Memory access success for inner data structs without deep copy + +The eBPF program is: + +```c +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int inner_deep_a = BPF_CORE_READ_USER(d, inner, deep, a); + bpf_printk("inner_deep_a = %d\n", inner_deep_a); + char* inner_deep_b = BPF_CORE_READ_USER(d, inner, deep, b); + bpf_printk("inner_deep_b[9] = %c\n", inner_deep_b[9]); + return 0; +} +``` + +Run it: + +```sh +$ sudo ./verify-memory-access/uprobe ../examples/btf-base-complete ../target-base-complete.btf + +libbpf: sec 'uprobe/examples/btf-base:add_test': found 7 CO-RE relocations +libbpf: CO-RE relocating [2] struct pt_regs: found target candidate [88] struct pt_regs in [vmlinux] +libbpf: prog 'add_test': relo #0: [2] struct pt_regs.di (0:14 @ offset 112) +libbpf: prog 'add_test': relo #0: matching candidate #0 [88] struct pt_regs.di (0:14 @ offset 112) +libbpf: prog 'add_test': relo #0: patched insn #0 (LDX/ST/STX) off 112 -> 112 +libbpf: CO-RE relocating [17] struct data: found target candidate [143228] struct data in [vmlinux] +libbpf: prog 'add_test': relo #1: [17] struct data.a (0:0 @ offset 0) +libbpf: prog 'add_test': relo #1: matching candidate #0 [143228] struct data.a (0:0 @ offset 0) +libbpf: prog 'add_test': relo #1: patched insn #4 (ALU/ALU64) imm 0 -> 0 +libbpf: prog 'add_test': relo #2: [17] struct data.c (0:2 @ offset 8) +libbpf: prog 'add_test': relo #2: matching candidate #0 [143228] struct data.c (0:2 @ offset 8) +libbpf: prog 'add_test': relo #2: patched insn #11 (ALU/ALU64) imm 8 -> 8 +libbpf: prog 'add_test': relo #3: [17] struct data.inner (0:4 @ offset 16) +libbpf: prog 'add_test': relo #3: matching candidate #0 [143228] struct data.inner (0:4 @ offset 16) +libbpf: prog 'add_test': relo #3: patched insn #38 (ALU/ALU64) imm 16 -> 16 +libbpf: CO-RE relocating [19] struct inner_memory_block: found target candidate [143231] struct inner_memory_block in [vmlinux] +libbpf: prog 'add_test': relo #4: [19] struct inner_memory_block.deep (0:2 @ offset 16) +libbpf: prog 'add_test': relo #4: matching candidate #0 [143231] struct inner_memory_block.deep (0:2 @ offset 16) +libbpf: prog 'add_test': relo #4: patched insn #46 (ALU/ALU64) imm 16 -> 16 +libbpf: CO-RE relocating [24] struct deep_memory_block: found target candidate [143234] struct deep_memory_block in [vmlinux] +libbpf: prog 'add_test': relo #5: [24] struct deep_memory_block.a (0:0 @ offset 0) +libbpf: prog 'add_test': relo #5: matching candidate #0 [143234] struct deep_memory_block.a (0:0 @ offset 0) +libbpf: prog 'add_test': relo #5: patched insn #52 (ALU/ALU64) imm 0 -> 0 +libbpf: prog 'add_test': relo #6: [24] struct deep_memory_block.b (0:1 @ offset 4) +libbpf: prog 'add_test': relo #6: matching candidate #0 [143234] struct deep_memory_block.b (0:1 @ offset 4) +libbpf: prog 'add_test': relo #6: patched insn #84 (ALU/ALU64) imm 4 -> 4 +libbpf: map '.rodata.str1.1': created successfully, fd=3 +libbpf: elf: symbol address match for 'add_test' in '../examples/btf-base-complete': 0x1160 +Successfully started! Press Ctrl+C to stop. +``` + +### Memory access failed for inner data structs because of out of bounds + +If we change the eBPF program to access the memory out of bounds, the verification will fail. + +```c +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int inner_deep_a = BPF_CORE_READ_USER(d, inner, deep, a); + bpf_printk("inner_deep_a = %d\n", inner_deep_a); + char* inner_deep_b = BPF_CORE_READ_USER(d, inner, deep, b); + // memory access out of bounds + bpf_printk("inner_deep_b[11] = %c\n", inner_deep_b[100]); + return 0; +} +``` + +test: + +```c +$ sudo ./verify-memory-access/uprobe_failed ../examples/btf-base-complete ../target-base-complete.btf +... +; bpf_printk("inner_deep_b[11] = %c\n", inner_deep_b[100]); @ uprobe_failed.bpf.c:44 +57: (6b) *(u16 *)(r10 -12) = r1 ; R1_w=2659 R10=fp0 fp-16=??mm?mmm +58: (b7) r1 = 622869792 ; R1_w=0x25203d20 +59: (63) *(u32 *)(r10 -16) = r1 ; R1_w=0x25203d20 R10=fp0 fp-16=??mm0x25203d20 +60: (18) r1 = 0x5d31315b625f7065 ; R1_w=0x5d31315b625f7065 +62: (7b) *(u64 *)(r10 -24) = r1 ; R1_w=0x5d31315b625f7065 R10=fp0 fp-24_w=0x5d31315b625f7065 +63: (7b) *(u64 *)(r10 -32) = r8 ; R8=0x65645f72656e6e69 R10=fp0 fp-32_w=0x65645f72656e6e69 +64: (73) *(u8 *)(r10 -10) = r9 ; R9=0 R10=fp0 fp-16=?0mmmmmm +65: (71) r3 = *(u8 *)(r10 +68) +invalid read from stack R10 off=68 size=1 +processed 63 insns (limit 1000000) max_states_per_insn 0 total_states 3 peak_states 3 mark_read 2 +-- END PROG LOAD LOG -- +libbpf: prog 'add_test': failed to load: -13 +libbpf: failed to load object 'uprobe_failed_bpf' +libbpf: failed to load BPF skeleton 'uprobe_failed_bpf': -13 +Failed to load and verify BPF skeleton +``` + +## Verify resource allocation and deallocation with psudo ufuncs + +See [verify-resource-allocation](verify-resource-allocation) directory. + +First, give kernel the psudo ufunc information through the kernel module: + +```sh +cd module +make +sudo insmod hello.ko +``` + +Check the kernel output: + +```sh +$ sudo dmesg | tail +[83824.574456] Hello, world! +[83824.574952] bpf_kfunc_example: Module loaded successfully +``` + +The psudo ufunc information is like: + +```c +__bpf_kfunc struct data *my_alloc_data(void) +{ + // here we only use it for verification + return NULL; +} + +__bpf_kfunc void my_free_data(struct data *d) +{ + // here we only use it for verification + return; +} +/*Auto generated code end*/ + +__bpf_kfunc_end_defs(); + +BTF_KFUNCS_START(bpf_kfunc_example_ids_set) +BTF_ID_FLAGS(func, my_alloc_data, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, my_free_data, KF_RELEASE) +BTF_KFUNCS_END(bpf_kfunc_example_ids_set) +``` + +Note in previous approach, we just use the BTF information of userspace program to verify the eBPF program, so we don't need a additional kernel module. In this case, the kernel may try to link the psudo kfunc to the eBPF program. If it don't have a implementation in the module, the verification will fail. + +And this can only be run in userspace, since run it in kernel is meaningless(The "ufunc" is just a placeholder kfunc, don't have a implementation in the kernel). + +### Verify resource allocation and deallocation success with psudo ufunc information + +The eBPF progam is like: + +```c +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + struct data *alloced = my_alloc_data(); + if (alloced == NULL) { + bpf_printk("Failed to allocate data\n"); + return 0; + } + my_free_data(alloced); + return 0; +} +``` + +Then you can run it successfuly: + +```console +$ sudo ./verify-resource-allocation/uprobe ../examples/btf-base-complete ../target-base-complete.btf + +libbpf: loaded kernel BTF from '/sys/kernel/btf/vmlinux' +libbpf: extern (func ksym) 'my_alloc_data': resolved to hello [143233] +libbpf: extern (func ksym) 'my_free_data': resolved to hello [143235] +libbpf: map '.rodata.str1.1': created successfully, fd=3 +libbpf: elf: symbol address match for 'add_test' in '../examples/btf-base-complete': 0x1160 +Successfully started! Press Ctrl+C to stop. +``` + +### Verify resource allocation and deallocation failed + +If don't release resource allocated by userspace application, the verification will fail. + +code: + +```c +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + struct data *alloced = my_alloc_data(); + if (alloced == NULL) { + bpf_printk("Failed to allocate data\n"); + return 0; + } + return 0; +} +``` + +run it: + +```console +$ sudo ./verify-resource-allocation/uprobe_failed ../examples/btf-base-complete ../target-base-complete.btf + +0: R1=ctx() R10=fp0 +; struct data *alloced = my_alloc_data(); @ uprobe_failed.bpf.c:43 +0: (85) call my_alloc_data#143233 ; R0_w=ptr_or_null_data(id=2,ref_obj_id=2) refs=2 +; if (alloced == NULL) { @ uprobe_failed.bpf.c:44 +1: (55) if r0 != 0x0 goto pc+15 17: R0_w=ptr_data(ref_obj_id=2) R10=fp0 refs=2 +17: (b7) r0 = 0 ; R0_w=0 refs=2 +18: (95) exit +Unreleased reference id=2 alloc_insn=0 +processed 18 insns (limit 1000000) max_states_per_insn 1 total_states 1 peak_states 1 mark_read 0 +-- END PROG LOAD LOG -- +libbpf: prog 'add_test': failed to load: -22 +libbpf: failed to load object 'uprobe_failed_bpf' +libbpf: failed to load BPF skeleton 'uprobe_failed_bpf': -22 +Failed to load and verify BPF skeleton +``` + +Or if you don't check the return value of `my_alloc_data`, the verification will also fail, like: + +```c +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + struct data *alloced = my_alloc_data(); + + my_free_data(alloced); + return 0; +} +``` + +run it: + +```console +$ sudo ./verify-resource-allocation/uprobe ../examples/btf-base-complete ../target-base-complete.btf + +libbpf: loaded kernel BTF from '/sys/kernel/btf/vmlinux' +libbpf: extern (func ksym) 'my_alloc_data': resolved to hello [143233] +libbpf: extern (func ksym) 'my_free_data': resolved to hello [143235] +libbpf: prog 'add_test': BPF program load failed: Permission denied +libbpf: prog 'add_test': -- BEGIN PROG LOAD LOG -- +0: R1=ctx() R10=fp0 +; struct data *alloced = my_alloc_data(); @ uprobe.bpf.c:43 +0: (85) call my_alloc_data#143233 ; R0_w=ptr_or_null_data(id=2,ref_obj_id=2) refs=2 +; my_free_data(alloced); @ uprobe.bpf.c:45 +1: (bf) r1 = r0 ; R0_w=ptr_or_null_data(id=2,ref_obj_id=2) R1_w=ptr_or_null_data(id=2,ref_obj_id=2) refs=2 +2: (85) call my_free_data#143235 +Possibly NULL pointer passed to trusted arg0 +processed 3 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 +-- END PROG LOAD LOG -- +libbpf: prog 'add_test': failed to load: -13 +libbpf: failed to load object 'uprobe_bpf' +libbpf: failed to load BPF skeleton 'uprobe_bpf': -13 +Failed to load and verify BPF skeleton +``` + +### Run ufunc in userspace + +Since it's meaningless to run ufunc in kernel, so we can only run it in userspace. + +load eBPF program with ufunc: + +```sh +LD_PRELOAD=/home/yunwei37/.bpftime/libbpftime-syscall-server.so ./verify-resource-allocation/uprobe ../examples/btf-base-complete ../target-base-complete.btf +``` + +And run the userspace program: + +```console +# LD_PRELOAD=/home/yunwei37/.bpftime/libbpftime-agent.so ../examples/btf-base-complete + +my_alloc_data +my_free_data +``` + diff --git a/src/38-btf-uprobe/test-verify/minimal/.gitignore b/src/38-btf-uprobe/test-verify/minimal/.gitignore new file mode 100644 index 00000000..ce7d5eb6 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/minimal/.gitignore @@ -0,0 +1,14 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml +ecli +uprobe +.output +test +victim +uprobe-override +syscall +ufilter diff --git a/src/38-btf-uprobe/test-verify/minimal/Makefile b/src/38-btf-uprobe/test-verify/minimal/Makefile new file mode 100644 index 00000000..4ffe9320 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/minimal/Makefile @@ -0,0 +1,144 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../../../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../../../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +LIBBLAZESYM_SRC := $(abspath ../../../third_party/blazesym/) +LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a) +LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h) +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../../../third_party/vmlinux/$(ARCH)/vmlinux.h +# Use our own libbpf API headers and Linux UAPI headers distributed with +# libbpf to avoid dependency on system-wide headers, which could be missing or +# outdated +INCLUDES := -I$(OUTPUT) -I../../../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = uprobe # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall + +CARGO ?= $(shell which cargo) +ifeq ($(strip $(CARGO)),) +BZS_APPS := +else +BZS_APPS := # profile +APPS += $(BZS_APPS) +# Required by libblazesym +ALL_LDFLAGS += -lrt -ldl -lpthread -lm +endif + +# 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 +# architecture-specific dirs will be "missing" on some architectures/distros - +# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, +# sys/cdefs.h etc. might be missing. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) victim + +victim: + $(Q)$(CC) $(CFLAGS) victim.c -o victim + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + + +$(LIBBLAZESYM_SRC)/target/release/libblazesym.a:: + $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release + +$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB, $@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@ + +$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB,$@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@ + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER) + +$(BZS_APPS): $(LIBBLAZESYM_OBJ) + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/src/38-btf-uprobe/test-verify/minimal/README.md b/src/38-btf-uprobe/test-verify/minimal/README.md new file mode 100644 index 00000000..528a4756 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/minimal/README.md @@ -0,0 +1,125 @@ +# minimal examples + +- `uprobe/uretprobe`: trace userspace functions at start or and. No affect the control flow. +- `uprobe-override`: replace the userspace function with a eBPF function + +You can use `bpf_override_return` to change the control flow and return value of the function. + +```c +/* + * bpf_override_return + * + * Used for error injection, this helper uses kprobes to override + * the return value of the probed function, and to set it to *rc*. + * The first argument is the context *regs* on which the kprobe + * works. + * + * This helper works by setting the PC (program counter) + * to an override function which is run in place of the original + * probed function. This means the probed function is not run at + * all. The replacement function just returns with the required + * value. + * + * This helper has security implications, and thus is subject to + * restrictions. It is only available if the kernel was compiled + * with the **CONFIG_BPF_KPROBE_OVERRIDE** configuration + * option, and in this case it only works on functions tagged with + * **ALLOW_ERROR_INJECTION** in the kernel code. + * + * Also, the helper is only available for the architectures having + * the CONFIG_FUNCTION_ERROR_INJECTION option. As of this writing, + * x86 architecture is the only one to support this feature. + * + * Returns + * 0 + */ +static long (*bpf_override_return)(struct pt_regs *regs, __u64 rc) = (void *) 58; +``` + +## uprobe trace + +This code is a BPF (Berkeley Packet Filter) program written in C, often used for tracing and monitoring activities in the Linux kernel. BPF allows you to run custom programs within the kernel without modifying its source code. The code you provided creates a BPF program that uses a BPF map to count the number of times the `uprobe` function is called within a specified cgroup. + +```c +#include +#include +#include "bits.bpf.h" +#include "maps.bpf.h" + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1024); + __type(key, u64); + __type(value, u64); +} libc_uprobe_calls_total SEC(".maps"); + +SEC("uprobe/libc.so.6:uprobe") +int do_count(struct pt_regs *ctx) +{ + u64 cgroup_id = bpf_get_current_cgroup_id(); + + increment_map(&libc_uprobe_calls_total, &cgroup_id, 1); + + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; +``` + +from + +Here's a breakdown of the code: + +1. **Headers Inclusion**: + - ``: Provides access to kernel data structures and definitions. + - ``: Includes helper functions and macros for BPF programs. + - `"bits.bpf.h"`: Custom header file (assumed to contain additional definitions). + - `"maps.bpf.h"`: Custom header file (assumed to contain definitions related to BPF maps). + +2. **Definition of BPF Map**: + The code defines a BPF map named `libc_uprobe_calls_total` using the `struct` syntax. This map is of type `BPF_MAP_TYPE_HASH` (hash map) with a maximum of 1024 entries. The keys and values are of type `u64` (unsigned 64-bit integer). + +3. **Map Definition Attributes**: + The attributes specified within the map definition (`__uint`, `__type`) set properties of the map, such as its type, maximum number of entries, and types of keys and values. + +4. **BPF Program**: + - The program is associated with a `uprobe` on the `uprobe` function in the `libc.so.6` library. + - The `do_count` function is executed when the `uprobe` function is called. + - It retrieves the current cgroup ID using `bpf_get_current_cgroup_id()`. + - Then, it increments the `libc_uprobe_calls_total` map with the cgroup ID as the key and increments the associated value by 1. + +5. **License Information**: + The `LICENSE[]` array contains the license information for the BPF program. In this case, the program is licensed under the GPL (GNU General Public License). + +The purpose of this BPF program is to track and count the number of `uprobe` calls that occur within specific cgroups in the Linux kernel. It uses a BPF hash map to store and update the counts. This can be useful for monitoring memory allocation patterns and resource usage within different cgroups. + +### how to run uprobe + +server + +```console +example/minimal# LD_PRELOAD=~/.bpftime/libbpftime-syscall-server.so ./uprobe +``` + +client + +```console +example/minimal# LD_PRELOAD=~/.bpftime/libbpftime-agent.so ./victim +``` + +## Syscall + +### how to run syscall + +server + +```sh +LD_PRELOAD=~/.bpftime/libbpftime-syscall-server.so ./syscall +``` + +client + +```sh +sudo ~/.bpftime/bpftime start -s ./victim +# or AGENT_SO=build/runtime/agent/libbpftime-agent.so LD_PRELOAD=build/attach/text_segment_transformer/libbpftime-agent-transformer.so ./victim +``` diff --git a/src/38-btf-uprobe/test-verify/minimal/uprobe.bpf.c b/src/38-btf-uprobe/test-verify/minimal/uprobe.bpf.c new file mode 100644 index 00000000..35f2a8d3 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/minimal/uprobe.bpf.c @@ -0,0 +1,13 @@ +#define BPF_NO_GLOBAL_DATA +#include +#include +#include + +SEC("uprobe/./victim:target_func") +int do_uprobe_trace(struct pt_regs *ctx) +{ + bpf_printk("target_func called.\n"); + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/src/38-btf-uprobe/test-verify/minimal/uprobe.c b/src/38-btf-uprobe/test-verify/minimal/uprobe.c new file mode 100644 index 00000000..3d439b5c --- /dev/null +++ b/src/38-btf-uprobe/test-verify/minimal/uprobe.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe.skel.h" +#include + +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +int main(int argc, char **argv) +{ + struct uprobe_bpf *skel; + int err; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = uprobe_bpf__open(); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Load & verify BPF programs */ + err = uprobe_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + int attach = uprobe_bpf__attach(skel); + if (attach) { + fprintf(stderr, "Failed to attach BPF skeleton\n"); + err = -1; + goto cleanup; + } + while (!exiting) { + sleep(1); + } +cleanup: + /* Clean up */ + uprobe_bpf__destroy(skel); + + return err < 0 ? -err : 0; +} diff --git a/src/38-btf-uprobe/test-verify/minimal/victim.c b/src/38-btf-uprobe/test-verify/minimal/victim.c new file mode 100644 index 00000000..1821ab1d --- /dev/null +++ b/src/38-btf-uprobe/test-verify/minimal/victim.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +int target_func() { + int res = open("/dev/null", O_RDONLY); + printf("target_func\n"); + close(res); + return 0; +} + +int main(int argc, char *argv[]) { + while(1) { + sleep(1); + target_func(); + } + return 0; +} diff --git a/src/38-btf-uprobe/test-verify/module/.gitignore b/src/38-btf-uprobe/test-verify/module/.gitignore new file mode 100644 index 00000000..9523173a --- /dev/null +++ b/src/38-btf-uprobe/test-verify/module/.gitignore @@ -0,0 +1,23 @@ +# Ignore object files and kernel modules +*.o +*.ko +*.mod +*.mod.c +*.symvers +*.order + +# Ignore temporary and backup files +*~ +*.bak +*.tmp +*.swp + +# Ignore build directory if generated +/Module.symvers +/Modules.markers +/Module.markers +/modules.order + +# Ignore other automatically generated files +*.cmd +.tmp_versions/ diff --git a/src/38-btf-uprobe/test-verify/module/Makefile b/src/38-btf-uprobe/test-verify/module/Makefile new file mode 100644 index 00000000..44afa319 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/module/Makefile @@ -0,0 +1,11 @@ +obj-m += hello.o # hello.o is the target + +# Enable BTF generation +KBUILD_CFLAGS += -g -O2 + +all: + # Compile the module with BTF information + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/src/38-btf-uprobe/test-verify/module/README.md b/src/38-btf-uprobe/test-verify/module/README.md new file mode 100644 index 00000000..d2dfadad --- /dev/null +++ b/src/38-btf-uprobe/test-verify/module/README.md @@ -0,0 +1,134 @@ +# write a basic kernel module + +## hello world + +Writing a Linux kernel module involves creating code that can be loaded into and unloaded from the kernel dynamically, without rebooting the system. Here’s a simple step-by-step guide to help you write a basic kernel module: + +### 1. Set Up Your Environment + +Make sure you have the Linux kernel headers installed and a suitable development environment ready. For Ubuntu or Debian, install them with: + +```bash +sudo apt-get install linux-headers-$(uname -r) build-essential +``` + +### 2. Write the Kernel Module Code + +Here’s an example of a very basic Linux kernel module: + +```c +// hello.c: A simple Linux kernel module +#include // Macros for module initialization +#include // Core header for loading modules +#include // Kernel logging macros + +// Function executed when the module is loaded +static int __init hello_init(void) +{ + printk(KERN_INFO "Hello, world!\n"); + return 0; // Return 0 if successful +} + +// Function executed when the module is removed +static void __exit hello_exit(void) +{ + printk(KERN_INFO "Goodbye, world!\n"); +} + +// Macros to define the module’s init and exit points +module_init(hello_init); +module_exit(hello_exit); + +MODULE_LICENSE("GPL"); // License type (GPL) +MODULE_AUTHOR("Your Name"); // Module author +MODULE_DESCRIPTION("A simple module"); // Module description +MODULE_VERSION("1.0"); // Module version +``` + +### 3. Create a Makefile + +To compile the kernel module, you’ll need a `Makefile`. Here's a simple one: + +```makefile +obj-m += hello.o # hello.o is the target + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean +``` + +### 4. Compile the Module + +Run the following command in the directory where your `hello.c` and `Makefile` are located: + +```bash +make +``` + +This will generate a file called `hello.ko`, which is the compiled kernel module. + +### 5. Load the Module + +To insert the module into the kernel, use `insmod`: + +```bash +sudo insmod hello.ko +``` + +### 6. Check the Logs + +To see the output from the `printk` statements, use the `dmesg` command: + +```bash +dmesg | tail +``` + +You should see something like: + +```txt +[ 1234.5678] Hello, world! +``` + +### 7. Remove the Module + +To unload the module, use `rmmod`: + +```bash +sudo rmmod hello +``` + +Again, check the logs using `dmesg`: + +```bash +sudo dmesg | tail +``` + +You should see: + +```txt +[ 1234.9876] Goodbye, world! +``` + +### 8. Clean Up + +To clean up the build files, run: + +```bash +make clean +``` + +### Notes + +- **License**: The `MODULE_LICENSE("GPL")` ensures the module is GPL-compliant, which allows it to use symbols (functions) exported by the kernel. +- **Debugging**: Use `printk` for logging within the module. It behaves similarly to `printf` but is designed for kernel space. +- **Module Parameters**: You can add parameters to modules using `module_param()` to pass arguments when the module is loaded. + +### Next Steps + +Once you are familiar with this basic example, you can explore: + +- Writing more advanced modules that interact with hardware or the filesystem. +- Using kernel-specific APIs like work queues, kthreads, or handling interrupts. +- Diving into eBPF or loadable kernel module techniques for debugging and tracing kernel events. diff --git a/src/38-btf-uprobe/test-verify/module/compact.h b/src/38-btf-uprobe/test-verify/module/compact.h new file mode 100644 index 00000000..5ff2115f --- /dev/null +++ b/src/38-btf-uprobe/test-verify/module/compact.h @@ -0,0 +1,11 @@ +// Compatible for lower kernel versions. No need in 6.11. +#ifndef BTF_SET8_KFUNCS +/* This flag implies BTF_SET8 holds kfunc(s) */ +#define BTF_SET8_KFUNCS (1 << 0) +#endif +#ifndef BTF_KFUNCS_START +#define BTF_KFUNCS_START(name) static struct btf_id_set8 __maybe_unused name = { .flags = BTF_SET8_KFUNCS }; +#endif +#ifndef BTF_KFUNCS_END +#define BTF_KFUNCS_END(name) +#endif diff --git a/src/38-btf-uprobe/test-verify/module/hello.c b/src/38-btf-uprobe/test-verify/module/hello.c new file mode 100644 index 00000000..0121ca54 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/module/hello.c @@ -0,0 +1,97 @@ +#include // Macros for module initialization +#include // Core header for loading modules +#include // Kernel logging macros +#include +#include +#include + +__bpf_kfunc struct data *my_alloc_data(void); +__bpf_kfunc void my_free_data(struct data *d); + +/* Define a kfunc function */ +__bpf_kfunc_start_defs(); + +/*Auto generated code start*/ +/*This type definition can be get from the BTF information of the userspace program*/ +struct deep_memory_block +{ + int a; + char b[10]; +}; + +struct inner_memory_block +{ + int a; + char b[10]; + struct deep_memory_block *deep; +}; + +struct data +{ + int a; + int b; + int c; + int d; + // represent a pointer to a memory block + struct inner_memory_block *inner; +}; + +__bpf_kfunc struct data *my_alloc_data(void) +{ + // here we only use it for verification + return NULL; +} + +__bpf_kfunc void my_free_data(struct data *d) +{ + // here we only use it for verification + return; +} +/*Auto generated code end*/ + +__bpf_kfunc_end_defs(); + +BTF_KFUNCS_START(bpf_kfunc_example_ids_set) +BTF_ID_FLAGS(func, my_alloc_data, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, my_free_data, KF_RELEASE) +BTF_KFUNCS_END(bpf_kfunc_example_ids_set) + +// Register the kfunc ID set +static const struct btf_kfunc_id_set bpf_kfunc_example_set = { + .owner = THIS_MODULE, + .set = &bpf_kfunc_example_ids_set, +}; + +// Function executed when the module is loaded +static int __init hello_init(void) +{ + int ret; + + printk(KERN_INFO "Hello, world!\n"); + // Register the BTF kfunc ID set + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_KPROBE, &bpf_kfunc_example_set); + if (ret) + { + pr_err("bpf_kfunc_example: Failed to register BTF kfunc ID set\n"); + return ret; + } + printk(KERN_INFO "bpf_kfunc_example: Module loaded successfully\n"); + return 0; // Return 0 if successful +} + +// Function executed when the module is removed +static void __exit hello_exit(void) +{ + // Unregister the BTF kfunc ID set + // unregister_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, &bpf_kfunc_example_set); + printk(KERN_INFO "Goodbye, world!\n"); +} + +// Macros to define the module’s init and exit points +module_init(hello_init); +module_exit(hello_exit); + +MODULE_LICENSE("GPL"); // License type (GPL) +MODULE_AUTHOR("Your Name"); // Module author +MODULE_DESCRIPTION("A simple module"); // Module description +MODULE_VERSION("1.0"); // Module version diff --git a/src/38-btf-uprobe/test-verify/verify-failed-no-btf/Makefile b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/Makefile new file mode 100644 index 00000000..9fec032f --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/Makefile @@ -0,0 +1,142 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../../../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../../../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../../../third_party/vmlinux/$(ARCH)/vmlinux.h +# Use our own libbpf API headers and Linux UAPI headers distributed with +# libbpf to avoid dependency on system-wide headers, which could be missing or +# outdated +INCLUDES := -I$(OUTPUT) -I../../../third_party/libbpf/include/ -I../../../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = uprobe uprobe_failed # minimal minimal_legacy kprobe fentry usdt sockfilter tc ksyscall + +CARGO ?= $(shell which cargo) +ifeq ($(strip $(CARGO)),) +BZS_APPS := +else +BZS_APPS := # profile +APPS += $(BZS_APPS) +# Required by libblazesym +ALL_LDFLAGS += -lrt -ldl -lpthread -lm +endif + +# 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 +# architecture-specific dirs will be "missing" on some architectures/distros - +# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, +# sys/cdefs.h etc. might be missing. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) merge-btf + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + + +$(LIBBLAZESYM_SRC)/target/release/libblazesym.a:: + $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release + +$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB, $@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@ + +$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB,$@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@ + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER) + +$(BZS_APPS): $(LIBBLAZESYM_OBJ) + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +merge-btf: merge-btf.c + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) merge-btf.c $(LIBBPF_OBJ) $(INCLUDES) $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/src/38-btf-uprobe/test-verify/verify-failed-no-btf/merge-btf.c b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/merge-btf.c new file mode 100644 index 00000000..5bb83ea9 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/merge-btf.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *btf_base_path = argv[1]; + char *btf_src_path = argv[2]; + char *btf_dst_path = argv[3]; + struct btf *btf_src, *btf_base; + int err; + unsigned int size; + const void* btf_data; + FILE *fp; + + + if (argc != 4) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Used for merge btf info"); + return 1; + } + + btf_base = btf__parse(btf_base_path, NULL); + if (!btf_base) + { + fprintf(stderr, "Failed to parse BTF object '%s': %s\n", btf_base_path, strerror(errno)); + return 1; + } + + btf_src = btf__parse(btf_src_path, NULL); + if (!btf_src) + { + fprintf(stderr, "Failed to parse BTF object '%s': %s\n", btf_src_path, strerror(errno)); + return 1; + } + + err = btf__add_btf(btf_base, btf_src); + if (err < 0) + { + fprintf(stderr, "Failed to add BTF object '%s': %s\n", btf_src_path, strerror(errno)); + return 1; + } + + btf_data = btf__raw_data(btf_base, &size); + if (!btf_data) + { + fprintf(stderr, "Failed to get raw data of BTF object '%s': %s\n", btf_base_path, strerror(errno)); + return 1; + } + fp = fopen(btf_dst_path, "w"); + if (!fp) + { + fprintf(stderr, "Failed to open BTF object '%s': %s\n", btf_dst_path, strerror(errno)); + return 1; + } + fwrite(btf_data, size, 1, fp); + fclose(fp); + + return 0; +} \ No newline at end of file diff --git a/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.bpf.c b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.bpf.c new file mode 100644 index 00000000..97c9f2c2 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.bpf.c @@ -0,0 +1,32 @@ +#define BPF_NO_GLOBAL_DATA +// #define BPF_NO_PRESERVE_ACCESS_INDEX +#include +#include +#include + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record) +#endif + +struct data { + int a; + int c; + int d; +}; + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute pop +#endif + + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int a = 0, c = 0; + bpf_probe_read_user(&a, sizeof(a), &d->a); + bpf_probe_read_user(&c, sizeof(c), &d->c); + bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c); + return a + c; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.c b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.c new file mode 100644 index 00000000..a3a0d3d9 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe.skel.h" + +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +int main(int argc, char **argv) +{ + struct uprobe_bpf *skel; + int err; + LIBBPF_OPTS(bpf_object_open_opts , opts, + ); + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); + if (argc != 3 && argc != 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + if (argc == 3) + opts.btf_custom_path = argv[2]; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = uprobe_bpf__open_opts(&opts); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Load & verify BPF programs */ + err = uprobe_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + + uprobe_opts.func_name = "add_test"; + skel->links.add_test = bpf_program__attach_uprobe_opts( + skel->progs.add_test, -1 /* self pid */, argv[1] /* binary path */, + 0 /* offset for function */, &uprobe_opts /* opts */); + if (!skel->links.add_test) { + err = -errno; + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + printf("Successfully started! Press Ctrl+C to stop.\n"); + fflush(stdout); + while (!exiting) { + sleep(1); + } +cleanup: + /* Clean up */ + uprobe_bpf__destroy(skel); + + return err < 0 ? -err : 0; +} diff --git a/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.bpf.c b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.bpf.c new file mode 100644 index 00000000..fa423787 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.bpf.c @@ -0,0 +1,32 @@ +#define BPF_NO_GLOBAL_DATA +// #define BPF_NO_PRESERVE_ACCESS_INDEX +#include +#include +#include + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record) +#endif + +struct data { + int a; + int e; + int d; +}; + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute pop +#endif + + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int a = 0, e = 0; + bpf_probe_read_user(&a, sizeof(a), &d->a); + bpf_probe_read_user(&e, sizeof(e), &d->e); + bpf_printk("add_test(&d) %d + %d = %d\n", a, e, a + e); + return a + e; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.c b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.c new file mode 100644 index 00000000..424a6035 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-failed-no-btf/uprobe_failed.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe_failed.skel.h" + +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +int main(int argc, char **argv) +{ + struct uprobe_failed_bpf *skel; + int err; + LIBBPF_OPTS(bpf_object_open_opts , opts, + ); + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); + if (argc != 3 && argc != 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + if (argc == 3) + opts.btf_custom_path = argv[2]; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = uprobe_failed_bpf__open_opts(&opts); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Load & verify BPF programs */ + err = uprobe_failed_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + + uprobe_opts.func_name = "add_test"; + skel->links.add_test = bpf_program__attach_uprobe_opts( + skel->progs.add_test, -1 /* self pid */, argv[1] /* binary path */, + 0 /* offset for function */, &uprobe_opts /* opts */); + if (!skel->links.add_test) { + err = -errno; + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + printf("Successfully started! Press Ctrl+C to stop.\n"); + fflush(stdout); + while (!exiting) { + sleep(1); + } +cleanup: + /* Clean up */ + uprobe_failed_bpf__destroy(skel); + + return err < 0 ? -err : 0; +} diff --git a/src/38-btf-uprobe/test-verify/verify-memory-access/Makefile b/src/38-btf-uprobe/test-verify/verify-memory-access/Makefile new file mode 100644 index 00000000..9fec032f --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-memory-access/Makefile @@ -0,0 +1,142 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../../../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../../../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../../../third_party/vmlinux/$(ARCH)/vmlinux.h +# Use our own libbpf API headers and Linux UAPI headers distributed with +# libbpf to avoid dependency on system-wide headers, which could be missing or +# outdated +INCLUDES := -I$(OUTPUT) -I../../../third_party/libbpf/include/ -I../../../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = uprobe uprobe_failed # minimal minimal_legacy kprobe fentry usdt sockfilter tc ksyscall + +CARGO ?= $(shell which cargo) +ifeq ($(strip $(CARGO)),) +BZS_APPS := +else +BZS_APPS := # profile +APPS += $(BZS_APPS) +# Required by libblazesym +ALL_LDFLAGS += -lrt -ldl -lpthread -lm +endif + +# 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 +# architecture-specific dirs will be "missing" on some architectures/distros - +# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, +# sys/cdefs.h etc. might be missing. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) merge-btf + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + + +$(LIBBLAZESYM_SRC)/target/release/libblazesym.a:: + $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release + +$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB, $@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@ + +$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB,$@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@ + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER) + +$(BZS_APPS): $(LIBBLAZESYM_OBJ) + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +merge-btf: merge-btf.c + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) merge-btf.c $(LIBBPF_OBJ) $(INCLUDES) $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/src/38-btf-uprobe/test-verify/verify-memory-access/merge-btf.c b/src/38-btf-uprobe/test-verify/verify-memory-access/merge-btf.c new file mode 100644 index 00000000..5bb83ea9 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-memory-access/merge-btf.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *btf_base_path = argv[1]; + char *btf_src_path = argv[2]; + char *btf_dst_path = argv[3]; + struct btf *btf_src, *btf_base; + int err; + unsigned int size; + const void* btf_data; + FILE *fp; + + + if (argc != 4) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Used for merge btf info"); + return 1; + } + + btf_base = btf__parse(btf_base_path, NULL); + if (!btf_base) + { + fprintf(stderr, "Failed to parse BTF object '%s': %s\n", btf_base_path, strerror(errno)); + return 1; + } + + btf_src = btf__parse(btf_src_path, NULL); + if (!btf_src) + { + fprintf(stderr, "Failed to parse BTF object '%s': %s\n", btf_src_path, strerror(errno)); + return 1; + } + + err = btf__add_btf(btf_base, btf_src); + if (err < 0) + { + fprintf(stderr, "Failed to add BTF object '%s': %s\n", btf_src_path, strerror(errno)); + return 1; + } + + btf_data = btf__raw_data(btf_base, &size); + if (!btf_data) + { + fprintf(stderr, "Failed to get raw data of BTF object '%s': %s\n", btf_base_path, strerror(errno)); + return 1; + } + fp = fopen(btf_dst_path, "w"); + if (!fp) + { + fprintf(stderr, "Failed to open BTF object '%s': %s\n", btf_dst_path, strerror(errno)); + return 1; + } + fwrite(btf_data, size, 1, fp); + fclose(fp); + + return 0; +} \ No newline at end of file diff --git a/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.bpf.c b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.bpf.c new file mode 100644 index 00000000..13b8a46e --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.bpf.c @@ -0,0 +1,47 @@ +#define BPF_NO_GLOBAL_DATA +// #define BPF_NO_PRESERVE_ACCESS_INDEX +#include +#include +#include +#include + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record) +#endif + + +struct deep_memory_block { + int a; + char b[10]; +}; + +struct inner_memory_block { + int a; + char b[10]; + struct deep_memory_block *deep; +}; + +struct data { + int a; + int b; + int c; + int d; + // represent a pointer to a memory block + struct inner_memory_block *inner; +}; + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute pop +#endif + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int inner_deep_a = BPF_CORE_READ_USER(d, inner, deep, a); + bpf_printk("inner_deep_a = %d\n", inner_deep_a); + char* inner_deep_b = BPF_CORE_READ_USER(d, inner, deep, b); + bpf_printk("inner_deep_b[9] = %c\n", inner_deep_b[9]); + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.c b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.c new file mode 100644 index 00000000..a3a0d3d9 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe.skel.h" + +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +int main(int argc, char **argv) +{ + struct uprobe_bpf *skel; + int err; + LIBBPF_OPTS(bpf_object_open_opts , opts, + ); + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); + if (argc != 3 && argc != 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + if (argc == 3) + opts.btf_custom_path = argv[2]; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = uprobe_bpf__open_opts(&opts); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Load & verify BPF programs */ + err = uprobe_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + + uprobe_opts.func_name = "add_test"; + skel->links.add_test = bpf_program__attach_uprobe_opts( + skel->progs.add_test, -1 /* self pid */, argv[1] /* binary path */, + 0 /* offset for function */, &uprobe_opts /* opts */); + if (!skel->links.add_test) { + err = -errno; + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + printf("Successfully started! Press Ctrl+C to stop.\n"); + fflush(stdout); + while (!exiting) { + sleep(1); + } +cleanup: + /* Clean up */ + uprobe_bpf__destroy(skel); + + return err < 0 ? -err : 0; +} diff --git a/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.bpf.c b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.bpf.c new file mode 100644 index 00000000..e08ce95f --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.bpf.c @@ -0,0 +1,48 @@ +#define BPF_NO_GLOBAL_DATA +// #define BPF_NO_PRESERVE_ACCESS_INDEX +#include +#include +#include +#include + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record) +#endif + + +struct deep_memory_block { + int a; + char b[10]; +}; + +struct inner_memory_block { + int a; + char b[10]; + struct deep_memory_block *deep; +}; + +struct data { + int a; + int b; + int c; + int d; + // represent a pointer to a memory block + struct inner_memory_block *inner; +}; + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute pop +#endif + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + int inner_deep_a = BPF_CORE_READ_USER(d, inner, deep, a); + bpf_printk("inner_deep_a = %d\n", inner_deep_a); + char* inner_deep_b = BPF_CORE_READ_USER(d, inner, deep, b); + // memory access out of bounds + bpf_printk("inner_deep_b[11] = %c\n", inner_deep_b[100]); + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.c b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.c new file mode 100644 index 00000000..424a6035 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-memory-access/uprobe_failed.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe_failed.skel.h" + +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +int main(int argc, char **argv) +{ + struct uprobe_failed_bpf *skel; + int err; + LIBBPF_OPTS(bpf_object_open_opts , opts, + ); + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); + if (argc != 3 && argc != 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + if (argc == 3) + opts.btf_custom_path = argv[2]; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = uprobe_failed_bpf__open_opts(&opts); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Load & verify BPF programs */ + err = uprobe_failed_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + + uprobe_opts.func_name = "add_test"; + skel->links.add_test = bpf_program__attach_uprobe_opts( + skel->progs.add_test, -1 /* self pid */, argv[1] /* binary path */, + 0 /* offset for function */, &uprobe_opts /* opts */); + if (!skel->links.add_test) { + err = -errno; + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + printf("Successfully started! Press Ctrl+C to stop.\n"); + fflush(stdout); + while (!exiting) { + sleep(1); + } +cleanup: + /* Clean up */ + uprobe_failed_bpf__destroy(skel); + + return err < 0 ? -err : 0; +} diff --git a/src/38-btf-uprobe/test-verify/verify-resource-allocation/Makefile b/src/38-btf-uprobe/test-verify/verify-resource-allocation/Makefile new file mode 100644 index 00000000..9fec032f --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-resource-allocation/Makefile @@ -0,0 +1,142 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../../../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../../../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../../../third_party/vmlinux/$(ARCH)/vmlinux.h +# Use our own libbpf API headers and Linux UAPI headers distributed with +# libbpf to avoid dependency on system-wide headers, which could be missing or +# outdated +INCLUDES := -I$(OUTPUT) -I../../../third_party/libbpf/include/ -I../../../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = uprobe uprobe_failed # minimal minimal_legacy kprobe fentry usdt sockfilter tc ksyscall + +CARGO ?= $(shell which cargo) +ifeq ($(strip $(CARGO)),) +BZS_APPS := +else +BZS_APPS := # profile +APPS += $(BZS_APPS) +# Required by libblazesym +ALL_LDFLAGS += -lrt -ldl -lpthread -lm +endif + +# 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 +# architecture-specific dirs will be "missing" on some architectures/distros - +# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, +# sys/cdefs.h etc. might be missing. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) merge-btf + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + + +$(LIBBLAZESYM_SRC)/target/release/libblazesym.a:: + $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release + +$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB, $@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@ + +$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB,$@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@ + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER) + +$(BZS_APPS): $(LIBBLAZESYM_OBJ) + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +merge-btf: merge-btf.c + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) merge-btf.c $(LIBBPF_OBJ) $(INCLUDES) $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/src/38-btf-uprobe/test-verify/verify-resource-allocation/merge-btf.c b/src/38-btf-uprobe/test-verify/verify-resource-allocation/merge-btf.c new file mode 100644 index 00000000..5bb83ea9 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-resource-allocation/merge-btf.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *btf_base_path = argv[1]; + char *btf_src_path = argv[2]; + char *btf_dst_path = argv[3]; + struct btf *btf_src, *btf_base; + int err; + unsigned int size; + const void* btf_data; + FILE *fp; + + + if (argc != 4) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Used for merge btf info"); + return 1; + } + + btf_base = btf__parse(btf_base_path, NULL); + if (!btf_base) + { + fprintf(stderr, "Failed to parse BTF object '%s': %s\n", btf_base_path, strerror(errno)); + return 1; + } + + btf_src = btf__parse(btf_src_path, NULL); + if (!btf_src) + { + fprintf(stderr, "Failed to parse BTF object '%s': %s\n", btf_src_path, strerror(errno)); + return 1; + } + + err = btf__add_btf(btf_base, btf_src); + if (err < 0) + { + fprintf(stderr, "Failed to add BTF object '%s': %s\n", btf_src_path, strerror(errno)); + return 1; + } + + btf_data = btf__raw_data(btf_base, &size); + if (!btf_data) + { + fprintf(stderr, "Failed to get raw data of BTF object '%s': %s\n", btf_base_path, strerror(errno)); + return 1; + } + fp = fopen(btf_dst_path, "w"); + if (!fp) + { + fprintf(stderr, "Failed to open BTF object '%s': %s\n", btf_dst_path, strerror(errno)); + return 1; + } + fwrite(btf_data, size, 1, fp); + fclose(fp); + + return 0; +} \ No newline at end of file diff --git a/src/38-btf-uprobe/test-verify/verify-resource-allocation/ufunc.bpf.h b/src/38-btf-uprobe/test-verify/verify-resource-allocation/ufunc.bpf.h new file mode 100644 index 00000000..95046f13 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-resource-allocation/ufunc.bpf.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (c) 2022, eunomia-bpf org + * All rights reserved. + */ +#ifndef UFUNC_HELPER_H +#define UFUNC_HELPER_H + +typedef unsigned long long uint64_t; +typedef long long int64_t; +typedef int int32_t; +// global context not support +// uint64_t context; + +union arg_val { + uint64_t uint64; + int64_t int64; + int32_t int32; + double double_val; + void *ptr; +}; + +struct arg_list { + uint64_t args[6]; +}; + +#define UFUNC_HELPER_ID_DISPATCHER 1000 +#define UFUNC_HELPER_ID_FIND_ID 1001 + +static const uint64_t (*ufunc_call)(uint64_t id, uint64_t arg_list) = (void *) + UFUNC_HELPER_ID_DISPATCHER; +static const uint64_t (*ufunc_find_func_id)(const char *func_name) = (void *) + UFUNC_HELPER_ID_FIND_ID; + +// func: function id +#define UFUNC_CALL_0(func) \ + ({ \ + struct arg_list argn = { 0 }; \ + ufunc_call(func, (uint64_t) & argn); \ + }) + +// func: function id +#define UFUNC_CALL_1(func, arg1) \ + ({ \ + struct arg_list argn = { 0 }; \ + argn.args[0] = (uint64_t)arg1; \ + ufunc_call(func, (uint64_t) & argn); \ + }) + +// func: function id +#define UFUNC_CALL_2(func, arg1, arg2) \ + ({ \ + struct arg_list argn = { 0 }; \ + argn.args[0] = (uint64_t)arg1; \ + argn.args[1] = (uint64_t)arg2; \ + ufunc_call(func, (uint64_t) & argn); \ + }) + +// func: function id +#define UFUNC_CALL_3(func, arg1, arg2, arg3) \ + ({ \ + struct arg_list argn = { 0 }; \ + argn.args[0] = (uint64_t)arg1; \ + argn.args[1] = (uint64_t)arg2; \ + argn.args[2] = (uint64_t)arg3; \ + ufunc_call(func, (uint64_t) & argn); \ + }) + +// func: function name +#define UFUNC_CALL_NAME_0(func_name) \ + ({ \ + char funcname[] = func_name; \ + uint64_t func_id = ufunc_find_func_id(funcname); \ + UFUNC_CALL_0(func_id); \ + }) + +#define UFUNC_CALL_NAME_1(func_name, arg1) \ + ({ \ + char funcname[] = func_name; \ + uint64_t func_id = ufunc_find_func_id(funcname); \ + UFUNC_CALL_1(func_id, arg1); \ + }) + +#define UFUNC_CALL_NAME_2(func_name, arg1, arg2) \ + ({ \ + char funcname[] = func_name; \ + uint64_t func_id = ufunc_find_func_id(funcname); \ + UFUNC_CALL_2(func_id, arg1, arg2); \ + }) + +#define UFUNC_CALL_NAME_3(func_name, arg1, arg2, arg3) \ + ({ \ + char funcname[] = func_name; \ + uint64_t func_id = ufunc_find_func_id(funcname); \ + UFUNC_CALL_3(func_id, arg1, arg2, arg3); \ + }) + +#endif \ No newline at end of file diff --git a/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.bpf.c b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.bpf.c new file mode 100644 index 00000000..ec7c500c --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.bpf.c @@ -0,0 +1,57 @@ +#define BPF_NO_GLOBAL_DATA +// #define BPF_NO_PRESERVE_ACCESS_INDEX +#include +#include +#include +#include + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record) +#endif + + +struct deep_memory_block { + int a; + char b[10]; +}; + +struct inner_memory_block { + int a; + char b[10]; + struct deep_memory_block *deep; +}; + +struct data { + int a; + int b; + int c; + int d; + // represent a pointer to a memory block + struct inner_memory_block *inner; +}; + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute pop +#endif + +#ifdef KERNEL_VERIFY_PROTO +extern struct data* my_alloc_data(void) __ksym; +extern void my_free_data(struct data *d) __ksym; +#else +#define my_alloc_data() ((struct data*)UFUNC_CALL_NAME_0("my_alloc_data")) +#define my_free_data(d) UFUNC_CALL_NAME_1("my_free_data", d) +#endif + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + struct data *alloced = my_alloc_data(); + if (alloced == NULL) { + bpf_printk("Failed to allocate data\n"); + return 0; + } + my_free_data(alloced); + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.c b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.c new file mode 100644 index 00000000..a3a0d3d9 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe.skel.h" + +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +int main(int argc, char **argv) +{ + struct uprobe_bpf *skel; + int err; + LIBBPF_OPTS(bpf_object_open_opts , opts, + ); + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); + if (argc != 3 && argc != 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + if (argc == 3) + opts.btf_custom_path = argv[2]; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = uprobe_bpf__open_opts(&opts); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Load & verify BPF programs */ + err = uprobe_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + + uprobe_opts.func_name = "add_test"; + skel->links.add_test = bpf_program__attach_uprobe_opts( + skel->progs.add_test, -1 /* self pid */, argv[1] /* binary path */, + 0 /* offset for function */, &uprobe_opts /* opts */); + if (!skel->links.add_test) { + err = -errno; + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + printf("Successfully started! Press Ctrl+C to stop.\n"); + fflush(stdout); + while (!exiting) { + sleep(1); + } +cleanup: + /* Clean up */ + uprobe_bpf__destroy(skel); + + return err < 0 ? -err : 0; +} diff --git a/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.bpf.c b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.bpf.c new file mode 100644 index 00000000..e400e6a5 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.bpf.c @@ -0,0 +1,51 @@ +#define BPF_NO_GLOBAL_DATA +// #define BPF_NO_PRESERVE_ACCESS_INDEX +#include +#include +#include +#include + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record) +#endif + + +struct deep_memory_block { + int a; + char b[10]; +}; + +struct inner_memory_block { + int a; + char b[10]; + struct deep_memory_block *deep; +}; + +struct data { + int a; + int b; + int c; + int d; + // represent a pointer to a memory block + struct inner_memory_block *inner; +}; + +#ifndef BPF_NO_PRESERVE_ACCESS_INDEX +#pragma clang attribute pop +#endif + +extern struct data* my_alloc_data(void) __ksym; +extern void my_free_data(struct data *d) __ksym; + +SEC("uprobe/examples/btf-base:add_test") +int BPF_UPROBE(add_test, struct data *d) +{ + struct data *alloced = my_alloc_data(); + if (alloced == NULL) { + bpf_printk("Failed to allocate data\n"); + return 0; + } + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.c b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.c new file mode 100644 index 00000000..424a6035 --- /dev/null +++ b/src/38-btf-uprobe/test-verify/verify-resource-allocation/uprobe_failed.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2020 Facebook */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uprobe_failed.skel.h" + +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +int main(int argc, char **argv) +{ + struct uprobe_failed_bpf *skel; + int err; + LIBBPF_OPTS(bpf_object_open_opts , opts, + ); + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); + if (argc != 3 && argc != 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + if (argc == 3) + opts.btf_custom_path = argv[2]; + + /* Set up libbpf errors and debug info callback */ + libbpf_set_print(libbpf_print_fn); + + /* Cleaner handling of Ctrl-C */ + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Load and verify BPF application */ + skel = uprobe_failed_bpf__open_opts(&opts); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + /* Load & verify BPF programs */ + err = uprobe_failed_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } + + uprobe_opts.func_name = "add_test"; + skel->links.add_test = bpf_program__attach_uprobe_opts( + skel->progs.add_test, -1 /* self pid */, argv[1] /* binary path */, + 0 /* offset for function */, &uprobe_opts /* opts */); + if (!skel->links.add_test) { + err = -errno; + fprintf(stderr, "Failed to attach uprobe: %d\n", err); + goto cleanup; + } + printf("Successfully started! Press Ctrl+C to stop.\n"); + fflush(stdout); + while (!exiting) { + sleep(1); + } +cleanup: + /* Clean up */ + uprobe_failed_bpf__destroy(skel); + + return err < 0 ? -err : 0; +}