/*
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 */
#include <lcrash.h>

#define C_AFLAG (1 << C_LFLG_SHFT)
#define C_RFLAG (2 << C_LFLG_SHFT)

static char * filename = NULL;

/* Forward declaration
 */
void symtab_usage(command_t *);

/* read named module struct from dump
 */

/*
 * symtab_cmd() -- Run the 'addmap' command.
 */
int
symtab_cmd(command_t *cmd)
{
	int i, mode, symcnt=0;
	maplist_t *last_ml, *ml;
	uint64_t  t_off=0, d_off=0, rd_off = 0, b_off = 0;
	uint64_t  t_len=0, d_len=0, rd_len=0, b_len=0, mod_size=0;
	char*     objname=NULL;
	char      section_ksym[272];
	void*     dump_module = NULL;
	syment_t*  sp;
	kaddr_t    modaddr=0;


	if (cmd->flags & C_AFLAG) {
		fprintf(cmd->ofp, "Adding symbol table.\n");
		for(last_ml=STP; last_ml!=NULL; last_ml=last_ml->next){
			if(!strcmp(filename, last_ml->mapfile)){
				fprintf(KL_ERRORFP, 
					"Symbol table already added.\n");
				return(1);
			}
			if(!last_ml->next){
				break;
			}
			
		}

		switch (cmd->nargs) {
		case 8:
			kl_get_value(cmd->args[0], &mode, 0, &t_off);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"text_offset.\n");
				return(1);
			}
			kl_get_value(cmd->args[1], &mode, 0, &d_off);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"data_offset.\n");
				return(1);
			}
			kl_get_value(cmd->args[2], &mode, 0, &rd_off);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"rodata_offset.\n");
				return(1);
			}
			kl_get_value(cmd->args[3], &mode, 0, &b_off);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"bss_offset.\n");
				return(1);
			}
			kl_get_value(cmd->args[4], &mode, 0, &t_len);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"text_length.\n");
				return(1);
			}
			kl_get_value(cmd->args[5], &mode, 0, &d_len);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"data_length.\n");
				return(1);
			}
			kl_get_value(cmd->args[6], &mode, 0, &rd_len);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"rodata_length.\n");
				return(1);
			}
			kl_get_value(cmd->args[7], &mode, 0, &b_len);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"bss_length.\n");
				return(1);
			}
			break;
		case 2:
			kl_get_value(cmd->args[0], &mode, 0, &t_off);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"offset.\n");
				return(1);
			}
			d_off = rd_off = b_off = t_off;
			kl_get_value(cmd->args[1], &mode, 0, &mod_size);
			if(KL_ERROR){
				fprintf(KL_ERRORFP, "Invalid value for "
					"size.\n");
				return(1);
			}
			break;
		case 1:
			objname = strdup(cmd->args[0]);
			/* get ELF section offsets from ksymtab */
			sprintf(section_ksym, "__insmod_%s_S.text", objname);
			sp = KL_LKUP_SYMNAME(section_ksym, SYM_MAP_KSYM,
					     strlen(section_ksym));
			if(sp){
			        t_off = sp->s_addr;
				strcat(section_ksym, "_L%llu");
				sscanf(sp->s_name, section_ksym, &t_len);
			}
			sprintf(section_ksym, "__insmod_%s_S.data", objname);
			sp = KL_LKUP_SYMNAME(section_ksym, SYM_MAP_KSYM,
					     strlen(section_ksym));
			if(sp){
			        d_off = sp->s_addr;
				strcat(section_ksym, "_L%llu");
				sscanf(sp->s_name, section_ksym, &d_len);
			}
			sprintf(section_ksym, "__insmod_%s_S.rodata", objname);
			sp = KL_LKUP_SYMNAME(section_ksym, SYM_MAP_KSYM,
					     strlen(section_ksym));
			if(sp){
			        rd_off = sp->s_addr;
				strcat(section_ksym, "_L%llu");
				sscanf(sp->s_name, section_ksym, &rd_len);
			}
			sprintf(section_ksym, "__insmod_%s_S.bss", objname);
			sp = KL_LKUP_SYMNAME(section_ksym, SYM_MAP_KSYM,
					     strlen(section_ksym));
			if(sp){
			        b_off = sp->s_addr;
				strcat(section_ksym, "_L%llu");
				sscanf(sp->s_name, section_ksym, &b_len);
			}
			/* determine length from struct module */
			if(kl_get_module(objname, &modaddr, &dump_module)){
				fprintf(KL_ERRORFP,
					"Could not load module structure "
					"from dump for module: %s\n",
					objname);
				return(1);
			}
			mod_size = KL_UINT(dump_module, "module", "size");
			kl_free_block(dump_module);
			break;
		case 0:
			if(!strcmp(filename, KL_KSYMTAB)){
				fprintf(cmd->ofp, "\n\tLoading ksyms"
					" from dump ...");
				if (kl_init_ksyms(0)){
					fprintf(cmd->ofp, "\nFailed.\n");
					if(KL_ERROR == KLE_NO_MODULE_LIST) {
						fprintf(KL_ERRORFP, "Reason: ");
						kl_print_error();
						kl_reset_error();
					}
				} else {
					fprintf(cmd->ofp, "\nDone.\n");
				}
				return(0);
			}
		default:
			fprintf(KL_ERRORFP, "Invalid commandline.\n");
			symtab_usage(cmd);
			return(1);
		}
		fprintf(cmd->ofp,"           filename: %s", filename);
		print_value(cmd->ofp, "\n       text_offset: ", 
			    t_off, 10, ADDR_FLG);
		print_value(cmd->ofp, "\n       data_offset: ", 
			    d_off, 10, ADDR_FLG);
		print_value(cmd->ofp, "\n     rodata_offset: ", 
			    rd_off, 10, ADDR_FLG);
		print_value(cmd->ofp, "\n        bss_offset: ", 
			    b_off, 10, ADDR_FLG);
		print_value(cmd->ofp, "\n       text_length: ", 
			    t_len, 10, UNSIGNED_FLG);
		print_value(cmd->ofp, "\n       data_length: ", 
			    d_len, 10, UNSIGNED_FLG);
		print_value(cmd->ofp, "\n     rodata_length: ", 
			    rd_len, 10, UNSIGNED_FLG);
		print_value(cmd->ofp, "\n        bss_length: ", 
			    b_len, 10, UNSIGNED_FLG);
		print_value(cmd->ofp, "\n              size: ", 
			    mod_size, 10, UNSIGNED_FLG);
		fprintf(cmd->ofp, "\n");
		sync();
		if(!(ml = (maplist_t*) malloc(sizeof(maplist_t)))){
			fprintf(KL_ERRORFP, "Out of memory.");
			return(1);
		}
		memset(ml, 0, sizeof(maplist_t));

		ml->data_off = d_off;
		ml->text_off = t_off;
		ml->rodata_off = rd_off;
		ml->bss_off = b_off;
		ml->text_len = t_len;
		ml->data_len = d_len;
		ml->rodata_len = rd_len;
		ml->bss_len = b_len;
		ml->size = mod_size;

		if(kl_read_syminfo(filename, ml, 0)){
			fprintf(KL_ERRORFP, "Can't get symbol table "
				"from file: %s.\n", filename);
			if(ml->objname){
				free(ml->objname);
			}
			free(ml);
			return(1);
		}
		ml->mapfile = strdup(filename);
		ml->objname = objname;
		if(STP){
			last_ml->next=ml;
		}else{
			STP=ml;
		}
		fprintf(cmd->ofp, "Done.\n");
	} else if (cmd->flags & C_RFLAG) {
		if(cmd->nargs){
			fprintf(KL_ERRORFP, "Invalid commandline.\n");
			symtab_usage(cmd);
			return(1);
		} else {
			fprintf(cmd->ofp, "Removing symbol table.\n");
			for(ml=STP; ml!=NULL; ml=ml->next){
				if(!strcmp(filename, ml->mapfile)){
					kl_free_syminfo(filename);
					fprintf(cmd->ofp, "Done.\n");
					return(0);
				}
			}
			fprintf(KL_ERRORFP, "Symbol table not found.\n");
			return(1);
		}
	} else if (cmd->flags & C_LIST) {
		char * name = NULL;
		if(cmd->nargs == 1){
			/* display symbols of specified symbol table */
			name = cmd->args[0];
		} else {
			fprintf(cmd->ofp, "Loaded symbol tables:\n");
		}
		print_banner(cmd->ofp, "%}75", cmd->flags|SMAJOR);
		for (ml=STP; ml!=NULL; ml=ml->next){
			if(name && (strcmp(name, ml->mapfile))){
				continue;
			}

			if(ml->syminfo){
				symcnt=ml->syminfo->symcnt;
			} else {
				symcnt=0;
			}
				
			print_value(cmd->ofp, "#SYMS: ",
				    UINT64(symcnt), 8, UNSIGNED_FLG);
			if (ml->objname){
				fprintf(cmd->ofp," %s [%s]\n",
					ml->mapfile, ml->objname);
			} else {
				fprintf(cmd->ofp," %s\n", ml->mapfile);
			}
			print_value(cmd->ofp, " OFFSETS TEXT: ", 
				    ml->text_off, 10, ADDR_FLG);
			print_value(cmd->ofp, " LENGTHS TEXT: ", 
				    ml->text_len, 10, UNSIGNED_FLG);
			print_value(cmd->ofp, " SIZE: ", 
				    ml->size, 8, UNSIGNED_FLG);
			print_value(cmd->ofp, "\n         DATA: ", 
				    ml->data_off, 10, ADDR_FLG);
			print_value(cmd->ofp, "         DATA: ", 
				    ml->data_len, 10, UNSIGNED_FLG);
			print_value(cmd->ofp, "\n       RODATA: ", 
				    ml->rodata_off, 10, ADDR_FLG);
			print_value(cmd->ofp, "       RODATA: ", 
				    ml->rodata_len, 10, UNSIGNED_FLG);
			print_value(cmd->ofp, "\n          BSS: ", 
				    ml->bss_off, 10, ADDR_FLG);
			print_value(cmd->ofp, "          BSS: ", 
				    ml->bss_len, 10, UNSIGNED_FLG);
			fprintf(cmd->ofp, "\n");
			if(cmd->flags & C_FULL){
				fprintf(cmd->ofp, "\n");
				symbol_banner(cmd->ofp, BANNER|SMINOR);
				for(i=0; i<symcnt; i++){
					print_symbol(0,
						     ml->syminfo->symaddrs[i],
						     cmd->flags, cmd->ofp);
				}
				symbol_banner(cmd->ofp, SMINOR);
			}

		}
		print_banner(cmd->ofp, "%}75", cmd->flags|SMAJOR);
	} else {
		fprintf(KL_ERRORFP, "No action specified.\n");
		symtab_usage(cmd);
		return(1);
	}

	return(0);
}

