/*
 * lib/klib/kl_symbol.c
 *
 * This file provides a standard interfact for access to symbol and 
 * type information.
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH,
 *                    IBM Corporation
 */
#include <klib.h>
#include <assert.h>
#include <stdlib.h>

/*
 * syment_cmp()
 *
 * comparison function for quick sort of syment_t array
 */
static int
syment_cmp(const void *sp_a, const void *sp_b)
{
	syment_t *const a = *(syment_t **) sp_a;
	syment_t *const b = *(syment_t **) sp_b;

	return ((a->s_addr > b->s_addr) - (a->s_addr < b->s_addr));
}

/*
 * alloc_syment()
 */
static syment_t *
alloc_syment(kaddr_t addr, int type, char *name) 
{
	syment_t *sp;
	
	if ((sp = (syment_t *)malloc(sizeof(syment_t)))) {
		memset(sp, 0, sizeof(syment_t));
		sp->s_addr = addr;
		sp->s_type = type;
		sp->s_name = (char *)strdup(name);
	}
	return(sp);
}

/*
 * kl_init_syminfo() 
 *
 * Initialize KLIB symbol table information
 */
maplist_t *
kl_init_syminfo(char *mapfile, int flag)
{
	maplist_t *ml;
	if( (ml = (maplist_t*)malloc(sizeof(maplist_t)) )){
		memset(ml, 0, sizeof(maplist_t));
		ml->mapfile = strdup(mapfile);
		ml->objname = NULL;
		if(kl_read_syminfo(mapfile, ml, flag)){
			free(ml);
			return(NULL);
		}
	}

	return(ml);
}
/*
 * kl_insert_symbols()
 *
 * Insert symbols from from list of syment_t into symtab_t, and sorting symbols
 */

static void
kl_insert_symbols(symtab_t * stp, syment_t *syment_list, int flag)
{
	int i, ret, dot_cnt = 0;
	syment_t *sp;

	if (stp->symcnt) {
		stp->symaddrs = 
			(syment_t **)malloc(sizeof(syment_t) * stp->symcnt);
		sp = syment_list;
		for (i = 0; i < stp->symcnt; i++) {
			stp->symaddrs[i] = sp;
			ret = kl_insert_btnode((btnode_t **)&stp->symnames, 
				(btnode_t *)sp, DUPLICATES_OK);
			if (ret == -1) {
				fprintf(KL_ERRORFP, "i=%d sp=0x%"FMTPTR"x\n",
					i, UADDR(sp));
				break;
			}
			if (!flag && ((dot_cnt++ % 1000) == 0)) {
				fprintf(KL_ERRORFP, ".");
			}
			sp = sp->s_next;
		}
		qsort(stp->symaddrs, stp->symcnt, sizeof(syment_t*),
		      syment_cmp );

		/* finally correct next pointers, to
		   point to next entry in the sorted array */
		for(i=0; i<stp->symcnt - 1; i++){
			stp->symaddrs[i]->s_next = stp->symaddrs[i+1];
		}

		stp->symaddrs[i]->s_next = NULL;
	}
}

/*
 * kl_init_ksyms() 
 *
 * Initialize KLIB symbol table information
 */
