/*
    symbol.c.

    Symbol handling routines for ksymoops.

    Copyright 1999 Keith Owens <kaos@ocs.com.au>.
    Released under the GNU Public Licence, Version 2.

 */

#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 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 (i < ss->used-1 && address == (s+1)->address &&
	        (s->type == 'G' || s->type == 'g'))
		/* more than one symbol on the same address, skip the ones that
		 * came from modutils assist.
		 */
		continue; 
	    if (start)
		*start = i;
	    return(s);
	}
	else
	    break;
    }
    if (start)
	*start = i;
    return NULL;
}

/* Add a symbol to a symbol set, address in binary */
int add_symbol_n(SYMBOL_SET *ss, const 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 _R(smp_)?xxxxxxxx. */
    RE_COMPILE(&re_symbol_ver,
	"^(.*)_R(smp_)?[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);
    DEBUG(4, "regexec %d", i);
    if (i == 0)
	re_strings(&re_symbol_ver, symbol, re_symbol_ver_pmatch,
	    &string);

    DEBUG(4, "%s %s '%c' %d '%s'",
	ss->source, format_address(address, NULL),
	type, keep, i == 0 ? string[1] : symbol);
    if (ss->used > ss->alloc)
	FATAL("ss %s used (%d) > alloc (%d)", ss->source, ss->used, ss->alloc);
    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;
	DEBUG(4, "increasing %s from %d to %d entries",
	    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;
    s->module = NULL;	/* only used in final map */
    re_strings_free(&re_symbol_ver, &string);
    return(ss->used++);
}

/* Add a symbol to a symbol set, address in character */
int add_symbol(SYMBOL_SET *ss, const char *address, const char type,
               const char keep, const char *symbol)
{
    addr_t a;
    static char const procname[] = "add_symbol";
    errno = 0;
    a = hexstring(address);
    if (errno) {
	ERROR("address '%s' is in error", address);
	perror(prefix);
    }
    return(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 addr_t address,
		  const OPTIONS *options)
{
    int i = 0, l;
    SYMBOL *s;
    static char *map = NULL;
    static int size = 0;
    static const char procname[] = "map_address_n";

    DEBUG(3, "%s %s", ss->source, format_address(address, options));
    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;
	if (s->module)
	    l += strlen(s->module)+2;  /* [module] */
    }
    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.
	 */
	addr_t offset = address - s->address;
	if (offset > 32768)
	    snprintf(map, size,
		     options->hex ? "<END_OF_CODE+%llx/????>"
		    : "<END_OF_CODE+%lld/????>",
		offset);
	else
	    snprintf(map, size,
		     options->hex ? "<%s+%llx/????>"
		    : "<%s+%lld/????>",
		s->name, offset);
    }
    else {
	snprintf(map, size,
	         options->hex ? "<%s%s%s%s+%llx/%llx>"
		: "<%s%s%s%s+%lld/%lld>",
	    s->module ? "[" : "",
	    s->module ? s->module : "",
	    s->module ? "]" : "",
	    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";

    DEBUG(2, "table %s, before %d", 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)
		    FATAL("table %s is not sorted", ss->source);
	    }
	    break;
	}
    }
    for (j = i, s = ss->symbol+j; j < ss->used; ++j, ++s) {
	DEBUG(4, "dropped %s", s->name);
	free(s->name);
    }
    ss->used = i;
    DEBUG(2, "table %s, after %d", ss->source, 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)
{
    static char const procname[] = "ss_sort_atn";
    DEBUG(1, "%s", 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;
    static char const procname[] = "ss_sort_na";
    DEBUG(1, "%s", 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;
    static char const procname[] = "ss_copy";
    DEBUG(4, "%s", 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(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);
    DEBUG(2, "%s %s %s", 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))
	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;
    static char const procname[] = "compare_Version";

    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;
    DEBUG(1, "Version %s", format_Version(s0->address));
    for (i = 0; i < ss_Version.used; ++i, ++s) {
	if (s->address != s0->address) {
	    /* format_Version uses static area, do separate calls */
	    WARNING_S("Version mismatch.  %s says %s,",
		s0->name, format_Version(s0->address));
	    WARNING_E(" %s says %s.  Expect lots of address mismatches.",
		s->name, format_Version(s->address));
	}
    }
    prev_used = ss_Version.used;
}
