summaryrefslogtreecommitdiffstats
path: root/tools/perf/tests/maps.c
diff options
context:
space:
mode:
authorMiguel Ojeda <ojeda@kernel.org>2026-08-16 15:32:33 +0200
committerMiguel Ojeda <ojeda@kernel.org>2026-08-23 21:27:17 +0200
commit5febf432df1cfa5b25d99b54c32103fafbdd0eb9 (patch)
tree93a1ed1d337bc53adf1e572c2586d347703b286e /tools/perf/tests/maps.c
downloadlinux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.tar.gz
linux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.zip
kbuild: rust: keep Rust objects out of Clang LTO with inline helpersgrafted
Under `CONFIG_LTO_CLANG` + `CONFIG_RUST_INLINE_HELPERS`, one may hit `objtool` errors such as: vmlinux.o: error: objtool: _R..._3Gsp4boot+0xd6a: can't find jump dest instruction at .text._R..._3Gsp4boot+0x1dfd The reason is that in such builds, the Clang invocation that compiles the combined Rust plus helpers bitcode emits LLVM bitcode (again) -- the final code generation happens in the linker's LTO step, which the `-mllvm` trap options passed to Clang do not reach. This, in turn, means that unreachable traps are missing, and the impossible paths do not merely fallthrough to the next symbol, but past the end of their own section, since LTO builds place each function in its own section. Thus filter `CC_FLAGS_LTO` out of the Clang invocation, so that it always emits machine code directly, with the traps in place. Assisted-by: LLM Cc: Gary Guo <gary@garyguo.net> Cc: Boqun Feng <boqun@kernel.org> Cc: Alice Ryhl <aliceryhl@google.com> Cc: Matthew Maurer <mmaurer@google.com> Cc: Josh Poimboeuf <jpoimboe@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: stable@vger.kernel.org Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust") Acked-by: Gary Guo <gary@garyguo.net> Link: https://patch.msgid.link/20260816133233.197500-2-ojeda@kernel.org Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Diffstat (limited to 'tools/perf/tests/maps.c')
-rw-r--r--tools/perf/tests/maps.c245
1 files changed, 245 insertions, 0 deletions
diff --git a/tools/perf/tests/maps.c b/tools/perf/tests/maps.c
new file mode 100644
index 000000000..2c05d62f4
--- /dev/null
+++ b/tools/perf/tests/maps.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <inttypes.h>
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include "tests.h"
+#include "map.h"
+#include "maps.h"
+#include "dso.h"
+#include "debug.h"
+
+struct map_def {
+ const char *name;
+ u64 start;
+ u64 end;
+};
+
+struct check_maps_cb_args {
+ struct map_def *merged;
+ unsigned int i;
+};
+
+static int check_maps_cb(struct map *map, void *data)
+{
+ struct check_maps_cb_args *args = data;
+ struct map_def *merged = &args->merged[args->i];
+
+ if (map__start(map) != merged->start ||
+ map__end(map) != merged->end ||
+ strcmp(dso__name(map__dso(map)), merged->name) ||
+ refcount_read(map__refcnt(map)) != 1) {
+ return 1;
+ }
+ args->i++;
+ return 0;
+}
+
+static int failed_cb(struct map *map, void *data __maybe_unused)
+{
+ pr_debug("\tstart: %" PRIu64 " end: %" PRIu64 " name: '%s' refcnt: %d\n",
+ map__start(map),
+ map__end(map),
+ dso__name(map__dso(map)),
+ refcount_read(map__refcnt(map)));
+
+ return 0;
+}
+
+static int check_maps(struct map_def *merged, unsigned int size, struct maps *maps)
+{
+ bool failed = false;
+
+ if (maps__nr_maps(maps) != size) {
+ pr_debug("Expected %d maps, got %d", size, maps__nr_maps(maps));
+ failed = true;
+ } else {
+ struct check_maps_cb_args args = {
+ .merged = merged,
+ .i = 0,
+ };
+ failed = maps__for_each_map(maps, check_maps_cb, &args);
+ }
+ if (failed) {
+ pr_debug("Expected:\n");
+ for (unsigned int i = 0; i < size; i++) {
+ pr_debug("\tstart: %" PRIu64 " end: %" PRIu64 " name: '%s' refcnt: 1\n",
+ merged[i].start, merged[i].end, merged[i].name);
+ }
+ pr_debug("Got:\n");
+ maps__for_each_map(maps, failed_cb, NULL);
+ }
+ return failed ? TEST_FAIL : TEST_OK;
+}
+
+static int test__maps__merge_in(struct test_suite *t __maybe_unused, int subtest __maybe_unused)
+{
+ unsigned int i;
+ struct map_def bpf_progs[] = {
+ { "bpf_prog_1", 200, 300 },
+ { "bpf_prog_2", 500, 600 },
+ { "bpf_prog_3", 800, 900 },
+ };
+ struct map_def merged12[] = {
+ { "kcore1", 100, 200 },
+ { "bpf_prog_1", 200, 300 },
+ { "kcore1", 300, 500 },
+ { "bpf_prog_2", 500, 600 },
+ { "kcore1", 600, 800 },
+ { "bpf_prog_3", 800, 900 },
+ { "kcore1", 900, 1000 },
+ };
+ struct map_def merged3[] = {
+ { "kcore1", 100, 200 },
+ { "bpf_prog_1", 200, 300 },
+ { "kcore1", 300, 500 },
+ { "bpf_prog_2", 500, 600 },
+ { "kcore1", 600, 800 },
+ { "bpf_prog_3", 800, 900 },
+ { "kcore1", 900, 1000 },
+ { "kcore3", 1000, 1100 },
+ };
+ struct map *map_kcore1, *map_kcore2, *map_kcore3;
+ int ret;
+ struct maps *maps = maps__new(NULL);
+
+ TEST_ASSERT_VAL("failed to create maps", maps);
+
+ for (i = 0; i < ARRAY_SIZE(bpf_progs); i++) {
+ struct map *map;
+
+ map = dso__new_map(bpf_progs[i].name);
+ TEST_ASSERT_VAL("failed to create map", map);
+
+ map__set_start(map, bpf_progs[i].start);
+ map__set_end(map, bpf_progs[i].end);
+ TEST_ASSERT_VAL("failed to insert map", maps__insert(maps, map) == 0);
+ map__put(map);
+ }
+
+ map_kcore1 = dso__new_map("kcore1");
+ TEST_ASSERT_VAL("failed to create map", map_kcore1);
+
+ map_kcore2 = dso__new_map("kcore2");
+ TEST_ASSERT_VAL("failed to create map", map_kcore2);
+
+ map_kcore3 = dso__new_map("kcore3");
+ TEST_ASSERT_VAL("failed to create map", map_kcore3);
+
+ /* kcore1 map overlaps over all bpf maps */
+ map__set_start(map_kcore1, 100);
+ map__set_end(map_kcore1, 1000);
+
+ /* kcore2 map hides behind bpf_prog_2 */
+ map__set_start(map_kcore2, 550);
+ map__set_end(map_kcore2, 570);
+
+ /* kcore3 map hides behind bpf_prog_3, kcore1 and adds new map */
+ map__set_start(map_kcore3, 880);
+ map__set_end(map_kcore3, 1100);
+
+ ret = maps__merge_in(maps, map_kcore1);
+ TEST_ASSERT_VAL("failed to merge map", !ret);
+
+ ret = check_maps(merged12, ARRAY_SIZE(merged12), maps);
+ TEST_ASSERT_VAL("merge check failed", !ret);
+
+ ret = maps__merge_in(maps, map_kcore2);
+ TEST_ASSERT_VAL("failed to merge map", !ret);
+
+ ret = check_maps(merged12, ARRAY_SIZE(merged12), maps);
+ TEST_ASSERT_VAL("merge check failed", !ret);
+
+ ret = maps__merge_in(maps, map_kcore3);
+ TEST_ASSERT_VAL("failed to merge map", !ret);
+
+ ret = check_maps(merged3, ARRAY_SIZE(merged3), maps);
+ TEST_ASSERT_VAL("merge check failed", !ret);
+
+ maps__zput(maps);
+ map__zput(map_kcore1);
+ map__zput(map_kcore2);
+ map__zput(map_kcore3);
+ return TEST_OK;
+}
+
+static int test__maps__fixup_overlap_and_insert(struct test_suite *t __maybe_unused,
+ int subtest __maybe_unused)
+{
+ struct map_def initial_maps[] = {
+ { "target_map", 1000, 2000 },
+ { "next_map", 3000, 4000 },
+ };
+ struct map_def insert_split = { "split_map", 1400, 1600 };
+ struct map_def expected_after_split[] = {
+ { "target_map", 1000, 1400 },
+ { "split_map", 1400, 1600 },
+ { "target_map", 1600, 2000 },
+ { "next_map", 3000, 4000 },
+ };
+
+ struct map_def insert_eclipse = { "eclipse_map", 2500, 4500 };
+ struct map_def expected_final[] = {
+ { "target_map", 1000, 1400 },
+ { "split_map", 1400, 1600 },
+ { "target_map", 1600, 2000 },
+ { "eclipse_map", 2500, 4500 },
+ /* "next_map" (3000-4000) is removed */
+ };
+
+ struct map *map_split, *map_eclipse;
+ int ret;
+ unsigned int i;
+ struct maps *maps = maps__new(NULL);
+
+ TEST_ASSERT_VAL("failed to create maps", maps);
+
+ for (i = 0; i < ARRAY_SIZE(initial_maps); i++) {
+ struct map *map = dso__new_map(initial_maps[i].name);
+
+ TEST_ASSERT_VAL("failed to create map", map);
+ map__set_start(map, initial_maps[i].start);
+ map__set_end(map, initial_maps[i].end);
+ TEST_ASSERT_VAL("failed to insert map", maps__insert(maps, map) == 0);
+ map__put(map);
+ }
+
+ // Check splitting.
+ map_split = dso__new_map(insert_split.name);
+ TEST_ASSERT_VAL("failed to create split map", map_split);
+ map__set_start(map_split, insert_split.start);
+ map__set_end(map_split, insert_split.end);
+
+ ret = maps__fixup_overlap_and_insert(maps, map_split);
+ TEST_ASSERT_VAL("failed to fixup and insert split map", !ret);
+
+ map__zput(map_split);
+ ret = check_maps(expected_after_split, ARRAY_SIZE(expected_after_split), maps);
+ TEST_ASSERT_VAL("split check failed", !ret);
+
+ // Check cover 1 map with another.
+ map_eclipse = dso__new_map(insert_eclipse.name);
+ TEST_ASSERT_VAL("failed to create eclipse map", map_eclipse);
+ map__set_start(map_eclipse, insert_eclipse.start);
+ map__set_end(map_eclipse, insert_eclipse.end);
+
+ ret = maps__fixup_overlap_and_insert(maps, map_eclipse);
+ TEST_ASSERT_VAL("failed to fixup and insert eclipse map", !ret);
+
+ map__zput(map_eclipse);
+ ret = check_maps(expected_final, ARRAY_SIZE(expected_final), maps);
+ TEST_ASSERT_VAL("eclipse check failed", !ret);
+
+ maps__zput(maps);
+ return TEST_OK;
+}
+
+static struct test_case tests__maps[] = {
+ TEST_CASE("Test merge_in interface", maps__merge_in),
+ TEST_CASE("Test fix up overlap interface", maps__fixup_overlap_and_insert),
+ { .name = NULL, }
+};
+
+struct test_suite suite__maps = {
+ .desc = "Maps - per process mmaps abstraction",
+ .test_cases = tests__maps,
+};