int
kl_init_ksyms(int flag)
{
	int i, dot_cnt = 0;
	size_t nsyms = 0, size_modsym = 0;
	void *dump_module = NULL, *dump_modsym = NULL, *dump_name = NULL;
	kaddr_t next_module = 0, syms = 0;
	unsigned long value;
	kaddr_t name;
	symtab_t *stp;
	syment_t *cur_syment = NULL;
	syment_t *syment_list = NULL;
	maplist_t *ml;

	if( (ml = (maplist_t*)malloc(sizeof(maplist_t)) )){
		memset(ml, 0, sizeof(maplist_t));
		ml->mapfile = strdup(KL_KSYMTAB);
		ml->objname = NULL;
	}

	stp = (symtab_t *)malloc(sizeof(symtab_t));
	memset(stp, 0, sizeof(symtab_t));
	
	dump_name = kl_alloc_block(KL_SYMBOL_NAME_LEN, K_TEMP);
	if(KL_ERROR){
		return(1);
	}

	do{
		if(kl_get_module(NULL, &next_module, &dump_module)){
			kl_free_block(dump_name);
			kl_free_block(dump_module);
			kl_free_block(dump_modsym);
			return(1);
		}

		syms=kl_kaddr(dump_module, "module", "syms");
		nsyms=KL_UINT(dump_module, "module", "nsyms");
		for(i=0; i<nsyms; i++){
			if(kl_get_structure(syms, "module_symbol" ,
					    &size_modsym, &dump_modsym)){
				kl_free_block(dump_name);
				kl_free_block(dump_module);
				kl_free_block(dump_modsym);
				return(1);
			}
			value=KL_UINT(dump_modsym, "module_symbol", "value");
			name=kl_kaddr(dump_modsym, "module_symbol", "name");
			memset(dump_name, 0, KL_SYMBOL_NAME_LEN);
			GET_BLOCK(name, KL_SYMBOL_NAME_LEN, dump_name);
			if (!flag && ((dot_cnt++ % 1000) == 0)) {
				fprintf(KL_ERRORFP, ".");
			}

			if (cur_syment) {
				cur_syment->s_next = alloc_syment(value,
								  SYM_KSYM,
								  dump_name);
				cur_syment = cur_syment->s_next;
			} else {
				syment_list = alloc_syment(value, SYM_KSYM,
							   dump_name);
				cur_syment = syment_list;
			}
			stp->symcnt++;

			syms += size_modsym;
		}
		next_module = kl_kaddr(dump_module, "module", "next");
	} while(next_module);

	kl_insert_symbols(stp, syment_list, flag);

	ml->syminfo=stp;
	ml->next=STP->next;
	ml->maplist_type=SYM_MAP_KSYM;
	STP->next=ml;

	kl_free_block(dump_name);
	kl_free_block(dump_module);
	kl_free_block(dump_modsym);
	return(0);
}

/*
 * kl_read_syminfo() 
 *
 * read symbol table information
 */
