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

#include <string.h>
#include "crash.h"

#if LINUXVER == 24 /*XXX*/
#include <asm/processor.h>	/* for THREAD_SIZE */
#else
#define PAGE_SIZE	4096
#endif

#include "asm.h"
#include "stacksize.h"		/* for STACKSIZE */

PRIVATE addr_t trace();
struct commandtable command_trace =
	{"trace", trace, "address", "print kernel backtrace"};

PRIVATE char stack[STACKSIZE];

PRIVATE addr_t
trace()
{
	addr_t eip, esp, stacktop, retad;
	int i, argsize;
	const struct symtable *sym;
	unsigned char funchead[34];	/* MAGIC NUMBER 34 :) */
	int pid;
	extern const char *getvaluemod();

	if (argcnt < 2 || argcnt > 2) {
		THROW(usage);
	}

	eip = esp = stacktop = 0;
	pid = getinfo_fromtask(getvalue(args[1]), &eip, &esp, &stacktop);
	mprintf("PID = %d\n", pid);
	memread(stacktop, sizeof(stack), stack, "stack area");
	mprintf(" %8lx  %8lx  ", esp, eip);
	if ((sym = searchsym_byaddr(eip)) == NULL) {
		mprintf("?\n");
	} else {
		mprintf("%s+%lx ()\n", sym->name, eip - sym->addr);
	}

#define	INTHESTACK(x) ((x) >= stacktop && (x) + sizeof(addr_t) <= stacktop + STACKSIZE)
	if (!INTHESTACK(esp))
		return 0;
	esp = *(unsigned int *)&stack[esp - stacktop] + sizeof(addr_t);
	if (!INTHESTACK(esp))
		return 0;
	retad = *(unsigned int *)&stack[esp - stacktop];
	sym = searchsym_byaddr(retad);
	argsize = 0;

	while (1) {
		mprintf("[%8lx]", esp);
		if (! INTHESTACK(esp)) {
			mprintf("\n");
			break;
		}

		mprintf(" %8lx  ", retad);
		if (sym == NULL) {
			mprintf("?\n");
			break;
		}
		mprintf("%s+%lx (", sym->name, retad - sym->addr);
		if (sym->addr <= retad && sym->addr + 1 >= retad) {
			/* illegal case */
			mprintf(")\n");
			break;
		}

		memread(sym->addr, sizeof(funchead), funchead, sym->name);
		/* push ebp */
		if (funchead[0] == 0x55) {
			/* mov ebp,esp */
			if (funchead[1] == 0x89 && funchead[2] == 0xe5) {
				addr_t tmp = *(addr_t *)&stack[esp - stacktop - sizeof(addr_t)];
				if (INTHESTACK(tmp) && esp <= tmp) {
					/* i wonder this coding no effect now */
					esp = tmp + sizeof(addr_t);
					i = sizeof(funchead);
					argsize = 0;
				} else {
					esp += 2 * sizeof(addr_t);
					i = 3;
				}
			} else {
				esp += 2 * sizeof(addr_t); /* 1 for ret addr */
				i = 1;
			}
		/* sub esp,xx */
		} else if (funchead[0] == 0x83 && funchead[1] == 0xec) {
			esp += funchead[2] + sizeof(addr_t);
			i = 3;
		/* sub esp,xx */
		} else if (funchead[0] == 0x81 && funchead[1] == 0xec) {
			esp += funchead[2] + funchead[3] * 256 + sizeof(addr_t);
			i = 6;
		/* push eax / cld */
		} else if ((funchead[0] == 0x50 && funchead[1] == 0xfc) || funchead[0] == 0) {
			/* system_call(), start_kernel */
			mprintf(")\n");
			break;
		} else {
			/* unknown */
			esp += sizeof(addr_t);	/* for return address */
			i = 0;
		}
		/* Analyze function header loop */
		while (i < sizeof(funchead)) {
			int c = funchead[i];
#define ISPUSH(x)  ((x) >= 0x50 && (x) <= 0x57)
#define ISPOP(x)   ((x) >= 0x58 && (x) <= 0x5f)

			/* push REG */
			if (ISPUSH(c)) {
				if (c == 0x50 && i > 1)
					break;	/* push eax */
				if (i < sizeof(funchead) - 1 && funchead[i + 1] == 0xe8)
					break;	/* next is call */
				if (i < sizeof(funchead) - 3 && ISPUSH(funchead[i + 1])) {
					if (funchead[i + 2] == 0xe8)
						break;	/* push push call */
					else if (ISPUSH(funchead[i + 2]) && funchead[i + 3] == 0xe8) {
						if (i < sizeof(funchead) - 9 && !ISPOP(funchead[i + 8]))
							esp += sizeof(addr_t) * 3;
						else if (i < sizeof(funchead) - 10 && !ISPOP(funchead[i + 9]))
							esp += sizeof(addr_t) * 2;
						break;	/* push push push call */
					}
				}
				esp += sizeof(addr_t);
			/* sub esp,xx */
			} else if (c == 0x83 && i < sizeof(funchead) - 2 && funchead[i + 1] == 0xec) {
				esp += funchead[i + 2];
				i += 3;
				break;	/* for sys_lstat64 */
			/* add esp,xx */
			} else if (c == 0x83 && i < sizeof(funchead) - 2 && funchead[i + 1] == 0xc4) {
				/* unsigned? but it makes loop */
				esp += funchead[i + 2];
				i += 3;
				continue;
			/* sub esp,xx */
			} else if (c == 0x81 && i < sizeof(funchead) - 3 && funchead[i + 1] == 0xec) {
				esp += funchead[i + 2] + funchead[i + 3] * 256;
				i += 6;
				continue;
			/* push es/push cs/push ss/push ds */
			} else if (c == 0x06 || c == 0x0e || c == 0x16 || c == 0x1e) {
				esp += 4;
			/* GRP-A */
			} else if (code1[c].type == GROUPA) {
				i += code1[c].nbyte;
				(void)getvaluemod(1, funchead + i - 1, &i);
				if (c == 0x81)
					i += 4;
				else
					i += 1;
				continue;
			/* call, mov-imm */
			} else if (c == 0xe8 || c == 0xc6 || c == 0xc7)
				break;
#if 1
			/* mov */
			else if (c == 0x8b)
				break;
#endif
			else if (code1[c].type == MOD0_4 || code1[c].type == MOD1_4 || code1[c].type == GROUPE) {
				i += code1[c].nbyte;
				(void)getvaluemod(1, funchead + i - 1, &i);
				continue;
			} else if (code1[c].type == MOD0_1 || code1[c].type == MOD1_1 || code1[c].type == GROUPD) {
				i += code1[c].nbyte;
				(void)getvaluemod(0, funchead + i - 1, &i);
				continue;
			} else if (code1[c].type == OP2B) {
				i += code2[funchead[i + 1]].nbyte;
				continue;
			/* jxx */
			} else if (code1[c].type == RELAD1) {
				break;
			}
			i += code1[c].nbyte;
		}
		esp += argsize;
		argsize = 0;

		if (! INTHESTACK(esp)) {
			mprintf(")\n");
			continue;
		}
		retad = *(unsigned int *)&stack[esp - stacktop];
		if ((sym = searchsym_byaddr(retad)) == NULL ||
		    (sym->addr <= retad && sym->addr + 1 >= retad)) {
			/* maybe stack analyze fail, do RECOVERY code */
			/* save the pointers */
			int esp_save = esp, retad_save = retad;
			/* search the next function from stack */
			while (esp++, INTHESTACK(esp)) {
				retad = *(unsigned int *)&stack[esp - stacktop];
				if ((sym = searchsym_byaddr(retad)) != NULL &&
				    (sym->type == 'T' || sym->type == 't' || sym->type == 0))
					break;	/*found*/
				sym = NULL;
			}
			if (sym == NULL) {
				/* recovery fail */
				esp = esp_save;
				retad = retad_save;
				sym = searchsym_byaddr(retad);
				mprintf(")\n");
				continue;
			}
			mprintf("?");
		}

		memread(retad, sizeof(funchead), funchead, sym->name);
		if (funchead[0] == 0xe9 && (strcmp(sym->name, "stext_lock") == 0 || strncmp(sym->name, "_text_lock_", 11) == 0)) {
			mprintf(")\n[%8lx] %8lx  [%s]\n", esp, retad, getsymstr(retad));
			retad += *(unsigned int *)&funchead[1];
			sym = searchsym_byaddr(retad);
			continue;
		}
		/* Analyze after call loop */
		for (i = 0; i < sizeof(funchead) - 1; ) {
			int c = funchead[i];
			/* add esp,xx */
			if (c == 0x83 && funchead[i + 1] == 0xc4) {
				if (argsize == 0 && i < sizeof(funchead) - 3 && funchead[i + 3] != 0xc3)
					argsize = (signed char)funchead[i + 2];
				break;
			/* push */
			} else if (ISPUSH(c) || c == 0x6a || c == 0x9c) {
				break;
			/* pop */
			} else if (ISPOP(c)) {
				argsize += sizeof(addr_t);
				i++;
			/* jmp */
			} else if (c == 0xe9) {
				if (i > 0)
					break;
				memread(retad + i + 5 + funchead[1] + funchead[2] * 256, sizeof(funchead), funchead, sym->name);
				/* add esp,xx */
				if (funchead[0] == 0x83 && funchead[1] == 0xc4)
					argsize = (signed char)funchead[2];
				break;
			/* jmp */
			} else if (c == 0xeb) {
				if (i > 0)
					break;
				memread(retad + i + 2 + (signed char)funchead[1], sizeof(funchead), funchead, sym->name);
				/* add esp,xx */
				if (funchead[0] == 0x83 && funchead[1] == 0xc4)
					argsize = (signed char)funchead[2];
				break;
			/* call / sti / int */
			} else if (c == 0xe8 || c == 0xfb || c == 0xcd) {
				break;
			} else if (code1[c].type == MOD0_4 || code1[c].type == MOD1_4 || code1[c].type == GROUPE) {
				i += code1[c].nbyte;
				(void)getvaluemod(1, funchead + i - 1, &i);
			} else if (code1[c].type == MOD0_1 || code1[c].type == MOD1_1 || code1[c].type == GROUPD) {
				i += code1[c].nbyte;
				(void)getvaluemod(0, funchead + i - 1, &i);
			} else if (c == 0x0f) {
				break;
			} else if (c == 0xc7) {
				i += code1[c].nbyte;
				(void)getvaluemod(1, funchead + i - 1, &i);
				i += 4;
			/* jxx */
			} else if (code1[c].type == RELAD1) {
				break;
			} else {
				i += code1[c].nbyte;
			}
		}
		if (argsize) {
			for (i = 0; i < argsize; i += sizeof(int)) {
				if (i) mprintf(",");
				if (!INTHESTACK(esp + sizeof(int) + i))
					break;
				mprintf("%x", *(int*)&stack[esp - stacktop + sizeof(int) + i]);
			}
		} else /*if (strcmp(sym->name, "system_call") == 0)*/ {
			for (i = 0; i < 4 * sizeof(int); i += sizeof(int)) {
				if (i) mprintf(",");
				if (!INTHESTACK(esp + sizeof(int) + i))
					break;
				mprintf("%x", *(int*)&stack[esp - stacktop + sizeof(int) + i]);
			}
		}
		mprintf(")\n");
	}
	return 0;
}
