/*****************************************************************************
 * FILE: kdeb.c                                                              *
 *									     *
 * DESC:								     *
 *      - support for kernel debugging                                       *
 *      - kernel ptrace                                                      *
 *									     *
 * Copyright (C) 1993,1994                                                   *
 *	Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld		     *
 *	email: rainer@mathematik.uni-bielefeld.de			     *
 *									     *
 *****************************************************************************/

#include "DPMI.H"
#include "KDEB.H"

#include "PROCESS.H"
#include "PTRACE.H"
#include "DOSERRNO.H"
#include "START32.H"
#include "ADOSX32.H"
#include "SIGNALS.H"
#include "PRINTF.H"

#define SINGLE_STEP	0x100
#define RESUME_FLAG	0x10000L
#define FLAG_MASK	0x00000dd9L
#define BREAKPOINT	0xcc

#define MAX_HARD	4
#define BP_FLAG_MASK	0x0FFF
#define BP_ENABLE	0x1000
#define BP_DISABLE	0x2000

typedef struct {
    DWORD   address;
    WORD    handle;
    WORD    flags;
    DWORD   data;
    DWORD   pt_address;
} HardBreakPoint;

static HardBreakPoint hbp[MAX_HARD];
static DWORD memaddress;

static int KDEB_set_hard_break(DWORD at_address, WORD flags)
{
    int i;

    if (!memaddress)
	GetBaseAddress(GetCS(), & memaddress);

    for (i = 1; i < MAX_HARD; i++)
	if (hbp[i].flags == 0)
	    break;
    if (i >= MAX_HARD)
	return -1;
    if (SetDebugWatchpoint(memaddress + at_address,
			   flags & BP_FLAG_MASK, &hbp[i].handle)) {
	puts("set_hard_break() error");
	return -1;
    }
    else {
	hbp[i].address = at_address;
	hbp[i].flags = flags | BP_ENABLE;
	return i;
    }
}

static int KDEB_del_hard_break(int no)
{
    if (no >= MAX_HARD || hbp[no].flags == 0)
	return -1;

    if (hbp[no].flags & BP_DISABLE) {
	/* already clear */
	hbp[no].address = 0;
	hbp[no].flags = 0;
	hbp[no].handle = 0;
	return 0;
    }

    if (ClearDebugWatchpoint(hbp[no].handle)) {
	puts("del_hard_break() error");
	return -1;
    }
    else {
	hbp[no].address = 0;
	hbp[no].flags = 0;
	hbp[no].handle = 0;
	return 0;
    }
}

static int KDEB_disable_hard_break(int no)
{
    if (no <= 1 && no >= MAX_HARD)
	return -1;
    if (hbp[no].flags & BP_DISABLE)
	return -1;
    if (ClearDebugWatchpoint(hbp[no].handle)) {
	puts("disable_hard_break() error");
	return -1;
    }
    else {
	hbp[no].flags &= BP_FLAG_MASK;
	hbp[no].flags |= BP_DISABLE;
	return 0;
    }
}

static int KDEB_enable_hard_break(int no)
{
    if (no <= 1 && no >= MAX_HARD)
	return -1;
    if (hbp[no].flags & BP_ENABLE)
	return -1;
    if (SetDebugWatchpoint(memaddress + hbp[no].address,
			hbp[no].flags & BP_FLAG_MASK, &hbp[no].handle)) {
	puts("enable_hard_break() error");
	return -1;
    }
    else {
	hbp[no].flags &= BP_FLAG_MASK;
	hbp[no].flags |= BP_ENABLE;
	return 0;
    }
}


int KDEB_check_breakpoints(DWORD pc)
{
    int i;
    WORD j;

    for (i = 0; i < MAX_HARD; i++)
	if (hbp[i].flags & BP_ENABLE) {
	    j = 0;
	    GetStateDebugWatchpoint(hbp[i].handle, &j);
	    if (j)
		ResetDebugWatchpoint(hbp[i].handle);
	}
    return ((i >= MAX_HARD) ? -1 : i);
}

void KDEB_enable_breakpoints(void)
{
    int i;
    for (i = 0; i < MAX_HARD; i++)
	if ((hbp[i].flags & BP_DISABLE) && hbp[i].pt_address)
	    KDEB_enable_hard_break(i);
}