int
kl_read_syminfo(char *mapfile, maplist_t *ml, int flag)
{
	int type, ret, dot_cnt = 0;
	FILE *fp;
	syment_t *cur_syment = (syment_t *)NULL;
	syment_t *syment_list = (syment_t *)NULL;
	kaddr_t addr;
	symtab_t *stp;
	char symtype, name[KL_SYMBOL_NAME_LEN+1];

	name[KL_SYMBOL_NAME_LEN] = '\0';
	if (!(fp = fopen(mapfile, "r"))) {
		ml->syminfo=NULL;
		return(1);
	}
	stp = (symtab_t *)malloc(sizeof(symtab_t));
	memset(stp, 0, sizeof(symtab_t));
	addr = 0;
	symtype = 0;
	name[0] = 0;
	for ( ;; ) {
		ret = fscanf(fp, "%"FMTPTR"x %c %s",
			&addr, &symtype, name);
		if (ret == EOF) {
			break;
		}
		if (ret == 0) {
			addr=0;
			ret = fscanf(fp, "%c %s", &symtype, name);
			if (ret != 2){
				break;
			}
		}
		switch (symtype) {

			case 'r': /* Local ReadOnly Data */
				addr += ml->rodata_off;
				type = SYM_LOCAL_DATA;
				break;

			case 'b': /* Local BSS Data */
			case 's':
				addr += ml->bss_off;
				type = SYM_LOCAL_DATA;
				break;

			case 'g': /* Static Local Data */
			case 'd': /* XXX need this on s390 for s390dbf */
				addr += ml->data_off;
				type = SYM_LOCAL_DATA;
				break;

			case 'R': /* Global ReadOnly Data */
				addr += ml->rodata_off;
				type = SYM_GLOBAL_DATA;
				break;

			case 'B':
			case 'S':
				addr += ml->bss_off;
				type = SYM_GLOBAL_DATA;
				break;

			case 'G': /* Static Global Data */
			case 'D':
				addr += ml->data_off;
				type = SYM_GLOBAL_DATA;
				break;

			case 't':
				addr += ml->text_off;
				type = SYM_LOCAL_TEXT;
				break;

			case 'T':
				addr += ml->text_off;
				type = SYM_GLOBAL_TEXT;
				break;

			case 'A':
				type = SYM_ABS;
				break;

			default:
				DUMP_BP();
				printf("%s: Warning: Unknown symbol type '%c'; "
					"ignoring\n", __ASSERT_FUNCTION, symtype);
				/* FALLTHROUGH */

			case '?': 
				/* Special Stuff: __ksymtab_* and __kstrtab_* */
				addr = symtype = 0;
				name[0] = 0;
				continue;			/* Ignore */
		}
		stp->symcnt++;
		if (!flag && ((dot_cnt++ % 1000) == 0)) {
			fprintf(KL_ERRORFP, ".");
		}

		if (cur_syment) {
			cur_syment->s_next = alloc_syment(addr, type, name);
			cur_syment = cur_syment->s_next;
		} else {
			syment_list = alloc_syment(addr, type, name);
			cur_syment = syment_list;
		}
		addr = 0;
		symtype = 0;
		name[0] = 0;
	}
	/* insert artificial section symbols to mark secions of modules */
	if (cur_syment) {
		if(ml->text_off){
			cur_syment->s_next = 
				alloc_syment(ml->text_off, SYM_ABS, KL_S_TEXT);
			cur_syment = cur_syment->s_next;
			stp->symcnt++;
			if(ml->text_len) {
				cur_syment->s_next = 
					alloc_syment(ml->text_off+ml->text_len,
						     SYM_ABS, KL_E_TEXT);
				cur_syment = cur_syment->s_next;
				stp->symcnt++;
			}
		}
		if(ml->data_off){
			cur_syment->s_next = 
				alloc_syment(ml->data_off, SYM_ABS, KL_S_DATA);
			cur_syment = cur_syment->s_next;
			stp->symcnt++;
			if(ml->data_len) {
				cur_syment->s_next = 
					alloc_syment(ml->data_off+ml->data_len,
						     SYM_ABS, KL_E_DATA);
				cur_syment = cur_syment->s_next;
				stp->symcnt++;
			}
		} 
		if(ml->rodata_off){
			cur_syment->s_next = 
				alloc_syment(ml->rodata_off, SYM_ABS, 
					     KL_S_RODATA);
			cur_syment = cur_syment->s_next;
			stp->symcnt++;
			if(ml->rodata_len) {
				cur_syment->s_next = 
					alloc_syment(ml->rodata_off +
						     ml->rodata_len, SYM_ABS,
						     KL_E_RODATA);
				cur_syment = cur_syment->s_next;
				stp->symcnt++;
			}
		} 
		if(ml->bss_off){
			cur_syment->s_next = 
				alloc_syment(ml->bss_off, SYM_ABS, KL_S_BSS);
			cur_syment = cur_syment->s_next;
			stp->symcnt++;
			if(ml->bss_len) {
				cur_syment->s_next = 
					alloc_syment(ml->bss_off + ml->bss_len,
						     SYM_ABS, KL_E_BSS);
				cur_syment = cur_syment->s_next;
				stp->symcnt++;
			}
		}
		if(ml->size){
			cur_syment->s_next =
				alloc_syment(ml->text_off + ml->size,
					     SYM_ABS, KL_SYM_END);
			cur_syment=cur_syment->s_next;
			stp->symcnt++;
		}
	}

	kl_insert_symbols(stp, syment_list, flag);

	ml->syminfo=stp;
	ml->maplist_type=SYM_MAP_FILE;
	return(0);
}

/*
 * kl_free_syminfo()
 */
