diff options
Diffstat (limited to 'gdb/loongarch-tdep.c')
-rw-r--r-- | gdb/loongarch-tdep.c | 309 |
1 files changed, 284 insertions, 25 deletions
diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c index 092127dd..9ee0eb3 100644 --- a/gdb/loongarch-tdep.c +++ b/gdb/loongarch-tdep.c @@ -26,6 +26,7 @@ #include "gdbcore.h" #include "linux-record.h" #include "loongarch-tdep.h" +#include "prologue-value.h" #include "record.h" #include "record-full.h" #include "reggroups.h" @@ -34,6 +35,33 @@ #include "trad-frame.h" #include "user-regs.h" +/* LoongArch frame cache structure. */ +struct loongarch_frame_cache +{ + /* The program counter at the start of the function. It is used to + identify this frame as a prologue frame. */ + CORE_ADDR func; + + /* The stack pointer at the time this frame was created; i.e. the + caller's stack pointer when this function was called. It is used + to identify this frame. */ + CORE_ADDR prev_sp; + + CORE_ADDR pc; + + int available_p; + + /* This register stores the frame base of the frame. */ + int framebase_reg; + + /* The framebase_offset is the distance from frame base to the prev_sp, + so the value of framebase_reg is just prev_sp - framebase_offset. */ + int framebase_offset; + + /* Saved register offsets. */ + trad_frame_saved_reg *saved_regs; +}; + /* Fetch the instruction at PC. */ static insn_t @@ -74,7 +102,9 @@ loongarch_insn_is_cond_branch (insn_t insn) || (insn & 0xfc000000) == 0x68000000 /* bltu */ || (insn & 0xfc000000) == 0x6c000000 /* bgeu */ || (insn & 0xfc000000) == 0x40000000 /* beqz */ - || (insn & 0xfc000000) == 0x44000000) /* bnez */ + || (insn & 0xfc000000) == 0x44000000 /* bnez */ + || (insn & 0xfc000300) == 0x48000000 /* bceqz */ + || (insn & 0xfc000300) == 0x48000100) /* bcnez */ return true; return false; } @@ -96,7 +126,9 @@ static bool loongarch_insn_is_ll (insn_t insn) { if ((insn & 0xff000000) == 0x20000000 /* ll.w */ - || (insn & 0xff000000) == 0x22000000) /* ll.d */ + || (insn & 0xff000000) == 0x22000000 /* ll.d */ + || (insn & 0xfffffc00) == 0x38578000 /* llacq.w */ + || (insn & 0xfffffc00) == 0x38578800) /* llacq.d */ return true; return false; } @@ -107,7 +139,10 @@ static bool loongarch_insn_is_sc (insn_t insn) { if ((insn & 0xff000000) == 0x21000000 /* sc.w */ - || (insn & 0xff000000) == 0x23000000) /* sc.d */ + || (insn & 0xff000000) == 0x23000000 /* sc.d */ + || (insn & 0xffff8000) == 0x38570000 /* sc.q */ + || (insn & 0xfffffc00) == 0x38578400 /* screl.w */ + || (insn & 0xfffffc00) == 0x38578c00) /* screl.d */ return true; return false; } @@ -118,13 +153,23 @@ loongarch_insn_is_sc (insn_t insn) static CORE_ADDR loongarch_scan_prologue (struct gdbarch *gdbarch, CORE_ADDR start_pc, CORE_ADDR limit_pc, const frame_info_ptr &this_frame, - struct trad_frame_cache *this_cache) + struct loongarch_frame_cache *this_cache) { CORE_ADDR cur_pc = start_pc, prologue_end = 0; int32_t sp = LOONGARCH_SP_REGNUM; int32_t fp = LOONGARCH_FP_REGNUM; + int32_t ra = LOONGARCH_RA_REGNUM; int32_t reg_value[32] = {0}; int32_t reg_used[32] = {1, 0}; + int i; + + /* Track 32 GPR, ORIG_A0, PC, BADV in prologue. */ + pv_t regs[LOONGARCH_USED_NUM_GREGSET]; + + for (i = 0; i < LOONGARCH_USED_NUM_GREGSET; i++) + regs[i] = pv_register (i, 0); + + pv_area stack (LOONGARCH_SP_REGNUM, gdbarch_addr_bit (gdbarch)); while (cur_pc < limit_pc) { @@ -134,27 +179,40 @@ loongarch_scan_prologue (struct gdbarch *gdbarch, CORE_ADDR start_pc, int32_t rj = loongarch_decode_imm ("5:5", insn, 0); int32_t rk = loongarch_decode_imm ("10:5", insn, 0); int32_t si12 = loongarch_decode_imm ("10:12", insn, 1); + int32_t si14 = loongarch_decode_imm ("10:14", insn, 1); int32_t si20 = loongarch_decode_imm ("5:20", insn, 1); if ((insn & 0xffc00000) == 0x02c00000 /* addi.d sp,sp,si12 */ && rd == sp && rj == sp && si12 < 0) { prologue_end = cur_pc + insn_len; + regs[rd] = pv_add_constant (regs[rj], si12); } else if ((insn & 0xffc00000) == 0x02c00000 /* addi.d fp,sp,si12 */ && rd == fp && rj == sp && si12 > 0) { prologue_end = cur_pc + insn_len; + regs[rd] = pv_add_constant (regs[rj], si12); } else if ((insn & 0xffc00000) == 0x29c00000 /* st.d rd,sp,si12 */ && rj == sp) { prologue_end = cur_pc + insn_len; + /* ra, fp, s0~s8 are saved by callee with sp as a base */ + if ((rd >= fp && rd <= fp + 9) || rd == ra) + { + stack.store (pv_add_constant (regs[sp], si12), register_size (gdbarch, rd), regs[rd]); + } } else if ((insn & 0xff000000) == 0x27000000 /* stptr.d rd,sp,si14 */ && rj == sp) { prologue_end = cur_pc + insn_len; + /* ra, fp, s0~s8 are saved by callee with sp as a base */ + if ((rd >= fp && rd <= fp + 9) || rd == ra) + { + stack.store (pv_add_constant (regs[sp], si14), register_size (gdbarch, rd), regs[rd]); + } } else if ((insn & 0xfe000000) == 0x14000000) /* lu12i.w rd,si20 */ { @@ -189,6 +247,38 @@ loongarch_scan_prologue (struct gdbarch *gdbarch, CORE_ADDR start_pc, if (prologue_end == 0) prologue_end = cur_pc; + if (this_cache == NULL) + return prologue_end; + + if (pv_is_register (regs[LOONGARCH_FP_REGNUM], LOONGARCH_SP_REGNUM)) + { + /* Frame pointer is fp. */ + this_cache->framebase_reg = LOONGARCH_FP_REGNUM; + this_cache->framebase_offset = -regs[LOONGARCH_FP_REGNUM].k; + } + else if (pv_is_register (regs[LOONGARCH_SP_REGNUM], LOONGARCH_SP_REGNUM)) + { + /* Try the stack pointer. */ + this_cache->framebase_reg = LOONGARCH_SP_REGNUM; + this_cache->framebase_offset = -regs[LOONGARCH_SP_REGNUM].k; + } + else + { + /* We're just out of luck. We don't know where the frame is. */ + this_cache->framebase_reg = -1; + this_cache->framebase_offset = 0; + } + + for (i = 0; i < LOONGARCH_USED_NUM_GREGSET; i++) + { + CORE_ADDR offset; + + if (stack.find_reg (gdbarch, i, &offset)) + { + this_cache->saved_regs[i].set_addr (offset); + } + } + return prologue_end; } @@ -314,6 +404,20 @@ loongarch_next_pc (struct regcache *regcache, CORE_ADDR cur_pc) if (rj != 0) next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1); } + else if ((insn & 0xfc000300) == 0x48000000) /* bceqz cj, offs21 */ + { + LONGEST cj = regcache_raw_get_signed (regcache, + loongarch_decode_imm ("5:3", insn, 0) + LOONGARCH_FIRST_FCC_REGNUM); + if (cj == 0) + next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1); + } + else if ((insn & 0xfc000300) == 0x48000100) /* bcnez cj, offs21 */ + { + LONGEST cj = regcache_raw_get_signed (regcache, + loongarch_decode_imm ("5:3", insn, 0) + LOONGARCH_FIRST_FCC_REGNUM); + if (cj != 0) + next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1); + } else if ((insn & 0xffff8000) == 0x002b0000) /* syscall */ { if (tdep->syscall_next_pc != nullptr) @@ -410,38 +514,157 @@ loongarch_frame_align (struct gdbarch *gdbarch, CORE_ADDR addr) return align_down (addr, 16); } +/* Analyze the function prologue for THIS_FRAME and populate the frame + cache CACHE. */ + +static void +loongarch_analyze_prologue (const frame_info_ptr &this_frame, + struct loongarch_frame_cache *cache) +{ + CORE_ADDR block_addr = get_frame_address_in_block (this_frame); + CORE_ADDR prologue_start = 0; + CORE_ADDR prologue_end = 0; + CORE_ADDR pc = get_frame_pc (this_frame); + struct gdbarch *gdbarch = get_frame_arch (this_frame); + + cache->pc = pc; + + /* Assume we do not find a frame. */ + cache->framebase_reg = -1; + cache->framebase_offset = 0; + + + if (find_pc_partial_function (block_addr, NULL, &prologue_start, + &prologue_end)) + { + struct symtab_and_line sal = find_pc_line (prologue_start, 0); + + if (sal.line == 0) + { + /* No line info so use the current PC. */ + prologue_end = pc; + } + else if (sal.end < prologue_end) + { + if (sal.symtab != NULL && sal.symtab->language () == language_asm) + { + /* This sal.end usually cannot point to prologue_end correctly + in asm file. */ + prologue_end = pc; + } + else + { + /* The next line begins after the prologue_end. */ + prologue_end = sal.end; + } + } + + } + else + { + /* We're in the boondocks: we have no idea where the start of the + function is. */ + return; + } + + prologue_end = std::min (prologue_end, pc); + loongarch_scan_prologue (gdbarch, prologue_start, prologue_end, nullptr, cache); +} + +/* Fill in *CACHE with information about the prologue of *THIS_FRAME. */ + +static void +loongarch_frame_cache_1 (const frame_info_ptr &this_frame, + struct loongarch_frame_cache *cache) +{ + CORE_ADDR unwound_fp; + int reg; + + loongarch_analyze_prologue (this_frame, cache); + + if (cache->framebase_reg == -1) + return; + + unwound_fp = get_frame_register_unsigned (this_frame, cache->framebase_reg); + if (unwound_fp == 0) + return; + + cache->prev_sp = unwound_fp; + cache->prev_sp += cache->framebase_offset; + + /* Calculate actual addresses of saved registers using offsets + determined by loongarch_scan_prologue. */ + for (reg = 0; reg < gdbarch_num_regs (get_frame_arch (this_frame)); reg++) + if (cache->saved_regs[reg].is_addr ()) + cache->saved_regs[reg].set_addr (cache->saved_regs[reg].addr () + + cache->prev_sp); + + cache->func = get_frame_func (this_frame); + + cache->available_p = 1; +} + /* Generate, or return the cached frame cache for frame unwinder. */ -static struct trad_frame_cache * +static struct loongarch_frame_cache * loongarch_frame_cache (const frame_info_ptr &this_frame, void **this_cache) { - struct trad_frame_cache *cache; - CORE_ADDR pc; + struct loongarch_frame_cache *cache; - if (*this_cache != nullptr) - return (struct trad_frame_cache *) *this_cache; + if (*this_cache != NULL) + return (struct loongarch_frame_cache *) *this_cache; - cache = trad_frame_cache_zalloc (this_frame); + cache = FRAME_OBSTACK_ZALLOC (struct loongarch_frame_cache); + cache->saved_regs = trad_frame_alloc_saved_regs (this_frame); *this_cache = cache; - trad_frame_set_reg_realreg (cache, LOONGARCH_PC_REGNUM, LOONGARCH_RA_REGNUM); - - pc = get_frame_address_in_block (this_frame); - trad_frame_set_id (cache, frame_id_build_unavailable_stack (pc)); + try + { + loongarch_frame_cache_1 (this_frame, cache); + } + catch (const gdb_exception_error &ex) + { + if (ex.error != NOT_AVAILABLE_ERROR) + throw; + } return cache; } +/* Implement the "stop_reason" frame_unwind method. */ + +static enum unwind_stop_reason +loongarch_frame_unwind_stop_reason (const frame_info_ptr &this_frame, + void **this_cache) +{ + struct loongarch_frame_cache *cache + = loongarch_frame_cache (this_frame, this_cache); + + if (!cache->available_p) + return UNWIND_UNAVAILABLE; + + /* We've hit a wall, stop. */ + if (cache->prev_sp == 0) + return UNWIND_OUTERMOST; + + return UNWIND_NO_REASON; +} + /* Implement the this_id callback for frame unwinder. */ static void loongarch_frame_this_id (const frame_info_ptr &this_frame, void **prologue_cache, struct frame_id *this_id) { - struct trad_frame_cache *info; + struct loongarch_frame_cache *cache + = loongarch_frame_cache (this_frame, prologue_cache); - info = loongarch_frame_cache (this_frame, prologue_cache); - trad_frame_get_id (info, this_id); + /* Our frame ID for a normal frame is the current function's starting + PC and the caller's SP when we were called. */ + if (!cache->available_p) + *this_id = frame_id_build_unavailable_stack (cache->func); + else + *this_id = frame_id_build (cache->prev_sp, cache->func); } /* Implement the prev_register callback for frame unwinder. */ @@ -450,17 +673,55 @@ static struct value * loongarch_frame_prev_register (const frame_info_ptr &this_frame, void **prologue_cache, int regnum) { - struct trad_frame_cache *info; + struct loongarch_frame_cache *cache + = loongarch_frame_cache (this_frame, prologue_cache); + + /* If we are asked to unwind the PC, then we need to return the RA + instead. The prologue may save PC, but it will point into this + frame's prologue, not the previou frame's resume location. */ + if (regnum == LOONGARCH_PC_REGNUM) + { + CORE_ADDR ra; + ra = frame_unwind_register_unsigned (this_frame, LOONGARCH_RA_REGNUM); + + return frame_unwind_got_constant (this_frame, regnum, ra); + } + + /* SP is generally not saved to the stack, but this frame is + identified by the next frame's stack pointer at the time of the + call. The value was already reconstructed into PREV_SP. */ + /* + addi.d $sp, $sp, -32 + st.d $ra, $sp, 24 + st.d $fp, $sp, 16 + addi.d $fp, $sp, 32 + + +->+----------+ + | | saved ra | + | | saved fp | + | | | + | | | + | +----------+ <- Previous SP == FP + | | saved ra | + +--| saved fp | + | | + | | + +----------+ <- SP + */ + + if (regnum == LOONGARCH_SP_REGNUM) + return frame_unwind_got_constant (this_frame, regnum, cache->prev_sp); + + return trad_frame_get_prev_register (this_frame, cache->saved_regs, regnum); - info = loongarch_frame_cache (this_frame, prologue_cache); - return trad_frame_get_register (info, this_frame, regnum); } +/* LoongArch prologue unwinder. */ static const struct frame_unwind_legacy loongarch_frame_unwind ( "loongarch prologue", /*.type =*/NORMAL_FRAME, /*.unwinder_class=*/FRAME_UNWIND_ARCH, - /*.stop_reason =*/default_frame_unwind_stop_reason, + /*.stop_reason =*/loongarch_frame_unwind_stop_reason, /*.this_id =*/loongarch_frame_this_id, /*.prev_register =*/loongarch_frame_prev_register, /*.unwind_data =*/nullptr, @@ -1879,7 +2140,7 @@ loongarch_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) set_gdbarch_frame_align (gdbarch, loongarch_frame_align); /* Breakpoint manipulation. */ - set_gdbarch_software_single_step (gdbarch, loongarch_software_single_step); + set_gdbarch_get_next_pcs (gdbarch, loongarch_software_single_step); set_gdbarch_breakpoint_kind_from_pc (gdbarch, loongarch_breakpoint::kind_from_pc); set_gdbarch_sw_breakpoint_from_kind (gdbarch, loongarch_breakpoint::bp_from_kind); set_gdbarch_have_nonsteppable_watchpoint (gdbarch, 1); @@ -2405,9 +2666,7 @@ loongarch_process_record (struct gdbarch *gdbarch, struct regcache *regcache, return ret; } -void _initialize_loongarch_tdep (); -void -_initialize_loongarch_tdep () +INIT_GDB_FILE (loongarch_tdep) { gdbarch_register (bfd_arch_loongarch, loongarch_gdbarch_init, nullptr); } |