/* $VER: debugger.c V0.4 (26.02.00)
 *
 * This file is part of the WarpOS debugger 'wosdb'
 * Copyright (c) 1999-2000  Frank Wille
 *
 *
 * v0.4  (26.02.00) phx
 *       Fixed wosdb_symbol(), which returns the correct symbol for
 *       stack frame trace backs now. Didn't work before, because symbols
 *       are not sorted by address.
 * v0.3a (07.05.99) phx
 *       wosdb_load() allows passing of an argument string to the
 *       program to be debugged.
 *       "@_..." and "@x..." symbol names are recognized.
 * v0.3  (04.05.99) phx
 *       Replaced initial LR of debugged task by an own ProgramExit()
 *       function, which notifies the debugger about the task's termination.
 *       Stepping into or setting a breakpoint in ProgramExit() leads to
 *       an immeadiate termination of the debugged task.
 * v0.2  (02.05.99) phx
 *       wosdb_validaddr(): checks whether an address belongs to the
 *       debugged program.
 * v0.1  (01.05.99) phx
 *       First usable version.
 * v0.0  (17.04.99) phx
 *       File created.
 */

#include <stdio.h>
#include <ctype.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <dos/doshunks.h>
#include <powerpc/powerpc.h>
#include <powerpc/tasksPPC.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <clib/powerpc_protos.h>
#include "debugger.h"


extern struct ExecBase *SysBase;
extern struct PPCBase *PowerPCBase;

APTR oldRunPPC;             /* original RunPPC() vector */
extern LONG newRunPPC();    /* new RunPPC() function (68k) */

/* the exception handlers (PPC assembler source) */
extern ULONG ExceptionCatch(struct EXCContext *);
extern ULONG ExceptionLeave(struct EXCContext *);
extern ULONG ProgramExit(ULONG);  /* notifies debugger */
extern void ProgramExitEnd();
extern void clearIABR(void);
extern ULONG readIABR(void);

/* exceptions */
struct EXCContext ec;
struct SpecRegs spr;
struct TaskPPC *thisTask;       /* this task */
struct TaskPPC *dbTask = NULL;  /* debugged task */
int sigExcept;                  /* task-exception signal */
int sigFinish;                  /* task-finished signal */
int sigRun = SIGBREAKB_CTRL_D;  /* continue debugged task */
BOOL sTrace = FALSE;            /* single step trace mode */
BOOL bTrace = FALSE;            /* branch trace mode */

/* debugged task */
ULONG initialLR;                /* init. LR (return addr) of debugged task */
ULONG result;                   /* result code of a finished PPC task */
static BOOL taskloaded = FALSE;
static ULONG stacksize = 0x10000;
static int priority = 0;
static void *xhdlC = NULL;      /* exception handler locks (catch/leave) */
static void *xhdlL = NULL;
static void *taskpool = NULL;   /* mem pool for all task allocations */
static int nSecs;               /* number of sections (68k section incl.) */
static int nSyms;               /* number of total symbols */
static struct Section *firstSec;/* first section of debugged task */
static struct MinList breakpoints;

static char buffer[1024];       /* multi-purpose buffer */


ULONG r2(void) = "\tmr\tr3,r2"; /* gets TOC-pointer */



static long getsymval(char *symname)
{
  struct Section *sec = firstSec;
  struct Symbol *sym;

  while (sec) {
    sym = sec->symbols;
    while (sym) {
      if (!strcmp(sym->name,symname))
        return ((long)sym->addr);
      sym = sym->next;
    }
    sec = sec->next;
  }
  return (0);
}