void
kl_free_syminfo(char *mapfile)
{
	int i;
	syment_t *sp;
	symtab_t *stp;
	maplist_t *ml, *prev_ml;

	for (prev_ml = NULL, ml=STP; ml != NULL;){
		if(mapfile){
			/* free only specified maplist */
			if(strcmp(mapfile, ml->mapfile)){
				prev_ml=ml;
				ml=ml->next;
				continue;
			}
		}
		if(ml->mapfile){
			free(ml->mapfile);
		}
		if(ml->objname){
			free(ml->objname);
		}
		if(ml->syminfo){
			stp = ml->syminfo;
			for (i = 0; i < stp->symcnt; i++) {
				if ((sp = stp->symaddrs[i])) {
					if (sp->s_name) {
						free(sp->s_name);
					}
					free(sp);
				}
			}
			if (stp->symaddrs) {
				free(stp->symaddrs);
			}
			free(stp);
		}
		if (prev_ml){
			prev_ml->next=ml->next;
			free(ml);
			ml=prev_ml;
		}
		else{
			STP=ml->next;
			free(ml);
			ml=STP;
		}
	}
}

syment_t *
kl_lkup_symname(char *name)
{
	syment_t *ret;

	if( (ret = _kl_lkup_symname(name, 0, 0)) ) {
		return(ret);
	} else {
		return (_kl_lkup_symname(name, SYM_MAP_KSYM, 0));
	}
}

/*
 * _kl_lkup_symname()
 */
syment_t *
_kl_lkup_symname(char *name, int list_type, size_t len)
{
	int max_depth;
	syment_t *sp = NULL;
	maplist_t *ml;

	/* Search for name in the types list
	 */
	kl_reset_error();
	KL_ERROR = KLE_BAD_SYMNAME;
	for(ml=STP; ml!=NULL; ml=ml->next){
		if(ml->maplist_type != list_type) {
			continue;
		}
		
		if(ml->maplist_type == SYM_MAP_KSYM){
			if((sp = (syment_t *)
			   _kl_find_btnode(ml->syminfo->symnames, name,
					   &max_depth, len))) {
				kl_reset_error();
				break;
			}
		} else {
			if((sp = (syment_t *)
			   _kl_find_btnode(ml->syminfo->symnames,
					   name, &max_depth, len))) {
				kl_reset_error();
				break;
			}
		}
	}
		     
	return(sp);
}

syment_t *
kl_lkup_symaddr(kaddr_t addr)
{
	syment_t *ret;
	if ( (ret = _kl_lkup_symaddr(addr, 0)) ) {
		return(ret);
	} else {
		return(_kl_lkup_symaddr(addr, SYM_MAP_KSYM));
	}
}

/*
 * _kl_lkup_symaddr()
 */
syment_t *
_kl_lkup_symaddr(kaddr_t addr, int list_type)
{
	int idx, first, last;
	symtab_t *stp;
	maplist_t *ml;

	for(ml=STP; ml!=NULL; ml=ml->next){
		if(ml->maplist_type != list_type) {
			continue;
		}

		kl_reset_error();

		stp = ml->syminfo;

		if (!stp ||  (stp->symcnt == 0)) {
			continue;
		}

		first = 0;
		last = stp->symcnt - 1;
		idx = (last - first) / 2;

		/* Make sure the address is within the valid range */
		if ((addr < stp->symaddrs[first]->s_addr) ||
		    (addr > stp->symaddrs[last]->s_addr)) {
			continue;
		}	

		while (idx != first) {
			if (stp->symaddrs[idx]->s_addr == addr) {
				return(stp->symaddrs[idx]);
			} else if (stp->symaddrs[idx]->s_addr < addr) {
				first = idx;
				idx = first + ((last - first) / 2);
			} else {
				last = idx;
				idx = first + ((last - first) / 2);
			}
		}

		if (addr == stp->symaddrs[first]->s_addr) {
			return(stp->symaddrs[first]);
		} else if (addr == stp->symaddrs[last]->s_addr) {
			return(stp->symaddrs[last]);
		} else {
			return(stp->symaddrs[first]);
		}
	}

	KL_ERROR = KLE_BAD_SYMADDR;
	return((syment_t *)NULL);
}

