/* $VER: stdui.c V0.3b (30.01.00)
 *
 * This file is part of the WarpOS debugger 'wosdb'
 * Copyright (c) 1999-2000  Frank Wille
 *
 *
 * v0.3b (30.01.00) phx
 *       Updated to year 2000.
 * v0.3a (06.05.99) phx
 *       '?' is not repeatable.
 *       The program to be debugged may have an argument string.
 * v0.3  (04.05.99) phx
 *       'S' shows all symbols.
 * v0.2  (02.05.99) phx
 *       Disassembly and memory dump display only valid memory regions.
 *       Command 'tb' shows stack frame trace-back.
 * v0.1  (01.05.99) phx
 *       First usable version.
 * v0.0  (17.04.99) phx
 *       File created.
 */

#include <powerpc/powerpc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "debugger.h"


/* status() */
#define ST_EXC 1        /* print name of exception */
#define ST_SPEC 2       /* print all special registers */
#define ST_FPR 4        /* print FPU registers */


static struct EXCContext *dbcontext;
static int taskloaded = 0;


static char *get_option_arg(int,char *[],int *);
static void show_version();
static void show_help();
static void debugger_loop(char *,char *);



main(int argc,char *argv[])
{
  static char argbuf[1024];
  char *p = argbuf;
  int i;
  char *filename = NULL;

  /* parse arguments */
  for (i=1; i<argc; i++) {
    if (filename) {
      char *s = argv[i];

      *p++ = ' ';
      while (*p = *s++)
        p++;
    }
    else {
      if (argv[i][0] == '-') {
        switch (argv[i][1]) {

          case 'h':
          case '?':
            show_help();
            exit(1);

          case 'v':
            show_version();
            exit(1);

          default:
            printf("Unknown option '%c' ignored.\n",argv[i][1]);
            break;
        }
      }
      else
        filename = argv[i];
    }
  }

  *p++ = '\n';
  *p = '\0';
  debugger_loop(filename,argbuf);
  exit(0);
}


static char *get_option_arg(int argc,char *argv[],int *i)
/* get pointer to the string, which either directly follows the option
   character or is stored in the next argument */
{
  if (argv[*i][2])
    return (&argv[*i][2]);
  else {
    if (++*i<argc) {
      if (argv[*i][0]!='-' || isdigit(argv[*i][1]))  /* another option? */
        return (argv[*i]);
      else
        --*i;
    }
  }
  return NULL;
}


static void show_version()
{
  printf(NAME " V%d.%d%c (c)1999-2000 by Frank Wille\n"
         "build date: " __DATE__ ", " __TIME__ "\n\n",
         VERSION,REVISION,PLEVEL?('a'+PLEVEL-1):' ');
}


static void show_help()
{
  show_version();

  printf("\nUsage: " NAME " [-hv?] [file name] [arguments]\n");
}


static void printgpr()
{
  int i;
  char r[8];

  for (i=0; i<32; i++) {
    if (i%8 == 0) {
      sprintf(r,"R%d-R%d:",i,i+7);
      printf("%-8s ",r);
    }
    printf("%08lx%c",dbcontext->ec_GPR[i],(i%8==7)?'\n':' ');
  }
}


static void printfpr()
{
  int i;
  char r[8];

  for (i=0; i<32; i++) {
    if (i%4 == 0) {
      sprintf(r,"F%d-F%d:",i,i+3);
      printf("%-8s    ",r);
    }
    printf("%-16.4e",dbcontext->ec_FPR[i]);
    if (i%4 == 3)
      printf("\n");
  }
}


static void printregs(int fpr)
{
  printf("MSR:   %08lx   XER:   %08lx   CR:    %08lx   FPSCR: %08lx\n"
         "LR:    %08lx   CTR:   %08lx\n\n",
         dbcontext->ec_SRR1,
         dbcontext->ec_XER,
         dbcontext->ec_CR,
         dbcontext->ec_FPSCR,
         dbcontext->ec_LR,
         dbcontext->ec_CTR);
  printgpr();
  if (fpr) {
    printf("\n");
    printfpr();
  }
}


