text: /*
   * Debugger ARM specific functions
   *
   * Copyright 2010-2013 André Hentschel
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
   */
  
  #include "debugger.h"
  
  #if defined(__arm__) && !defined(__ARMEB__)
  
  /*
   * Switch to disassemble Thumb code.
   */
  static BOOL db_disasm_thumb = FALSE;
  
  /*
   * Flag to indicate whether we need to display instruction,
   * or whether we just need to know the address of the next
   * instruction.
   */
  static BOOL db_display = FALSE;
  
  #define ARM_INSN_SIZE    4
  #define THUMB_INSN_SIZE  2
  #define THUMB2_INSN_SIZE 4
  
  #define ROR32(n, r) (((n) >> (r)) | ((n) << (32 - (r))))
  
  #define get_cond(ins)           tbl_cond[(ins >> 28) & 0x0f]
  #define get_nibble(ins, num)    ((ins >> (num * 4)) & 0x0f)
  
  static char const tbl_regs[][4] = {
      "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10",
      "fp", "ip", "sp", "lr", "pc", "cpsr"
  };
  
  static char const tbl_addrmode[][3] = {
      "da", "ia", "db", "ib"
  };
  
  static char const tbl_cond[][3] = {
      "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "", ""
  };
  
  static char const tbl_dataops[][4] = {
      "and", "eor", "sub", "rsb", "add", "adc", "sbc", "rsc", "tst", "teq", "cmp", "cmn", "orr",
      "mov", "bic", "mvn"
  };
  
  static char const tbl_shifts[][4] = {
      "lsl", "lsr", "asr", "ror"
  };
  
  static char const tbl_hiops_t[][4] = {
      "add", "cmp", "mov", "bx"
  };
  
  static char const tbl_aluops_t[][4] = {
      "and", "eor", "lsl", "lsr", "asr", "adc", "sbc", "ror", "tst", "neg", "cmp", "cmn", "orr",
      "mul", "bic", "mvn"
  };
  
  static char const tbl_immops_t[][4] = {
      "mov", "cmp", "add", "sub"
  };
  
  static char const tbl_sregops_t[][5] = {
      "strh", "ldsb", "ldrh", "ldsh"
  };
  
  static char const tbl_miscops_t2[][6] = {
      "rev", "rev16", "rbit", "revsh"
  };
  
  static char const tbl_width_t2[][2] = {
      "b", "h", "", "?"
  };
  
  static char const tbl_special_regs_t2[][12] = {
      "apsr", "iapsr", "eapsr", "xpsr", "rsvd", "ipsr", "epsr", "iepsr", "msp", "psp", "rsvd", "rsvd",
      "rsvd", "rsvd", "rsvd", "rsvd", "primask", "basepri", "basepri_max", "faultmask", "control"
  };
  
  static char const tbl_hints_t2[][6] = {
      "nop", "yield", "wfe", "wfi", "sev"
  };
  
  static UINT db_get_inst(void* addr, int size)
  {
      UINT result = 0;
      char buffer[4];
  
      if (dbg_read_memory(addr, buffer, size))
      {
          switch (size)
          {
          case 4:
              result = *(UINT*)buffer;
              break;
          case 2:
              result = *(WORD*)buffer;
              break;
          }
      }
      return result;
  }
  
  static void db_printsym(unsigned int addr)
  {
      ADDRESS64   a;
  
      a.Mode   = AddrModeFlat;
      a.Offset = addr;
  
      print_address(&a, TRUE);
  }
  
  static UINT arm_disasm_branch(UINT inst, ADDRESS64 *addr)
  {
      short link = (inst >> 24) & 0x01;
      int offset = (inst << 2) & 0x03ffffff;
  
      if (offset & 0x02000000) offset |= 0xfc000000;
      offset += 8;
  
      dbg_printf("\n\tb%s%s\t", link ? "l" : "", get_cond(inst));
      db_printsym(addr->Offset + offset);
      return 0;
  }
  
  static UINT arm_disasm_mul(UINT inst, ADDRESS64 *addr)
  {
      short accu = (inst >> 21) & 0x01;
      short condcodes = (inst >> 20) & 0x01;
  
      if (accu)
          dbg_printf("\n\tmla%s%s\t%s, %s, %s, %s", get_cond(inst), condcodes ? "s" : "",
                     tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)],
                     tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 3)]);
      else
          dbg_printf("\n\tmul%s%s\t%s, %s, %s", get_cond(inst), condcodes ? "s" : "",
                     tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)],
                     tbl_regs[get_nibble(inst, 2)]);
      return 0;
  }
  
  static UINT arm_disasm_longmul(UINT inst, ADDRESS64 *addr)
  {
      short sign = (inst >> 22) & 0x01;
      short accu = (inst >> 21) & 0x01;
      short condcodes = (inst >> 20) & 0x01;
  
      dbg_printf("\n\t%s%s%s%s\t%s, %s, %s, %s", sign ? "s" : "u", accu ? "mlal" : "mull",
                 get_cond(inst), condcodes ? "s" : "",
                 tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)],
                 tbl_regs[get_nibble(inst, 0)], tbl_regs[get_nibble(inst, 2)]);
      return 0;
  }
  
  static UINT arm_disasm_swp(UINT inst, ADDRESS64 *addr)
  {
      short byte = (inst >> 22) & 0x01;
  
      dbg_printf("\n\tswp%s%s\t%s, %s, [%s]", get_cond(inst), byte ? "b" : "",
                 tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 0)],
                 tbl_regs[get_nibble(inst, 4)]);
      return 0;
  }
  
  static UINT arm_disasm_halfwordtrans(UINT inst, ADDRESS64 *addr)
  {
      short halfword  = (inst >> 5)  & 0x01;
      short sign      = (inst >> 6)  & 0x01;
      short load      = (inst >> 20) & 0x01;
      short writeback = (inst >> 21) & 0x01;
      short immediate = (inst >> 22) & 0x01;
      short direction = (inst >> 23) & 0x01;
      short indexing  = (inst >> 24) & 0x01;
      short offset    = ((inst >> 4) & 0xf0) + (inst & 0x0f);
  
      if (!direction) offset *= -1;
  
      dbg_printf("\n\t%s%s%s%s%s", load ? "ldr" : "str", sign ? "s" : "",
                 halfword ? "h" : (sign ? "b" : ""), writeback ? "t" : "", get_cond(inst));
      dbg_printf("\t%s, ", tbl_regs[get_nibble(inst, 3)]);
      if (indexing)
      {
          if (immediate)
              dbg_printf("[%s, #%d]", tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("[%s, %s]", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
      }
      else
      {
          if (immediate)
              dbg_printf("[%s], #%d", tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("[%s], %s", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
      }
      return 0;
  }
  
  static UINT arm_disasm_branchreg(UINT inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\tb%s\t%s", get_cond(inst), tbl_regs[get_nibble(inst, 0)]);
      return 0;
  }
  
  static UINT arm_disasm_branchxchg(UINT inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\tbx%s\t%s", get_cond(inst), tbl_regs[get_nibble(inst, 0)]);
      return 0;
  }
  
  static UINT arm_disasm_mrstrans(UINT inst, ADDRESS64 *addr)
  {
      short src = (inst >> 22) & 0x01;
  
      dbg_printf("\n\tmrs%s\t%s, %s", get_cond(inst), tbl_regs[get_nibble(inst, 3)],
                 src ? "spsr" : "cpsr");
      return 0;
  }
  
  static UINT arm_disasm_msrtrans(UINT inst, ADDRESS64 *addr)
  {
      short immediate = (inst >> 25) & 0x01;
      short dst = (inst >> 22) & 0x01;
      short simple = (inst >> 16) & 0x01;
  
      if (simple || !immediate)
      {
          dbg_printf("\n\tmsr%s\t%s, %s", get_cond(inst), dst ? "spsr" : "cpsr",
                     tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      dbg_printf("\n\tmsr%s\t%s, #%u", get_cond(inst), dst ? "spsr" : "cpsr",
                 ROR32(inst & 0xff, 2 * get_nibble(inst, 2)));
      return 0;
  }
  
  static UINT arm_disasm_wordmov(UINT inst, ADDRESS64 *addr)
  {
      short top = (inst >> 22) & 0x01;
  
      dbg_printf("\n\tmov%s%s\t%s, #%u", top ? "t" : "w", get_cond(inst),
                 tbl_regs[get_nibble(inst, 3)], (get_nibble(inst, 4) << 12) | (inst & 0x0fff));
      return 0;
  }
  
  static UINT arm_disasm_nop(UINT inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\tnop%s", get_cond(inst));
      return 0;
  }
  
  static UINT arm_disasm_dataprocessing(UINT inst, ADDRESS64 *addr)
  {
      short condcodes = (inst >> 20) & 0x01;
      short opcode    = (inst >> 21) & 0x0f;
      short immediate = (inst >> 25) & 0x01;
      short no_op1    = (opcode & 0x0d) == 0x0d;
      short no_dst    = (opcode & 0x0c) == 0x08;
  
      dbg_printf("\n\t%s%s%s", tbl_dataops[opcode], condcodes ? "s" : "", get_cond(inst));
      if (!no_dst) dbg_printf("\t%s, ", tbl_regs[get_nibble(inst, 3)]);
      else dbg_printf("\t");
  
      if (no_op1)
      {
          if (immediate)
              dbg_printf("#%u", ROR32(inst & 0xff, 2 * get_nibble(inst, 2)));
          else
              dbg_printf("%s", tbl_regs[get_nibble(inst, 0)]);
      }
      else
      {
          if (immediate)
              dbg_printf("%s, #%u", tbl_regs[get_nibble(inst, 4)],
                         ROR32(inst & 0xff, 2 * get_nibble(inst, 2)));
          else if (((inst >> 4) & 0xff) == 0x00) /* no shift */
              dbg_printf("%s, %s", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          else if (((inst >> 4) & 0x09) == 0x01) /* register shift */
              dbg_printf("%s, %s, %s %s", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)],
                         tbl_shifts[(inst >> 5) & 0x03], tbl_regs[(inst >> 8) & 0x0f]);
          else if (((inst >> 4) & 0x01) == 0x00) /* immediate shift */
              dbg_printf("%s, %s, %s #%d", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)],
                         tbl_shifts[(inst >> 5) & 0x03], (inst >> 7) & 0x1f);
          else
              return inst;
      }
      return 0;
  }
  
  static UINT arm_disasm_singletrans(UINT inst, ADDRESS64 *addr)
  {
      short load      = (inst >> 20) & 0x01;
      short writeback = (inst >> 21) & 0x01;
      short byte      = (inst >> 22) & 0x01;
      short direction = (inst >> 23) & 0x01;
      short indexing  = (inst >> 24) & 0x01;
      short immediate = !((inst >> 25) & 0x01);
      short offset    = inst & 0x0fff;
  
      if (!direction) offset *= -1;
  
      dbg_printf("\n\t%s%s%s%s", load ? "ldr" : "str", byte ? "b" : "", writeback ? "t" : "",
                 get_cond(inst));
      dbg_printf("\t%s, ", tbl_regs[get_nibble(inst, 3)]);
      if (indexing)
      {
          if (immediate)
              dbg_printf("[%s, #%d]", tbl_regs[get_nibble(inst, 4)], offset);
          else if (((inst >> 4) & 0xff) == 0x00) /* no shift */
              dbg_printf("[%s, %s]", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          else if (((inst >> 4) & 0x01) == 0x00) /* immediate shift (there's no register shift) */
              dbg_printf("[%s, %s, %s #%d]", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)],
                         tbl_shifts[(inst >> 5) & 0x03], (inst >> 7) & 0x1f);
          else
              return inst;
      }
      else
      {
          if (immediate)
              dbg_printf("[%s], #%d", tbl_regs[get_nibble(inst, 4)], offset);
          else if (((inst >> 4) & 0xff) == 0x00) /* no shift */
              dbg_printf("[%s], %s", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          else if (((inst >> 4) & 0x01) == 0x00) /* immediate shift (there's no register shift) */
              dbg_printf("[%s], %s, %s #%d", tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)],
                         tbl_shifts[(inst >> 5) & 0x03], (inst >> 7) & 0x1f);
          else
              return inst;
      }
      return 0;
  }
  
  static UINT arm_disasm_blocktrans(UINT inst, ADDRESS64 *addr)
  {
      short load      = (inst >> 20) & 0x01;
      short writeback = (inst >> 21) & 0x01;
      short psr       = (inst >> 22) & 0x01;
      short addrmode  = (inst >> 23) & 0x03;
      short i;
      short last=15;
      for (i=15;i>=0;i--)
          if ((inst>>i) & 1)
          {
              last = i;
              break;
          }
  
      dbg_printf("\n\t%s%s%s\t%s%s, {", load ? "ldm" : "stm", tbl_addrmode[addrmode], get_cond(inst),
                 tbl_regs[get_nibble(inst, 4)], writeback ? "!" : "");
      for (i=0;i<=15;i++)
          if ((inst>>i) & 1)
          {
              if (i == last) dbg_printf("%s", tbl_regs[i]);
              else dbg_printf("%s, ", tbl_regs[i]);
          }
      dbg_printf("}%s", psr ? "^" : "");
      return 0;
  }
  
  static UINT arm_disasm_swi(UINT inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\tswi%s\t#%d", get_cond(inst), inst & 0x00ffffff);
      return 0;
  }
  
  static UINT arm_disasm_coproctrans(UINT inst, ADDRESS64 *addr)
  {
      WORD CRm    = inst & 0x0f;
      WORD CP     = (inst >> 5)  & 0x07;
      WORD CPnum  = (inst >> 8)  & 0x0f;
      WORD CRn    = (inst >> 16) & 0x0f;
      WORD load   = (inst >> 20) & 0x01;
      WORD CP_Opc = (inst >> 21) & 0x07;
  
      dbg_printf("\n\t%s%s\t%u, %u, %s, cr%u, cr%u, {%u}", load ? "mrc" : "mcr", get_cond(inst), CPnum,
                 CP, tbl_regs[get_nibble(inst, 3)], CRn, CRm, CP_Opc);
      return 0;
  }
  
  static UINT arm_disasm_coprocdataop(UINT inst, ADDRESS64 *addr)
  {
      WORD CRm    = inst & 0x0f;
      WORD CP     = (inst >> 5)  & 0x07;
      WORD CPnum  = (inst >> 8)  & 0x0f;
      WORD CRd    = (inst >> 12) & 0x0f;
      WORD CRn    = (inst >> 16) & 0x0f;
      WORD CP_Opc = (inst >> 20) & 0x0f;
  
      dbg_printf("\n\tcdp%s\t%u, %u, cr%u, cr%u, cr%u, {%u}", get_cond(inst),
                 CPnum, CP, CRd, CRn, CRm, CP_Opc);
      return 0;
  }
  
  static UINT arm_disasm_coprocdatatrans(UINT inst, ADDRESS64 *addr)
  {
      WORD CPnum  = (inst >> 8)  & 0x0f;
      WORD CRd    = (inst >> 12) & 0x0f;
      WORD load      = (inst >> 20) & 0x01;
      WORD writeback = (inst >> 21) & 0x01;
      WORD translen  = (inst >> 22) & 0x01;
      WORD direction = (inst >> 23) & 0x01;
      WORD indexing  = (inst >> 24) & 0x01;
      short offset    = (inst & 0xff) << 2;
  
      if (!direction) offset *= -1;
  
      dbg_printf("\n\t%s%s%s", load ? "ldc" : "stc", translen ? "l" : "", get_cond(inst));
      if (indexing)
          dbg_printf("\t%u, cr%u, [%s, #%d]%s", CPnum, CRd, tbl_regs[get_nibble(inst, 4)], offset, writeback?"!":"");
      else
          dbg_printf("\t%u, cr%u, [%s], #%d", CPnum, CRd, tbl_regs[get_nibble(inst, 4)], offset);
      return 0;
  }
  
  static WORD thumb_disasm_hireg(WORD inst, ADDRESS64 *addr)
  {
      short dst = inst & 0x07;
      short src = (inst >> 3) & 0x07;
      short h2  = (inst >> 6) & 0x01;
      short h1  = (inst >> 7) & 0x01;
      short op  = (inst >> 8) & 0x03;
  
      if (h1) dst += 8;
      if (h2) src += 8;
  
      if (op == 2 && dst == src) /* mov rx, rx */
      {
          dbg_printf("\n\tnop");
          return 0;
      }
  
      if (op == 3)
          dbg_printf("\n\tb%sx\t%s", h1?"l":"", tbl_regs[src]);
      else
          dbg_printf("\n\t%s\t%s, %s", tbl_hiops_t[op], tbl_regs[dst], tbl_regs[src]);
  
      return 0;
  }
  
  static WORD thumb_disasm_aluop(WORD inst, ADDRESS64 *addr)
  {
      short dst = inst & 0x07;
      short src = (inst >> 3) & 0x07;
      short op  = (inst >> 6) & 0x0f;
  
      dbg_printf("\n\t%s\t%s, %s", tbl_aluops_t[op], tbl_regs[dst], tbl_regs[src]);
  
      return 0;
  }
  
  static WORD thumb_disasm_pushpop(WORD inst, ADDRESS64 *addr)
  {
      short lrpc = (inst >> 8)  & 0x01;
      short load = (inst >> 11) & 0x01;
      short i;
      short last;
  
      for (i=7;i>=0;i--)
          if ((inst>>i) & 1) break;
      last = i;
  
      dbg_printf("\n\t%s\t{", load ? "pop" : "push");
  
      for (i=0;i<=7;i++)
          if ((inst>>i) & 1)
          {
              if (i == last) dbg_printf("%s", tbl_regs[i]);
              else dbg_printf("%s, ", tbl_regs[i]);
          }
      if (lrpc)
          dbg_printf("%s%s", last ? ", " : "", load ? "pc" : "lr");
  
      dbg_printf("}");
      return 0;
  }
  
  static WORD thumb_disasm_blocktrans(WORD inst, ADDRESS64 *addr)
  {
      short load = (inst >> 11) & 0x01;
      short i;
      short last;
  
      for (i=7;i>=0;i--)
          if ((inst>>i) & 1) break;
      last = i;
  
      dbg_printf("\n\t%s\t%s!, {", load ? "ldmia" : "stmia", tbl_regs[(inst >> 8) & 0x07]);
  
      for (i=0;i<=7;i++)
          if ((inst>>i) & 1)
          {
              if (i == last) dbg_printf("%s", tbl_regs[i]);
              else dbg_printf("%s, ", tbl_regs[i]);
          }
  
      dbg_printf("}");
      return 0;
  }
  
  static WORD thumb_disasm_swi(WORD inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\tswi\t#%d", inst & 0x00ff);
      return 0;
  }
  
  static WORD thumb_disasm_condbranch(WORD inst, ADDRESS64 *addr)
  {
      WORD offset = inst & 0x00ff;
      dbg_printf("\n\tb%s\t", tbl_cond[(inst >> 8) & 0x0f]);
      db_printsym(addr->Offset + offset);
      return 0;
  }
  
  static WORD thumb_disasm_uncondbranch(WORD inst, ADDRESS64 *addr)
  {
      short offset = (inst & 0x07ff) << 1;
  
      if (offset & 0x0800) offset |= 0xf000;
      offset += 4;
  
      dbg_printf("\n\tb\t");
      db_printsym(addr->Offset + offset);
      return 0;
  }
  
  static WORD thumb_disasm_loadadr(WORD inst, ADDRESS64 *addr)
  {
      WORD src = (inst >> 11) & 0x01;
      WORD offset = (inst & 0xff) << 2;
  
      dbg_printf("\n\tadd\t%s, %s, #%d", tbl_regs[(inst >> 8) & 0x07], src ? "sp" : "pc", offset);
      return 0;
  }
  
  static WORD thumb_disasm_ldrpcrel(WORD inst, ADDRESS64 *addr)
  {
      WORD offset = (inst & 0xff) << 2;
      dbg_printf("\n\tldr\t%s, [pc, #%u]", tbl_regs[(inst >> 8) & 0x07], offset);
      return 0;
  }
  
  static WORD thumb_disasm_ldrsprel(WORD inst, ADDRESS64 *addr)
  {
      WORD offset = (inst & 0xff) << 2;
      dbg_printf("\n\t%s\t%s, [sp, #%u]", (inst & 0x0800)?"ldr":"str", tbl_regs[(inst >> 8) & 0x07], offset);
      return 0;
  }
  
  static WORD thumb_disasm_addsprel(WORD inst, ADDRESS64 *addr)
  {
      WORD offset = (inst & 0x7f) << 2;
      if ((inst >> 7) & 0x01)
          dbg_printf("\n\tsub\tsp, sp, #%u", offset);
      else
          dbg_printf("\n\tadd\tsp, sp, #%u", offset);
      return 0;
  }
  
  static WORD thumb_disasm_ldrimm(WORD inst, ADDRESS64 *addr)
  {
      WORD offset = (inst & 0x07c0) >> 6;
      dbg_printf("\n\t%s%s\t%s, [%s, #%u]", (inst & 0x0800)?"ldr":"str", (inst & 0x1000)?"b":"",
                 tbl_regs[inst & 0x07], tbl_regs[(inst >> 3) & 0x07], (inst & 0x1000)?offset:(offset << 2));
      return 0;
  }
  
  static WORD thumb_disasm_ldrhimm(WORD inst, ADDRESS64 *addr)
  {
      WORD offset = (inst & 0x07c0) >> 5;
      dbg_printf("\n\t%s\t%s, [%s, #%u]", (inst & 0x0800)?"ldrh":"strh",
                 tbl_regs[inst & 0x07], tbl_regs[(inst >> 3) & 0x07], offset);
      return 0;
  }
  
  static WORD thumb_disasm_ldrreg(WORD inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\t%s%s\t%s, [%s, %s]", (inst & 0x0800)?"ldr":"str", (inst & 0x0400)?"b":"",
                 tbl_regs[inst & 0x07], tbl_regs[(inst >> 3) & 0x07], tbl_regs[(inst >> 6) & 0x07]);
      return 0;
  }
  
  static WORD thumb_disasm_ldrsreg(WORD inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\t%s\t%s, [%s, %s]", tbl_sregops_t[(inst >> 10) & 0x03],
                 tbl_regs[inst & 0x07], tbl_regs[(inst >> 3) & 0x07], tbl_regs[(inst >> 6) & 0x07]);
      return 0;
  }
  
  static WORD thumb_disasm_immop(WORD inst, ADDRESS64 *addr)
  {
      WORD op = (inst >> 11) & 0x03;
      dbg_printf("\n\t%s\t%s, #%u", tbl_immops_t[op], tbl_regs[(inst >> 8) & 0x07], inst & 0xff);
      return 0;
  }
  
  static WORD thumb_disasm_nop(WORD inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\tnop");
      return 0;
  }
  
  static WORD thumb_disasm_addsub(WORD inst, ADDRESS64 *addr)
  {
      WORD op = (inst >> 9) & 0x01;
      WORD immediate = (inst >> 10) & 0x01;
  
      dbg_printf("\n\t%s\t%s, %s, ", op ? "sub" : "add",
                 tbl_regs[inst & 0x07], tbl_regs[(inst >> 3) & 0x07]);
      if (immediate)
          dbg_printf("#%d", (inst >> 6) & 0x07);
      else
          dbg_printf("%s", tbl_regs[(inst >> 6) & 0x07]);
      return 0;
  }
  
  static WORD thumb_disasm_movshift(WORD inst, ADDRESS64 *addr)
  {
      WORD op = (inst >> 11) & 0x03;
      dbg_printf("\n\t%s\t%s, %s, #%u", tbl_shifts[op],
                 tbl_regs[inst & 0x07], tbl_regs[(inst >> 3) & 0x07], (inst >> 6) & 0x1f);
      return 0;
  }
  
  static UINT thumb2_disasm_srtrans(UINT inst, ADDRESS64 *addr)
  {
      UINT fromsr = (inst >> 21) & 0x03;
      UINT sysreg = inst & 0xff;
  
      if (fromsr == 3 && get_nibble(inst,4) == 0x0f && sysreg <= 20)
      {
          dbg_printf("\n\tmrs\t%s, %s", tbl_regs[get_nibble(inst, 2)], tbl_special_regs_t2[sysreg]);
          return 0;
      }
  
      if (fromsr == 0 && sysreg <= 20)
      {
          dbg_printf("\n\tmsr\t%s, %s", tbl_special_regs_t2[sysreg], tbl_regs[get_nibble(inst, 4)]);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_hint(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 8) & 0x07;
      WORD op2 = inst & 0xff;
  
      if (op1) return inst;
  
      if (op2 <= 4)
      {
          dbg_printf("\n\t%s", tbl_hints_t2[op2]);
          return 0;
      }
  
      if (op2 & 0xf0)
      {
          dbg_printf("\n\tdbg\t#%u", get_nibble(inst, 0));
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_miscctrl(UINT inst, ADDRESS64 *addr)
  {
      WORD op = (inst >> 4) & 0x0f;
  
      switch (op)
      {
      case 2:
          dbg_printf("\n\tclrex");
          break;
      case 4:
          dbg_printf("\n\tdsb\t#%u", get_nibble(inst, 0));
          break;
      case 5:
          dbg_printf("\n\tdmb\t#%u", get_nibble(inst, 0));
          break;
      case 6:
          dbg_printf("\n\tisb\t#%u", get_nibble(inst, 0));
          break;
      default:
          return inst;
      }
  
      return 0;
  }
  
  static UINT thumb2_disasm_branch(UINT inst, ADDRESS64 *addr)
  {
      UINT S  = (inst >> 26) & 0x01;
      UINT L  = (inst >> 14) & 0x01;
      UINT I1 = !(((inst >> 13) & 0x01) ^ S);
      UINT C  = !((inst >> 12) & 0x01);
      UINT I2 = !(((inst >> 11) & 0x01) ^ S);
      UINT offset = (inst & 0x000007ff) << 1;
  
      if (C)
      {
          offset |= I1 << 19 | I2 << 18 | (inst & 0x003f0000) >> 4;
          if (S) offset |= 0x0fff << 20;
      }
      else
      {
          offset |= I1 << 23 | I2 << 22 | (inst & 0x03ff0000) >> 4;
          if (S) offset |= 0xff << 24;
      }
  
      dbg_printf("\n\tb%s%s\t", L ? "l" : "", C ? tbl_cond[(inst >> 22) & 0x0f] : "");
      db_printsym(addr->Offset + offset + 4);
      return 0;
  }
  
  static UINT thumb2_disasm_misc(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 20) & 0x03;
      WORD op2 = (inst >> 4) & 0x03;
  
      if (get_nibble(inst, 4) != get_nibble(inst, 0))
          return inst;
  
      if (op1 == 3 && op2 == 0)
      {
          dbg_printf("\n\tclz\t%s, %s", tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      if (op1 == 1)
      {
          dbg_printf("\n\t%s\t%s, %s", tbl_miscops_t2[op2], tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_dataprocessingreg(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 20) & 0x07;
      WORD op2 = (inst >> 4) & 0x0f;
  
      if (!op2)
      {
          dbg_printf("\n\t%s%s\t%s, %s, %s", tbl_shifts[op1 >> 1], (op1 & 1)?"s":"",
                     tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 4)],
                     tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      if ((op2 & 0x0C) == 0x08 && get_nibble(inst, 4) == 0x0f)
      {
          dbg_printf("\n\t%sxt%s\t%s, %s", (op1 & 1)?"u":"s", (op1 & 4)?"b":"h",
                     tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 0)]);
          if (op2 & 0x03)
              dbg_printf(", ROR #%u", (op2 & 3) * 8);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_mul(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 20) & 0x07;
      WORD op2 = (inst >> 4) & 0x03;
  
      if (op1)
          return inst;
  
      if (op2 == 0 && get_nibble(inst, 3) != 0xf)
      {
          dbg_printf("\n\tmla\t%s, %s, %s, %s", tbl_regs[get_nibble(inst, 2)],
                                                  tbl_regs[get_nibble(inst, 4)],
                                                  tbl_regs[get_nibble(inst, 0)],
                                                  tbl_regs[get_nibble(inst, 3)]);
          return 0;
      }
  
      if (op2 == 0 && get_nibble(inst, 3) == 0xf)
      {
          dbg_printf("\n\tmul\t%s, %s, %s", tbl_regs[get_nibble(inst, 2)],
                                              tbl_regs[get_nibble(inst, 4)],
                                              tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      if (op2 == 1)
      {
          dbg_printf("\n\tmls\t%s, %s, %s, %s", tbl_regs[get_nibble(inst, 2)],
                                                  tbl_regs[get_nibble(inst, 4)],
                                                  tbl_regs[get_nibble(inst, 0)],
                                                  tbl_regs[get_nibble(inst, 3)]);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_longmuldiv(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 20) & 0x07;
      WORD op2 = (inst >> 4) & 0x0f;
  
      if (op2 == 0)
      {
          switch (op1)
          {
          case 0:
              dbg_printf("\n\tsmull\t");
              break;
          case 2:
              dbg_printf("\n\tumull\t");
              break;
          case 4:
              dbg_printf("\n\tsmlal\t");
              break;
          case 6:
              dbg_printf("\n\tumlal\t");
              break;
          default:
              return inst;
          }
          dbg_printf("%s, %s, %s, %s", tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 2)],
                                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      if (op2 == 0xffff)
      {
          switch (op1)
          {
          case 1:
              dbg_printf("\n\tsdiv\t");
              break;
          case 3:
              dbg_printf("\n\tudiv\t");
              break;
          default:
              return inst;
          }
          dbg_printf("%s, %s, %s", tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 4)],
                                     tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_str(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 21) & 0x07;
      WORD op2 = (inst >> 6) & 0x3f;
  
      if ((op1 & 0x03) == 3) return inst;
  
      if (!(op1 & 0x04) && inst & 0x0800)
      {
          int offset;
          dbg_printf("\n\tstr%s\t%s, [%s", tbl_width_t2[op1 & 0x03], tbl_regs[get_nibble(inst, 3)],
                     tbl_regs[get_nibble(inst, 4)]);
  
          offset = inst & 0xff;
          if (!(inst & 0x0200)) offset *= -1;
  
          if (!(inst & 0x0400) && (inst & 0x0100)) dbg_printf("], #%i", offset);
          else if (inst & 0x0400) dbg_printf(", #%i]%s", offset, (inst & 0x0100)?"!":"");
          else return inst;
          return 0;
      }
  
      if (!(op1 & 0x04) && !op2)
      {
          dbg_printf("\n\tstr%s\t%s, [%s, %s, LSL #%u]", tbl_width_t2[op1 & 0x03],
                     tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)],
                     tbl_regs[get_nibble(inst, 0)], (inst >> 4) & 0x3);
          return 0;
      }
  
      if (op1 & 0x04)
      {
          dbg_printf("\n\tstr%s\t%s, [%s, #%u]", tbl_width_t2[op1 & 0x03],
                     tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)], inst & 0x0fff);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_ldrword(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 23) & 0x01;
      WORD op2 = (inst >> 6) & 0x3f;
      int offset;
  
      if (get_nibble(inst, 4) == 0x0f)
      {
          offset = inst & 0x0fff;
  
          if (!op1) offset *= -1;
          offset += 3;
  
          dbg_printf("\n\tldr\t%s, ", tbl_regs[get_nibble(inst, 3)]);
          db_printsym(addr->Offset + offset);
          return 0;
      }
  
      if (!op1 && !op2)
      {
          dbg_printf("\n\tldr\t%s, [%s, %s, LSL #%u]", tbl_regs[get_nibble(inst, 3)],
                     tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)], (inst >> 4) & 0x3);
          return 0;
      }
  
      if (!op1 && (op2 & 0x3c) == 0x38)
      {
          dbg_printf("\n\tldrt\t%s, [%s, #%u]", tbl_regs[get_nibble(inst, 3)],
                     tbl_regs[get_nibble(inst, 4)], inst & 0xff);
          return 0;
      }
  
      dbg_printf("\n\tldr\t%s, [%s", tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)]);
  
      if (op1)
      {
          dbg_printf(", #%u]", inst & 0x0fff);
          return 0;
      }
  
      offset = inst & 0xff;
      if (!(inst & 0x0200)) offset *= -1;
  
      if (!(inst & 0x0400) && (inst & 0x0100)) dbg_printf("], #%i", offset);
      else if (inst & 0x0400) dbg_printf(", #%i]%s", offset, (inst & 0x0100)?"!":"");
      else return inst;
  
      return 0;
  }
  
  static UINT thumb2_disasm_preload(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 23) & 0x03;
  
      if (!(op1 & 0x01) && !((inst >> 6) & 0x3f) && get_nibble(inst, 4) != 15)
      {
          WORD shift = (inst >> 4) & 0x03;
          dbg_printf("\n\t%s\t[%s, %s", op1?"pli":"pld", tbl_regs[get_nibble(inst, 4)],
                     tbl_regs[get_nibble(inst, 0)]);
          if (shift) dbg_printf(", lsl #%u]", shift);
          else dbg_printf("]");
          return 0;
      }
  
      if (get_nibble(inst, 4) != 15)
      {
          dbg_printf("\n\t%s\t[%s, #%d]", (op1 & 0x02)?"pli":"pld", tbl_regs[get_nibble(inst, 4)],
                     (op1 & 0x01)?(inst & 0x0fff):(-1 * (inst & 0xff)));
          return 0;
      }
  
      if (get_nibble(inst, 4) == 15)
      {
          int offset = inst & 0x0fff;
          if (!op1) offset *= -1;
          dbg_printf("\n\t%s\t", (op1 & 0x02)?"pli":"pld");
          db_printsym(addr->Offset + offset + 4);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_ldrnonword(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 23) & 0x03;
      WORD hw  = (inst >> 21) & 0x01;
  
      if (!(op1 & 0x01) && !((inst >> 6) & 0x3f) && get_nibble(inst, 4) != 15)
      {
          WORD shift = (inst >> 4) & 0x03;
          dbg_printf("\n\t%s%s\t%s, [%s, %s", op1?"ldrs":"ldr", hw?"h":"b",
                     tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)],
                     tbl_regs[get_nibble(inst, 0)]);
          if (shift) dbg_printf(", lsl #%u]", shift);
          else dbg_printf("]");
          return 0;
      }
  
      if (!(op1 & 0x01) && ((inst >> 8) & 0x0f) == 14 && get_nibble(inst, 4) != 15)
      {
          WORD offset = inst & 0xff;
          dbg_printf("\n\t%s%s\t%s, [%s", op1?"ldrs":"ldr", hw?"ht":"bt",
                     tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)]);
          if (offset) dbg_printf(", #%u]", offset);
          else dbg_printf("]");
          return 0;
      }
  
      if (get_nibble(inst, 4) != 15)
      {
          int offset;
  
          dbg_printf("\n\t%s%s\t%s, [%s", (op1 & 0x02)?"ldrs":"ldr", hw?"h":"b",
                     tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)]);
  
          if (op1 & 0x01)
          {
              dbg_printf(", #%u]", inst & 0x0fff);
              return 0;
          }
  
          offset = inst & 0xff;
          if (!(inst & 0x0200)) offset *= -1;
  
          if (!(inst & 0x0400) && (inst & 0x0100)) dbg_printf("], #%i", offset);
          else if (inst & 0x0400) dbg_printf(", #%i]%s", offset, (inst & 0x0100)?"!":"");
          else return inst;
  
          return 0;
      }
  
      if (get_nibble(inst, 4) == 15)
      {
          int offset = inst & 0x0fff;
          if (!op1) offset *= -1;
          dbg_printf("\n\t%s%s\t%s, ", (op1 & 0x02)?"ldrs":"ldr", hw?"h":"b",
                     tbl_regs[get_nibble(inst, 3)]);
          db_printsym(addr->Offset + offset + 4);
          return 0;
      }
  
      return inst;
  }
  
  static UINT thumb2_disasm_dataprocessing(UINT inst, ADDRESS64 *addr)
  {
      WORD op = (inst >> 20) & 0x1f;
      WORD imm5 = ((inst >> 10) & 0x1c) + ((inst >> 6) & 0x03);
  
      switch (op)
      {
      case 0:
      {
          WORD offset = ((inst >> 15) & 0x0800) + ((inst >> 4) & 0x0700) + (inst & 0xff);
          if (get_nibble(inst, 4) == 15)
          {
              dbg_printf("\n\tadr\t%s, ", tbl_regs[get_nibble(inst, 2)]);
              db_printsym(addr->Offset + offset + 4);
          }
          else
              dbg_printf("\n\taddw\t%s, %s, #%u", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      }
      case 4:
      case 12:
      {
          WORD offset = ((inst >> 15) & 0x0800) + ((inst >> 4) & 0xf000) +
                        ((inst >>  4) & 0x0700) + (inst & 0xff);
          dbg_printf("\n\t%s\t%s, #%u", op == 12 ? "movt" : "movw", tbl_regs[get_nibble(inst, 2)],
                     offset);
          return 0;
      }
      case 10:
      {
          int offset = ((inst >> 15) & 0x0800) + ((inst >> 4) & 0x0700) + (inst & 0xff);
          if (get_nibble(inst, 4) == 15)
          {
              offset *= -1;
              dbg_printf("\n\tadr\t%s, ", tbl_regs[get_nibble(inst, 2)]);
              db_printsym(addr->Offset + offset + 4);
          }
          else
              dbg_printf("\n\tsubw\t%s, %s, #%u", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      }
      case 16:
      case 18:
      case 24:
      case 26:
      {
          BOOL sign = op < 24;
          WORD sh = (inst >> 21) & 0x01;
          WORD sat = (inst & 0x1f);
          if (sign) sat++;
          if (imm5)
              dbg_printf("\n\t%s\t%s, #%u, %s, %s #%u", sign ? "ssat" : "usat",
                         tbl_regs[get_nibble(inst, 2)], sat, tbl_regs[get_nibble(inst, 4)],
                         sh ? "asr" : "lsl", imm5);
          else
              dbg_printf("\n\t%s\t%s, #%u, %s", sign ? "ssat" : "usat", tbl_regs[get_nibble(inst, 2)],
                         sat, tbl_regs[get_nibble(inst, 4)]);
          return 0;
      }
      case 20:
      case 28:
      {
          WORD width = (inst & 0x1f) + 1;
          dbg_printf("\n\t%s\t%s, %s, #%u, #%u", op == 28 ? "ubfx" : "sbfx",
                     tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 4)], imm5, width);
          return 0;
      }
      case 22:
      {
          WORD msb = (inst & 0x1f) + 1 - imm5;
          if (get_nibble(inst, 4) == 15)
              dbg_printf("\n\tbfc\t%s, #%u, #%u", tbl_regs[get_nibble(inst, 2)], imm5, msb);
          else
              dbg_printf("\n\tbfi\t%s, %s, #%u, #%u", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], imm5, msb);
          return 0;
      }
      default:
          return inst;
      }
  }
  
  static UINT thumb2_disasm_dataprocessingmod(UINT inst, ADDRESS64 *addr)
  {
      WORD op = (inst >> 21) & 0x0f;
      WORD sf = (inst >> 20) & 0x01;
      WORD offset = ((inst >> 15) & 0x0800) + ((inst >> 4) & 0x0700) + (inst & 0xff);
  
      /* FIXME: use ThumbExpandImm_C */
  
      switch (op)
      {
      case 0:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\ttst\t%s, #%u", tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("\n\tand%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 1:
          dbg_printf("\n\tbic%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 2:
          if (get_nibble(inst, 4) == 15)
              dbg_printf("\n\tmov%s\t%s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)], offset);
          else
              dbg_printf("\n\torr%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 3:
          if (get_nibble(inst, 4) == 15)
              dbg_printf("\n\tmvn%s\t%s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)], offset);
          else
              dbg_printf("\n\torn%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 4:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\tteq\t%s, #%u", tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("\n\teor%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 8:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\tcmn\t%s, #%u", tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("\n\tadd%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 10:
          dbg_printf("\n\tadc%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 11:
          dbg_printf("\n\tsbc%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 13:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\tcmp\t%s, #%u", tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("\n\tsub%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      case 14:
          dbg_printf("\n\trsb%s\t%s, %s, #%u", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      default:
          return inst;
      }
  }
  
  static UINT thumb2_disasm_dataprocessingshift(UINT inst, ADDRESS64 *addr)
  {
      WORD op = (inst >> 21) & 0x0f;
      WORD sf = (inst >> 20) & 0x01;
      WORD imm5 = ((inst >> 10) & 0x1c) + ((inst >> 6) & 0x03);
      WORD type = (inst >> 4) & 0x03;
  
      if (!imm5 && (type == 1 || type == 2)) imm5 = 32;
      else if (!imm5 && type == 3) type = 4;
  
      switch (op)
      {
      case 0:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\ttst\t%s, %s", tbl_regs[get_nibble(inst, 4)],
                         tbl_regs[get_nibble(inst, 0)]);
          else
              dbg_printf("\n\tand%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 1:
          dbg_printf("\n\tbic%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 2:
          if (get_nibble(inst, 4) == 15)
          {
              if (type == 4)
                  dbg_printf("\n\trrx%s\t%s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 0)]);
              else if (!type && !imm5)
                  dbg_printf("\n\tmov%s\t%s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 0)]);
              else
                  dbg_printf("\n\t%s%s\t%s, %s, #%u", tbl_shifts[type], sf ? "s" : "", tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 0)], imm5);
              return 0;
          }
          else
              dbg_printf("\n\torr%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 3:
          if (get_nibble(inst, 4) == 15)
              dbg_printf("\n\tmvn%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          else
              dbg_printf("\n\torn%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 4:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\tteq\t%s, %s", tbl_regs[get_nibble(inst, 4)],
                         tbl_regs[get_nibble(inst, 0)]);
          else
              dbg_printf("\n\teor%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 8:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\tcmn\t%s, %s", tbl_regs[get_nibble(inst, 4)],
                         tbl_regs[get_nibble(inst, 0)]);
          else
              dbg_printf("\n\tadd%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 10:
          dbg_printf("\n\tadc%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 11:
          dbg_printf("\n\tsbc%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 13:
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\tcmp\t%s, %s", tbl_regs[get_nibble(inst, 4)],
                         tbl_regs[get_nibble(inst, 0)]);
          else
              dbg_printf("\n\tsub%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      case 14:
          dbg_printf("\n\trsb%s\t%s, %s, %s", sf ? "s" : "", tbl_regs[get_nibble(inst, 2)],
                     tbl_regs[get_nibble(inst, 4)], tbl_regs[get_nibble(inst, 0)]);
          break;
      default:
          return inst;
      }
  
      if (type == 4)
          dbg_printf(", rrx");
      else if (type || imm5)
          dbg_printf(", %s #%u", tbl_shifts[type], imm5);
      return 0;
  }
  
  static UINT thumb2_disasm_coprocdat(UINT inst, ADDRESS64 *addr)
  {
      WORD opc2 = (inst >> 5) & 0x07;
  
      dbg_printf("\n\tcdp%s\tp%u, #%u, cr%u, cr%u, cr%u", (inst & 0x10000000)?"2":"",
                 get_nibble(inst, 2), get_nibble(inst, 5), get_nibble(inst, 3),
                 get_nibble(inst, 4), get_nibble(inst, 0));
  
      if (opc2) dbg_printf(", #%u", opc2);
      return 0;
  }
  
  static UINT thumb2_disasm_coprocmov1(UINT inst, ADDRESS64 *addr)
  {
      WORD opc1 = (inst >> 21) & 0x07;
      WORD opc2 = (inst >> 5) & 0x07;
  
      dbg_printf("\n\t%s%s\tp%u, #%u, %s, cr%u, cr%u", (inst & 0x00100000)?"mrc":"mcr",
                 (inst & 0x10000000)?"2":"", get_nibble(inst, 2), opc1,
                 tbl_regs[get_nibble(inst, 3)], get_nibble(inst, 4), get_nibble(inst, 0));
  
      if (opc2) dbg_printf(", #%u", opc2);
      return 0;
  }
  
  static UINT thumb2_disasm_coprocmov2(UINT inst, ADDRESS64 *addr)
  {
      dbg_printf("\n\t%s%s\tp%u, #%u, %s, %s, cr%u", (inst & 0x00100000)?"mrrc":"mcrr",
                 (inst & 0x10000000)?"2":"", get_nibble(inst, 2), get_nibble(inst, 1),
                 tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)], get_nibble(inst, 0));
  
      return 0;
  }
  
  static UINT thumb2_disasm_coprocdatatrans(UINT inst, ADDRESS64 *addr)
  {
      WORD indexing  = (inst >> 24) & 0x01;
      WORD direction = (inst >> 23) & 0x01;
      WORD translen  = (inst >> 22) & 0x01;
      WORD writeback = (inst >> 21) & 0x01;
      WORD load      = (inst >> 20) & 0x01;
      short offset    = (inst & 0xff) << 2;
  
      if (!direction) offset *= -1;
  
      dbg_printf("\n\t%s%s%s", load ? "ldc" : "stc", (inst & 0x10000000)?"2":"", translen ? "l" : "");
      if (indexing)
      {
          if (load && get_nibble(inst, 4) == 15)
          {
              dbg_printf("\tp%u, cr%u, ", get_nibble(inst, 2), get_nibble(inst, 3));
              db_printsym(addr->Offset + offset + 4);
          }
          else
              dbg_printf("\tp%u, cr%u, [%s, #%d]%s", get_nibble(inst, 2), get_nibble(inst, 3), tbl_regs[get_nibble(inst, 4)], offset, writeback?"!":"");
      }
      else
      {
          if (writeback)
              dbg_printf("\tp%u, cr%u, [%s], #%d", get_nibble(inst, 2), get_nibble(inst, 3), tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("\tp%u, cr%u, [%s], {%u}", get_nibble(inst, 2), get_nibble(inst, 3), tbl_regs[get_nibble(inst, 4)], inst & 0xff);
      }
      return 0;
  }
  
  static UINT thumb2_disasm_ldrstrmul(UINT inst, ADDRESS64 *addr)
  {
      short load      = (inst >> 20) & 0x01;
      short writeback = (inst >> 21) & 0x01;
      short decbefore = (inst >> 24) & 0x01;
      short i;
      short last=15;
      for (i=15;i>=0;i--)
          if ((inst>>i) & 1)
          {
              last = i;
              break;
          }
  
      if (writeback && get_nibble(inst, 4) == 13)
          dbg_printf("\n\t%s\t{", load ? "pop" : "push");
      else
          dbg_printf("\n\t%s%s\t%s%s, {", load ? "ldm" : "stm", decbefore ? "db" : "ia",
                     tbl_regs[get_nibble(inst, 4)], writeback ? "!" : "");
      for (i=0;i<=15;i++)
          if ((inst>>i) & 1)
          {
              if (i == last) dbg_printf("%s", tbl_regs[i]);
              else dbg_printf("%s, ", tbl_regs[i]);
          }
      dbg_printf("}");
      return 0;
  }
  
  static UINT thumb2_disasm_ldrstrextbr(UINT inst, ADDRESS64 *addr)
  {
      WORD op1 = (inst >> 23) & 0x03;
      WORD op2 = (inst >> 20) & 0x03;
      WORD op3 = (inst >>  4) & 0x0f;
      WORD indexing  = (inst >> 24) & 0x01;
      WORD direction = (inst >> 23) & 0x01;
      WORD writeback = (inst >> 21) & 0x01;
      WORD load      = (inst >> 20) & 0x01;
      short offset   = (inst & 0xff) << 2;
  
      if (op1 == 1 && op2 == 1 && op3 < 2)
      {
          WORD halfword = (inst >> 4) & 0x01;
          if (halfword)
              dbg_printf("\n\ttbh\t [%s, %s, lsl #1]", tbl_regs[get_nibble(inst, 4)],
                         tbl_regs[get_nibble(inst, 0)]);
          else
              dbg_printf("\n\ttbb\t [%s, %s]", tbl_regs[get_nibble(inst, 4)],
                         tbl_regs[get_nibble(inst, 0)]);
          return 0;
      }
  
      if (op1 == 0 && op2 < 2)
      {
          if (get_nibble(inst, 2) == 15)
              dbg_printf("\n\tldrex\t %s, [%s, #%u]", tbl_regs[get_nibble(inst, 3)],
                         tbl_regs[get_nibble(inst, 4)], offset);
          else
              dbg_printf("\n\tstrex\t %s, %s, [%s, #%u]", tbl_regs[get_nibble(inst, 2)],
                         tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)], offset);
          return 0;
      }
  
      if (op1 == 1 && op2 < 2)
      {
          WORD halfword = (inst >> 4) & 0x01;
          if (get_nibble(inst, 0) == 15)
              dbg_printf("\n\tldrex%s\t %s, [%s]", halfword ? "h" : "b",
                         tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 4)]);
          else
              dbg_printf("\n\tstrex%s\t %s, %s, [%s]", halfword ? "h" : "b",
                         tbl_regs[get_nibble(inst, 0)], tbl_regs[get_nibble(inst, 3)],
                         tbl_regs[get_nibble(inst, 4)]);
          return 0;
      }
  
      if (!direction) offset *= -1;
      dbg_printf("\n\t%s\t", load ? "ldrd" : "strd");
      if (indexing)
      {
          if (load && get_nibble(inst, 4) == 15)
          {
              dbg_printf("%s, %s, ", tbl_regs[get_nibble(inst, 3)], tbl_regs[get_nibble(inst, 2)]);
              db_printsym(addr->Offset + offset + 4);
          }
          else
              dbg_printf("%s, %s, [%s, #%d]%s", tbl_regs[get_nibble(inst, 3)],
                         tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 4)], offset,
                         writeback?"!":"");
      }
      else
          dbg_printf("%s, %s, [%s], #%d", tbl_regs[get_nibble(inst, 3)],
                     tbl_regs[get_nibble(inst, 2)], tbl_regs[get_nibble(inst, 4)], offset);
      return 0;
  }
  
  struct inst_arm
  {
          UINT mask;
          UINT pattern;
          UINT (*func)(UINT, ADDRESS64*);
  };
  
  static const struct inst_arm tbl_arm[] = {
      { 0x0e000000, 0x0a000000, arm_disasm_branch },
      { 0x0fc000f0, 0x00000090, arm_disasm_mul },
      { 0x0f8000f0, 0x00800090, arm_disasm_longmul },
      { 0x0fb00ff0, 0x01000090, arm_disasm_swp },
      { 0x0e000090, 0x00000090, arm_disasm_halfwordtrans },
      { 0x0ffffff0, 0x012fff00, arm_disasm_branchreg },
      { 0x0ffffff0, 0x012fff10, arm_disasm_branchxchg },
      { 0x0fbf0fff, 0x010f0000, arm_disasm_mrstrans },
      { 0x0dbef000, 0x0128f000, arm_disasm_msrtrans },
      { 0x0fb00000, 0x03000000, arm_disasm_wordmov },
      { 0x0fffffff, 0x0320f000, arm_disasm_nop },
      { 0x0c000000, 0x00000000, arm_disasm_dataprocessing },
      { 0x0c000000, 0x04000000, arm_disasm_singletrans },
      { 0x0e000000, 0x08000000, arm_disasm_blocktrans },
      { 0x0f000000, 0x0f000000, arm_disasm_swi },
      { 0x0f000010, 0x0e000010, arm_disasm_coproctrans },
      { 0x0f000010, 0x0e000000, arm_disasm_coprocdataop },
      { 0x0e000000, 0x0c000000, arm_disasm_coprocdatatrans },
      { 0x00000000, 0x00000000, NULL }
  };
  
  struct inst_thumb16
  {
          WORD mask;
          WORD pattern;
          WORD (*func)(WORD, ADDRESS64*);
  };
  
  static const struct inst_thumb16 tbl_thumb16[] = {
      { 0xfc00, 0x4400, thumb_disasm_hireg },
      { 0xfc00, 0x4000, thumb_disasm_aluop },
      { 0xf600, 0xb400, thumb_disasm_pushpop },
      { 0xf000, 0xc000, thumb_disasm_blocktrans },
      { 0xff00, 0xdf00, thumb_disasm_swi },
      { 0xf000, 0xd000, thumb_disasm_condbranch },
      { 0xf800, 0xe000, thumb_disasm_uncondbranch },
      { 0xf000, 0xa000, thumb_disasm_loadadr },
      { 0xf800, 0x4800, thumb_disasm_ldrpcrel },
      { 0xf000, 0x9000, thumb_disasm_ldrsprel },
      { 0xff00, 0xb000, thumb_disasm_addsprel },
      { 0xe000, 0x6000, thumb_disasm_ldrimm },
      { 0xf000, 0x8000, thumb_disasm_ldrhimm },
      { 0xf200, 0x5000, thumb_disasm_ldrreg },
      { 0xf200, 0x5200, thumb_disasm_ldrsreg },
      { 0xe000, 0x2000, thumb_disasm_immop },
      { 0xff00, 0xbf00, thumb_disasm_nop },
      { 0xf800, 0x1800, thumb_disasm_addsub },
      { 0xe000, 0x0000, thumb_disasm_movshift },
      { 0x0000, 0x0000, NULL }
  };
  
  static const struct inst_arm tbl_thumb32[] = {
      { 0xfff0f000, 0xf3e08000, thumb2_disasm_srtrans },
      { 0xfff0f000, 0xf3808000, thumb2_disasm_srtrans },
      { 0xfff0d000, 0xf3a08000, thumb2_disasm_hint },
      { 0xfff0d000, 0xf3b08000, thumb2_disasm_miscctrl },
      { 0xf8008000, 0xf0008000, thumb2_disasm_branch },
      { 0xffc0f0c0, 0xfa80f080, thumb2_disasm_misc },
      { 0xff80f000, 0xfa00f000, thumb2_disasm_dataprocessingreg },
      { 0xff8000c0, 0xfb000000, thumb2_disasm_mul },
      { 0xff8000f0, 0xfb800000, thumb2_disasm_longmuldiv },
      { 0xff8000f0, 0xfb8000f0, thumb2_disasm_longmuldiv },
      { 0xff100000, 0xf8000000, thumb2_disasm_str },
      { 0xff700000, 0xf8500000, thumb2_disasm_ldrword },
      { 0xfe70f000, 0xf810f000, thumb2_disasm_preload },
      { 0xfe500000, 0xf8100000, thumb2_disasm_ldrnonword },
      { 0xfa008000, 0xf2000000, thumb2_disasm_dataprocessing },
      { 0xfa008000, 0xf0000000, thumb2_disasm_dataprocessingmod },
      { 0xfe008000, 0xea000000, thumb2_disasm_dataprocessingshift },
      { 0xef000010, 0xee000000, thumb2_disasm_coprocdat },
      { 0xef000010, 0xee000010, thumb2_disasm_coprocmov1 },
      { 0xefe00000, 0xec400000, thumb2_disasm_coprocmov2 },
      { 0xee000000, 0xec000000, thumb2_disasm_coprocdatatrans },
      { 0xfe402000, 0xe8000000, thumb2_disasm_ldrstrmul },
      { 0xfe400000, 0xe8400000, thumb2_disasm_ldrstrextbr },
      { 0x00000000, 0x00000000, NULL }
  };
  
  /***********************************************************************
   *              disasm_one_insn
   *
   * Disassemble instruction at 'addr'. addr is changed to point to the
   * start of the next instruction.
   */
  void be_arm_disasm_one_insn(ADDRESS64 *addr, int display)
  {
      struct inst_arm *a_ptr = (struct inst_arm *)&tbl_arm;
      struct inst_thumb16 *t_ptr = (struct inst_thumb16 *)&tbl_thumb16;
      struct inst_arm *t2_ptr = (struct inst_arm *)&tbl_thumb32;
      UINT inst;
      WORD tinst;
      int size;
      int matched = 0;
  
      char tmp[64];
      DWORD_PTR* pval;
  
      if (!memory_get_register(CV_ARM_CPSR, &pval, tmp, sizeof(tmp)))
          dbg_printf("\n\tmemory_get_register failed: %s", tmp);
      else
          db_disasm_thumb = (*pval & 0x20) != 0;
  
      db_display = display;
  
      if (!db_disasm_thumb)
      {
          size = ARM_INSN_SIZE;
          inst = db_get_inst( memory_to_linear_addr(addr), size );
          while (a_ptr->func) {
              if ((inst & a_ptr->mask) == a_ptr->pattern) {
                      matched = 1;
                      break;
              }
              a_ptr++;
          }
  
          if (!matched) {
              dbg_printf("\n\tUnknown ARM Instruction: %08x", inst);
              addr->Offset += size;
          }
          else
          {
              if (!a_ptr->func(inst, addr))
                  addr->Offset += size;
          }
          return;
      }
      else
      {
          WORD *taddr = memory_to_linear_addr(addr);
          tinst = db_get_inst( taddr, THUMB_INSN_SIZE );
          switch (tinst & 0xf800)
          {
              case 0xe800:
              case 0xf000:
              case 0xf800:
                  size = THUMB2_INSN_SIZE;
                  taddr++;
                  inst = db_get_inst( taddr, THUMB_INSN_SIZE );
                  inst |= (tinst << 16);
  
                  while (t2_ptr->func) {
                      if ((inst & t2_ptr->mask) == t2_ptr->pattern) {
                              matched = 1;
                              break;
                      }
                      t2_ptr++;
                  }
  
                  if (!matched) {
                      dbg_printf("\n\tUnknown Thumb2 Instruction: %08x", inst);
                      addr->Offset += size;
                  }
                  else
                  {
                      if (!t2_ptr->func(inst, addr))
                          addr->Offset += size;
                  }
                  return;
              default:
                  break;
          }
  
          size = THUMB_INSN_SIZE;
          while (t_ptr->func) {
              if ((tinst & t_ptr->mask) == t_ptr->pattern) {
                      matched = 1;
                      break;
              }
              t_ptr++;
          }
  
          if (!matched) {
              dbg_printf("\n\tUnknown Thumb Instruction: %04x", tinst);
              addr->Offset += size;
          }
          else
          {
              if (!t_ptr->func(tinst, addr))
                  addr->Offset += size;
          }
          return;
      }
  }
  
  static BOOL be_arm_get_addr(HANDLE hThread, const CONTEXT* ctx,
                              enum be_cpu_addr bca, ADDRESS64* addr)
  {
      switch (bca)
      {
      case be_cpu_addr_pc:
          return be_cpu_build_addr(hThread, ctx, addr, 0, ctx->Pc);
      case be_cpu_addr_stack:
          return be_cpu_build_addr(hThread, ctx, addr, 0, ctx->Sp);
      case be_cpu_addr_frame:
          return be_cpu_build_addr(hThread, ctx, addr, 0, ctx->R11);
      }
      return FALSE;
  }
  
  static BOOL be_arm_get_register_info(int regno, enum be_cpu_addr* kind)
  {
      switch (regno)
      {
      case CV_ARM_PC:  *kind = be_cpu_addr_pc; return TRUE;
      case CV_ARM_R0 + 11: *kind = be_cpu_addr_frame; return TRUE;
      case CV_ARM_SP:  *kind = be_cpu_addr_stack; return TRUE;
      }
      return FALSE;
  }
  
  static void be_arm_single_step(CONTEXT* ctx, BOOL enable)
  {
  }
  
  static void be_arm_print_context(HANDLE hThread, const CONTEXT* ctx, int all_regs)
  {
      static const char condflags[] = "NZCV";
      int i;
      char        buf[8];
  
      switch (ctx->Cpsr & 0x1F)
      {
      case 0:  strcpy(buf, "User26"); break;
      case 1:  strcpy(buf, "FIQ26"); break;
      case 2:  strcpy(buf, "IRQ26"); break;
      case 3:  strcpy(buf, "SVC26"); break;
      case 16: strcpy(buf, "User"); break;
      case 17: strcpy(buf, "FIQ"); break;
      case 18: strcpy(buf, "IRQ"); break;
      case 19: strcpy(buf, "SVC"); break;
      case 23: strcpy(buf, "ABT"); break;
      case 27: strcpy(buf, "UND"); break;
      default: strcpy(buf, "UNKNWN"); break;
      }
  
      dbg_printf("Register dump:\n");
      dbg_printf("%s %s Mode\n", (ctx->Cpsr & 0x20) ? "Thumb" : "ARM", buf);
  
      strcpy(buf, condflags);
      for (i = 0; buf[i]; i++)
          if (!((ctx->Cpsr >> 26) & (1 << (sizeof(condflags) - i))))
              buf[i] = '-';
  
      dbg_printf(" Pc:%08x Sp:%08x Lr:%08x Cpsr:%08x(%s)\n",
                 ctx->Pc, ctx->Sp, ctx->Lr, ctx->Cpsr, buf);
      dbg_printf(" r0:%08x r1:%08x r2:%08x r3:%08x\n",
                 ctx->R0, ctx->R1, ctx->R2, ctx->R3);
      dbg_printf(" r4:%08x r5:%08x r6:%08x r7:%08x\n",
                 ctx->R4, ctx->R5, ctx->R6, ctx->R7);
      dbg_printf(" r8:%08x r9:%08x r10:%08x r11:%08x r12:%08x\n",
                 ctx->R8, ctx->R9, ctx->R10, ctx->R11, ctx->R12);
  
      if (all_regs) dbg_printf( "Floating point ARM dump not implemented\n" );
  }
  
  static void be_arm_print_segment_info(HANDLE hThread, const CONTEXT* ctx)
  {
  }
  
  static struct dbg_internal_var be_arm_ctx[] =
  {
      {CV_ARM_R0 +  0,    "r0",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R0),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  1,    "r1",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R1),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  2,    "r2",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R2),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  3,    "r3",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R3),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  4,    "r4",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R4),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  5,    "r5",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R5),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  6,    "r6",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R6),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  7,    "r7",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R7),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  8,    "r8",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R8),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  9,    "r9",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R9),     dbg_itype_unsigned_int},
      {CV_ARM_R0 +  10,   "r10",          (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R10),    dbg_itype_unsigned_int},
      {CV_ARM_R0 +  11,   "r11",          (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R11),    dbg_itype_unsigned_int},
      {CV_ARM_R0 +  12,   "r12",          (DWORD_PTR*)FIELD_OFFSET(CONTEXT, R12),    dbg_itype_unsigned_int},
      {CV_ARM_SP,         "sp",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, Sp),     dbg_itype_unsigned_int},
      {CV_ARM_LR,         "lr",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, Lr),     dbg_itype_unsigned_int},
      {CV_ARM_PC,         "pc",           (DWORD_PTR*)FIELD_OFFSET(CONTEXT, Pc),     dbg_itype_unsigned_int},
      {CV_ARM_CPSR,       "cpsr",         (DWORD_PTR*)FIELD_OFFSET(CONTEXT, Cpsr),   dbg_itype_unsigned_int},
      {0,                 NULL,           0,                                         dbg_itype_none}
  };
  
  static BOOL be_arm_is_step_over_insn(const void* insn)
  {
      dbg_printf("be_arm_is_step_over_insn: not done\n");
      return FALSE;
  }
  
  static BOOL be_arm_is_function_return(const void* insn)
  {
      dbg_printf("be_arm_is_function_return: not done\n");
      return FALSE;
  }
  
  static BOOL be_arm_is_break_insn(const void* insn)
  {
      dbg_printf("be_arm_is_break_insn: not done\n");
      return FALSE;
  }
  
  static BOOL be_arm_is_func_call(const void* insn, ADDRESS64* callee)
  {
      return FALSE;
  }
  
  static BOOL be_arm_is_jump(const void* insn, ADDRESS64* jumpee)
  {
      return FALSE;
  }
  
  static BOOL be_arm_insert_Xpoint(HANDLE hProcess, const struct be_process_io* pio,
                                   CONTEXT* ctx, enum be_xpoint_type type,
                                   void* addr, unsigned long* val, unsigned size)
  {
      SIZE_T              sz;
  
      switch (type)
      {
      case be_xpoint_break:
          if (!size) return FALSE;
          if (!pio->read(hProcess, addr, val, 4, &sz) || sz != 4) return FALSE;
      default:
          dbg_printf("Unknown/unsupported bp type %c\n", type);
          return FALSE;
      }
      return TRUE;
  }
  
  static BOOL be_arm_remove_Xpoint(HANDLE hProcess, const struct be_process_io* pio,
                                   CONTEXT* ctx, enum be_xpoint_type type,
                                   void* addr, unsigned long val, unsigned size)
  {
      SIZE_T              sz;
  
      switch (type)
      {
      case be_xpoint_break:
          if (!size) return FALSE;
          if (!pio->write(hProcess, addr, &val, 4, &sz) || sz == 4) return FALSE;
          break;
      default:
          dbg_printf("Unknown/unsupported bp type %c\n", type);
          return FALSE;
      }
      return TRUE;
  }
  
  static BOOL be_arm_is_watchpoint_set(const CONTEXT* ctx, unsigned idx)
  {
      dbg_printf("be_arm_is_watchpoint_set: not done\n");
      return FALSE;
  }
  
  static void be_arm_clear_watchpoint(CONTEXT* ctx, unsigned idx)
  {
      dbg_printf("be_arm_clear_watchpoint: not done\n");
  }
  
  static int be_arm_adjust_pc_for_break(CONTEXT* ctx, BOOL way)
  {
      INT step = (ctx->Cpsr & 0x20) ? 2 : 4;
  
      if (way)
      {
          ctx->Pc -= step;
          return -step;
      }
      ctx->Pc += step;
      return step;
  }
  
  static BOOL be_arm_fetch_integer(const struct dbg_lvalue* lvalue, unsigned size,
                                   BOOL is_signed, LONGLONG* ret)
  {
      if (size != 1 && size != 2 && size != 4 && size != 8) return FALSE;
  
      memset(ret, 0, sizeof(*ret)); /* clear unread bytes */
      /* FIXME: this assumes that debuggee and debugger use the same
       * integral representation
       */
      if (!memory_read_value(lvalue, size, ret)) return FALSE;
  
      /* propagate sign information */
      if (is_signed && size < 8 && (*ret >> (size * 8 - 1)) != 0)
      {
          ULONGLONG neg = -1;
          *ret |= neg << (size * 8);
      }
      return TRUE;
  }
  
  static BOOL be_arm_fetch_float(const struct dbg_lvalue* lvalue, unsigned size,
                                 long double* ret)
  {
      char        tmp[sizeof(long double)];
  
      /* FIXME: this assumes that debuggee and debugger use the same
       * representation for reals
       */
      if (!memory_read_value(lvalue, size, tmp)) return FALSE;
  
      if (size == sizeof(float)) *ret = *(float*)tmp;
      else if (size == sizeof(double)) *ret = *(double*)tmp;
      else if (size == sizeof(long double)) *ret = *(long double*)tmp;
      else return FALSE;
  
      return TRUE;
  }
  
  static BOOL be_arm_store_integer(const struct dbg_lvalue* lvalue, unsigned size,
                                   BOOL is_signed, LONGLONG val)
  {
      /* this is simple if we're on a little endian CPU */
      return memory_write_value(lvalue, size, &val);
  }
  
  struct backend_cpu be_arm =
  {
      IMAGE_FILE_MACHINE_ARMNT,
      4,
      be_cpu_linearize,
      be_cpu_build_addr,
      be_arm_get_addr,
      be_arm_get_register_info,
      be_arm_single_step,
      be_arm_print_context,
      be_arm_print_segment_info,
      be_arm_ctx,
      be_arm_is_step_over_insn,
      be_arm_is_function_return,
      be_arm_is_break_insn,
      be_arm_is_func_call,
      be_arm_is_jump,
      be_arm_disasm_one_insn,
      be_arm_insert_Xpoint,
      be_arm_remove_Xpoint,
      be_arm_is_watchpoint_set,
      be_arm_clear_watchpoint,
      be_arm_adjust_pc_for_break,
      be_arm_fetch_integer,
      be_arm_fetch_float,
      be_arm_store_integer,
  };
  #endif
  