/*
** Debugger with ptrace()
**
** (c) Rainer Schnitker 1994
*/

#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include "ansi.h"

#include <process.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <signal.h>

#include "syms.h"
#include "unassmbl.h"
#include "breakp.h"
#include "dpmi.h"
#include "ansi.h"

extern int get_input(char *, int, int);

static int pid;
static int uaddr;
static char **org_env;
static int sigtrap;

struct reg_names {
    char *name;
    int size;
    int ofs;
};
extern struct reg_names regs[];

typedef enum {
    Unknown,
    CONT,
    STEP,
    NEXT,
    LIST,
    DUMP,
    DISP,
    SHELL,
    BREAK_SET, BREAK_LIST, BREAK_CLEAR, BREAK_ENABLE, BREAK_DISABLE,
    REGS,
    SET,
    WHERE,
    FIND,
    NPX,
    PROCESS,
    USER,
    SEL,
    HELP,
    QUIT
} deb_commands;

typedef struct {
    char *cp;
    deb_commands t;
} item;

static item cmds[] =
{
    "go", CONT, "g", CONT,
    "cont", CONT, "c", CONT,
    "step", STEP, "s", STEP,
    "next", NEXT, "n", NEXT,
    "list", LIST, "l", LIST,
    "dump", DUMP, "d", DUMP,
    "disp", DISP,
    "reg", REGS, "r", REGS,
    "where", WHERE, "w", WHERE,
    "find", FIND, "f", FIND,
    "bp", BREAK_SET,
    "bc", BREAK_CLEAR,
    "bd", BREAK_DISABLE,
    "be", BREAK_ENABLE,
    "bl", BREAK_LIST,
    "set", SET,
    "process", PROCESS,
    "user", USER,
    "shell", SHELL,
    "sel", SEL,
    "npx", NPX,
    "help", HELP, "h", HELP, "?", HELP,
    "quit", QUIT, "q", QUIT,
    0, 0
};

char *sig_text[] =
{
    NULL,
    "SIGHUP",
    "SIGINT",
    "SIGQUIT",
    "SIGILL",
    "SIGTRAP",
    "SIGABRT",
    "SIGEMT",
    "SIGFPE",
    "SIGKILL",
    "SIGBUS",
    "SIGSEGV",
    "SIGSYS",
    "SIGPIPE",
    "SIGALRM",
    "SIGTERM",
    "16"
    "17"
    "SIGCLD",
    "19"
    "20"
    "SIGBREAK"};


static ansi_flag=0;
void ansi(int fg)
{
    if (!ansi_flag)
	return;
    printf("\033[%d;%dm", (fg & A_bold) ? 1 : 0, 30+(fg&7));
}

void read_memory(unsigned long addr, BYTE *buf, int len)
{
    unsigned long long pc, r, i;

    for (pc = addr, i = 0; pc < addr + len; pc++, i++) {
	errno = 0;
	r = ptrace(PTRACE_PEEKDATA, pid, pc, 0);
	if (errno != 0)
	    break;
	else
	    buf[i] = (BYTE) r&0xff;
    }
}

int ptrace_get_register(int off)
{
    int r;
    errno = 0;
    r = ptrace(PTRACE_PEEKUSER, pid, uaddr + off, 0);
    if (r == -1 && errno)
	perror("ptrace");
    return r;
}

int ptrace_put_register(int off, long value)
{
    int r;
    errno = 0;
    r = ptrace(PTRACE_POKEUSER, pid, uaddr + off, value);
    if (r == -1 && errno)
	perror("ptrace");
    return r;
}


static void show_regs(void)
{
    static int seg_no[6] = { CS, DS, ES, SS, FS, GS };
    static char * seg_name[6] = { "cs", "ds", "es", "ss", "fs", "gs" };

    int i, r, j = 1;

    for (i = 0; i <= 9; ++i) {
	errno = 0;
	r = ptrace_get_register(regs[i].ofs * 4);
	if (errno != 0) {
	    perror("ptrace");
	    return;
	}
	if (++j == 4) {
	    j = 0;
	    putchar('\n');
	}
	printf("%s=%.8x ", regs[i].name + 1, r);
    }
    putchar('\n');
    for (i = 0; i <= 5; ++i) {
	errno = 0;
	r = ptrace_get_register(seg_no[i] * 4);
	if (errno != 0) {
	    perror("ptrace");
	    return;
	}
	printf("%s=%.4x ", seg_name[i], r & 0xFFFF);
    }
    putchar('\n');
}