static char *getvalue(long *val,char *buf)
{
  if (isdigit((unsigned char)*buf)) {
    /* numerical constant */
    sscanf(buf++,"%li",val);
    if (*buf=='x' || *buf=='x')
      buf++;
    while (isxdigit((unsigned char)*buf))
      buf++;
  }

  else if (*buf=='@' && *(buf+1)!='_' && *(buf+1)!='x') {
    /* register contents */
    int r;

    buf++;
    if (*buf == 'r' || *buf == 'R') {
      sscanf(++buf,"%i",&r);
      *val = (long)ec.ec_GPR[r];
      while (isdigit((unsigned char)*buf))
        buf++;
    }
    else if ((*buf&0xdf) == 'L' && (*(buf+1)&0xdf) == 'R') {
      *val = (long)ec.ec_LR;
      buf += 2;
    }
    else if ((*buf&0xdf) == 'C' && (*(buf+1)&0xdf) == 'T' &&
             (*(buf+2)&0xdf) == 'R') {
      *val = (long)ec.ec_CTR;
      buf += 3;
    }
    else if ((*buf&0xdf) == 'P' && (*(buf+1)&0xdf) == 'C') {
      *val = (long)ec.ec_UPC.ec_SRR0;
      buf += 2;
    }
  }

  else {
    /* symbol value */
    char *p = buffer;

    while (isalnum((unsigned char)*buf) || *buf=='_' || *buf=='@') {
      *p++ = *buf++;
    }
    *p = '\0';
    *val = getsymval(buffer);
  }
  return (buf);
}


static struct enode *exptree(void *expool,char *buf)
{
  struct enode *on=NULL,*vn,*rn;

  for (;;) {
    while (isspace((unsigned char)*buf))
      buf++;

    if (*buf == '(') {
      /* evaluate term */
      int lev=1;

      if ((vn = exptree(expool,++buf)) == NULL)
        return (NULL);
      while (lev>0) {
        if (*buf == '(')
          lev++;
        else if (*buf == ')')
          lev--;
        buf++;
      }
      if (vn->operation != OPERAND)
        vn->u.priority = TERMPRI;
    }
    else {
      if ((vn = AllocPooledPPC(expool,sizeof(struct enode))) == NULL)
        return (NULL);
      vn->root = on;
      vn->left = vn->right = NULL;
      vn->operation = OPERAND;
      buf = getvalue(&vn->u.value,buf);
    }

    if (on != NULL)
      on->right = vn;
    else
      rn = vn;
    while (isspace((unsigned char)*buf))
      buf++;
    if (*buf=='\0' || *buf=='\n' || *buf==')')
      return (rn);

    /* get operation */
    if ((on = AllocPooledPPC(expool,sizeof(struct enode))) == NULL)
      return (NULL);
    on->right = NULL;
    switch (*buf++) {
      case '+':
        on->operation = PLUS;
        on->u.priority = PLUSPRI;
        break;
      case '-':
        on->operation = MINUS;
        on->u.priority = MINUSPRI;
        break;
      case '*':
        on->operation = MULT;
        on->u.priority = MULTPRI;
        break;
      case '/':
        on->operation = DIV;
        on->u.priority = DIVPRI;
        break;
      default:
        return (NULL);
    }

    if (on->u.priority <= rn->u.priority || rn->operation == OPERAND) {
      on->root = NULL;
      rn->root = on;
      on->left = rn;
      rn = on;
    }
    else {
      on->root = rn;
      rn->right->root = on;
      on->left = rn->right;
      rn->right = on;
    }
  }
}


static long calctree(struct enode *n)
{
  if (n->operation == OPERAND) {
    return (n->u.value);
  }
  else {
    long x = calctree(n->left);
    long y = calctree(n->right);

    switch (n->operation) {
      case PLUS:
        return (x+y);
      case MINUS:
        return (x-y);
      case MULT:
        return (x*y);
      case DIV:
        return (x/y);
      default:
        return (0);
    }
  }
}


static BPTR read_sections(char *name)
{
  BPTR seglist;

  if (seglist = LoadSeg(name)) {
    ULONG *p;
    struct Section *s = NULL;

    /* get length and address of all sections */
    p = (ULONG *)BADDR(seglist);
    nSecs = 0;
    while (p) {
      if (s) {
        s->next = AllocPooledPPC(taskpool,sizeof(struct Section));
        s = s->next;
      }
      else
        firstSec = s = AllocPooledPPC(taskpool,sizeof(struct Section));
      if (s) {
        s->next = NULL;
        s->addr = (ADDR)(p+1);
        s->len = *(p-1);
        s->symbols = NULL;
        nSecs++;
        p = (ULONG *)BADDR(*p);
      }
      else {
        /* out of memory */
        DeletePoolPPC(taskpool);
        taskpool = NULL;
        UnLoadSeg(seglist);
        return (0);
      }
    }
  }
  return (seglist);
}