static void printspecregs()
{
  struct SpecRegs *sr = wosdb_getspecregs();

  printf("HID0:  %08lx   HID1:  %08lx   TBL:   %08lx   TBU:   %08lx\n"
         "PVR:   %08lx   DEC:   %08lx   SDR1:  %08lx   EAR:   %08lx\n"
         "SRR0:  %08lx   SRR1:  %08lx   DAR:   %08lx   DSISR: %08lx\n",
         sr->srHID0,sr->srHID1,sr->srTBL,sr->srTBU,
         sr->srPVR,sr->srDEC,sr->srSDR1,sr->srEAR,
         dbcontext->ec_UPC.ec_SRR0,dbcontext->ec_SRR1,
         dbcontext->ec_DAR,dbcontext->ec_DSISR);
}


static void printinstr(ADDR addr)
{
  struct Breakpoint *bp;
  struct DisasmPara_PPC dp;
  char opcode[10];
  char operands[256];
  char *lab;

  if (wosdb_validaddr(addr) != 0) {
    dp.opcode = opcode;
    dp.operands = operands;
    dp.instr = dp.iaddr = (ppc_word *)addr;
    bp = wosdb_hidebreakpoint((ADDR)dp.iaddr);
    PPC_Disassemble(&dp);

    wosdb_opersymbols(&dp,256);
    if (lab = wosdb_label(addr))
      printf("%-26.26s%c%-7s %s\n",lab,bp?'>':' ',opcode,operands);
    else
      printf("%08lx:  %08lx       %c%-7s %s\n",
             (ULONG)addr,*(ULONG *)addr,bp?'>':' ',opcode,operands);
    wosdb_showbreakpoint(bp);
  }

  else
    printf("%08lx:  ********        ---     ---\n",(ULONG)addr);
}


static void printasc(unsigned char *p,int sep,int cnt)
{
  unsigned char c;
  char buf[20];
  int i,v;

  for (i=0; i<sep; buf[i++]=' ');
  for (i=0; i<cnt; i++) {
    if ((i&3)==0)
      v = wosdb_validaddr((ADDR)p);
    if (v != 0) {
      c = *p++;
      buf[sep+i] = (char)((((c)>=0x20 && (c)<=0x7e) ||
                         ((c)>=0xa0 && (c)!=0xad)) ? c : '.');
    }
    else
      buf[sep+i] = '.';
  }
  buf[sep+cnt] = '\0';
  printf("%s\n",buf);
}


static void printbreakpoints(struct MinList *bplist)
{
  struct Breakpoint *b,*next;
  int n=1;
  char *lab;

  for (b = (struct Breakpoint *)(bplist->mlh_Head);
       next = (struct Breakpoint *)b->n.mln_Succ; b = next) {
    printf("#%d: 0x%08lx %c",n++,(ULONG)b->addr,b->temporary?'T':' ');
    if (lab = wosdb_label(b->addr))
      printf(" %s\n",lab);
    else
      printf("\n");
  }
}


static void status(int flags)
{
  if (flags & ST_EXC)
    printf("\n%s\n",wosdb_exceptinfo());
  if (flags & ST_SPEC)
    printspecregs();
  printregs(flags & ST_FPR);
  printf("\n");
  printinstr((ADDR)dbcontext->ec_UPC.ec_PC);
}


static void finished()
{
  printf("Task finished. Return code = 0x%08lx\n",wosdb_taskresult());
  taskloaded = 0;
}


static char *skipeq(char *p)
{
  while (*p != '=' && *p != '\0')
    p++;
  if (*p++ == '\0')
    return (NULL);
  while (isspace((unsigned char)*p))
    p++;
  return (p);
}


