/*
 * Copyright (C) 2000-2002 ASANO Masahiro
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "crash.h"

PRIVATE addr_t dsymbol();
const commandtable_t command_dsymbol =
	{"dsymbol", dsymbol, "[-s] address", "print data symbol name\n  -s  search statically only"};

PRIVATE int nsym;
PRIVATE int nalloc;
struct symtable *symtable;

#define NHASH	4096
PRIVATE struct symtable *symhash[NHASH];

PRIVATE int mem;

PRIVATE addr_t kmem_low = 0;
PRIVATE addr_t kmem_high = -1;

void
init_mem()
{
	/* open memory device */
	if (physname) {
		if ((mem = open(physname, O_RDONLY)) == -1) {
			perror(physname);
			exit(1);
		}
	} else {
		if ((mem = open("/dev/kmem", O_RDONLY)) == -1) {
			perror("/dev/kmem");
			exit(1);
		}
	}
	(void)fcntl(mem, F_SETFD, 1);
}

int
memread(addr, size, buf, errmsg)
	addr_t addr, size;
	void *buf;
	const char *errmsg;
{
	if (physname) {
		/* Currently, not work... */
#ifdef RESTRICT_KERNEL_MEM
	} else if (addr < kmem_low) {
		THROWF("Error: address out of range: %lx", addr);
#endif
	}
	if (lseek(mem, (off_t)addr, SEEK_SET) != (off_t)addr) {
		THROWF("Error: seek to %lx", addr);
	}
	(void)memset(buf, '\0', size);
	if (read(mem, buf, (size_t)size) != (size_t)size) {
		THROWF2("Error: while reading %s address %lx", errmsg, addr);
	}
	return 0;
}

/* symbol table hash function */
PRIVATE int
gethash(name)
	const unsigned char *name;
{
	unsigned ret = 0;
	while (*name)
		ret = ret * 61 + *name++;
	return ret % NHASH;
}

/* compare for qsort(3) */
PRIVATE int
sfunc(a, b)
	const struct symtable *a, *b;
{
	if (a->addr > b->addr)
		return 1;
	else if (a->addr < b->addr)
		return - 1;
	else
		return 0;
}

void
addsym(name, value, type)
	const char *name;
	addr_t value;
	int type;
{
	if (nsym >= nalloc) {
		nalloc *= 2;
		symtable = realloc(symtable, nalloc * sizeof(struct symtable));
		if (symtable == NULL) {
			perror("FATAL: memory allocation");
			exit(1);
		}
	}
	symtable[nsym].addr = value;
	symtable[nsym].type = type;
	if (strlen(name) < sizeof(symtable[0].nbuf)) {
		strcpy(symtable[nsym].nbuf, name);
		symtable[nsym].name = NULL; /* flag for inner buffer */
	} else {
		if ((symtable[nsym].name = strdup(name)) == NULL) {
			perror("strdup");
			exit(1);
		}
	}
	nsym++;
}

