From 08b9c608aa9cf12ec7d7dfacff3dbc843be83cd0 Mon Sep 17 00:00:00 2001 From: Maxim Grigoriev Date: Wed, 9 Mar 2011 02:25:12 +0000 Subject: [PATCH] 2011-03-08 Maxim Grigoriev * xtensa-tdep.c (xtensa_read_register): New function. (xtensa_write_register): New function. (xtensa_find_register_by_name): New function. (xtensa_windowed_frame_cache): Update comments in type description. (xtensa_frame_cache): Likewise. (xtensa_window_interrupt_insn): New function. (xtensa_frame_cache): Add analysis for Xtensa Window Exception frames. (xtensa_insn_kind): Add new instructions. (rwx_special_register): New function. (call0_classify_opcode): Add new instructions to the analysis. (a0_saved, a7_saved, a11_saved): New variables. (a0_was_saved, a7_was_saved, a11_was_saved): New variables. (execute_l32e): New function. (execute_s32e): New function. (xtensa_exception_handler_t): New type. (execute_code): New function. (xtensa_window_interrupt_frame_cache): New function to conduct frame analysis for Xtensa Window Exception handlers. --- gdb/ChangeLog | 21 +++ gdb/xtensa-tdep.c | 397 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 398 insertions(+), 20 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 4086fc6..2ced03c 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,26 @@ 2011-03-08 Maxim Grigoriev + * xtensa-tdep.c (xtensa_read_register): New function. + (xtensa_write_register): New function. + (xtensa_find_register_by_name): New function. + (xtensa_windowed_frame_cache): Update comments in type description. + (xtensa_frame_cache): Likewise. + (xtensa_window_interrupt_insn): New function. + (xtensa_frame_cache): Add analysis for Xtensa Window Exception frames. + (xtensa_insn_kind): Add new instructions. + (rwx_special_register): New function. + (call0_classify_opcode): Add new instructions to the analysis. + (a0_saved, a7_saved, a11_saved): New variables. + (a0_was_saved, a7_was_saved, a11_was_saved): New variables. + (execute_l32e): New function. + (execute_s32e): New function. + (xtensa_exception_handler_t): New type. + (execute_code): New function. + (xtensa_window_interrupt_frame_cache): New function to conduct frame + analysis for Xtensa Window Exception handlers. + +2011-03-08 Maxim Grigoriev + * xtensa-tdep.c (TX_PS): New. (windowing_enabled): Update to count for Call0 ABI. (xtensa_hextochar): New. diff --git a/gdb/xtensa-tdep.c b/gdb/xtensa-tdep.c index 49ec8fc..41c30d7 100644 --- a/gdb/xtensa-tdep.c +++ b/gdb/xtensa-tdep.c @@ -161,6 +161,21 @@ areg_number (struct gdbarch *gdbarch, int ar_regnum, unsigned int wb) 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. @@ -210,6 +225,22 @@ extract_call_winsize (struct gdbarch *gdbarch, CORE_ADDR pc) /* 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) @@ -907,14 +938,13 @@ typedef struct xtensa_windowed_frame_cache { 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; @@ -968,10 +998,10 @@ typedef struct xtensa_call0_frame_cache 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 { @@ -1064,6 +1094,38 @@ xtensa_dummy_id (struct gdbarch *gdbarch, struct frame_info *this_frame) 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. */ @@ -1195,6 +1257,11 @@ call0_frame_cache (struct frame_info *this_frame, 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) { @@ -1302,9 +1369,8 @@ 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) { @@ -1321,11 +1387,18 @@ xtensa_frame_cache (struct frame_info *this_frame, void **this_cache) (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; @@ -1941,11 +2014,33 @@ typedef enum { 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. */ @@ -1970,6 +2065,10 @@ call0_classify_opcode (xtensa_isa isa, xtensa_opcode opc) 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 @@ -1999,6 +2098,12 @@ call0_classify_opcode (xtensa_isa isa, xtensa_opcode opc) 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; } @@ -2028,7 +2133,7 @@ call0_track_op (struct gdbarch *gdbarch, 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; @@ -2458,6 +2563,258 @@ analysis failed in this frame. GDB command execution stopped.")); 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. -- 2.7.4