static int do_run(int cmd)
{
    int s, t, p, r;

    s = ptrace(cmd, pid, 0, 0);
    if (s < 0) {
	perror("ptrace");
	return 0;
    }
    p = wait(&t);
    if (p != pid)
        puts("pid != wait pid");

    if (p == -1)
	perror("wait");
    if ((t & 0377) != 0177) {
        printf("process %d terminated (%d)\n", p, (t >> 8) & 0xff);
        printf("\033[0m");
	exit(0);
    }
    else {
        if ((t >> 8) == SIGTRAP)
	    sigtrap = 1;
	else {
	    printf("process %p stopped by signal %s\n", p, sig_text[t >> 8]);
	    sigtrap = 0;
	}
    }
    errno = 0;
    r = ptrace_get_register(EIP * 4);
    if (errno != 0) {
	perror("ptrace");
	return 0;
    }
    return r;
}

static int run(int cmd)
{
    int i, r, t;

    if (cmd == PTRACE_STEP)
	return do_run(PTRACE_STEP);

    /* RESUME, if breakpoint at eip, do one step */
    r = ptrace_get_register(EIP * 4);
    for (i = 0; i < MAX_BP; ++i)
	if (bp[i].addr == r && bp[i].status == BP_ENABLE)
	    break;
    if ((i < MAX_BP) &&
	    (ptrace(PTRACE_PEEKTEXT, pid, r, 0) & 0xFF) != 0xCD)
	do_run(PTRACE_STEP);

    insert_breakpoints(pid);
    r = do_run(PTRACE_RESUME);
    remove_breakpoints(pid);

    /* correct eip, take back 1 step */
    if (r) {
	r -= sigtrap;
	ptrace(PTRACE_POKEUSER, pid, uaddr + EIP * 4, r);
    }
    return r;
}

static void dump(unsigned long n)
{
    static unsigned long dump_addr;
    int i, j;
    BYTE dump_buf[16];

    if (n)
	dump_addr = n;
    if (dump_addr == 0)
	return;

    for (i = 0; i < 8; ++i) {
	printf("%08lx: ", dump_addr);
	for (j = 0; j < 16; j++) {
	    dump_buf[j] = (BYTE) ptrace(PTRACE_PEEKDATA, pid, dump_addr++, 0);
	    printf("%02X ", dump_buf[j] & 0xFF);
	}
	putchar(' ');
	for (j = 0; j < 16; ++j) {
	    char c = dump_buf[j];
	    if (isalnum(c) || ispunct(c))
		putchar(c);
	    else
		putchar('.');
	}
	putchar('\n');
    }
}