/*
 * kl_funcname() -- Get the function name from a specific address.
 *
 *  Note that the function name returned does NOT need to be freed
 *  up by the caller.
 */
char *
kl_funcname(kaddr_t pc)
{
	syment_t *sp;

	if ((sp = kl_lkup_symaddr(pc))) {
		if ((sp->s_type == SYM_LOCAL_TEXT) ||
				(sp->s_type == SYM_GLOBAL_TEXT)) {
			return(sp->s_name);
		}
	}
	return((char *)NULL);
}

/*
 * kl_funcaddr() -- Get the start address for function pc is from 
 */
kaddr_t
kl_funcaddr(kaddr_t pc)
{
	syment_t *sp;

	if ((sp = kl_lkup_symaddr(pc))) {
		if ((sp->s_type == SYM_LOCAL_TEXT) ||
				(sp->s_type == SYM_GLOBAL_TEXT)) {
			return(sp->s_addr);
		}
	}
	return((kaddr_t)NULL);
}

/*
 * kl_funcsize() -- Get the size of a function in bytes
 */
int
kl_funcsize(kaddr_t pc)
{
	int size;
	syment_t *sp;

	if ((sp = kl_lkup_symaddr(pc))) {
		if ((sp->s_type == SYM_LOCAL_TEXT) ||
				(sp->s_type == SYM_GLOBAL_TEXT)) {
			if (sp->s_next) {
				size = sp->s_next->s_addr - sp->s_addr;
				return(size);
			}
		}
	}
	return(0);
}

/*
 * kl_get_similar_name() -- This function gets the queue of symbol names which
 *                          match 'name' for symbol name completion. 
 *                          It saves the number of candidates to 'sym_cnt'. 
 *                          It saves the maximum length of the candidates to 
 *                          'maxlen'.
 *                          It saves a string for completion to 'retstr'.
 *                          Return the head of queue.
 */
syment_t *
kl_get_similar_name(char *name, char *retstr, int *sym_cnt, int *maxlen) 
{
	syment_t *sq_cur, *sq_head = (syment_t *)NULL, *sq_tail = (syment_t *)NULL;
	int	namelen;
	int found;
	maplist_t *ml;
	int i;

	/* Search for name in the types list
	 */
	kl_reset_error();
	KL_ERROR = KLE_BAD_SYMNAME;
	namelen = strlen(name);
	for(ml=STP; ml!=NULL; ml=ml->next){
		if (ml->maplist_type == SYM_MAP_KSYM || ml->maplist_type == SYM_MAP_FILE) {
			/* get the queue of candidates for symbol name */
			sq_cur = (syment_t *)kl_first_btnode(ml->syminfo->symnames);
			found = 0;
			do {
				if (!namelen || !strncmp(name, sq_cur->s_name, namelen)) {
					if (!found)
						found = 1;
					if (!*sym_cnt) {
						strcpy(retstr, sq_cur->s_name+namelen);
						*maxlen = strlen(sq_cur->s_name);
						sq_head = sq_tail = sq_cur;
					} else {
						sq_tail->s_forward = sq_cur;
						sq_tail = sq_cur;
						if (retstr[0] != '\0') {
							/* get the identical part of string of 
							   candidates and save to 'retstr' */
							for (i = 0; retstr[i] != '\0' &&
								retstr[i] == *(sq_cur->s_name+namelen+i);
								i++); 
							retstr[i] = '\0';
						}
						/* get the maximum length of candidates and save to 'maxlen' */
						if (*maxlen < strlen(sq_cur->s_name)) {
							*maxlen = strlen(sq_cur->s_name);
						}
					}
					sq_tail->s_forward = (syment_t *)0;
					(*sym_cnt)++;
				} else {
					if (found)
						break;
				}
			} while ((sq_cur = (syment_t *)kl_next_btnode((btnode_t *)sq_cur)) != NULL);
		} else {
			continue;
		}
	}
	return(sq_head); /* return the head of queue */
}