void
init_sym()
{
	FILE *fp;
	addr_t addr;
	char type;
	char buf[256];
	int i, hash;

	if (symname == NULL) {
		symname = "/boot/System.map";
	}
	if ((fp = fopen(symname, "r")) == NULL) {
		perror(symname);
		exit(1);
	}
	nsym = 0;
	nalloc = 3000;	/* initial */
	if ((symtable = malloc(nalloc * sizeof(struct symtable))) == NULL) {
		perror("FATAL: memory allocation");
		exit(1);
	}
	while (fscanf(fp, "%lx %c %255s", &addr, &type, buf) == 3) {
		if (addr < 0x40000000)
			continue;
		/* type: see info (binutils::nm) */
		if (type == '?' && strcmp(buf, "init_task_union") != 0)
			continue;
		/* drivers/pci/names.c */
		if (buf[0] == '_' && buf[1] == '_' && (strncmp(buf + 2, "devices_", 8) == 0 || strncmp(buf + 2, "devicestr_", 10) == 0))
			continue;
		if (strchr(buf, '.') != 0)
			continue;
#if LINUXVER == 25
		/* for 2.5 system_call() */
		if (strcmp(buf, "syscall_call") == 0)
			continue;
#endif
		addsym(buf, addr, type);
	}
	fclose(fp);

	/* available area (System.map must be sorted by address) */
	kmem_low = symtable[0].addr;
	kmem_high = symtable[nsym - 1].addr;

	/* get module name list */
	for (i = 0; i < nsym; i++) {
		const char *p = symtable[i].name? symtable[i].name:
						  symtable[i].nbuf;
		if (*p == 'm' && (strcmp(p + 1, "odules_list") == 0 || strcmp(p + 1, "odule_list") == 0)) {
			get_module_symbol(symtable[i].addr);
			break;
		}
	}

	/* resize symbol table */
	symtable = realloc(symtable, nsym * sizeof(struct symtable));
	nalloc = nsym;

	/* sort by address (for binary search) */
	qsort(symtable, nsym, sizeof(struct symtable), sfunc);

	/* make hash table */
	for (i = 0; i < NHASH; i++) {
		symhash[i] = NULL;
	}
	for (i = 0; i < nsym; i++) {
		/* set name address after sorting */
		if (symtable[i].name == NULL) {
			symtable[i].name = symtable[i].nbuf;
		}
		hash = gethash(symtable[i].name);
		symtable[i].next = symhash[hash];
		symhash[hash] = &symtable[i];
	}

	if (!quiet) {
		mprintf("%d kernel symbols available\n", nsym);
	}
#ifdef DEBUG
	for (i = 0; i < NHASH; i++) {
		struct symtable *p;
		int n = 0;

		for (p = symhash[i]; p != NULL; p = p->next)
			n++;
		printf("%d ", n);
	}
	printf("\n");
	{
		struct symtable *p;
		for (p = symhash[0]; p != NULL; p = p->next)
			printf("(%s):%x ", p->name, p->addr);
		printf("\n");
	}
#endif
}

int
getmsyms_fromfile(path, text, data, bss)
	const char *path;
	addr_t text;
	addr_t data;
	addr_t bss;
{
	char cmd[512], buf[256];
	FILE *in;
	addr_t addr;
	char type;

	if (path == 0)
		return 1;
	if (access(path, R_OK) == -1)
		return 1;
	sprintf(cmd, NMPATH " %s", path);
	if ((in = popen(cmd, "r")) == NULL)
		return 1;

	while (fgets(cmd, sizeof(buf), in) != NULL) {
		if (sscanf(cmd, "%lx %c %255s", &addr, &type, buf) != 3)
			continue;
		switch (type) {
		case 't':
			if (strchr(buf, '.'))
				continue;
		case 'T':
			if (text == 0)
				continue;
			addr += text;
			break;
		case 'b':
			if (strchr(buf, '.'))
				continue;
		case 'B':
			if (bss == 0)
				continue;
			addr += bss;
			break;
		case 'd':
			if (strchr(buf, '.'))
				continue;
		case 'D':
			if (data == 0)
				continue;
			addr += data;
			break;
		default:
#ifdef DEBUG
			mprintf(FPTR " %c %s\n", addr, type, buf);
#endif
			continue;
		}
#ifdef DEBUG
		mprintf(FPTR " %c %s\n", addr, type, buf);
#endif
		addsym(buf, addr, type);
	}
	fclose(in);
	return 0;
}

addr_t
searchaddr_bysym(name)
	const char *name;
{
	struct symtable *p, **pp;
	const int n = gethash(name);

	p = symhash[n];
	if (p == NULL)
		return 0;			/* not found */
	if (strcmp(p->name, name) == 0)
		return p->addr;			/* found at first */

	for (pp = &p->next, p = *pp; p != NULL; pp = &p->next, p = *pp) {
		if (strcmp(p->name, name) == 0) {
			*pp = p->next;
			p->next = symhash[n];
			symhash[n] = p;
			return p->addr;		/* found */
		}
	}
	return 0;				/* not found */
}

