cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

vmlinux-kallsyms.c (9970B)


      1// SPDX-License-Identifier: GPL-2.0
      2#include <linux/compiler.h>
      3#include <linux/rbtree.h>
      4#include <inttypes.h>
      5#include <string.h>
      6#include <ctype.h>
      7#include <stdlib.h>
      8#include "dso.h"
      9#include "map.h"
     10#include "symbol.h"
     11#include <internal/lib.h> // page_size
     12#include "tests.h"
     13#include "debug.h"
     14#include "machine.h"
     15
     16#define UM(x) kallsyms_map->unmap_ip(kallsyms_map, (x))
     17
     18static bool is_ignored_symbol(const char *name, char type)
     19{
     20	/* Symbol names that exactly match to the following are ignored.*/
     21	static const char * const ignored_symbols[] = {
     22		/*
     23		 * Symbols which vary between passes. Passes 1 and 2 must have
     24		 * identical symbol lists. The kallsyms_* symbols below are
     25		 * only added after pass 1, they would be included in pass 2
     26		 * when --all-symbols is specified so exclude them to get a
     27		 * stable symbol list.
     28		 */
     29		"kallsyms_addresses",
     30		"kallsyms_offsets",
     31		"kallsyms_relative_base",
     32		"kallsyms_num_syms",
     33		"kallsyms_names",
     34		"kallsyms_markers",
     35		"kallsyms_token_table",
     36		"kallsyms_token_index",
     37		/* Exclude linker generated symbols which vary between passes */
     38		"_SDA_BASE_",		/* ppc */
     39		"_SDA2_BASE_",		/* ppc */
     40		NULL
     41	};
     42
     43	/* Symbol names that begin with the following are ignored.*/
     44	static const char * const ignored_prefixes[] = {
     45		"$",			/* local symbols for ARM, MIPS, etc. */
     46		".LASANPC",		/* s390 kasan local symbols */
     47		"__crc_",		/* modversions */
     48		"__efistub_",		/* arm64 EFI stub namespace */
     49		"__kvm_nvhe_",		/* arm64 non-VHE KVM namespace */
     50		"__AArch64ADRPThunk_",	/* arm64 lld */
     51		"__ARMV5PILongThunk_",	/* arm lld */
     52		"__ARMV7PILongThunk_",
     53		"__ThumbV7PILongThunk_",
     54		"__LA25Thunk_",		/* mips lld */
     55		"__microLA25Thunk_",
     56		NULL
     57	};
     58
     59	/* Symbol names that end with the following are ignored.*/
     60	static const char * const ignored_suffixes[] = {
     61		"_from_arm",		/* arm */
     62		"_from_thumb",		/* arm */
     63		"_veneer",		/* arm */
     64		NULL
     65	};
     66
     67	/* Symbol names that contain the following are ignored.*/
     68	static const char * const ignored_matches[] = {
     69		".long_branch.",	/* ppc stub */
     70		".plt_branch.",		/* ppc stub */
     71		NULL
     72	};
     73
     74	const char * const *p;
     75
     76	for (p = ignored_symbols; *p; p++)
     77		if (!strcmp(name, *p))
     78			return true;
     79
     80	for (p = ignored_prefixes; *p; p++)
     81		if (!strncmp(name, *p, strlen(*p)))
     82			return true;
     83
     84	for (p = ignored_suffixes; *p; p++) {
     85		int l = strlen(name) - strlen(*p);
     86
     87		if (l >= 0 && !strcmp(name + l, *p))
     88			return true;
     89	}
     90
     91	for (p = ignored_matches; *p; p++) {
     92		if (strstr(name, *p))
     93			return true;
     94	}
     95
     96	if (type == 'U' || type == 'u')
     97		return true;
     98	/* exclude debugging symbols */
     99	if (type == 'N' || type == 'n')
    100		return true;
    101
    102	if (toupper(type) == 'A') {
    103		/* Keep these useful absolute symbols */
    104		if (strcmp(name, "__kernel_syscall_via_break") &&
    105		    strcmp(name, "__kernel_syscall_via_epc") &&
    106		    strcmp(name, "__kernel_sigtramp") &&
    107		    strcmp(name, "__gp"))
    108			return true;
    109	}
    110
    111	return false;
    112}
    113
    114static int test__vmlinux_matches_kallsyms(struct test_suite *test __maybe_unused,
    115					int subtest __maybe_unused)
    116{
    117	int err = TEST_FAIL;
    118	struct rb_node *nd;
    119	struct symbol *sym;
    120	struct map *kallsyms_map, *vmlinux_map, *map;
    121	struct machine kallsyms, vmlinux;
    122	struct maps *maps;
    123	u64 mem_start, mem_end;
    124	bool header_printed;
    125
    126	/*
    127	 * Step 1:
    128	 *
    129	 * Init the machines that will hold kernel, modules obtained from
    130	 * both vmlinux + .ko files and from /proc/kallsyms split by modules.
    131	 */
    132	machine__init(&kallsyms, "", HOST_KERNEL_ID);
    133	machine__init(&vmlinux, "", HOST_KERNEL_ID);
    134
    135	maps = machine__kernel_maps(&vmlinux);
    136
    137	/*
    138	 * Step 2:
    139	 *
    140	 * Create the kernel maps for kallsyms and the DSO where we will then
    141	 * load /proc/kallsyms. Also create the modules maps from /proc/modules
    142	 * and find the .ko files that match them in /lib/modules/`uname -r`/.
    143	 */
    144	if (machine__create_kernel_maps(&kallsyms) < 0) {
    145		pr_debug("machine__create_kernel_maps failed");
    146		err = TEST_SKIP;
    147		goto out;
    148	}
    149
    150	/*
    151	 * Step 3:
    152	 *
    153	 * Load and split /proc/kallsyms into multiple maps, one per module.
    154	 * Do not use kcore, as this test was designed before kcore support
    155	 * and has parts that only make sense if using the non-kcore code.
    156	 * XXX: extend it to stress the kcorre code as well, hint: the list
    157	 * of modules extracted from /proc/kcore, in its current form, can't
    158	 * be compacted against the list of modules found in the "vmlinux"
    159	 * code and with the one got from /proc/modules from the "kallsyms" code.
    160	 */
    161	if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms") <= 0) {
    162		pr_debug("machine__load_kallsyms failed");
    163		err = TEST_SKIP;
    164		goto out;
    165	}
    166
    167	/*
    168	 * Step 4:
    169	 *
    170	 * kallsyms will be internally on demand sorted by name so that we can
    171	 * find the reference relocation * symbol, i.e. the symbol we will use
    172	 * to see if the running kernel was relocated by checking if it has the
    173	 * same value in the vmlinux file we load.
    174	 */
    175	kallsyms_map = machine__kernel_map(&kallsyms);
    176
    177	/*
    178	 * Step 5:
    179	 *
    180	 * Now repeat step 2, this time for the vmlinux file we'll auto-locate.
    181	 */
    182	if (machine__create_kernel_maps(&vmlinux) < 0) {
    183		pr_info("machine__create_kernel_maps failed");
    184		goto out;
    185	}
    186
    187	vmlinux_map = machine__kernel_map(&vmlinux);
    188
    189	/*
    190	 * Step 6:
    191	 *
    192	 * Locate a vmlinux file in the vmlinux path that has a buildid that
    193	 * matches the one of the running kernel.
    194	 *
    195	 * While doing that look if we find the ref reloc symbol, if we find it
    196	 * we'll have its ref_reloc_symbol.unrelocated_addr and then
    197	 * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines
    198	 * to fixup the symbols.
    199	 */
    200	if (machine__load_vmlinux_path(&vmlinux) <= 0) {
    201		pr_info("Couldn't find a vmlinux that matches the kernel running on this machine, skipping test\n");
    202		err = TEST_SKIP;
    203		goto out;
    204	}
    205
    206	err = 0;
    207	/*
    208	 * Step 7:
    209	 *
    210	 * Now look at the symbols in the vmlinux DSO and check if we find all of them
    211	 * in the kallsyms dso. For the ones that are in both, check its names and
    212	 * end addresses too.
    213	 */
    214	map__for_each_symbol(vmlinux_map, sym, nd) {
    215		struct symbol *pair, *first_pair;
    216
    217		sym  = rb_entry(nd, struct symbol, rb_node);
    218
    219		if (sym->start == sym->end)
    220			continue;
    221
    222		mem_start = vmlinux_map->unmap_ip(vmlinux_map, sym->start);
    223		mem_end = vmlinux_map->unmap_ip(vmlinux_map, sym->end);
    224
    225		first_pair = machine__find_kernel_symbol(&kallsyms, mem_start, NULL);
    226		pair = first_pair;
    227
    228		if (pair && UM(pair->start) == mem_start) {
    229next_pair:
    230			if (arch__compare_symbol_names(sym->name, pair->name) == 0) {
    231				/*
    232				 * kallsyms don't have the symbol end, so we
    233				 * set that by using the next symbol start - 1,
    234				 * in some cases we get this up to a page
    235				 * wrong, trace_kmalloc when I was developing
    236				 * this code was one such example, 2106 bytes
    237				 * off the real size. More than that and we
    238				 * _really_ have a problem.
    239				 */
    240				s64 skew = mem_end - UM(pair->end);
    241				if (llabs(skew) >= page_size)
    242					pr_debug("WARN: %#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n",
    243						 mem_start, sym->name, mem_end,
    244						 UM(pair->end));
    245
    246				/*
    247				 * Do not count this as a failure, because we
    248				 * could really find a case where it's not
    249				 * possible to get proper function end from
    250				 * kallsyms.
    251				 */
    252				continue;
    253			} else {
    254				pair = machine__find_kernel_symbol_by_name(&kallsyms, sym->name, NULL);
    255				if (pair) {
    256					if (UM(pair->start) == mem_start)
    257						goto next_pair;
    258
    259					pr_debug("WARN: %#" PRIx64 ": diff name v: %s k: %s\n",
    260						 mem_start, sym->name, pair->name);
    261				} else {
    262					pr_debug("WARN: %#" PRIx64 ": diff name v: %s k: %s\n",
    263						 mem_start, sym->name, first_pair->name);
    264				}
    265
    266				continue;
    267			}
    268		} else if (mem_start == kallsyms.vmlinux_map->end) {
    269			/*
    270			 * Ignore aliases to _etext, i.e. to the end of the kernel text area,
    271			 * such as __indirect_thunk_end.
    272			 */
    273			continue;
    274		} else if (is_ignored_symbol(sym->name, sym->type)) {
    275			/*
    276			 * Ignore hidden symbols, see scripts/kallsyms.c for the details
    277			 */
    278			continue;
    279		} else {
    280			pr_debug("ERR : %#" PRIx64 ": %s not on kallsyms\n",
    281				 mem_start, sym->name);
    282		}
    283
    284		err = -1;
    285	}
    286
    287	if (verbose <= 0)
    288		goto out;
    289
    290	header_printed = false;
    291
    292	maps__for_each_entry(maps, map) {
    293		struct map *
    294		/*
    295		 * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while
    296		 * the kernel will have the path for the vmlinux file being used,
    297		 * so use the short name, less descriptive but the same ("[kernel]" in
    298		 * both cases.
    299		 */
    300		pair = maps__find_by_name(kallsyms.kmaps, (map->dso->kernel ?
    301								map->dso->short_name :
    302								map->dso->name));
    303		if (pair) {
    304			pair->priv = 1;
    305		} else {
    306			if (!header_printed) {
    307				pr_info("WARN: Maps only in vmlinux:\n");
    308				header_printed = true;
    309			}
    310			map__fprintf(map, stderr);
    311		}
    312	}
    313
    314	header_printed = false;
    315
    316	maps__for_each_entry(maps, map) {
    317		struct map *pair;
    318
    319		mem_start = vmlinux_map->unmap_ip(vmlinux_map, map->start);
    320		mem_end = vmlinux_map->unmap_ip(vmlinux_map, map->end);
    321
    322		pair = maps__find(kallsyms.kmaps, mem_start);
    323		if (pair == NULL || pair->priv)
    324			continue;
    325
    326		if (pair->start == mem_start) {
    327			if (!header_printed) {
    328				pr_info("WARN: Maps in vmlinux with a different name in kallsyms:\n");
    329				header_printed = true;
    330			}
    331
    332			pr_info("WARN: %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as",
    333				map->start, map->end, map->pgoff, map->dso->name);
    334			if (mem_end != pair->end)
    335				pr_info(":\nWARN: *%" PRIx64 "-%" PRIx64 " %" PRIx64,
    336					pair->start, pair->end, pair->pgoff);
    337			pr_info(" %s\n", pair->dso->name);
    338			pair->priv = 1;
    339		}
    340	}
    341
    342	header_printed = false;
    343
    344	maps = machine__kernel_maps(&kallsyms);
    345
    346	maps__for_each_entry(maps, map) {
    347		if (!map->priv) {
    348			if (!header_printed) {
    349				pr_info("WARN: Maps only in kallsyms:\n");
    350				header_printed = true;
    351			}
    352			map__fprintf(map, stderr);
    353		}
    354	}
    355out:
    356	machine__exit(&kallsyms);
    357	machine__exit(&vmlinux);
    358	return err;
    359}
    360
    361DEFINE_SUITE("vmlinux symtab matches kallsyms", vmlinux_matches_kallsyms);