static void command_loop(void)
{
    static unsigned long next_inst;
    static unsigned long this_inst;
    static int cmd;
    static char prompt[] = "cmd:";

    unsigned long temp, list_inst;
    int i, j, len;
    char input[40];
    char *argv[10];
    char *name;

    putchar('\n');
    show_regs();

    list_inst = this_inst = ptrace_get_register(EIP * 4);
    next_inst = unassemble(this_inst, 1);

    while (1) {
	ansi(A_white);

	printf("%s", prompt);
	fflush(stdout);
	len = get_input(input, 40, 0);

	ansi(A_yellow);

	/* make strings */
	j = 0;
	for (i = 0; input[i] != 0; i++)
	    if (input[i] == ' ') {
		input[i] = 0;
		if (input[i + 1] == ' ')
		    continue;
		if (input[i + 1] == 0)
		    continue;
		argv[j++] = input + i + 1;
	    }
	argv[j] = 0;

	if (*input == 0) {	/* only return */
	    for (i = sizeof(prompt); i != 0; i--) {
		putchar('\b'), putchar(' '), putchar('\b');
		fflush(stdout);
		argv[0] = NULL;
	    }
	} else {		/* new input */
	    putchar('\n');
	    fflush(stdout);

	    list_inst = this_inst;

	    for (i = 0; cmds[i].cp; i++)
		if (strcmp(cmds[i].cp, input) == 0) {
		    cmd = cmds[i].t;
		    i = -1;
		    break;
		}
	    if (i != -1 && *input)
		cmd = Unknown;
	}

	switch (cmd) {

	case HELP:
	    printf("go <v>       g - go or continue execution\n");
	    printf("cont <v>     c - continue execution\n");
	    printf("step         s - step through instruction\n");
	    printf("next         n - step to next instruction\n");
	    printf("list <v>     l - list instructions at <v>\n");
	    printf("dump <v>     d - dump memory at <v>\n");
	    printf("disp v         - display symbol v\n");
	    printf("reg <m> <v>  r - show/set registers\n");
	    printf("where        w - display list of active functions\n");
	    printf("find         f - find a symbol/location (wildcard)\n");
	    printf("bp v           - set breakpoint at v\n");
	    printf("bx no          - x=List/Clear/Dis-/Enable breakpoint no\n");
	    printf("set m v <s>    - memory to value; size=byte/word\n");
	    printf("process        - show process data\n");
	    printf("sel s <n>      - show n selectors start with s\n");
	    printf("shell argv     - execute other program\n");
	    printf("quit         q - terminate debugger\n");
	    printf("help         ? - this text\n");
	    break;

	case CONT:
	    if (argv[0]) {
		temp = syms_name2val(argv[0]);
		if (undefined_symbol)
		    break;
		i = set_bp(temp);
		run(PTRACE_RESUME);
		delete_bp(i);
	    } else
		run(PTRACE_RESUME);
	    break;

	case STEP:
	    if ((ptrace(PTRACE_PEEKTEXT, pid, this_inst, 0) & 0xFF) == 0xCD)
		goto Next;	    /* not INT instr */
	    run(PTRACE_STEP);
	    break;

	case NEXT:
	    if (last_unassemble_unconditional || last_unassemble_jump)
		run(PTRACE_STEP);
	    else {
		Next:
		i = set_bp(next_inst);
		run(PTRACE_RESUME);
		delete_bp(i);
	    }
	    break;

	case LIST:
	    if (argv[0]) {	/* weitere eingabe */
		temp = syms_name2val(argv[0]);
		if (!undefined_symbol)
		    list_inst = temp;
		else
		    list_inst = this_inst;
	    }
	    for (i = 1; i <= 10; i++)
		list_inst = unassemble(list_inst, 0);
	    break;

	case DUMP:
	    if (argv[0] == NULL) {
		dump(0);
		break;
	    }
	    else
		temp = syms_name2val(argv[0]);
	    if (!undefined_symbol) {
		printf("%s: adr 0x%08x val 0x%08lx\n", argv[0], temp,
			ptrace(PTRACE_PEEKDATA, pid, temp, 0));
		dump(temp);
	    }
	    break;

	case DISP:
	    if (!argv[0])
		break;
	    temp = syms_name2val(argv[0]);
	    if (!undefined_symbol)
		printf("%s: adr 0x%08x val 0x%08lx\n", argv[0], temp,
			ptrace(PTRACE_PEEKDATA, pid, temp, 0));
	    break;

	case BREAK_SET:
	    if (argv[0]) {
		temp = syms_name2val(argv[0]);
		if (undefined_symbol)
		    break;
		i = set_bp(temp);
		if (i>0)
		    printf("breakpoint %d set\n",i);
	    }
	    break;

	case BREAK_CLEAR:
	    if (argv[0]) {
		sscanf(argv[0], "%d", &i);
		delete_bp(i);
	    }
	    break;

	case BREAK_ENABLE:
	    if (argv[0]) {
		sscanf(argv[0], "%d", &i);
		enable_bp(i);
	    }
	    break;

	case BREAK_DISABLE:
	    if (argv[0]) {
		sscanf(argv[0], "%d", &i);
		disable_bp(i);
	    }
	    break;

	case BREAK_LIST:
	    printf("Breakpoint list:\n");
	    for (i = 0; i < MAX_BP; i++)
		if (bp[i].status) {
		    name = syms_val2name(bp[i].addr, &temp);
		    printf("bp %d at %08X ", i, bp[i].addr);
		    if (name)
			printf("%s", name);
		    if (temp)
			printf("+%lX ", temp);
		    if (bp[i].status == BP_DISABLE)
			printf(" - disabled");
		    printf("\n");
		}
	    break;

	case REGS:
	    if (argv[0] == NULL) {
		show_regs();
		break;
	    }
	    if (argv[1] == NULL)
		break;
	    temp = syms_name2val(argv[1]);
	    if (undefined_symbol)
		break;
	    j = 0;
	    for (i = 0; regs[i].name; i++)
		if (strcmp(regs[i].name, argv[0]) == 0) {
		    int r = ptrace_get_register(regs[i].ofs * 4);

		    j=1;
		    switch (regs[i].size) {
		    case 0:
			r = (r & 0xffff00ff) | ((temp & 0xff) << 8);
			break;
		    case 1:
			* (char *) & r = * (char *) & temp;
			break;
		    case 2:
			* (short *) & r = * (short *) & temp;
			break;
		    case 4:
			r = temp;
			break;
		    }
		    ptrace_put_register(regs[i].ofs * 4, r);
		    break;
		}
	    if (!j)
		puts("register not found");
	    break;

	case SHELL:
	    if (argv[0] == NULL)
		break;
	    if (spawnvpe(P_WAIT, argv[0], argv, org_env) < 0)
		printf("error spawn %s\n", argv[0]);
	    break;

	case FIND:
	    if (argv[0] == NULL)
		break;
	    if (strpbrk(argv[0], "*?") != NULL) {
		syms_listwild(argv[0]);
		break;
	    }
	    temp = syms_name2val(argv[0]);
	    if (!undefined_symbol) {
		name = syms_val2name(this_inst, &temp);
		printf("0x%08lx %s", this_inst, name);
		if (temp)
		    printf("+%lx", temp);
		name = syms_val2line(this_inst, &i, 0);
		if (name)
		    printf(", line %d in file %s", i, name);
		putchar('\n');
	    }
	    break;

	case SET:
	    if (argv[0] == NULL || argv[1] == NULL)
		break;
	    if (*(argv[0]) == '%') {
		puts("Use the command 'reg' or 'r'");
		break;
	    }
	    else {
		DWORD pokeat;
		int size;

		temp = syms_name2val(argv[1]);
		if (undefined_symbol)
		    break;
		pokeat = syms_name2val(argv[0]);
		if (undefined_symbol)
		    break;

		if (argv[2]) {
		    if (*(argv[2]) == 'b')
			size = 1;
		    else if (*(argv[2]) == 'w')
			size = 2;
		} else
		    size=0;
		if (size) {   /* value */
		    DWORD oldv = ptrace(PTRACE_PEEKTEXT, pid, pokeat, 0);
		    if (size = 1)
			* (char *) & temp = * (char *) & temp;
		    else
			* (short *) & temp = * (short *) & temp;
		}
		ptrace(PTRACE_POKETEXT, pid, pokeat, temp);
	    }
	    break;

	case PROCESS:
	    printf("pid %d ppid %d\n\n", getpid(), getppid());
	    PrintFreeMemInfo();
	    break;

	case SEL:
	    if (argv[0] == NULL)
		break;
	    if (argv[1] == NULL)
		i = 1;
	    else
		i = atoi(argv[1]);
	    temp = syms_name2val(argv[0]);
	    if (undefined_symbol)
		break;
	    show_descriptor(temp, i);
	    break;

	case QUIT:
            printf("\033[0m");
	    return;

	case Unknown:
	    printf("unknown command, try (H)elp or ?\n");
	    break;

	}   /* switch */

	if (cmd == CONT || cmd == NEXT || cmd == STEP) {
	    list_inst = this_inst = ptrace_get_register(EIP * 4);
	    next_inst = unassemble(this_inst, 1);
	}
    }	/* while */
}