static void printhelp()
{
  printf("+\t\tskip to next instruction\n");
  printf("-\t\tskip to previous instruction\n");
  printf("?\t\tshow help\n");
  printf("? <exp>\t\tevaluate expression\n");
  printf("@r<n> = <exp>\tset GP register\n");
  printf("@f<n> = <flt>\tset FPU register\n");
  printf("L \"name\" [args]\tload WOS program for debugging\n");
  printf("U\t\tunload current program\n");
  printf("S\t\tshow all symbols\n");
  printf("b\t\tshow all breakpoints\n");
  printf("b <exp>\t\tset breakpoint\n");
  printf("bt <exp>\tset temporary breakpoint\n");
  printf("bc <exp>\tclear breakpoint\n");
  printf("--press return--");
  getchar();
  printf("d [<exp>]\tdisassemble\n");
  printf("fpr\t\tshow FPU registers\n");
  printf("g\t\trun program\n");
  printf("g <exp>\t\trun program until address reached\n");
  printf("gpr\t\tshow GP registers\n");
  printf("m [<exp>]\tmemory dump\n");
  printf("n\t\tbreak at next instruction\n");
  printf("r\t\tshow all registers and current instruction\n");
  printf("s\t\tstep single instruction\n");
  printf("tb\t\tshow stack frame trace-back\n");
  printf("x\t\texit debugger, unload debug task\n");
}


