return (areg > 15) ? -1 : areg;
}
+static inline unsigned long
+xtensa_read_register (int regnum)
+{
+ ULONGEST value;
+
+ regcache_raw_read_unsigned (get_current_regcache (), regnum, &value);
+ return (unsigned long) value;
+}
+
+static inline void
+xtensa_write_register (int regnum, ULONGEST value)
+{
+ regcache_raw_write_unsigned (get_current_regcache (), regnum, value);
+}
+
/* Return the window size of the previous call to the function from which we
have just returned.
/* REGISTER INFORMATION */
+/* Find register by name. */
+static int
+xtensa_find_register_by_name (struct gdbarch *gdbarch, char *name)
+{
+ int i;
+
+ for (i = 0; i < gdbarch_num_regs (gdbarch)
+ + gdbarch_num_pseudo_regs (gdbarch);
+ i++)
+
+ if (strcasecmp (gdbarch_tdep (gdbarch)->regmap[i].name, name) == 0)
+ return i;
+
+ return -1;
+}
+
/* Returns the name of a register. */
static const char *
xtensa_register_name (struct gdbarch *gdbarch, int regnum)
{
int wb; /* WINDOWBASE of the previous frame. */
int callsize; /* Call size of this frame. */
- int ws; /* WINDOWSTART of the previous frame. It
- keeps track of life windows only. If there
- is no bit set for the window, that means it
- had been already spilled because of window
- overflow. */
-
- /* Spilled A-registers from the previous frame.
- AREGS[i] == -1, if corresponding AR is alive. */
+ int ws; /* WINDOWSTART of the previous frame. It keeps track of
+ life windows only. If there is no bit set for the
+ window, that means it had been already spilled
+ because of window overflow. */
+
+ /* Addresses of spilled A-registers.
+ AREGS[i] == -1, if corresponding AR is alive. */
CORE_ADDR aregs[XTENSA_NUM_SAVED_AREGS];
} xtensa_windowed_frame_cache_t;
typedef struct xtensa_frame_cache
{
CORE_ADDR base; /* Stack pointer of this frame. */
- CORE_ADDR pc; /* PC at the entry point to the function. */
- CORE_ADDR ra; /* The raw return address (without CALLINC). */
- CORE_ADDR ps; /* The PS register of this frame. */
- CORE_ADDR prev_sp; /* Stack Pointer of the previous frame. */
+ CORE_ADDR pc; /* PC of this frame at the function entry point. */
+ CORE_ADDR ra; /* The raw return address of this frame. */
+ CORE_ADDR ps; /* The PS register of the previous (older) frame. */
+ CORE_ADDR prev_sp; /* Stack Pointer of the previous (older) frame. */
int call0; /* It's a call0 framework (else windowed). */
union
{
return frame_id_build (fp + SP_ALIGNMENT, pc);
}
+/* Returns true, if instruction to execute next is unique to Xtensa Window
+ Interrupt Handlers. It can only be one of L32E, S32E, RFWO, or RFWU. */
+
+static int
+xtensa_window_interrupt_insn (struct gdbarch *gdbarch, CORE_ADDR pc)
+{
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ unsigned int insn = read_memory_integer (pc, 4, byte_order);
+ unsigned int code;
+
+ if (byte_order == BFD_ENDIAN_BIG)
+ {
+ /* Check, if this is L32E or S32E. */
+ code = insn & 0xf000ff00;
+ if ((code == 0x00009000) || (code == 0x00009400))
+ return 1;
+ /* Check, if this is RFWU or RFWO. */
+ code = insn & 0xffffff00;
+ return ((code == 0x00430000) || (code == 0x00530000));
+ }
+ else
+ {
+ /* Check, if this is L32E or S32E. */
+ code = insn & 0x00ff000f;
+ if ((code == 0x090000) || (code == 0x490000))
+ return 1;
+ /* Check, if this is RFWU or RFWO. */
+ code = insn & 0x00ffffff;
+ return ((code == 0x00003400) || (code == 0x00003500));
+ }
+}
+
/* Returns the best guess about which register is a frame pointer
for the function containing CURRENT_PC. */
xtensa_frame_cache_t *cache,
CORE_ADDR pc, CORE_ADDR litbase);
+static void
+xtensa_window_interrupt_frame_cache (struct frame_info *this_frame,
+ xtensa_frame_cache_t *cache,
+ CORE_ADDR pc);
+
static struct xtensa_frame_cache *
xtensa_frame_cache (struct frame_info *this_frame, void **this_cache)
{
}
if ((cache->prev_sp == 0) && ( ra != 0 ))
- /* If RA is equal to 0 this frame is an outermost frame.
- Leave cache->prev_sp unchanged marking the boundary of the
- frame stack. */
+ /* If RA is equal to 0 this frame is an outermost frame. Leave
+ cache->prev_sp unchanged marking the boundary of the frame stack. */
{
if ((cache->wd.ws & (1 << cache->wd.wb)) == 0)
{
(gdbarch, gdbarch_tdep (gdbarch)->a0_base + 1,
cache->wd.wb);
- cache->prev_sp = get_frame_register_unsigned (this_frame,
- regnum);
+ cache->prev_sp = xtensa_read_register (regnum);
}
}
}
+ else if (xtensa_window_interrupt_insn (gdbarch, pc))
+ {
+ /* Execution stopped inside Xtensa Window Interrupt Handler. */
+
+ xtensa_window_interrupt_frame_cache (this_frame, cache, pc);
+ /* Everything was set already, including cache->base. */
+ return cache;
+ }
else /* Call0 framework. */
{
unsigned int litbase_regnum = gdbarch_tdep (gdbarch)->litbase_regnum;
c0opc_mov, /* Moving a register to a register. */
c0opc_movi, /* Moving an immediate to a register. */
c0opc_l32r, /* Loading a literal. */
- c0opc_s32i, /* Storing word at fixed offset from a base
- register. */
+ c0opc_s32i, /* Storing word at fixed offset from a base register. */
+ c0opc_rwxsr, /* RSR, WRS, or XSR instructions. */
+ c0opc_l32e, /* L32E instruction. */
+ c0opc_s32e, /* S32E instruction. */
+ c0opc_rfwo, /* RFWO instruction. */
+ c0opc_rfwu, /* RFWU instruction. */
c0opc_NrOf /* Number of opcode classifications. */
} xtensa_insn_kind;
+/* Return true, if OPCNAME is RSR, WRS, or XSR instruction. */
+
+static int
+rwx_special_register (const char *opcname)
+{
+ char ch = *opcname++;
+
+ if ((ch != 'r') && (ch != 'w') && (ch != 'x'))
+ return 0;
+ if (*opcname++ != 's')
+ return 0;
+ if (*opcname++ != 'r')
+ return 0;
+ if (*opcname++ != '.')
+ return 0;
+
+ return 1;
+}
/* Classify an opcode based on what it means for Call0 prologue analysis. */
opclass = c0opc_break;
else if (strcasecmp (opcname, "entry") == 0)
opclass = c0opc_entry;
+ else if (strcasecmp (opcname, "rfwo") == 0)
+ opclass = c0opc_rfwo;
+ else if (strcasecmp (opcname, "rfwu") == 0)
+ opclass = c0opc_rfwu;
else if (xtensa_opcode_is_branch (isa, opc) > 0
|| xtensa_opcode_is_jump (isa, opc) > 0
|| xtensa_opcode_is_loop (isa, opc) > 0
else if (strcasecmp (opcname, "s32i") == 0
|| strcasecmp (opcname, "s32i.n") == 0)
opclass = c0opc_s32i;
+ else if (strcasecmp (opcname, "l32e") == 0)
+ opclass = c0opc_l32e;
+ else if (strcasecmp (opcname, "s32e") == 0)
+ opclass = c0opc_s32e;
+ else if (rwx_special_register (opcname))
+ opclass = c0opc_rwxsr;
return opclass;
}
break;
case c0opc_add:
/* 3 operands: dst, src1, src2. */
- gdb_assert (nods == 3);
+ gdb_assert (nods == 3);
if (src[odv[1]].fr_reg == C0_CONST)
{
dst[odv[0]].fr_reg = src[odv[2]].fr_reg;
cache->c0.c0_fp = fp;
}
+static CORE_ADDR a0_saved;
+static CORE_ADDR a7_saved;
+static CORE_ADDR a11_saved;
+static int a0_was_saved;
+static int a7_was_saved;
+static int a11_was_saved;
+
+/* Simulate L32E insn: AT <-- ref (AS + offset). */
+static void
+execute_l32e (struct gdbarch *gdbarch, int at, int as, int offset, CORE_ADDR wb)
+{
+ int atreg = arreg_number (gdbarch, gdbarch_tdep (gdbarch)->a0_base + at, wb);
+ int asreg = arreg_number (gdbarch, gdbarch_tdep (gdbarch)->a0_base + as, wb);
+ CORE_ADDR addr = xtensa_read_register (asreg) + offset;
+ unsigned int spilled_value
+ = read_memory_unsigned_integer (addr, 4, gdbarch_byte_order (gdbarch));
+
+ if ((at == 0) && !a0_was_saved)
+ {
+ a0_saved = xtensa_read_register (atreg);
+ a0_was_saved = 1;
+ }
+ else if ((at == 7) && !a7_was_saved)
+ {
+ a7_saved = xtensa_read_register (atreg);
+ a7_was_saved = 1;
+ }
+ else if ((at == 11) && !a11_was_saved)
+ {
+ a11_saved = xtensa_read_register (atreg);
+ a11_was_saved = 1;
+ }
+
+ xtensa_write_register (atreg, spilled_value);
+}
+
+/* Simulate S32E insn: AT --> ref (AS + offset). */
+static void
+execute_s32e (struct gdbarch *gdbarch, int at, int as, int offset, CORE_ADDR wb)
+{
+ int atreg = arreg_number (gdbarch, gdbarch_tdep (gdbarch)->a0_base + at, wb);
+ int asreg = arreg_number (gdbarch, gdbarch_tdep (gdbarch)->a0_base + as, wb);
+ CORE_ADDR addr = xtensa_read_register (asreg) + offset;
+ ULONGEST spilled_value = xtensa_read_register (atreg);
+
+ write_memory_unsigned_integer (addr, 4,
+ gdbarch_byte_order (gdbarch),
+ spilled_value);
+}
+
+#define XTENSA_MAX_WINDOW_INTERRUPT_HANDLER_LEN 200
+
+typedef enum {
+ xtWindowOverflow,
+ xtWindowUnderflow,
+ xtNoExceptionHandler
+} xtensa_exception_handler_t;
+
+/* Execute insn stream from current PC until hitting RFWU or RFWO.
+ Return type of Xtensa Window Interrupt Handler on success. */
+static xtensa_exception_handler_t
+execute_code (struct gdbarch *gdbarch, CORE_ADDR current_pc, CORE_ADDR wb)
+{
+ xtensa_isa isa;
+ xtensa_insnbuf ins, slot;
+ char ibuf[XTENSA_ISA_BSZ];
+ CORE_ADDR ia, bt, ba;
+ xtensa_format ifmt;
+ int ilen, islots, is;
+ xtensa_opcode opc;
+ int insn_num = 0;
+ int fail = 0;
+ void (*func) (struct gdbarch *, int, int, int, CORE_ADDR);
+
+ int at, as, offset;
+ int num_operands;
+
+ /* WindowUnderflow12 = true, when inside _WindowUnderflow12. */
+ int WindowUnderflow12 = (current_pc & 0x1ff) >= 0x140;
+
+ isa = xtensa_default_isa;
+ gdb_assert (XTENSA_ISA_BSZ >= xtensa_isa_maxlength (isa));
+ ins = xtensa_insnbuf_alloc (isa);
+ slot = xtensa_insnbuf_alloc (isa);
+ ba = 0;
+ ia = current_pc;
+ bt = ia;
+
+ a0_was_saved = 0;
+ a7_was_saved = 0;
+ a11_was_saved = 0;
+
+ while (insn_num++ < XTENSA_MAX_WINDOW_INTERRUPT_HANDLER_LEN)
+ {
+ if (ia + xtensa_isa_maxlength (isa) > bt)
+ {
+ ba = ia;
+ bt = (ba + XTENSA_ISA_BSZ);
+ if (target_read_memory (ba, ibuf, bt - ba) != 0)
+ return xtNoExceptionHandler;
+ }
+ xtensa_insnbuf_from_chars (isa, ins, &ibuf[ia-ba], 0);
+ ifmt = xtensa_format_decode (isa, ins);
+ if (ifmt == XTENSA_UNDEFINED)
+ return xtNoExceptionHandler;
+ ilen = xtensa_format_length (isa, ifmt);
+ if (ilen == XTENSA_UNDEFINED)
+ return xtNoExceptionHandler;
+ islots = xtensa_format_num_slots (isa, ifmt);
+ if (islots == XTENSA_UNDEFINED)
+ return xtNoExceptionHandler;
+ for (is = 0; is < islots; ++is)
+ {
+ if (xtensa_format_get_slot (isa, ifmt, is, ins, slot))
+ return xtNoExceptionHandler;
+ opc = xtensa_opcode_decode (isa, ifmt, is, slot);
+ if (opc == XTENSA_UNDEFINED)
+ return xtNoExceptionHandler;
+ switch (call0_classify_opcode (isa, opc))
+ {
+ case c0opc_illegal:
+ case c0opc_flow:
+ case c0opc_entry:
+ case c0opc_break:
+ /* We expect none of them here. */
+ return xtNoExceptionHandler;
+ case c0opc_l32e:
+ func = execute_l32e;
+ break;
+ case c0opc_s32e:
+ func = execute_s32e;
+ break;
+ case c0opc_rfwo: /* RFWO. */
+ /* Here, we return from WindowOverflow handler and,
+ if we stopped at the very beginning, which means
+ A0 was saved, we have to restore it now. */
+ if (a0_was_saved)
+ {
+ int arreg = arreg_number (gdbarch,
+ gdbarch_tdep (gdbarch)->a0_base,
+ wb);
+ xtensa_write_register (arreg, a0_saved);
+ }
+ return xtWindowOverflow;
+ case c0opc_rfwu: /* RFWU. */
+ /* Here, we return from WindowUnderflow handler.
+ Let's see if either A7 or A11 has to be restored. */
+ if (WindowUnderflow12)
+ {
+ if (a11_was_saved)
+ {
+ int arreg = arreg_number (gdbarch,
+ gdbarch_tdep (gdbarch)->a0_base + 11,
+ wb);
+ xtensa_write_register (arreg, a11_saved);
+ }
+ }
+ else if (a7_was_saved)
+ {
+ int arreg = arreg_number (gdbarch,
+ gdbarch_tdep (gdbarch)->a0_base + 7,
+ wb);
+ xtensa_write_register (arreg, a7_saved);
+ }
+ return xtWindowUnderflow;
+ default: /* Simply skip this insns. */
+ continue;
+ }
+
+ /* Decode arguments for L32E / S32E and simulate their execution. */
+ if ( xtensa_opcode_num_operands (isa, opc) != 3 )
+ return xtNoExceptionHandler;
+ if (xtensa_operand_get_field (isa, opc, 0, ifmt, is, slot, &at))
+ return xtNoExceptionHandler;
+ if (xtensa_operand_decode (isa, opc, 0, &at))
+ return xtNoExceptionHandler;
+ if (xtensa_operand_get_field (isa, opc, 1, ifmt, is, slot, &as))
+ return xtNoExceptionHandler;
+ if (xtensa_operand_decode (isa, opc, 1, &as))
+ return xtNoExceptionHandler;
+ if (xtensa_operand_get_field (isa, opc, 2, ifmt, is, slot, &offset))
+ return xtNoExceptionHandler;
+ if (xtensa_operand_decode (isa, opc, 2, &offset))
+ return xtNoExceptionHandler;
+
+ (*func) (gdbarch, at, as, offset, wb);
+ }
+
+ ia += ilen;
+ }
+ return xtNoExceptionHandler;
+}
+
+/* Handle Window Overflow / Underflow exception frames. */
+
+static void
+xtensa_window_interrupt_frame_cache (struct frame_info *this_frame,
+ xtensa_frame_cache_t *cache,
+ CORE_ADDR pc)
+{
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ CORE_ADDR ps, wb, ws, ra;
+ int epc1_regnum, i, regnum;
+ xtensa_exception_handler_t eh_type;
+
+ /* Read PS, WB, and WS from the hardware. Note that PS register
+ must be present, if Windowed ABI is supported. */
+ ps = xtensa_read_register (gdbarch_ps_regnum (gdbarch));
+ wb = xtensa_read_register (gdbarch_tdep (gdbarch)->wb_regnum);
+ ws = xtensa_read_register (gdbarch_tdep (gdbarch)->ws_regnum);
+
+ /* Execute all the remaining instructions from Window Interrupt Handler
+ by simulating them on the remote protocol level. On return, set the
+ type of Xtensa Window Interrupt Handler, or report an error. */
+ eh_type = execute_code (gdbarch, pc, wb);
+ if (eh_type == xtNoExceptionHandler)
+ error (_("\
+Unable to decode Xtensa Window Interrupt Handler's code."));
+
+ cache->ps = ps ^ PS_EXC; /* Clear the exception bit in PS. */
+ cache->call0 = 0; /* It's Windowed ABI. */
+
+ /* All registers for the cached frame will be alive. */
+ for (i = 0; i < XTENSA_NUM_SAVED_AREGS; i++)
+ cache->wd.aregs[i] = -1;
+
+ if (eh_type == xtWindowOverflow)
+ cache->wd.ws = ws ^ (1 << wb);
+ else /* eh_type == xtWindowUnderflow. */
+ cache->wd.ws = ws | (1 << wb);
+
+ cache->wd.wb = (ps & 0xf00) >> 8; /* Set WB to OWB. */
+ regnum = arreg_number (gdbarch, gdbarch_tdep (gdbarch)->a0_base,
+ cache->wd.wb);
+ ra = xtensa_read_register (regnum);
+ cache->wd.callsize = WINSIZE (ra);
+ cache->prev_sp = xtensa_read_register (regnum + 1);
+ /* Set regnum to a frame pointer of the frame being cached. */
+ regnum = xtensa_scan_prologue (gdbarch, pc);
+ regnum = arreg_number (gdbarch,
+ gdbarch_tdep (gdbarch)->a0_base + regnum,
+ cache->wd.wb);
+ cache->base = get_frame_register_unsigned (this_frame, regnum);
+
+ /* Read PC of interrupted function from EPC1 register. */
+ epc1_regnum = xtensa_find_register_by_name (gdbarch,"epc1");
+ if (epc1_regnum < 0)
+ error(_("Unable to read Xtensa register EPC1"));
+ cache->ra = xtensa_read_register (epc1_regnum);
+ cache->pc = get_frame_func (this_frame);
+}
+
/* Skip function prologue.