#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>
#include <ctype.h>

static	int	rtrace(uvlong, uvlong, uvlong);
static	int	ctrace(uvlong, uvlong, uvlong);
static	int	i386trace(uvlong, uvlong, uvlong);
static	int	amd64trace(uvlong, uvlong, uvlong);
static	uvlong	getval(uvlong);
static	void	inithdr(int);
static	void	fatal(char*, ...);
static	void	readstack(void);

static	Fhdr	fhdr;
static	int	interactive;

#define	FRAMENAME	".frame"

static void
usage(void)
{
	fprint(2, "usage: ktrace [-i] kernel pc sp [link]\n");
	exits("usage");
}

static void
printaddr(char *addr, uvlong pc)
{
	int i;
	char *p;

	/*
	 * reformat the following.
	 *
	 * foo+1a1 -> src(foo+0x1a1);
	 * 10101010 -> src(0x10101010);
	 */

	if(strlen(addr) == 8 && strchr(addr, '+') == nil){
		for(i=0; i<8; i++)
			if(!isxdigit(addr[i]))
				break;
		if(i == 8){
			print("src(%#.8llux); // 0x%s\n", pc, addr);
			return;
		}
	}

	if(p=strchr(addr, '+')){
		*p++ = 0;
		print("src(%#.8llux); // %s+0x%s\n", pc, addr, p);
	}else
		print("src(%#.8llux); // %s\n", pc, addr);
}

static void (*fmt)(char*, uvlong) = printaddr;

void
main(int argc, char *argv[])
{
	int (*t)(uvlong, uvlong, uvlong);
	uvlong pc, sp, link;
	int fd;

	ARGBEGIN{
	case 'i':
		interactive++;
		break;
	default:
		usage();
	}ARGEND

	link = 0;
	t = ctrace;
	switch(argc){
	case 4:
		t = rtrace;
		link = strtoull(argv[3], 0, 16);
		break;
	case 3:
		break;
	default:
		usage();
	}
	pc = strtoull(argv[1], 0, 16);
	sp = strtoull(argv[2], 0, 16);
	if(!interactive)
		readstack();

	fd = open(argv[0], OREAD);
	if(fd < 0)
		fatal("can't open %s: %r", argv[0]);
	inithdr(fd);
	switch(fhdr.magic){
	case I_MAGIC:	/* intel 386 */
		t = i386trace;
		break;
	case S_MAGIC:	/* amd64 */
		t = amd64trace;
		break;
	case A_MAGIC:	/* 68020 */
	case J_MAGIC:	/* intel 960 */
		t = ctrace;
		break;
	case K_MAGIC:	/* sparc */
	case D_MAGIC:	/* amd 29000 */
	case V_MAGIC:	/* mips 3000 */
	case M_MAGIC:	/* mips 4000 */
	case E_MAGIC:	/* arm 7-something */
	case Q_MAGIC:	/* powerpc */
	case N_MAGIC:	/* mips 4000 LE */
	case L_MAGIC:	/* dec alpha */
		t = rtrace;
		break;
	case X_MAGIC:	/* att dsp 3210 */
		sysfatal("can't ktrace %s", argv[0]);
		break;
	default:
		fprint(2, "%s: warning: can't tell what type of stack %s uses; assuming it's %s\n",
			argv0, argv[0], argc == 4 ? "risc" : "cisc");
		break;
	}
	(*t)(pc, sp, link);
	exits(0);
}

static void
inithdr(int fd)
{
	seek(fd, 0, 0);
	if(!crackhdr(fd, &fhdr))
		fatal("read text header");

	if(syminit(fd, &fhdr) < 0)
		fatal("%r\n");
}

static int
rtrace(uvlong pc, uvlong sp, uvlong link)
{
	Symbol s, f;
	char buf[128];
	uvlong oldpc;
	int i;

	i = 0;
	while(findsym(pc, CTEXT, &s)) {
		if(pc == s.value)	/* at first instruction */
			f.value = 0;
		else if(findlocal(&s, FRAMENAME, &f) == 0)
			break;

		symoff(buf, sizeof buf, pc, CANY);
		fmt(buf, pc);

		oldpc = pc;
		if(s.type == 'L' || s.type == 'l' || pc <= s.value+mach->pcquant){
			if(link == 0)
				fprint(2, "%s: need to supply a valid link register\n", argv0);
			pc = link;
		}else{
			pc = getval(sp);
			if(pc == 0)
				break;
		}

		if(pc == 0 || (pc == oldpc && f.value == 0))
			break;

		sp += f.value;

		if(++i > 40)
			break;
	}
	return i;
}

static int
ctrace(uvlong pc, uvlong sp, uvlong link)
{
	Symbol s;
	char buf[128];
	int found;
	uvlong opc, moved;
	long j;

	USED(link);
	j = 0;
	opc = 0;
	while(pc && opc != pc) {
		moved = pc2sp(pc);
		if (moved == ~0){
			print("pc2sp(%#.8llux) = -1 %r\n", pc);
			break;
		}
		found = findsym(pc, CTEXT, &s);
		if (!found){
			print("findsym fails\n");
			break;
		}
		symoff(buf, sizeof buf, pc, CANY);
		fmt(buf, pc);

		sp += moved;
		opc = pc;
		pc = getval(sp);
		if(pc == 0)
			break;
		sp += mach->szaddr;	/*assumes address size = stack width*/
		if(++j > 40)
			break;
	}
	return j;
}