void KDEB_disable_breakpoints(void)
{
    int i;
    for (i = 0; i < MAX_HARD; i++)
	if ((hbp[i].flags & BP_ENABLE) && hbp[i].pt_address)
	    KDEB_disable_hard_break(i);
}



/* +++++++++++++ PTRACE in KERNEL +++++++++++++++++ */

/* crt0.s */
extern unsigned long rsx_data_start;
extern unsigned long rsx_data_ends;

/*
 * simulate ptrace(pid,POKETEXT,address,BREAKPOINT/ORGINAL)
 */
static int KDEB_ptrace_poketext(DWORD address, DWORD value)
{
    int i;
    DWORD bp = value;
    DWORD bp_addr = address;

    for (i = 0 ; i <= 3 ; i++) {
	if ((bp & 0xff) == BREAKPOINT)
	    break;
	bp >>= 8;
	bp_addr ++;
    }

    /* ptrace: set breakpoint in text */
    if (i <= 3) {
	printf("POKETEXT new break %lX\n", address);
	if ((i = KDEB_set_hard_break(bp_addr, BREAK_CODE)) > 0) {
	    KDEB_disable_hard_break(i);
	    hbp[i].data = value;
	    hbp[i].pt_address = address;
	    return 0;
	}
	else {
	    printf("POKETEXT break fail %lX\n", address);
	    return -1;
	}
    }
    /* ptrace: delete breakpoint in text */
    else {
	for (i = 0; i < MAX_HARD; i++)
	    if (address == hbp[i].pt_address)
		break;
	if (i >= MAX_HARD) {
	    printf("POKETEXT address %lX\n", address);
	    * (DWORD *) address = value;
	}
	else {
	    printf("POKETEXT delete break %lX\n", address);
	    hbp[i].data = hbp[i].pt_address = 0;
	    KDEB_del_hard_break(i);
	}
	return 0;
    }
}

/*
 * simulate ptrace(pid,PEEKTEXT,address, 0)
 */
static int KDEB_ptrace_peektext(DWORD address, DWORD * value)
{
    int i;

    for (i = 0; i < MAX_HARD; i++)
	if (address == hbp[i].pt_address)
	    break;
    if (i >= MAX_HARD) {
	/* printf("PEEKTEXT address %lX\n", address); */
	*value = * (DWORD *) address;
    }
    else {
	/* printf("PEEKTEXT peek break %lX\n", address); */
	*value = hbp[i].data;
    }
    return 0;
}

/*
 * simulate ptrace(pid,POKEDATA,address,value)
 */
static int KDEB_ptrace_pokedata(DWORD address, DWORD value)
{
    if (address < 4096 || address >= rsx_data_ends) {
	puts("KDEB: illegal address");
	return EMX_EINVAL;
    }
    if (address == (DWORD) & npz) {
	puts("KDEB: don't touch npz");
	return EMX_EINVAL;
    }
    * (DWORD *) address = value;
    return 0;
}

/*
 * simulate ptrace(pid,PEEKDATA,address,0)
 */