static ULONG filesize(FILE *fp)
{
  long oldpos,size;

  if ((oldpos = ftell(fp)) >= 0)
    if (fseek(fp,0,SEEK_END) >= 0)
      if ((size = ftell(fp)) >= 0)
        if (fseek(fp,oldpos,SEEK_SET) >= 0)
          return ((ULONG)size);
  return (0);
}


static void get_debug_info(char *name)
{
  FILE *f;
  ULONG size;
  ULONG *hunks;

  nSyms = 0;
  if (f = fopen(name,"r")) {
    if (size = filesize(f)) {
      if (hunks = AllocPooledPPC(taskpool,size)) {
        if (fread(hunks,1,size,f) == size) {
          if (*hunks++ == HUNK_HEADER) {
            int i,n,cnt;
            struct Section *s = firstSec;
            struct Symbol *lastsym,*newsym;
            ULONG type;

            n = *hunks;
            hunks += n ? (n+2) : 1;
            n = *hunks;
            hunks += n+3;
            if (n > nSecs)
              n = nSecs;

            /* scan all n sections for debugging information */
            for (i=0; i<n; i++,s=s->next) {
              lastsym = s->symbols;
              while ((type = *hunks++) != HUNK_END) {

                switch (type & 0xffff) {
                  case HUNK_CODE:
                  case HUNK_DATA:
                  case HUNK_NAME:
                  case HUNK_DEBUG:
                    hunks += *hunks+1;
                    break;

                  case HUNK_BSS:
                    hunks++;
                    break;

                  case HUNK_RELOC32:
                    while ((cnt = *hunks++) != 0)
                      hunks += cnt+1;
                    break;

                  case HUNK_RELOC32SHORT:
                  case HUNK_DREL32:  /* V37 RELOC32SHORT */
                    while ((cnt = *(UWORD *)hunks) != 0)
                      hunks += cnt+2;
                    hunks++;
                    if ((ULONG)hunks & 3)
                      hunks++;  /* longword alignment */
                    break;

                  case HUNK_SYMBOL:
                    while ((cnt = *hunks++) != 0) {
                      if (newsym = AllocPooledPPC(taskpool,
                                                  sizeof(struct Symbol))) {
                        if (newsym->name = AllocPooledPPC(taskpool,
                                                          (cnt<<2)+1)) {
                          memcpy(newsym->name,hunks,cnt<<2);
                          *(newsym->name+(cnt<<2)) = '\0';
                          newsym->addr = s->addr + *(hunks+cnt);
                          newsym->next = NULL;

                          if (lastsym)
                            lastsym->next = newsym;
                          else
                            s->symbols = newsym;
                          lastsym = newsym;
                          nSyms++;
                        }
                      }
                      hunks += cnt+1;
                    }
                    break;

                  default:
                    /* hunk type not supported - bail out! */
                    fclose(f);
                    return;
                }
              }
            }
          }
        }
      }
    }
    fclose(f);
  }
}


static BOOL setbreakpt(ADDR addr,BOOL tmp)
{
  struct Breakpoint *b,*next,*new;

  if (!taskloaded)
    return (FALSE);
  addr = (ADDR)((ULONG)addr & ~3);
  for (b = (struct Breakpoint *)breakpoints.mlh_Head;
       next = (struct Breakpoint *)b->n.mln_Succ; b = next) {
    if (b->addr == addr) {
      b->temporary = tmp;
      return (TRUE);
    }
    if (b->addr > addr)
      break;
  }
  if (new = AllocPooledPPC(taskpool,sizeof(struct Breakpoint))) {
    new->addr = addr;
    new->contents = *(ULONG *)addr;
    new->temporary = tmp;
    _insertbefore(&new->n,&b->n);
    *(ULONG *)addr = TRAP_INS;
    SetCache(CACHE_DCACHEFLUSH,addr,4);
    SetCache(CACHE_ICACHEINV,0,0);
    return (TRUE);
  }
  return (FALSE);
}


static BOOL clrbreakpt(ADDR addr,BOOL tmp)
{
  struct Breakpoint *b,*next;

  if (!taskloaded)
    return (FALSE);
  addr = (ADDR)((ULONG)addr & ~3);
  for (b = (struct Breakpoint *)breakpoints.mlh_Head;
       next = (struct Breakpoint *)b->n.mln_Succ; b = next) {
    if (b->addr > addr)
      break;
    if (b->addr == addr) {
      if (tmp==TRUE && b->temporary==FALSE)
        continue;
      _remove(&b->n);
      *(ULONG *)addr = b->contents;
      SetCache(CACHE_DCACHEFLUSH,addr,4);
      SetCache(CACHE_ICACHEINV,0,0);
      FreePooledPPC(taskpool,b,sizeof(struct Breakpoint));
      return (TRUE);
    }
  }
  return (FALSE);
}