#define _SYMTAB_USAGE \
"\n        -l [-f] [symtable]"\
"\n        -a symtable modulename"\
"\n        -a symtable offset size"\
"\n        -a symtable t_off d_off rd_off b_off t_len d_len rd_len b_len"\
"\n        -r symtable"\
"\n        [-w outfile]"

#define _SYMTAB_HELP \
"Add/remove/list symbol table information.\n"\
"\nOPTIONS:"\
"\n -l [symtable]"\
"\n       List information of (all) symbol table(s)."\
"\n -l -f [symtable]"\
"\n       Show full list of symbols of (all) symbol table(s)."\
"\n -a symtable modulename"\
"\n       Add new symbol table belonging to module 'modulename'."\
"\n -a symtable t_off d_off rd_off b_off t_len d_len rd_len b_len"\
"\n       Add new symbol table using given segment offsets and lengths"\
"\n       (off=offset, len=length, t=text, d=data, rd=rodata, b=bss)."\
"\n -a symtable offset size"\
"\n       Add new symbol table using given 'offset' and 'size'."\
"\n       Regard 'size' as size of object file corresponding to 'symtable'."\
"\n -r symtable"\
"\n       Remove symbol table 'symtable'."\
"\n -a __ksymtab__"\
"\n -r __ksymtab__"\
"\n -l [-f] __ksymtab__"\
"\n       Add, remove or list table of exported kernel symbols.\n"\
"\nYou can use only one of the above command lines at the same time."

