| // SPDX-License-Identifier: GPL-2.0 | 
 | #include <linux/compiler.h> | 
 | #include <linux/string.h> | 
 | #include <sys/mman.h> | 
 | #include <limits.h> | 
 | #include "debug.h" | 
 | #include "dso.h" | 
 | #include "machine.h" | 
 | #include "thread.h" | 
 | #include "symbol.h" | 
 | #include "map.h" | 
 | #include "util.h" | 
 | #include "tests.h" | 
 |  | 
 | struct test_info { | 
 | 	struct machine *machine; | 
 | 	struct thread *thread; | 
 | }; | 
 |  | 
 | static int init_test_info(struct test_info *ti) | 
 | { | 
 | 	ti->machine = machine__new_host(); | 
 | 	if (!ti->machine) { | 
 | 		pr_debug("machine__new_host() failed!\n"); | 
 | 		return TEST_FAIL; | 
 | 	} | 
 |  | 
 | 	/* Create a dummy thread */ | 
 | 	ti->thread = machine__findnew_thread(ti->machine, 100, 100); | 
 | 	if (!ti->thread) { | 
 | 		pr_debug("machine__findnew_thread() failed!\n"); | 
 | 		return TEST_FAIL; | 
 | 	} | 
 |  | 
 | 	return TEST_OK; | 
 | } | 
 |  | 
 | static void exit_test_info(struct test_info *ti) | 
 | { | 
 | 	thread__put(ti->thread); | 
 | 	machine__delete(ti->machine); | 
 | } | 
 |  | 
 | struct dso_map { | 
 | 	struct dso *dso; | 
 | 	struct map *map; | 
 | }; | 
 |  | 
 | static int find_map_cb(struct map *map, void *d) | 
 | { | 
 | 	struct dso_map *data = d; | 
 |  | 
 | 	if (map__dso(map) != data->dso) | 
 | 		return 0; | 
 | 	data->map = map; | 
 | 	return 1; | 
 | } | 
 |  | 
 | static struct map *find_module_map(struct machine *machine, struct dso *dso) | 
 | { | 
 | 	struct dso_map data = { .dso = dso }; | 
 |  | 
 | 	machine__for_each_kernel_map(machine, find_map_cb, &data); | 
 |  | 
 | 	return data.map; | 
 | } | 
 |  | 
 | static void get_test_dso_filename(char *filename, size_t max_sz) | 
 | { | 
 | 	if (dso_to_test) | 
 | 		strlcpy(filename, dso_to_test, max_sz); | 
 | 	else | 
 | 		perf_exe(filename, max_sz); | 
 | } | 
 |  | 
 | static int create_map(struct test_info *ti, char *filename, struct map **map_p) | 
 | { | 
 | 	struct dso *dso = machine__findnew_dso(ti->machine, filename); | 
 |  | 
 | 	/* | 
 | 	 * If 'filename' matches a current kernel module, must use a kernel | 
 | 	 * map. Find the one that already exists. | 
 | 	 */ | 
 | 	if (dso && dso__kernel(dso) != DSO_SPACE__USER) { | 
 | 		*map_p = find_module_map(ti->machine, dso); | 
 | 		dso__put(dso); | 
 | 		if (!*map_p) { | 
 | 			pr_debug("Failed to find map for current kernel module %s", | 
 | 				 filename); | 
 | 			return TEST_FAIL; | 
 | 		} | 
 | 		map__get(*map_p); | 
 | 		return TEST_OK; | 
 | 	} | 
 |  | 
 | 	dso__put(dso); | 
 |  | 
 | 	/* Create a dummy map at 0x100000 */ | 
 | 	*map_p = map__new(ti->machine, 0x100000, 0xffffffff, 0, NULL, | 
 | 			  PROT_EXEC, 0, NULL, filename, ti->thread); | 
 | 	if (!*map_p) { | 
 | 		pr_debug("Failed to create map!"); | 
 | 		return TEST_FAIL; | 
 | 	} | 
 |  | 
 | 	return TEST_OK; | 
 | } | 
 |  | 
 | static int test_dso(struct dso *dso) | 
 | { | 
 | 	struct symbol *last_sym = NULL; | 
 | 	struct rb_node *nd; | 
 | 	int ret = TEST_OK; | 
 |  | 
 | 	/* dso__fprintf() prints all the symbols */ | 
 | 	if (verbose > 1) | 
 | 		dso__fprintf(dso, stderr); | 
 |  | 
 | 	for (nd = rb_first_cached(dso__symbols(dso)); nd; nd = rb_next(nd)) { | 
 | 		struct symbol *sym = rb_entry(nd, struct symbol, rb_node); | 
 |  | 
 | 		if (sym->type != STT_FUNC && sym->type != STT_GNU_IFUNC) | 
 | 			continue; | 
 |  | 
 | 		/* Check for overlapping function symbols */ | 
 | 		if (last_sym && sym->start < last_sym->end) { | 
 | 			pr_debug("Overlapping symbols:\n"); | 
 | 			symbol__fprintf(last_sym, stderr); | 
 | 			symbol__fprintf(sym, stderr); | 
 | 			ret = TEST_FAIL; | 
 | 		} | 
 | 		/* Check for zero-length function symbol */ | 
 | 		if (sym->start == sym->end) { | 
 | 			pr_debug("Zero-length symbol:\n"); | 
 | 			symbol__fprintf(sym, stderr); | 
 | 			ret = TEST_FAIL; | 
 | 		} | 
 | 		last_sym = sym; | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int subdivided_dso_cb(struct dso *dso, struct machine *machine __maybe_unused, void *d) | 
 | { | 
 | 	struct dso *text_dso = d; | 
 |  | 
 | 	if (dso != text_dso && strstarts(dso__short_name(dso), dso__short_name(text_dso))) | 
 | 		if (test_dso(dso) != TEST_OK) | 
 | 			return -1; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int process_subdivided_dso(struct machine *machine, struct dso *dso) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = machine__for_each_dso(machine, subdivided_dso_cb, dso); | 
 |  | 
 | 	return ret < 0 ? TEST_FAIL : TEST_OK; | 
 | } | 
 |  | 
 | static int test_file(struct test_info *ti, char *filename) | 
 | { | 
 | 	struct map *map = NULL; | 
 | 	int ret, nr; | 
 | 	struct dso *dso; | 
 |  | 
 | 	pr_debug("Testing %s\n", filename); | 
 |  | 
 | 	ret = create_map(ti, filename, &map); | 
 | 	if (ret != TEST_OK) | 
 | 		return ret; | 
 |  | 
 | 	dso = map__dso(map); | 
 | 	nr = dso__load(dso, map); | 
 | 	if (nr < 0) { | 
 | 		pr_debug("dso__load() failed!\n"); | 
 | 		ret = TEST_FAIL; | 
 | 		goto out_put; | 
 | 	} | 
 |  | 
 | 	if (nr == 0) { | 
 | 		pr_debug("DSO has no symbols!\n"); | 
 | 		ret = TEST_SKIP; | 
 | 		goto out_put; | 
 | 	} | 
 |  | 
 | 	ret = test_dso(dso); | 
 |  | 
 | 	/* Module dso is split into many dsos by section */ | 
 | 	if (ret == TEST_OK && dso__kernel(dso) != DSO_SPACE__USER) | 
 | 		ret = process_subdivided_dso(ti->machine, dso); | 
 | out_put: | 
 | 	map__put(map); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int test__symbols(struct test_suite *test __maybe_unused, int subtest __maybe_unused) | 
 | { | 
 | 	char filename[PATH_MAX]; | 
 | 	struct test_info ti; | 
 | 	int ret; | 
 |  | 
 | 	ret = init_test_info(&ti); | 
 | 	if (ret != TEST_OK) | 
 | 		return ret; | 
 |  | 
 | 	get_test_dso_filename(filename, sizeof(filename)); | 
 |  | 
 | 	ret = test_file(&ti, filename); | 
 |  | 
 | 	exit_test_info(&ti); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | DEFINE_SUITE("Symbols", symbols); |