const struct symtable *
searchsym_byaddr(addr)
	addr_t addr;
{
	int min = 0;
	int max = nsym - 1;
	static int cached = 0;

	if (symtable[cached].addr > addr) {
		max = cached - 1;
	} else if (cached >= max) {
		min = cached;
	} else if (symtable[cached + 1].addr <= addr) {
		min = cached + 1;
	} else {
		/* cache hit */
		if (symtable[cached].addr == kmem_high && addr != kmem_high)
			return NULL;
		return &symtable[cached];
	}

	while (min < max) {
		int id = (min + max) / 2;
		if (symtable[id].addr > addr) {
			max = id - 1;
		} else if (symtable[id].addr < addr) {
			min = id + 1;
		} else {
			/* (symtable[id].addr == addr) */
			return &symtable[cached = id];
		}
	}
	if (addr < symtable[min].addr) {
		if (--min < 0)
			return NULL;
	} else if (min >= nsym - 1 && addr > symtable[min].addr) {
		return NULL;	/* over */
	}
	cached = min;
	if (symtable[min].addr == kmem_high && addr != kmem_high)
		return NULL;	/* between static-linked and module address */
	return &symtable[min];
}

const char *
getsymstr(addr)
	addr_t addr;
{
	static char buf[256];
	const struct symtable *p;

	if ((p = searchsym_byaddr(addr)) == NULL) {
		return NULL;
	}
	if (addr == p->addr) {
		return p->name;
	} else if (p == &symtable[nsym - 1] || p->addr == 0) {
		return NULL;
	} else if (sprintf(buf, "%s+%lx", p->name, addr - p->addr) > sizeof(buf)) {
		fprintf(stderr, "getsymstr: buffer overflow\n");
		exit(1);
	}
	return buf;
}

PRIVATE addr_t
dsymbol()
{
	int i, c;
	addr_t addr;
	const struct symtable *p;
	int fault = 0;
	int sflag = 0;

	while ((c = getopt(argcnt, args, "s")) != EOF) {
		switch (c) {
		case 's':
			sflag = 1;
			break;
		default:
			THROW(usage);
		}
	}

	if (argcnt == optind) {
		THROW(usage);
	}
	for (i = optind; args[i]; i++) {
		addr = getaddr(args[i]);
		if ((p = searchsym_byaddr(addr)) != NULL) {
			if (addr == p->addr) {
				mprintf("%s\n", p->name);
			} else {
				mprintf("%s+%lx\n", p->name, addr - p->addr);
			}
		} else if (sflag == 1) {
			fault = 1;
		} else if (search_task(addr)) {
		} else if (search_slab(addr)) {
		} else {
			fault = 1;
		}
	}
	if (fault) {
		THROW("symbol not found");
	}
	return 0;
}

void
pmode(aflag)
	int aflag;
{
	int ltyp;

	switch (aflag & S_IFMT) {
	case S_IFREG:		ltyp = 'f';	break;
	case S_IFDIR:		ltyp = 'd';	break;
	case S_IFBLK:		ltyp = 'b';	break;
	case S_IFCHR:		ltyp = 'c';	break;
	case S_IFIFO:		ltyp = 'p';	break;
	case S_IFSOCK:		ltyp = 's';	break;
	case S_IFLNK:		ltyp = 'l';	break;
	default:		ltyp = '?';	break;
	}
	mprintf("%c%03o", ltyp, aflag & 0777);
}

unsigned int
m_ntohl(net)
	unsigned int net;
{
	return ntohl(net);
}

unsigned short
m_ntohs(net)
	unsigned short net;
{
	return ntohs(net);
}