static int
i386trace(uvlong pc, uvlong sp, uvlong link)
{
	int i;
	uvlong osp;
	Symbol s, f;
	char buf[128];

	USED(link);
	i = 0;
	osp = 0;
	while(findsym(pc, CTEXT, &s)) {

		symoff(buf, sizeof buf, pc, CANY);
		fmt(buf, pc);

//XXX		s.value &= ~(uintptr)0;
		if(pc != s.value) {	/* not at first instruction */
			if(findlocal(&s, FRAMENAME, &f) == 0)
				break;
			sp += f.value-mach->szaddr;
		}else if(strcmp(s.name, "forkret") == 0){
//XXX
			print("//passing interrupt frame; last pc found at sp=%#llux\n", osp);

			sp +=  15 * mach->szaddr;		/* pop interrupt frame */
		}

		pc = getval(sp);
//XXX
		if(pc == 0 && strcmp(s.name, "forkret") == 0){
			sp += 3 * mach->szaddr;			/* pop iret eip, cs, eflags */
			print("//guessing call through invalid pointer, try again at sp=%#llux\n", sp);
			s.name = "";
			pc = getval(sp);
		}
		if(pc == 0) {
			print("//didn't find pc at sp=%#llux, last pc found at sp=%#llux\n", sp, osp);
			break;
		}
		osp = sp;

		sp += mach->szaddr;
//XXX
		if(strcmp(s.name, "forkret") == 0)
			sp += 2 * mach->szaddr;			/* pop iret cs, eflags */

		if(++i > 40)
			break;
	}
	return i;
}

static int
amd64trace(uvlong pc, uvlong sp, uvlong link)
{
	int i, istrap;
	uvlong osp;
	Symbol s, f;
	char buf[128];

	USED(link);
	i = 0;
	osp = 0;
	while(findsym(pc, CTEXT, &s)) {

		symoff(buf, sizeof buf, pc, CANY);
		fmt(buf, pc);

		istrap = strcmp(s.name, "_intrp") == 0 || strcmp(s.name, "_intre") == 0;
		if(istrap){
			print("//passing interrupt frame; last pc found at sp=%#llux\n", osp);
			/*
			 * Pop interrupt frame (ureg.h) up to the IP value.
			 */
			sp += 19 * mach->szaddr;
		} else if(pc != s.value) {	/* not at first instruction */
			if(findlocal(&s, FRAMENAME, &f) == 0)
				break;
			sp += f.value-mach->szaddr;
		}

		pc = getval(sp);
		if(pc == 0 && istrap){
			/*
			 * Pop IP, CS and FLAGS to get to the SP.
			 * The AMD64 aligns the interrupt stack on
			 * a 16-byte boundary so have the get the
			 * SP from the saved frame.
			 */
			sp += 3 * mach->szaddr;
			print("//guessing call through invalid pointer; try again at sp=%#llux\n", sp);
			s.name = "";
			sp = getval(sp);
			pc = getval(sp);
		}
		if(pc == 0) {
			print("//didn't find pc at sp=%#llux, last pc found at sp=%#llux\n", sp, osp);
			break;
		}
		osp = sp;

		if(!istrap)
			sp += mach->szaddr;

		if(++i > 40)
			break;
	}
	return i;
}

int naddr;
uvlong addr[1024];
uvlong val[1024];

static void
putval(uvlong a, uvlong v)
{
	if(naddr < nelem(addr)){
		addr[naddr] = a;
		val[naddr] = v;
		naddr++;
	}
}

static void
readstack(void)
{
	Biobuf b;
	char *p;
	char *f[64];
	int nf, i;

	Binit(&b, 0, OREAD);
	while(p=Brdline(&b, '\n')){
		p[Blinelen(&b)-1] = 0;
		nf = tokenize(p, f, nelem(f));
		for(i=0; i<nf; i++){
			if(p=strchr(f[i], '=')){
				*p++ = 0;
				putval(strtoull(f[i], 0, 16), strtoull(p, 0, 16));
			}
		}
	}
}

static uvlong
getval(uvlong a)
{
	char buf[256];
	int i, n;
	uvlong r;

	if(interactive){
		print("// data at %#8.8llux? ", a);
		n = read(0, buf, sizeof(buf)-1);
		if(n <= 0)
			return 0;
		buf[n] = '\0';
		r = strtoull(buf, 0, 16);
	}else{
		r = 0;
		for(i=0; i<naddr; i++)
			if(addr[i] == a)
				r = val[i];
	}

	return r;
}

static void
fatal(char *fmt, ...)
{
	char buf[4096];
	va_list arg;

	va_start(arg, fmt);
	vseprint(buf, buf+sizeof(buf), fmt, arg);
	va_end(arg);
	fprint(2, "ktrace: %s\n", buf);
	exits(buf);
}