static void remallbrkpts()
{
  struct Breakpoint *b,*next;

  if (!taskloaded)
    return;
  for (b = (struct Breakpoint *)breakpoints.mlh_Head;
       next = (struct Breakpoint *)b->n.mln_Succ; b = next) {
    _remove(&b->n);
    *(ULONG *)b->addr = b->contents;
    SetCache(CACHE_DCACHEFLUSH,b->addr,4);
    SetCache(CACHE_ICACHEINV,0,0);
    FreePooledPPC(taskpool,b,sizeof(struct Breakpoint));
  }
  clearIABR();
}


BOOL wosdb_setbreakpoint(ADDR addr)
{
  return (setbreakpt(addr,FALSE));
}


BOOL wosdb_tempbreakpoint(ADDR addr)
{
  return (setbreakpt(addr,TRUE));
}


BOOL wosdb_clearbreakpoint(ADDR addr)
{
  return (clrbreakpt(addr,FALSE));
}


struct Breakpoint *wosdb_hidebreakpoint(ADDR addr)
/* If the specified address matches a breakpoint, then hide it by */
/* temporarily restoring the original contents */
{
  struct Breakpoint *b,*next;
  ADDR a1 = (ADDR)((ULONG)addr & ~3);
  ADDR a2 = (ADDR)(((ULONG)addr+3) & ~3);

  if (!taskloaded)
    return (NULL);
  for (b = (struct Breakpoint *)breakpoints.mlh_Head;
       next = (struct Breakpoint *)b->n.mln_Succ; b = next) {
    if (b->addr > a2)
      return (NULL);
    if (b->addr == a1 || b->addr == a2) {
      *(ULONG *)addr = b->contents;
      SetCache(CACHE_DCACHEFLUSH,addr,4);
      SetCache(CACHE_ICACHEINV,0,0);
      return (b);
    }
  }
  return (NULL);
}


void wosdb_showbreakpoint(struct Breakpoint *b)
/* Reactivate a breakpoint, which was hidden by wosdb_hidebreakpoint() */
{
  if (b != NULL && taskloaded) {
    *(ULONG *)b->addr = TRAP_INS;
    SetCache(CACHE_DCACHEFLUSH,b->addr,4);
    SetCache(CACHE_ICACHEINV,0,0);
  }
}


struct LoadFile *wosdb_load(char *name,char *args)
{
  static struct LoadFile loadfile;
  struct LoadFile *lf = NULL;
  BPTR seglist;
  struct TagItem ti[16];