/*
 * symtab_usage() -- Print the usage string for the 'symbol' command.
 */
void
symtab_usage(command_t *cmd)
{
	CMD_USAGE(cmd, _SYMTAB_USAGE);
}

/*
 * symtab_help() -- Print the help information for the 'symbol' command.
 */
void
symtab_help(command_t *cmd)
{
	CMD_HELP(cmd, _SYMTAB_USAGE, _SYMTAB_HELP);
}

/*
 * symtab_parse() -- Parse the command line arguments for 'symbol'.
 */
int
symtab_parse(command_t *cmd)
{
	option_t *op;

	if (set_cmd_flags(cmd, (C_WRITE|C_LIST|C_FULL), "a:r:")) {
		return(1);
	}
	op = cmd->options;
	while (op) {
		switch(op->op_char) {
			case 'a':
				cmd->flags |= C_AFLAG;
				filename = op->op_arg;
				break;
			case 'r':
				cmd->flags |= C_RFLAG;
				filename = op->op_arg;
				break;
		}
		op = op->op_next;
	}
	return(0);
}

/*
 * symtab_complete() -- Complete arguments of 'symtab' command.
 */
char *
symtab_complete(command_t *cmd)
{
	char *ret;

	/* complete standard options (for example, -w option) arguments
	 */
	if ((ret = complete_standard_options(cmd)) != NOT_COMPLETED) {
		return(ret);
	}
	if ((cmd->nargs == 2) && (!strcmp(cmd->args[0],"-a"))) {
		return(complete_file_name(cmd->args[cmd->nargs -1], 100));
	} else {
		fprintf(cmd->ofp, "\n");
		symtab_usage(cmd);
		return(DRAW_NEW_ENTIRE_LINE);
	}
}