#define RUN_RSX 	0x1000L

static int check_dos_extender(void)
{
    if (_emx_env & RUN_RSX) {
        int rsx_exe = _emx_rev >> 16;
        int rsx_ver = _emx_rev & 0xFFFF;

        if (rsx_exe != 0)
            return 0;
    } else
        return 1;
}

static void print_version(void)
{
    if (_emx_env & RUN_RSX) {
        int rsx_env = _emx_rev >> 16;
        int rsx_ver = _emx_rev & 0xFFFF;

        if (rsx_env == 1) {
            printf("\033[33mRunning under RSXWIN , version %X%c", rsx_ver>>8, 'a' - 1 + (rsx_ver&0xFF));
	    printf("\033[0m\n");
        }
        else if (rsx_env == 2) {
            printf("\033[33mRunning under RSXNT , version %X.%02X\n", rsx_ver>>8, rsx_ver&0xFF);
	    printf("\033[0m\n");
        }
        else
            printf("Running under RSX , version %X.%02X\n", rsx_ver>>8, rsx_ver&0xFF);
    }
    else
        printf(" running under EMX , revision %X\n", _emx_rev);
}

int main(int argc, char **argv, char **envp)
{
    static char debug_file[260];
    struct user u;
    char *term;
    int i, start_argc;
    char *symfile;

    if (argc <= 1)
    {
        printf("usage: debnt [-symfile prg.sym] prg.exe <arg1 arg2 ...>\n");
        return 1;
    }

    print_version();

    if (check_dos_extender()) {
        term = getenv("TERM");
        if (term != NULL && strncmp(term, "ansi", 4) == 0)
            ansi_flag = 1;
    } else
        ansi_flag = 1;

    org_env = envp;

    if (argc >= 4 && strcmp (argv[1], "-symfile") == 0)
    {
        symfile = argv[2];
        strcpy(debug_file, argv[3]);
        start_argc = 3;
    }
    else
    {
        symfile = argv[1];
        start_argc = 1;
    }

    _defext(debug_file, ".exe");

    pid = spawnve(P_DEBUG | P_WINDOWED, debug_file, argv + start_argc, envp);

    if (pid < 0) {
        perror("Debugger spawnve");
        return 2;
    }

    printf("child PID %d\n", pid);
    errno = 0;
    uaddr = ptrace(PTRACE_PEEKUSER, pid, (char *) &u.u_ar0 - (char *) &u, 0);
    if (errno != 0)
	perror("ptrace");
    uaddr -= 0xe0000000;

    printf("debugging file %s\n", debug_file);
    fflush(stdout);

    syms_init(symfile);
    command_loop();

    return (0);
}