  if (taskpool = CreatePoolPPC(0,0x4000,0x2000)) {
    if (seglist = read_sections(name)) {
      get_debug_info(name);
      loadfile.name = name;
      loadfile.sections = nSecs;
      loadfile.symbols = nSyms;
      loadfile.firstsec = firstSec;
      loadfile.breakpoints = &breakpoints;
      loadfile.specregs = &spr;

      /* patch RunPPC() to set a breakpoint for the first instruction */
      oldRunPPC = SetFunction((struct Library *)PowerPCBase,
                              -30, /* RunPPC() LVO */
                              (APTR)newRunPPC);
      SetCache(CACHE_DCACHEFLUSH,&oldRunPPC,4);

      /* install a global exception handler to capture the new task */
      ti[0].ti_Tag = EXCATTR_CODE;
      ti[0].ti_Data = (ULONG)ExceptionCatch;
      ti[1].ti_Tag = EXCATTR_DATA;
      ti[1].ti_Data = r2();
      ti[2].ti_Tag = EXCATTR_EXCID;
      ti[2].ti_Data = EXCF_PROGRAM;
      ti[3].ti_Tag = EXCATTR_FLAGS;
      ti[3].ti_Data = EXCF_GLOBAL | EXCF_LARGECONTEXT;
      ti[4].ti_Tag = EXCATTR_PRI;
      ti[4].ti_Data = 127;
      ti[5].ti_Tag = TAG_DONE;

      if (xhdlC = SetExcHandler(ti)) {
        /* launch the task, which will immediately run in our breakpoint */

        ti[0].ti_Tag = NP_Seglist;
        ti[0].ti_Data = (ULONG)seglist;
        ti[1].ti_Tag = NP_FreeSeglist;
        ti[1].ti_Data = TRUE;
        ti[2].ti_Tag = NP_Name;
        ti[2].ti_Data = (ULONG)name;
        ti[3].ti_Tag = NP_StackSize;
        ti[3].ti_Data = stacksize;
        ti[4].ti_Tag = NP_Priority;
        ti[4].ti_Data = (ULONG)priority;
        ti[5].ti_Tag = NP_Cli;
        ti[5].ti_Data = TRUE;
        ti[6].ti_Tag = NP_Input;
        ti[6].ti_Data = (ULONG)Input();
        ti[7].ti_Tag = NP_Output;
        ti[7].ti_Data = (ULONG)Output();
        ti[8].ti_Tag = NP_Error;
        ti[8].ti_Data = (ULONG)Output();
        ti[9].ti_Tag = NP_CloseInput;
        ti[9].ti_Data = FALSE;
        ti[10].ti_Tag = NP_CloseOutput;
        ti[10].ti_Data = FALSE;
        ti[11].ti_Tag = NP_CloseError;
        ti[11].ti_Data = FALSE;
        ti[12].ti_Tag = NP_CommandName;
        ti[12].ti_Data = (ULONG)name;
        ti[13].ti_Tag = NP_Arguments;
        ti[13].ti_Data = (ULONG)args;
        ti[14].ti_Tag = TAG_DONE;
        if (CreateNewProc(ti)) {   /* start program and */
          WaitPPC(1L<<sigExcept);  /* wait for breakpoint exception */

          /* undo RunPPC()-patch and remove global handler */
          SetFunction((struct Library *)PowerPCBase,-30,oldRunPPC);
          RemExcHandler(xhdlC);

          /* install local handlers for exception-catch and */
          /* exception-leave */
          ti[0].ti_Tag = EXCATTR_CODE;
          ti[0].ti_Data = (ULONG)ExceptionCatch;
          ti[1].ti_Tag = EXCATTR_DATA;
          ti[1].ti_Data = r2();
          ti[2].ti_Tag = EXCATTR_TASK;
          ti[2].ti_Data = (ULONG)dbTask;
          ti[3].ti_Tag = EXCATTR_EXCID;
          ti[3].ti_Data = EXCF_MCHECK | EXCF_DACCESS | EXCF_IACCESS |
                          EXCF_ALIGN | EXCF_PROGRAM | EXCF_FPUN |
                          EXCF_TRACE | EXCF_IABR;
          ti[4].ti_Tag = EXCATTR_FLAGS;
          ti[4].ti_Data = EXCF_LOCAL | EXCF_LARGECONTEXT;
          ti[5].ti_Tag = EXCATTR_PRI;
          ti[5].ti_Data = 126;
          ti[6].ti_Tag = TAG_DONE;
          if (xhdlC = SetExcHandler(ti)) {
            ti[0].ti_Tag = EXCATTR_CODE;
            ti[0].ti_Data = (ULONG)ExceptionLeave;
            ti[1].ti_Tag = EXCATTR_DATA;
            ti[1].ti_Data = r2();
            ti[2].ti_Tag = EXCATTR_TASK;
            ti[2].ti_Data = (ULONG)dbTask;
            ti[3].ti_Tag = EXCATTR_EXCID;
            ti[3].ti_Data = EXCF_PROGRAM;
            ti[4].ti_Tag = EXCATTR_FLAGS;
            ti[4].ti_Data = EXCF_LOCAL | EXCF_LARGECONTEXT;
            ti[5].ti_Tag = EXCATTR_PRI;
            ti[5].ti_Data = 127;
            ti[6].ti_Tag = TAG_DONE;
            if (xhdlL = SetExcHandler(ti)) {
              taskloaded = TRUE;
              NewListPPC((struct List *)&breakpoints);

              /* Set breakpoint to PPC-task start address. */
              /* WarpOS now calls the task-code by a "blrl", so we can */
              /* find the start address in the LR register. */
              wosdb_tempbreakpoint((ADDR)ec.ec_LR);
              ec.ec_UPC.ec_SRR0 += 4;  /* skip "trap" instruction */

              /* let the task continue to run */
              SignalPPC(dbTask,1L<<sigRun);

              /* wait for exception at start-address */
              WaitPPC(1L<<sigExcept);  /* wait for breakpoint exception */
              clrbreakpt((ADDR)ec.ec_UPC.ec_PC,TRUE);

              /* set our own exit-function to notify the debugger */
              /* on task's termination */
              initialLR = ec.ec_LR;
              ec.ec_LR = (ULONG)ProgramExit;

              lf = &loadfile;
            }
            else
              RemExcHandler(xhdlC);
          }
        }
        else {
          SetFunction((struct Library *)PowerPCBase,-30,oldRunPPC);
          RemExcHandler(xhdlC);
        }
      }
      else
        SetFunction((struct Library *)PowerPCBase,-30,oldRunPPC);

      if (!lf) {
        /* something failed, free all */
        xhdlC = xhdlL = NULL;
        DeletePoolPPC(taskpool);
      }
    }
  }
  return (lf);
}


