text: /*
   * Oracle Linux DTrace.
   * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
   * Licensed under the Universal Permissive License v 1.0 as shown at
   * http://oss.oracle.com/licenses/upl.
   */
  /* @@xfail: dtv2 */
  
  /*
   * Check mappings between symbol names and addresses using /proc/kallmodsyms.
   */
  
  /* @@timeout: 60 */
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <dtrace.h>
  
  int nchecks = 0, nerrors = 0;
  
  typedef struct mysymbol{
  	long long unsigned addr;
  	long long unsigned size;
  	char *symname;
  	char *modname;
  	char type;
  } mysymbol_t;
  
  mysymbol_t *symbols = NULL;
  int maxnsymbols = 0, nsymbols = 0;
  
  int mycompare(const void *ap, const void *bp)
  {
  	mysymbol_t *a = (mysymbol_t *) ap;
  	mysymbol_t *b = (mysymbol_t *) bp;
  	if (a->addr < b->addr)
  		return -1;
  	if (a->addr > b->addr)
  		return +1;
  	if (a->size < b->size)
  		return -1;
  	if (a->size > b->size)
  		return +1;
  	if (a->type < b->type)
  		return +1;
  	if (a->type > b->type)
  		return -1;
  	return strcmp(a->symname, b->symname);
  }
  
  int read_symbols() {
  	char *line = NULL;
  	size_t line_n = 0;
  	FILE *fd;
  	int n_skip = 0, n_absolute = 0, kernel_flag = 0;
  
  	printf("read_symbols():\n");
  	if ((fd = fopen("/proc/kallmodsyms", "r")) == NULL) return 1;
  	while ((getline(&line, &line_n, fd)) > 0) {
  		char symname[256];
  		char modname[256] = "vmlinux]";
  
  		/*
  		 * Need to make more room.
  		 */
  		if (nsymbols >= maxnsymbols) {
  			maxnsymbols *= 2;
  			if (maxnsymbols == 0)
  				maxnsymbols = 128 * 1024;
  			symbols = (mysymbol_t *) realloc(symbols,
                              maxnsymbols * sizeof (mysymbol_t));
  			if (symbols == NULL) {
  				printf("ERROR: could not allocate symbols\n");
  				fclose(fd);
  				return 1;
  			}
                  }
  
  		/*
  		 * Read the symbol.
  		 */
  		if (sscanf(line, "%llx %llx %c %s [%s",
  		    &symbols[nsymbols].addr,
  		    &symbols[nsymbols].size,
  		    &symbols[nsymbols].type,
  		    symname, modname) < 4) {
  			printf("ERROR: expected at least 4 fields\n");
  			fclose(fd);
  			return 1;
  		}
  
  		/*
  		 * See dt_module.c, dt_modsym_update().
  		 */
  		if (symbols[nsymbols].type == 'a' ||
  		    symbols[nsymbols].type == 'A')
  			continue;
  #define KERNEL_FLAG_INIT_SCRATCH 0x80
  		if (strcmp(symname, "__init_scratch_begin") == 0) {
  			kernel_flag |= KERNEL_FLAG_INIT_SCRATCH;
  			continue;
  		} else if (strcmp(symname, "__init_scratch_end") == 0) {
  			kernel_flag &= ~ KERNEL_FLAG_INIT_SCRATCH;
  			continue;
  		} else if (kernel_flag & KERNEL_FLAG_INIT_SCRATCH) {
  			continue;
  		}
  #undef KERNEL_FLAG_INIT_SCRATCH
  		if (strcmp(modname, "bpf]") == 0)
  			continue;
  
  		/*
  		 * In libdtrace/dt_module.c function dt_modsym_update(),
  		 * we skip a number of symbols.  Do not test them.  The
  		 * list of skipped symbols might change over time.
  		 */
  #define strstarts(x) (strncmp(symname, x, strlen (x)) == 0)
  		if (0
  		    || strstarts("__crc_")        /* never triggers? */
  		    || strstarts("__ksymtab_")
  		    || strstarts("__kcrctab_")
  		    || strstarts("__kstrtab_")
  		    || strstarts("__param_")
  		    || strstarts("__syscall_meta__")
  		    || strstarts("__p_syscall_meta__")
  		    || strstarts("__event_")
  		    || strstarts("event_")
  		    || strstarts("ftrace_event_")
  		    || strstarts("types__")
  		    || strstarts("args__")
  		    || strstarts("__tracepoint_")
  		    || strstarts("__tpstrtab_")
  		    || strstarts("__tpstrtab__")
  		    || strstarts("__initcall_")
  		    || strstarts("__setup_")
  		    || strstarts("__pci_fixup_")
  		    ) {
  			n_skip++;
  			continue;
  		}
  #undef strstarts
  
  		modname[strlen(modname)-1] = '\0';
  		symbols[nsymbols].symname = strdup(symname);
  		symbols[nsymbols].modname = strdup(modname);
  		nsymbols += 1;
  	}
  	free(line);
  	fclose(fd);
  	printf("  %6d symbols kept\n", nsymbols);
  	printf("  %6d symbols skipped\n", n_skip);
  	return 0;
  }
  
  void print_symbol(int i) {
  	printf(" %16.16llx %8.8llx %32s %20s\n",
  	    (long long) symbols[i].addr,
  	    (long long) symbols[i].size,
  	    symbols[i].symname,
  	    symbols[i].modname);
  }
  
  /*
   * See if symbol i has a duplicate (same symbol and module names).
   * If so, return its index.
   * If not, return -1.
   */
  int duplicate(int i) {
  	int j;
  	char *s = symbols[i].symname;
  	char *m = symbols[i].modname;
  	for (j = 0; j < nsymbols; j++) {
  		if (j == i)
  			continue;
  		if (strcmp(s, symbols[j].symname) == 0 &&
  		    strcmp(m, symbols[j].modname) == 0)
  			return (j);
  	}
  	return (-1);
  }
  
  void match_at_addr(int i, GElf_Sym *symp, dtrace_syminfo_t *sip) {
  	int j, match = -1;
  
  	/*
  	 * We looked up the symbol by address (of symbol i).
  	 * So check the symbol address.
  	 */
  	if (symbols[i].addr != symp->st_value) {
  		nerrors++;
  		printf("ERROR: mismatch address\n");
  		printf("  expect:");
  		print_symbol(i);
  		printf("  actual: %16.16llx %8.8llx %32s %20s\n",
  		    (long long) symp->st_value,
  		    (long long) symp->st_size,
  		    sip->dts_name,
  		    sip->dts_object);
  	}
  
  	/*
  	 * Ideally, the found symbol should match symbol i completely, but
  	 * "lookup by address" can be ambiguous.  So just see if we can match
  	 * some known symbol at this address.
  	 */
  
  	if (strcmp(symbols[i].symname, sip->dts_name) == 0)
  		match = i;
  
  	j = i - 1;
  	while (match < 0 && j >= 0 && symbols[j].addr == symbols[i].addr) {
  		if (strcmp(symbols[j].symname, sip->dts_name) == 0)
  			match = j;
  		j--;
  	}
  
  	j = i + 1;
  	while (match < 0 && j < nsymbols && symbols[j].addr == symbols[i].addr) {
  		if (strcmp(symbols[j].symname, sip->dts_name) == 0)
  			match = j;
  		j++;
  	}
  
  	/*
  	 * Check that we found a symbol with the right address and name
  	 * and that it also matches what we expect on size and module.
  	 */
  
  	if (match < 0 ||
  	    symbols[match].size != symp->st_size ||
  	    strcmp(symbols[match].modname, sip->dts_object)) {
  		nerrors++;
  		printf("ERROR: no such reported symbol\n");
  		printf("  expect:");
  		print_symbol(i);
  		printf("  actual: %16.16llx %8.8llx %32s %20s\n",
  		    (long long) symp->st_value,
  		    (long long) symp->st_size,
  		    sip->dts_name,
  		    sip->dts_object);
  	}
  }
  
  int check_lookup_by_addr(dtrace_hdl_t *h) {
  	int i, n_zero = 0;
  	GElf_Sym sym;
  	dtrace_syminfo_t si;
  
  	printf("check look up symbol by address:\n");
  	for (i = 0; i < nsymbols; i++) {
  		/*
  		 * In libdtrace/dt_module.c function dtrace_addr_range_cmp(),
  		 * it is impossible for an address to match a range that has
  		 * zero size.  So skip those symbols here.
  		 */
  		if (symbols[i].size == 0) {
  			n_zero++;
  			continue;
  		}
  
  		nchecks++;
  		if (dtrace_lookup_by_addr
  		    (h, (GElf_Addr) symbols[i].addr, &sym, &si)) {
  
  			printf("ERROR: dtrace_lookup_by_addr failed: %s\n",
  			    dtrace_errmsg(h, dtrace_errno(h)));
  			printf("  expect:");
  			print_symbol(i);
  			nerrors++;
  		} else {
  			match_at_addr(i, &sym, &si);
  		}
  	}
  	printf("  %6d symbols not checked due to zero size\n", n_zero);
  }
  
  int check_lookup_by_name(dtrace_hdl_t *h, int specify_module) {
  	int i, n_dupl = 0;
  	GElf_Sym sym;
  	dtrace_syminfo_t si;
  
  	printf("check lookup by symbol name%s:\n",
  	    specify_module ? " and module" : "");
  	for (i = 0; i < nsymbols; i++) {
  		nchecks++;
  		if (dtrace_lookup_by_name(h,
  		    specify_module ? symbols[i].modname : DTRACE_OBJ_KMODS,
  		    symbols[i].symname, &sym, &si)) {
  
  			/*
  			 * If the module has another symbol with this name,
  			 * it is okay that the lookup failed.
  			 */
  			if (duplicate(i) >= 0) {
  				n_dupl++;
  				continue;
  			}
  
  			printf("ERROR: dtrace_lookup_by_name failed\n");
  			printf("  expect:");
  			print_symbol(i);
  			nerrors++;
  		} else {
  
  			if (symbols[i].addr != sym.st_value ||
  			    symbols[i].size != sym.st_size ||
  			    strcmp(symbols[i].symname, si.dts_name) ||
  			    strcmp(symbols[i].modname, si.dts_object)) {
  
  				printf("ERROR: mismatch\n");
  				printf("  expect:");
  				print_symbol(i);
  				printf("  actual: %16.16llx %8.8llx %32s %20s\n",
  				    (long long) sym.st_value,
  				    (long long) sym.st_size,
  				    si.dts_name,
  				    si.dts_object);
  				nerrors++;
  			}
  		}
  	}
  	printf("  %6d symbols not checked due to duplicate name\n", n_dupl);
  }
  
  int main(int argc, char **argv) {
  	int err;
  	dtrace_hdl_t *h = dtrace_open(DTRACE_VERSION, 0, &err);
  	if (h == NULL) {
  		printf("ERROR: dtrace_open %d |%s|\n",
  		    err, dtrace_errmsg(h, err));
  		return 1;
  	}
  
  	if (read_symbols() != 0)
  		return 1;
  	qsort(symbols, nsymbols, sizeof (mysymbol_t), &mycompare);
  
  	check_lookup_by_addr(h);
  	check_lookup_by_name(h, 1);
  /*	check_lookup_by_name(h, 0); */ /* do not expect this to pass */
  
  	dtrace_close(h);
  
  	printf("test tst.symbols.c had %d errors (out of %d)\n",
  	    nerrors, nchecks);
  	return (nerrors != 0);
  }
  