summaryrefslogtreecommitdiffstats
path: root/scripts/ksymoops/symbol.c
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/ksymoops/symbol.c')
-rw-r--r--scripts/ksymoops/symbol.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/scripts/ksymoops/symbol.c b/scripts/ksymoops/symbol.c
new file mode 100644
index 000000000..5c66cc994
--- /dev/null
+++ b/scripts/ksymoops/symbol.c
@@ -0,0 +1,444 @@
+/*
+ symbol.c.
+
+ Symbol handling routines for ksymoops.
+
+ Copyright Keith Owens <kaos@ocs.com.au>.
+ Released under the GNU Public Licence, Version 2.
+
+ Mon Jan 4 09:08:19 EST 1999
+ Version 0.6d
+ Cast Version to int, glibc 2.1 made elf_addr_t a long.
+
+ Tue Nov 3 02:31:01 EST 1998
+ Version 0.6
+ Fix end of code calculation.
+
+ Wed Oct 28 13:47:23 EST 1998
+ Version 0.4
+ Split into separate sources.
+ */
+
+#include "ksymoops.h"
+#include <errno.h>
+#include <malloc.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Initialise a symbol source */
+void ss_init(SYMBOL_SET *ss, const char *msg)
+{
+ memset(ss, '\0', sizeof(*ss));
+ ss->source = strdup(msg);
+ if (!ss->source)
+ malloc_error(msg);
+}
+
+/* Free dynamic data from a symbol source */
+void ss_free(SYMBOL_SET *ss)
+{
+ int i;
+ SYMBOL *s;
+ for (s = ss->symbol, i = 0; i < ss->used; ++i, ++s)
+ free(s->name);
+ free(ss->symbol);
+ free(ss->source);
+ memset(ss, '\0', sizeof(*ss));
+}
+
+/* Initialise common symbol sets */
+void ss_init_common(void)
+{
+ ss_init(&ss_Version, "Version_");
+}
+
+/* Find a symbol name in a symbol source. Brute force ascending order search,
+ * no hashing. If start is not NULL, it contains the starting point for the
+ * scan and is updated to point to the found entry. If the entry is not found,
+ * return NULL with start pointing to the next highest entry.
+ * NOTE: Assumes that ss is sorted by name.
+ */
+SYMBOL *find_symbol_name(const SYMBOL_SET *ss, const char *symbol, int *start)
+{
+ int i, l;
+ SYMBOL *s;
+ for (i = start ? *start : 0, s = ss->symbol+i; i < ss->used; ++i, ++s) {
+ if ((l = strcmp(symbol, s->name)) == 0) {
+ if (start)
+ *start = i;
+ return(s);
+ }
+ if (l < 0)
+ break;
+ }
+ if (start)
+ *start = i;
+ return NULL;
+}
+
+/* Find an address in a symbol source. Brute force ascending order search, no
+ * hashing. If start is not NULL, it contains the starting point for the scan
+ * and is updated to point to the found entry. If the entry is not found,
+ * return NULL with start pointing to the next highest entry.
+ * NOTE: Assumes that ss is sorted by address.
+ */
+static SYMBOL *find_symbol_address(const SYMBOL_SET *ss,
+ const elf_addr_t address, int *start)
+{
+ int i;
+ SYMBOL *s;
+ for (i = start ? *start : 0, s = ss->symbol+i; i < ss->used; ++i, ++s) {
+ if (address > s->address)
+ continue;
+ else if (address == s->address) {
+ if (start)
+ *start = i;
+ return(s);
+ }
+ else
+ break;
+ }
+ if (start)
+ *start = i;
+ return NULL;
+}
+
+/* Add a symbol to a symbol set, address in binary */
+void add_symbol_n(SYMBOL_SET *ss, const elf_addr_t address,
+ const char type, const char keep, const char *symbol)
+{
+ int i;
+ char **string = NULL;
+ SYMBOL *s;
+ static regex_t re_symbol_ver;
+ static regmatch_t *re_symbol_ver_pmatch;
+ static const char procname[] = "add_symbol_n";
+
+ /* Strip out any trailing symbol version _Rxxxxxxxx. */
+ re_compile(&re_symbol_ver,
+ "^(.*)_R[0-9a-fA-F]{8,}$",
+ REG_NEWLINE|REG_EXTENDED,
+ &re_symbol_ver_pmatch);
+
+ i = regexec(&re_symbol_ver, symbol,
+ re_symbol_ver.re_nsub+1, re_symbol_ver_pmatch, 0);
+ if (debug > 3)
+ fprintf(stderr, "DEBUG: %s regexec %d\n", procname, i);
+ if (i == 0)
+ re_strings(&re_symbol_ver, symbol, re_symbol_ver_pmatch,
+ &string);
+
+ if (debug > 3)
+ fprintf(stderr, "DEBUG: %s %s %s '%c' %d '%s'\n",
+ procname, ss->source, format_address(address),
+ type, keep, i == 0 ? string[1] : symbol);
+ if (ss->used > ss->alloc) {
+ fprintf(stderr,
+ "%s: fatal %s ss %s used (%d) > alloc (%d)\n",
+ procname, prefix, ss->source, ss->used, ss->alloc);
+ exit(2);
+ }
+ if (ss->used == ss->alloc) {
+ /* increase by 20% or 10, whichever is larger, arbitrary */
+ int newsize = ss->alloc*120/100;
+ if (newsize < ss->alloc+10)
+ newsize = ss->alloc+10;
+ if (debug > 3)
+ fprintf(stderr,
+ "DEBUG: %s increasing %s from %d to %d "
+ "entries\n",
+ procname, ss->source, ss->alloc, newsize);
+ ss->symbol = realloc(ss->symbol, newsize*sizeof(*(ss->symbol)));
+ if (!ss->symbol)
+ malloc_error("realloc ss");
+ ss->alloc = newsize;
+ }
+ s = ss->symbol+ss->used;
+ if (i == 0) {
+ s->name = string[1];
+ string[1] = NULL; /* don't free this one */
+ }
+ else {
+ s->name = strdup(symbol);
+ if (!s->name)
+ malloc_error("strdup symbol");
+ }
+ s->type = type;
+ s->keep = keep;
+ s->address = address;
+ ++ss->used;
+ re_strings_free(&re_symbol_ver, &string);
+}
+
+/* Add a symbol to a symbol set, address in character */
+void add_symbol(SYMBOL_SET *ss, const char *address, const char type,
+ const char keep, const char *symbol)
+{
+ elf_addr_t a;
+ static char const procname[] = "add_symbol";
+ errno = 0;
+ a = strtoul(address, NULL, 16);
+ if (errno) {
+ fprintf(stderr,
+ "%s: %s address '%s' is in error",
+ prefix, procname, address);
+ perror(" ");
+ ++errors;
+ }
+ add_symbol_n(ss, a, type, 1, symbol);
+}
+
+/* Map an address to symbol, offset and length, address in binary */
+char *map_address(const SYMBOL_SET *ss, const elf_addr_t address)
+{
+ int i = 0, l;
+ SYMBOL *s;
+ static char *map = NULL;
+ static int size = 0;
+ static const char procname[] = "map_address_n";
+
+ if (debug > 2)
+ fprintf(stderr, "DEBUG: %s %s %s\n",
+ procname, ss->source, format_address(address));
+ s = find_symbol_address(ss, address, &i);
+ if (!s && --i >= 0)
+ s = ss->symbol+i; /* address is between s and s+1 */
+
+ /* Extra map text is always < 100 bytes */
+ if (s)
+ l = strlen(s->name) + 100;
+ else
+ l = 100;
+ if (l > size) {
+ map = realloc(map, l);
+ if (!map)
+ malloc_error(procname);
+ size = l;
+ }
+ if (!s) {
+ if (ss->used == 0)
+ snprintf(map, size, "No symbols available");
+ else
+ snprintf(map, size, "Before first symbol");
+ }
+ else if ((i+1) >= ss->used) {
+ /* Somewhere past last symbol. Length of last section of code
+ * is unknown, arbitrary cutoff at 32K.
+ */
+ elf_addr_t offset = address - s->address;
+ if (offset > 32768)
+ snprintf(map, size, "<END_OF_CODE+%lx/????>", offset);
+ else
+ snprintf(map, size, "<%s+%lx/????>", s->name, offset);
+ }
+ else
+ snprintf(map, size,
+ "<%s+%lx/%lx>",
+ s->name, address - s->address,
+ (s+1)->address - s->address);
+ return(map);
+}
+
+/* After sorting, obsolete symbols are at the top. Delete them. */
+static void ss_compress(SYMBOL_SET *ss)
+{
+ int i, j;
+ SYMBOL *s;
+ static const char procname[] = "ss_compress";
+
+ if (debug > 1)
+ fprintf(stderr, "DEBUG: %s on table %s, before %d ",
+ procname, ss->source, ss->used);
+ for (i = 0, s = ss->symbol+i; i < ss->used; ++i, ++s) {
+ if (!s->keep) {
+ for (j = i; j < ss->used; ++j, ++s) {
+ if (s->keep) {
+ fprintf(stderr,
+ "%s: fatal %s table %s is not "
+ "sorted\n",
+ prefix, procname, ss->source);
+ exit(2);
+ }
+ }
+ break;
+ }
+ }
+ for (j = i, s = ss->symbol+j; j < ss->used; ++j, ++s)
+ free(s->name);
+ ss->used = i;
+ if (debug > 1)
+ fprintf(stderr, "after %d\n", ss->used);
+}
+
+static int ss_compare_atn(const void *a, const void *b)
+{
+ SYMBOL *c = (SYMBOL *) a;
+ SYMBOL *d = (SYMBOL *) b;
+ int i;
+
+ /* obsolete symbols to the top */
+ if (c->keep != d->keep)
+ return(d->keep - c->keep);
+ if (c->address > d->address)
+ return(1);
+ if (c->address < d->address)
+ return(-1);
+ if (c->type > d->type)
+ return(1);
+ if (c->type < d->type)
+ return(-1);
+ if ((i = strcmp(c->name, d->name)))
+ return(i);
+ return(0);
+}
+
+/* Sort a symbol set by address, type and name */
+void ss_sort_atn(SYMBOL_SET *ss)
+{
+ if (debug)
+ fprintf(stderr, "DEBUG: sorting symbols for %s (atn)\n",
+ ss->source);
+ qsort((char *) ss->symbol, (unsigned) ss->used,
+ sizeof(*(ss->symbol)), ss_compare_atn);
+ ss_compress(ss);
+}
+
+static int ss_compare_na(const void *a, const void *b)
+{
+ SYMBOL *c = (SYMBOL *) a;
+ SYMBOL *d = (SYMBOL *) b;
+ int i;
+
+ /* obsolete symbols to the top */
+ if (c->keep != d->keep)
+ return(d->keep - c->keep);
+ if ((i = strcmp(c->name, d->name)))
+ return(i);
+ if (c->address > d->address)
+ return(1);
+ if (c->address < d->address)
+ return(-1);
+ return(0);
+}
+
+/* Sort a symbol set by name and address, drop duplicates. There should be
+ * no duplicates but I have seen duplicates in ksyms on 2.0.35.
+ */
+void ss_sort_na(SYMBOL_SET *ss)
+{
+ int i;
+ SYMBOL *s;
+ if (debug)
+ fprintf(stderr, "DEBUG: sorting symbols for %s (na)\n",
+ ss->source);
+ qsort((char *) ss->symbol, (unsigned) ss->used,
+ sizeof(*(ss->symbol)), ss_compare_na);
+ ss_compress(ss);
+ s = ss->symbol;
+ for (i = 0; i < ss->used-1; ++i) {
+ if (strcmp(s->name, (s+1)->name) == 0 &&
+ s->address == (s+1)->address) {
+ if (s->type != ' ')
+ (s+1)->keep = 0;
+ else
+ s->keep = 0;
+ }
+ ++s;
+ }
+ qsort((char *) ss->symbol, (unsigned) ss->used,
+ sizeof(*(ss->symbol)), ss_compare_na);
+ ss_compress(ss);
+}
+
+/* Copy a symbol set, including all its strings */
+SYMBOL_SET *ss_copy(const SYMBOL_SET *ss)
+{
+ SYMBOL_SET *ssc;
+ if (debug > 3)
+ fprintf(stderr,
+ "DEBUG: ss_copy %s\n", ss->source);
+ ssc = malloc(sizeof(*ssc));
+ if (!ssc)
+ malloc_error("copy ssc");
+ ss_init(ssc, ss->source);
+ ssc->used = ss->used;
+ ssc->alloc = ss->used; /* shrink the copy */
+ ssc->symbol = malloc(ssc->used*sizeof(*(ssc->symbol)));
+ if (!(ssc->symbol))
+ malloc_error("copy ssc symbols");
+ memcpy(ssc->symbol, ss->symbol, ssc->used*sizeof(*(ssc->symbol)));
+ return(ssc);
+}
+
+/* Convert version number to major, minor string. */
+static const char *format_Version(elf_addr_t Version)
+{
+ static char string[12]; /* 255.255.255\0 worst case */
+ snprintf(string, sizeof(string), "%d.%d.%d",
+ (int) ((Version >> 16) & 0xff),
+ (int) ((Version >> 8) & 0xff),
+ (int) ((Version) & 0xff));
+ return(string);
+}
+
+/* Save version number. The "address" is the version number, the "symbol" is
+ * the source of the version.
+ */
+void add_Version(const char *version, const char *source)
+{
+ static char const procname[] = "add_Version";
+ int i = atoi(version);
+ if (debug > 1)
+ fprintf(stderr, "DEBUG: %s %s %s %s\n",
+ procname, source, version, format_Version(i));
+ add_symbol_n(&ss_Version, i, 'V', 1, source);
+}
+
+/* Extract Version_ number from a symbol set and save it. */
+void extract_Version(SYMBOL_SET *ss)
+{
+ int i = 0;
+ SYMBOL *s;
+
+ s = find_symbol_name(ss, "Version_", &i);
+ if (!s && i < ss->used)
+ s = ss->symbol+i; /* first symbol after "Version_" */
+ if (!s || strncmp(s->name, "Version_", 8))
+ return;
+ add_Version(s->name+8, ss->source);
+}
+
+/* Compare all extracted Version numbers. Silent unless there is a problem. */
+void compare_Version(void)
+{
+ int i = 0;
+ SYMBOL *s, *s0;
+ static int prev_used = 0;
+
+ if (!ss_Version.used)
+ return;
+ /* Only check if the Version table has changed in size */
+ if (prev_used == ss_Version.used)
+ return;
+
+ ss_sort_na(&ss_Version);
+ s0 = s = ss_Version.symbol;
+ if (debug)
+ fprintf(stderr, "DEBUG: Version %s\n",
+ format_Version(s0->address));
+ for (i = 0; i < ss_Version.used; ++i, ++s) {
+ if (s->address != s0->address) {
+ fprintf(stderr,
+ "Version mismatch error. %s says %s, ",
+ s0->name,
+ format_Version(s0->address));
+ fprintf(stderr,
+ "%s says %s. Expect lots of address "
+ "mismatches.\n",
+ s->name,
+ format_Version(s->address));
+ ++errors;
+ }
+ }
+ prev_used = ss_Version.used;
+}