void wosdb_unload()
{
  if (dbTask) {
    remallbrkpts();
    if (xhdlL) {
      RemExcHandler(xhdlL);
      xhdlL = NULL;
    }
    if (xhdlC) {
      RemExcHandler(xhdlC);
      xhdlC = NULL;
    }
    DeleteTaskPPC(dbTask);
    dbTask = NULL;
  }
  if (taskpool) {
    DeletePoolPPC(taskpool);
    taskpool = NULL;
  }
  taskloaded = FALSE;
}


ULONG wosdb_cont(BOOL strace,BOOL btrace)
/* start debugged task */
{
  struct Breakpoint *bp;
  ULONG sigmsk,eid;

  sTrace = strace;
  bTrace = btrace;

  bp = wosdb_hidebreakpoint((ADDR)ec.ec_UPC.ec_PC);
  SignalPPC(dbTask,1L<<sigRun);  /* run task */
  /* wait for next exception */
  sigmsk = WaitPPC((1L<<sigExcept) | (1L<<sigFinish));
  wosdb_showbreakpoint(bp);
  clearIABR();

  if (sigmsk & (1L<<sigFinish)) {
    /* task has finished... release it */
    wosdb_unload();
    return (EXCF_FINISHED);
  }

  if (ec.ec_ExcID == EXCF_PROGRAM &&
      *(ULONG *)ec.ec_UPC.ec_PC == TRAP_INS) {
    /* we have run into one of our breakpoints */
    clrbreakpt((ADDR)ec.ec_UPC.ec_PC,TRUE);  /* remove temp. breakpt. */
    eid = EXCF_BRKPT;
  }
  else
    eid = ec.ec_ExcID;

  /* check if we accidently entered our own ProgramExit() function */
  if (ec.ec_UPC.ec_SRR0 >= (ULONG)ProgramExit &&
      ec.ec_UPC.ec_SRR0 < (ULONG)ProgramExitEnd) {
    /* finish task */
    sTrace = bTrace = FALSE;
    remallbrkpts();
    SignalPPC(dbTask,1L<<sigRun);
    WaitPPC(1L<<sigFinish);
    wosdb_unload();
    eid = EXCF_FINISHED;
  }
  return (eid);
}


ULONG wosdb_taskresult()
{
  return (result);
}


char *wosdb_exceptinfo()
{
  switch (ec.ec_ExcID) {
    case EXCF_MCHECK:
      sprintf(buffer,"MACHINE CHECK");
      break;
    case EXCF_DACCESS:
      sprintf(buffer,"DATA ACCESS");
      break;
    case EXCF_IACCESS:
      sprintf(buffer,"INSTRUCTION ACCESS");
      break;
    case EXCF_INTERRUPT:
      sprintf(buffer,"EXTERNAL INTERRUPT");
      break;
    case EXCF_ALIGN:
      sprintf(buffer,"ALIGNMENT");
      break;
    case EXCF_PROGRAM:
      sprintf(buffer,"PROGRAM");
      break;
    case EXCF_FPUN:
      sprintf(buffer,"FP UNAVAILABLE");
      break;
    case EXCF_TRACE:
      sprintf(buffer,"TRACE");
      break;
    case EXCF_PERFMON:
      sprintf(buffer,"PERF MON");
      break;
    case EXCF_IABR:
      sprintf(buffer,"BREAKPOINT");
      break;
    default:
      sprintf(buffer,"UNKNOWN");
      break;
  }
  return (buffer);
}


