187 lines · c
1// SPDX-License-Identifier: GPL-2.02/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */3 4#include <test_progs.h>5#include "uretprobe_stack.skel.h"6#include "../sdt.h"7 8/* We set up target_1() -> target_2() -> target_3() -> target_4() -> USDT()9 * call chain, each being traced by our BPF program. On entry or return from10 * each target_*() we are capturing user stack trace and recording it in11 * global variable, so that user space part of the test can validate it.12 *13 * Note, we put each target function into a custom section to get those14 * __start_XXX/__stop_XXX symbols, generated by linker for us, which allow us15 * to know address range of those functions16 */17__attribute__((section("uprobe__target_4")))18__weak int target_4(void)19{20 STAP_PROBE1(uretprobe_stack, target, 42);21 return 42;22}23 24extern const void *__start_uprobe__target_4;25extern const void *__stop_uprobe__target_4;26 27__attribute__((section("uprobe__target_3")))28__weak int target_3(void)29{30 return target_4();31}32 33extern const void *__start_uprobe__target_3;34extern const void *__stop_uprobe__target_3;35 36__attribute__((section("uprobe__target_2")))37__weak int target_2(void)38{39 return target_3();40}41 42extern const void *__start_uprobe__target_2;43extern const void *__stop_uprobe__target_2;44 45__attribute__((section("uprobe__target_1")))46__weak int target_1(int depth)47{48 if (depth < 1)49 return 1 + target_1(depth + 1);50 else51 return target_2();52}53 54extern const void *__start_uprobe__target_1;55extern const void *__stop_uprobe__target_1;56 57extern const void *__start_uretprobe_stack_sec;58extern const void *__stop_uretprobe_stack_sec;59 60struct range {61 long start;62 long stop;63};64 65static struct range targets[] = {66 {}, /* we want target_1 to map to target[1], so need 1-based indexing */67 { (long)&__start_uprobe__target_1, (long)&__stop_uprobe__target_1 },68 { (long)&__start_uprobe__target_2, (long)&__stop_uprobe__target_2 },69 { (long)&__start_uprobe__target_3, (long)&__stop_uprobe__target_3 },70 { (long)&__start_uprobe__target_4, (long)&__stop_uprobe__target_4 },71};72 73static struct range caller = {74 (long)&__start_uretprobe_stack_sec,75 (long)&__stop_uretprobe_stack_sec,76};77 78static void validate_stack(__u64 *ips, int stack_len, int cnt, ...)79{80 int i, j;81 va_list args;82 83 if (!ASSERT_GT(stack_len, 0, "stack_len"))84 return;85 86 stack_len /= 8;87 88 /* check if we have enough entries to satisfy test expectations */89 if (!ASSERT_GE(stack_len, cnt, "stack_len2"))90 return;91 92 if (env.verbosity >= VERBOSE_NORMAL) {93 printf("caller: %#lx - %#lx\n", caller.start, caller.stop);94 for (i = 1; i < ARRAY_SIZE(targets); i++)95 printf("target_%d: %#lx - %#lx\n", i, targets[i].start, targets[i].stop);96 for (i = 0; i < stack_len; i++) {97 for (j = 1; j < ARRAY_SIZE(targets); j++) {98 if (ips[i] >= targets[j].start && ips[i] < targets[j].stop)99 break;100 }101 if (j < ARRAY_SIZE(targets)) { /* found target match */102 printf("ENTRY #%d: %#lx (in target_%d)\n", i, (long)ips[i], j);103 } else if (ips[i] >= caller.start && ips[i] < caller.stop) {104 printf("ENTRY #%d: %#lx (in caller)\n", i, (long)ips[i]);105 } else {106 printf("ENTRY #%d: %#lx\n", i, (long)ips[i]);107 }108 }109 }110 111 va_start(args, cnt);112 113 for (i = cnt - 1; i >= 0; i--) {114 /* most recent entry is the deepest target function */115 const struct range *t = va_arg(args, const struct range *);116 117 ASSERT_GE(ips[i], t->start, "addr_start");118 ASSERT_LT(ips[i], t->stop, "addr_stop");119 }120 121 va_end(args);122}123 124/* __weak prevents inlining */125__attribute__((section("uretprobe_stack_sec")))126__weak void test_uretprobe_stack(void)127{128 LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);129 struct uretprobe_stack *skel;130 int err;131 132 skel = uretprobe_stack__open_and_load();133 if (!ASSERT_OK_PTR(skel, "skel_open"))134 return;135 136 err = uretprobe_stack__attach(skel);137 if (!ASSERT_OK(err, "skel_attach"))138 goto cleanup;139 140 /* trigger */141 ASSERT_EQ(target_1(0), 42 + 1, "trigger_return");142 143 /*144 * Stacks captured on ENTRY uprobes145 */146 147 /* (uprobe 1) target_1 in stack trace*/148 validate_stack(skel->bss->entry_stack1, skel->bss->entry1_len,149 2, &caller, &targets[1]);150 /* (uprobe 1, recursed) */151 validate_stack(skel->bss->entry_stack1_recur, skel->bss->entry1_recur_len,152 3, &caller, &targets[1], &targets[1]);153 /* (uprobe 2) caller -> target_1 -> target_1 -> target_2 */154 validate_stack(skel->bss->entry_stack2, skel->bss->entry2_len,155 4, &caller, &targets[1], &targets[1], &targets[2]);156 /* (uprobe 3) */157 validate_stack(skel->bss->entry_stack3, skel->bss->entry3_len,158 5, &caller, &targets[1], &targets[1], &targets[2], &targets[3]);159 /* (uprobe 4) caller -> target_1 -> target_1 -> target_2 -> target_3 -> target_4 */160 validate_stack(skel->bss->entry_stack4, skel->bss->entry4_len,161 6, &caller, &targets[1], &targets[1], &targets[2], &targets[3], &targets[4]);162 163 /* (USDT): full caller -> target_1 -> target_1 -> target_2 (uretprobed)164 * -> target_3 -> target_4 (uretprobes) chain165 */166 validate_stack(skel->bss->usdt_stack, skel->bss->usdt_len,167 6, &caller, &targets[1], &targets[1], &targets[2], &targets[3], &targets[4]);168 169 /*170 * Now stacks captured on the way out in EXIT uprobes171 */172 173 /* (uretprobe 4) everything up to target_4, but excluding it */174 validate_stack(skel->bss->exit_stack4, skel->bss->exit4_len,175 5, &caller, &targets[1], &targets[1], &targets[2], &targets[3]);176 /* we didn't install uretprobes on target_2 and target_3 */177 /* (uretprobe 1, recur) first target_1 call only */178 validate_stack(skel->bss->exit_stack1_recur, skel->bss->exit1_recur_len,179 2, &caller, &targets[1]);180 /* (uretprobe 1) just a caller in the stack trace */181 validate_stack(skel->bss->exit_stack1, skel->bss->exit1_len,182 1, &caller);183 184cleanup:185 uretprobe_stack__destroy(skel);186}187