static void debugger_loop(char *filename,char *start_args)
{
  char cmdbuf[80],lastcmdbuf[80];
  int i,r,exit = 0;
  ULONG xcid,x;
  ADDR addr = 0;  /* current addr for mem-dump or disassembly */
  int nlines = 8; /* number of lines to display for mem-dump/disasm. */
  char *s;
  struct Breakpoint *bp;
  struct LoadFile *lf;

  printf("\n** " NAME " V%d.%d%c\n"
         "** (c)1999-2000 by Frank Wille <frank@phoenix.owl.de>\n",
         VERSION,REVISION,PLEVEL?('a'+PLEVEL-1):' ');

  if (dbcontext = wosdb_init()) {
    if (filename) {
      /* load and stop WOS program */
      if (lf = wosdb_load(filename,start_args)) {
        printf("Loaded %d sections with %d symbols.\n",
               lf->sections,lf->symbols);
        status(ST_EXC|ST_SPEC|ST_FPR);  /* show regs and cur. instruction */
        taskloaded = 1;
      }
      else {
        printf("Can't load \"%s\".\n",filename);
      }
    }

    /* the main loop */
    while (!exit) {
      printf("wosdb>");
      fflush(stdout);
      gets(cmdbuf);
      if (cmdbuf[0] == '\0')
        strcpy(cmdbuf,lastcmdbuf);
      else
        strcpy(lastcmdbuf,cmdbuf);

      switch (cmdbuf[0]) {

        case '+':                              /* PC++ */
          if (!taskloaded)
            break;
          printinstr((ADDR)(dbcontext->ec_UPC.ec_SRR0 += 4));
          break;

        case '-':                              /* PC-- */
          if (!taskloaded)
            break;
          printinstr((ADDR)(dbcontext->ec_UPC.ec_SRR0 -= 4));
          break;

        case '?':                              /* display expression */
          if (cmdbuf[1] == ' ') {
            x = wosdb_getexp(&cmdbuf[2]);
            printf("= 0x%08lx\n= %ld\n",x,(long)x);
          }
          else {                               /* print help */
            printhelp();
            lastcmdbuf[0] = '\0';
          }
          break;

        case '@':                              /* set register */
          if (!taskloaded)
            break;
          if (cmdbuf[1] == 'r' || cmdbuf[1] == 'R') {
            sscanf(&cmdbuf[2],"%i",&r);
            if (s = skipeq(&cmdbuf[2]))
              dbcontext->ec_GPR[r] = (ULONG)wosdb_getexp(s);
          }
          else if (cmdbuf[1] == 'f' || cmdbuf[1] == 'F') {
            sscanf(&cmdbuf[2],"%i",&r);
            if (s = skipeq(&cmdbuf[2])) {
              double df;

              sscanf(s,"%lf",&df);
              dbcontext->ec_FPR[r] = df;
            }
          }
          break;

        case 'L':                              /* load program */
          if (taskloaded) {
            printf("Unloading current task.\n");
            wosdb_unload();
            taskloaded = 0;
          }
          if (cmdbuf[1] == ' ') {
            static char fname[256];
            static char args[1024];
            char *a = args;

            /* get program name and arguments */
            s = &cmdbuf[2];
            i = 0;
            while (*s != '\"' && *s != '\0')
              s++;
            if (*s == '\"') {
              s++;
              while (*s != '\"' && *s != '\0')
                fname[i++] = *s++;
              fname[i] = '\0';
              if (*s == '\"')
                s++;
              while (isspace((unsigned char)*s))
                s++;
              while (*a = *s++)
                a++;
              *a++ = '\n';
              *a = '\0';

              /* load and stop WOS program */
              if (lf = wosdb_load(fname,args)) {
                printf("Loaded %d sections with %d symbols.\n",
                       lf->sections,lf->symbols);
                status(ST_EXC|ST_SPEC|ST_FPR);
                taskloaded = 1;
              }
              else {
                printf("Can't load \"%s\".\n",fname);
                taskloaded = 0;
              }
              break;
            }
          }
          printf("Missing file name.\n");
          break;

        case 'S':                              /* print all symbols */
          if (taskloaded) {
            struct Section *sec = lf->firstsec;
            struct Symbol *sym;
            int scnt=0;

            while (sec) {
              printf("Section %d at 0x%08lx size 0x%08lx:\n",
                     scnt++,(ULONG)sec->addr,sec->len);
              sym = sec->symbols;
              while (sym) {
                printf("\t0x%08lx  %s\n",(ULONG)sym->addr,sym->name);
                sym = sym->next;
              }
              sec = sec->next;
            }
          }
          break;

        case 'U':                              /* unload program */
          if (taskloaded) {
            printf("Unloading current task.\n");
            wosdb_unload();
            taskloaded = 0;
          }
          break;

        case 'b':                              /* breakpoint */
          if (!taskloaded)
            break;
          if (cmdbuf[1] == ' ') {
            /* set normal breakpoint */
            if (!wosdb_setbreakpoint((ADDR)wosdb_getexp(&cmdbuf[2])))
              printf("Can't set breakpoint!\n");
          }
          else if (cmdbuf[1] == 'c' && cmdbuf[2] == ' ') {
            /* clear breakpoint */
            wosdb_clearbreakpoint((ADDR)wosdb_getexp(&cmdbuf[3]));
          }
          else if (cmdbuf[1] == 't' && cmdbuf[2] == ' ') {
            /* set temporary breakpoint */
            if (!wosdb_tempbreakpoint((ADDR)wosdb_getexp(&cmdbuf[3])))
              printf("Can't set temporary breakpoint!\n");
          }
          else {
            /* show all breakpoints */
            printbreakpoints(lf->breakpoints);
          }
          break;

        case 'd':                              /* disassemble */
          if (cmdbuf[1] == ' ') {
            addr = (ADDR)((ULONG)wosdb_getexp(&cmdbuf[2]) & ~3);
            lastcmdbuf[1] = '\0';
          }
          for (i=0; i<nlines; i++,addr+=4) {
            printinstr(addr);
          }
          break;

        case 'f':                             /* show fpr */
          if (!taskloaded)
            break;
          if (cmdbuf[1] == 'p' && cmdbuf[2] == 'r') {
            /* show floating point registers */
            printfpr();
          }
          break;

        case 'g':                              /* go, show gpr */
          if (!taskloaded)
            break;
          if (cmdbuf[1] == 'p' && cmdbuf[2] == 'r') {
            /* show general purpose registers */
            printgpr();
          }
          else if (cmdbuf[1] == ' ') {
            /* run until address reached */
            wosdb_tempbreakpoint((ADDR)wosdb_getexp(&cmdbuf[2]));
            xcid = wosdb_cont(FALSE,FALSE); /* run until breakpoint reached */
            if (xcid==EXCF_FINISHED)
              finished();
            else
              status(xcid==EXCF_BRKPT ? 0 : ST_EXC|ST_SPEC);
          }
          else {
            /* run until end */
            xcid = wosdb_cont(FALSE,FALSE);
            if (xcid==EXCF_FINISHED)
              finished();
            else
              status(xcid==EXCF_BRKPT ? 0 : ST_EXC|ST_SPEC);
          }
          break;

        case 'm':                              /* memory dump */
          if (cmdbuf[1] == ' ') {
            addr = (ADDR)((ULONG)wosdb_getexp(&cmdbuf[2]) & ~3);
            lastcmdbuf[1] = '\0';
          }
          for (i=0; i<(nlines*4); i++,addr+=4) {
            if (s = wosdb_label(addr)) {
              if (i&3) {
                char spc[32];
                int si;

                for (si=0; si<(4-(i&3))*9; spc[si++]=' ');
                spc[si] = '\0';
                printf("%s",spc);
                printasc(addr-(i&3)*4,3,(i&3)*4);
                if ((i = (i+3) & ~3) >= (nlines*4))
                  break;
              }
              printf("%-15.15s ",s);
            }
            else if ((i&3) == 0)
              printf("%08lx:       ",(ULONG)addr);

            if (wosdb_validaddr(addr) != 0) {
              bp = wosdb_hidebreakpoint(addr);
              printf("%08lx ",*(ULONG *)addr);
              wosdb_showbreakpoint(bp);
            }
            else
              printf("******** ");
            if ((i&3) == 3)
              printasc(addr-12,3,16);
          }
          break;

        case 'n':                              /* next */
          if (!taskloaded)
            break;
          wosdb_tempbreakpoint((ADDR)dbcontext->ec_UPC.ec_PC + 4);
          xcid = wosdb_cont(FALSE,FALSE);  /* break at next instruction */
          if (xcid==EXCF_FINISHED)
            finished();
          else
            status(xcid==EXCF_BRKPT ? 0 : ST_EXC|ST_SPEC);
          break;

        case 'r':                              /* show registers */
          if (!taskloaded)
            break;
          status(ST_EXC|ST_SPEC|ST_FPR);  /* show regs and cur. instruction */
          break;

        case 's':                              /* step */
          if (!taskloaded)
            break;
          xcid = wosdb_cont(TRUE,FALSE);  /* step one instruction */
          if (xcid==EXCF_FINISHED)
            finished();
          else
            status(xcid==EXCF_TRACE ? 0 : ST_EXC|ST_SPEC);
          break;

        case 't':                              /* stack frame trace-back */
          if (cmdbuf[1] == 'b') {
            int depth = 0;
            ULONG *tb = (ULONG *)dbcontext->ec_GPR[1];
            ADDR ra;

            if (!taskloaded)
              break;
            while (tb = (ULONG *)*tb) {
              ra = (ADDR)*(tb+2);
              if (wosdb_validaddr(ra) != 0) {
                printf("\n> Level %d: Stack Frame 0x%08lx"
                       "  Return Addr 0x%08lx",
                       ++depth,(ULONG)tb,(ULONG)ra);
                if (s = wosdb_symbol(ra))
                  printf(" %s\n",s);
                else
                  printf("\n");
                printinstr(ra-4);
              }
            }
          }
          break;

        case 'x':                              /* exit */
          exit = 1;  /* exit debugger */
          if (taskloaded)
            wosdb_unload();
          break;
      }
    }

    wosdb_exit();
  }
}