char *wosdb_symbol(ADDR p)
/* returns string with symbol and offset, if a symbol exists */
{
  struct Section *sec = firstSec;
  struct Symbol *sym;
  char *label = NULL;
  ULONG offs = 0xffffffff;

  while (sec) {
    if (p >= sec->addr && p < (sec->addr+sec->len)) {
      sym = sec->symbols;
      while (sym) {
        /* @@@ this would be easier and faster when symbols are sorted */
        if (sym->addr <= p) {
          if ((p - sym->addr) < offs) {
            offs = p - sym->addr;
            label = sym->name;
          }
        }
        sym = sym->next;
      }
      if (label) {
        sprintf(buffer,"%s+%lx",label,offs);
        return (buffer);
      }
      else
        break;
    }
    sec = sec->next;
  }
  return (NULL);
}


char *wosdb_label(ADDR p)
/* returns string with symbol, if current address matches symbol addr. */
{
  struct Section *sec = firstSec;
  struct Symbol *sym;

  while (sec) {
    if (p >= sec->addr && p < (sec->addr+sec->len)) {
      sym = sec->symbols;
      while (sym) {
        if (sym->addr == p)
          return (sym->name);
        sym = sym->next;
      }
      break;
    }
    sec = sec->next;
  }
  return (NULL);
}


void wosdb_opersymbols(struct DisasmPara_PPC *dp,int len)
/* try to replace values in operand by symbols */
{
  ADDR a;
  char *op,*p,*sym;

  /* find beginning of last operand */
  op = p = dp->operands;
  while (*p) {
    if (*p++ == ',')
      op = p;
  }

  switch (dp->type) {

    case PPCINSTR_BRANCH:
      a = (ADDR)dp->iaddr + (long)dp->displacement;
      if (sym = wosdb_label(a))
        sprintf(op,"%s",sym);
      break;

    case PPCINSTR_LDST:
      a = (ADDR)(ec.ec_GPR[dp->sreg] + (long)dp->displacement);
      if (sym = wosdb_label(a))
        sprintf(op,"%s(r%d)",sym,(int)dp->sreg);
      break;
  }
}


int wosdb_validaddr(ADDR addr)
/* 0: invalid address */
/* 1: address belongs to normal RAM or 0x00000004 */
/* 2: address belongs to a section of the debugged program */
{
  int rc = 0;
  struct MemHeader *mh = (struct MemHeader *)SysBase->MemList.lh_Head;
  struct MemHeader *nextmh;

  addr = (ADDR)((ULONG)addr & ~3);
  while (nextmh = (struct MemHeader *)mh->mh_Node.ln_Succ) {
    if (addr >= (ADDR)((ULONG)mh->mh_Lower & ~255) &&
        addr < (ADDR)(((ULONG)mh->mh_Upper+255) & ~255)) {
      rc++;
      break;
    }
    mh = nextmh;
  }
  if (rc==0 && (ULONG)addr==4)  /* ExecBase 0x0004 is legal */
    rc++;

  if (rc!=0 && taskloaded) {
    struct Section *s = firstSec;

    while (s) {
      if (addr >= s->addr && addr < (s->addr+s->len)) {
        rc++;
        break;
      }
      s = s->next;
    }
  }

  return (rc);
}


ULONG wosdb_getexp(char *buf)
{
  void *expool;
  struct enode *root;
  ULONG val = 0;

  if (expool = CreatePoolPPC(0,0x1000,0x1000)) {
    if (root = exptree(expool,buf))
      val = (ULONG)calctree(root);
    DeletePoolPPC(expool);
  }
  return (val);
}


struct SpecRegs *wosdb_getspecregs()
{
  return (&spr);
}


struct EXCContext *wosdb_init()
{
  if ((sigExcept = AllocSignalPPC(-1)) < 0)
    return (NULL);
  if ((sigFinish = AllocSignalPPC(-1)) < 0) {
    FreeSignalPPC(sigExcept);
    return (NULL);
  }
  thisTask = FindTaskPPC(NULL);
  return (&ec);
}


void wosdb_exit()
{
  wosdb_unload();
  FreeSignalPPC(sigFinish);
  FreeSignalPPC(sigExcept);
}