static int KDEB_ptrace_peekdata(DWORD address, DWORD * value)
{
    if (address < 4096 || address >= rsx_data_ends) {
	puts("KDEB: illegal address");
	return EMX_EINVAL;
    }
    if (address == (DWORD) & npz) {
	*value = (DWORD) RSX_PROCESS.cptr;
	return 0;
    }
    *value = * (DWORD *) address;
    return 0;
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


int KDEB_ptrace(int request, DWORD addr, DWORD data, DWORD * ret)
{
    /* printf("KDEB %d %lX %lX\n", request, addr, data); */

    *ret = 0;

    switch (request) {

    case PTRACE_TRACEME:
    case PTRACE_SESSION:
	return 0;

    case PTRACE_PEEKTEXT:
    case PTRACE_PEEKDATA:
	if (addr > 0x10000L && addr < rsx_data_start)
	    return KDEB_ptrace_peektext(addr, ret);
	else
	    return KDEB_ptrace_peekdata(addr, ret);

    case PTRACE_POKETEXT:
	if (addr > 0x10000L && addr < rsx_data_start)
	    return KDEB_ptrace_poketext(addr, data);
	else
	    return KDEB_ptrace_pokedata(addr, data);

    case PTRACE_EXIT:
	KDEB_disable_breakpoints();
	return 0;

    case PTRACE_PEEKUSER:
	if (addr == 0x30) {	/* u_ar0  */
	    *ret = 0xE0000000 + ((DWORD) (UINT) & (RSX_PROCESS.regs));
	    return 0;
	} else {		/* peek regs */
	    DWORD *peekat;
	    peekat = (DWORD *) (UINT) (addr);

	    if (peekat < &(RSX_PROCESS.regs.gs) || peekat > &(RSX_PROCESS.regs.ss))
		return EMX_EIO;

	    *ret = *peekat;

	    if (peekat == &(RSX_PROCESS.regs.eip)) {
		int i;
		for (i = 0; i < MAX_HARD; i++)
		    if (RSX_PROCESS.regs.eip == hbp[i].address)
			break;
		if (i < MAX_HARD)
		    (*ret)++;
	    }

	    return 0;
	}

    case PTRACE_POKEUSER:
	/* don't change kernel registers ! */
	*ret = data;
	return 0;

    case PTRACE_STEP:
	/* printf("PTRACE step\n"); */
	RSX_PROCESS.regs.eflags |= SINGLE_STEP;
	npz = RSX_PROCESS.cptr;
	KDEB_rsx32_orgjmp();
	return 0;

    case PTRACE_RESUME:
	/* printf("PTRACE resume\n"); */
	RSX_PROCESS.regs.eflags &= ~SINGLE_STEP;
	npz = RSX_PROCESS.cptr;
	KDEB_enable_breakpoints();
	/* start first program */
	if (npz == & process[2] && npz->regs.eip == 0x10000)
	    back_from_syscall();
	KDEB_rsx32_orgjmp();
	return 0;

    default:
	return EMX_EIO;
    }
}

struct POPAD {
    unsigned edi;
    unsigned esi;
    unsigned ebp;
    unsigned esp;
    unsigned ebx;
    unsigned edx;
    unsigned ecx;
    unsigned eax;
};

#define RSXREG RSX_PROCESS.regs

static unsigned rsx_eip;
static unsigned rsx_flags;
static unsigned rsx_esp;

void KDEB_debug_handler(void)
{
    KDEB_check_breakpoints(RSXREG.eip);
    KDEB_disable_breakpoints();

    /*
    printf("Debug fault at %04X:%08lX  efl=%08lX\n",
	RSXREG.cs & 0xFFFF, RSXREG.eip, RSXREG.eflags);
    */

    rsx_eip = RSXREG.eip;
    RSX_PROCESS.p_status = PS_STOP;
    RSX_PROCESS.wait_return = (SIGTRAP << 8) | 127;
    RSX_PROCESS.p_flags |= PF_WAIT_WAIT;
    RSX_PROCESS.cptr = npz;
    npz = & process[1];
    EAX = ECX = 0;
    npz->p_status = PS_RUN;

    /* ptrace should go on other stack */
    stackp16 -= 8000;

    back_from_syscall();
}

volatile void KDEB_rsx32_orgjmp(void)
{
    struct POPAD pop;

    /* ptrace should go on other stack */
    stackp16 += 8000;

    pop.eax = RSXREG.eax;
    pop.ecx = RSXREG.ecx;
    pop.edx = RSXREG.edx;
    pop.ebx = RSXREG.ebx;
    pop.esp = RSXREG.esp;
    pop.ebp = RSXREG.ebp;
    pop.esi = RSXREG.esi;
    pop.edi = RSXREG.edi;

    rsx_eip = RSXREG.eip;
    rsx_flags = RSXREG.eflags;
    rsx_esp = RSXREG.esporg;

loop:
    __asm__ __volatile (
	"movl %0, %%esp \n\t"
	"popa \n\t"
	"nop \n\t"
	"movl  _rsx_esp, %%esp \n\t"
	"pushl _rsx_flags\n\t"
	"pushl %%cs \n\t"
	"pushl _rsx_eip \n\t"
	"iret \n\t"
	:
	: "r" ((unsigned)&pop)
	);
    goto loop;
}
