diff --git a/gdb/loongarch-linux-nat.c b/gdb/loongarch-linux-nat.c index 9f5760066b3..873628775e2 100644 --- a/gdb/loongarch-linux-nat.c +++ b/gdb/loongarch-linux-nat.c @@ -54,6 +54,11 @@ class loongarch_linux_nat_target final : public linux_nat_trad_target bool stopped_by_watchpoint () override; bool stopped_data_address (CORE_ADDR *) override; + int insert_hw_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) override; + int remove_hw_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) override; + /* Override the GNU/Linux inferior startup hook. */ void post_startup_inferior (ptid_t) override; @@ -489,7 +494,8 @@ loongarch_linux_nat_target::can_use_hw_breakpoint (enum bptype type, int cnt, } else if (type == bp_hardware_breakpoint) { - return 0; + if (loongarch_num_bp_regs == 0) + return 0; } else gdb_assert_not_reached ("unexpected breakpoint type"); @@ -624,6 +630,72 @@ loongarch_linux_nat_target::stopped_by_watchpoint () return stopped_data_address (&addr); } +/* Insert a hardware-assisted breakpoint at BP_TGT->reqstd_address. + Return 0 on success, -1 on failure. */ + +int +loongarch_linux_nat_target::insert_hw_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + int ret; + CORE_ADDR addr = bp_tgt->placed_address = bp_tgt->reqstd_address; + int len; + const enum target_hw_bp_type type = hw_execute; + struct loongarch_debug_reg_state *state + = loongarch_get_debug_reg_state (inferior_ptid.pid ()); + + gdbarch_breakpoint_from_pc (gdbarch, &addr, &len); + + if (show_debug_regs) + gdb_printf (gdb_stdlog, + "insert_hw_breakpoint on entry (addr=0x%08lx, len=%d))\n", + (unsigned long) addr, len); + + ret = loongarch_handle_breakpoint (type, addr, len, 1 /* is_insert */, + inferior_ptid, state); + + if (show_debug_regs) + { + loongarch_show_debug_reg_state (state, + "insert_hw_breakpoint", addr, len, type); + } + + return ret; +} + +/* Remove a hardware-assisted breakpoint at BP_TGT->placed_address. + Return 0 on success, -1 on failure. */ + +int +loongarch_linux_nat_target::remove_hw_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + int ret; + CORE_ADDR addr = bp_tgt->placed_address; + int len = 4; + const enum target_hw_bp_type type = hw_execute; + struct loongarch_debug_reg_state *state + = loongarch_get_debug_reg_state (inferior_ptid.pid ()); + + gdbarch_breakpoint_from_pc (gdbarch, &addr, &len); + + if (show_debug_regs) + gdb_printf (gdb_stdlog, + "remove_hw_breakpoint on entry (addr=0x%08lx, len=%d))\n", + (unsigned long) addr, len); + + ret = loongarch_handle_breakpoint (type, addr, len, 0 /* is_insert */, + inferior_ptid, state); + + if (show_debug_regs) + { + loongarch_show_debug_reg_state (state, + "remove_hw_watchpoint", addr, len, type); + } + + return ret; +} + /* Implement the virtual inf_ptrace_target::post_startup_inferior method. */ void diff --git a/gdb/nat/loongarch-hw-point.c b/gdb/nat/loongarch-hw-point.c index 44af61abd00..089f3bd49a9 100644 --- a/gdb/nat/loongarch-hw-point.c +++ b/gdb/nat/loongarch-hw-point.c @@ -27,6 +27,7 @@ /* Number of hardware breakpoints/watchpoints the target supports. They are initialized with values obtained via ptrace. */ +int loongarch_num_bp_regs; int loongarch_num_wp_regs; /* Given the hardware breakpoint or watchpoint type TYPE and its @@ -112,7 +113,10 @@ loongarch_dr_state_insert_one_point (ptid_t ptid, } else { - return -1; + num_regs = loongarch_num_bp_regs; + dr_addr_p = state->dr_addr_bp; + dr_ctrl_p = state->dr_ctrl_bp; + dr_ref_count = state->dr_ref_count_bp; } ctrl = loongarch_point_encode_ctrl_reg (type, len); @@ -184,7 +188,10 @@ loongarch_dr_state_remove_one_point (ptid_t ptid, } else { - return -1; + num_regs = loongarch_num_bp_regs; + dr_addr_p = state->dr_addr_bp; + dr_ctrl_p = state->dr_ctrl_bp; + dr_ref_count = state->dr_ref_count_bp; } ctrl = loongarch_point_encode_ctrl_reg (type, len); @@ -214,6 +221,20 @@ loongarch_dr_state_remove_one_point (ptid_t ptid, return 0; } +int +loongarch_handle_breakpoint (enum target_hw_bp_type type, CORE_ADDR addr, + int len, int is_insert, ptid_t ptid, + struct loongarch_debug_reg_state *state) +{ + if (is_insert) + return loongarch_dr_state_insert_one_point (ptid, state, type, addr, + len, -1); + else + return loongarch_dr_state_remove_one_point (ptid, state, type, addr, + len, -1); +} + + int loongarch_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr, int len, int is_insert, ptid_t ptid, @@ -234,12 +255,12 @@ bool loongarch_any_set_debug_regs_state (loongarch_debug_reg_state *state, bool watchpoint) { - int count = watchpoint ? loongarch_num_wp_regs : 0; + int count = watchpoint ? loongarch_num_wp_regs : loongarch_num_bp_regs; if (count == 0) return false; - const CORE_ADDR *addr = watchpoint ? state->dr_addr_wp : 0; - const unsigned int *ctrl = watchpoint ? state->dr_ctrl_wp : 0; + const CORE_ADDR *addr = watchpoint ? state->dr_addr_wp : state->dr_addr_bp; + const unsigned int *ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp; for (int i = 0; i < count; i++) if (addr[i] != 0 || ctrl[i] != 0) @@ -268,6 +289,12 @@ loongarch_show_debug_reg_state (struct loongarch_debug_reg_state *state, : "??unknown??")))); debug_printf (":\n"); + debug_printf ("\tBREAKPOINTs:\n"); + for (i = 0; i < loongarch_num_bp_regs; i++) + debug_printf ("\tBP%d: addr=%s, ctrl=0x%08x, ref.count=%d\n", + i, core_addr_to_string_nz (state->dr_addr_bp[i]), + state->dr_ctrl_bp[i], state->dr_ref_count_bp[i]); + debug_printf ("\tWATCHPOINTs:\n"); for (i = 0; i < loongarch_num_wp_regs; i++) debug_printf ("\tWP%d: addr=%s, ctrl=0x%08x, ref.count=%d\n", diff --git a/gdb/nat/loongarch-hw-point.h b/gdb/nat/loongarch-hw-point.h index 86e5aa3f452..fd79285a046 100644 --- a/gdb/nat/loongarch-hw-point.h +++ b/gdb/nat/loongarch-hw-point.h @@ -33,6 +33,7 @@ Neither of these values may exceed the width of dr_changed_t measured in bits. */ +#define LOONGARCH_HBP_MAX_NUM 8 #define LOONGARCH_HWP_MAX_NUM 8 @@ -53,12 +54,18 @@ struct loongarch_debug_reg_state { + /* hardware breakpoint */ + CORE_ADDR dr_addr_bp[LOONGARCH_HBP_MAX_NUM]; + unsigned int dr_ctrl_bp[LOONGARCH_HBP_MAX_NUM]; + unsigned int dr_ref_count_bp[LOONGARCH_HBP_MAX_NUM]; + /* hardware watchpoint */ CORE_ADDR dr_addr_wp[LOONGARCH_HWP_MAX_NUM]; unsigned int dr_ctrl_wp[LOONGARCH_HWP_MAX_NUM]; unsigned int dr_ref_count_wp[LOONGARCH_HWP_MAX_NUM]; }; +extern int loongarch_num_bp_regs; extern int loongarch_num_wp_regs; /* Invoked when IDXth breakpoint/watchpoint register pair needs to be @@ -68,6 +75,10 @@ void loongarch_notify_debug_reg_change (ptid_t ptid, int is_watchpoint, unsigned int idx); +int loongarch_handle_breakpoint (enum target_hw_bp_type type, CORE_ADDR addr, + int len, int is_insert, ptid_t ptid, + struct loongarch_debug_reg_state *state); + int loongarch_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr, int len, int is_insert, ptid_t ptid, struct loongarch_debug_reg_state *state); diff --git a/gdb/nat/loongarch-linux-hw-point.c b/gdb/nat/loongarch-linux-hw-point.c index cced5a388ab..fbc80fdc2a1 100644 --- a/gdb/nat/loongarch-linux-hw-point.c +++ b/gdb/nat/loongarch-linux-hw-point.c @@ -62,9 +62,6 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint, dr_changed_t *dr_changed_ptr; dr_changed_t dr_changed; - if (!is_watchpoint) - return -1; - if (info == NULL) { info = XCNEW (struct arch_lwp_info); @@ -74,14 +71,19 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint, if (show_debug_regs) { debug_printf ("loongarch_dr_change_callback: \n\tOn entry:\n"); - debug_printf ("\ttid%d, dr_changed_wp=0x%s\n", - tid, phex (info->dr_changed_wp, 8)); + debug_printf ("\ttid%d, dr_changed_bp=0x%s, " + "dr_changed_wp=0x%s\n", tid, + phex (info->dr_changed_bp, 8), + phex (info->dr_changed_wp, 8)); } - dr_changed_ptr = &info->dr_changed_wp; + dr_changed_ptr = is_watchpoint ? &info->dr_changed_wp + : &info->dr_changed_bp; dr_changed = *dr_changed_ptr; - gdb_assert (idx >= 0 && idx <= loongarch_num_wp_regs); + gdb_assert (idx >= 0 + && (idx <= (is_watchpoint ? loongarch_num_wp_regs + : loongarch_num_bp_regs))); /* The actual update is done later just before resuming the lwp, we just mark that one register pair needs updating. */ @@ -95,8 +97,10 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint, if (show_debug_regs) { - debug_printf ("\tOn exit:\n\ttid%d, dr_changed_wp=0x%s\n", - tid, phex (info->dr_changed_wp, 8)); + debug_printf ("\tOn exit:\n\ttid%d, dr_changed_bp=0x%s, " + "dr_changed_wp=0x%s\n", tid, + phex (info->dr_changed_bp, 8), + phex (info->dr_changed_wp, 8)); } return 0; @@ -136,9 +140,9 @@ loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state, memset (®s, 0, sizeof (regs)); iov.iov_base = ®s; - count = watchpoint ? loongarch_num_wp_regs : 0; - addr = watchpoint ? state->dr_addr_wp : 0; - ctrl = watchpoint ? state->dr_ctrl_wp : 0; + count = watchpoint ? loongarch_num_wp_regs : loongarch_num_bp_regs; + addr = watchpoint ? state->dr_addr_wp : state->dr_addr_bp; + ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp; if (count == 0) return; @@ -151,7 +155,9 @@ loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state, regs.dbg_regs[i].ctrl = ctrl[i]; } - if (ptrace(PTRACE_SETREGSET, tid, NT_LOONGARCH_HW_WATCH, (void *) &iov)) + if (ptrace(PTRACE_SETREGSET, tid, + watchpoint ? NT_LOONGARCH_HW_WATCH : NT_LOONGARCH_HW_BREAK, + (void *) &iov)) { if (errno == EINVAL) error (_("Invalid argument setting hardware debug registers")); @@ -194,6 +200,25 @@ loongarch_linux_get_debug_reg_capacity (int tid) loongarch_num_wp_regs = 0; } + /* Get hardware breakpoint register info. */ + result = ptrace (PTRACE_GETREGSET, tid, NT_LOONGARCH_HW_BREAK, &iov); + if ( result == 0) + { + loongarch_num_bp_regs = LOONGARCH_DEBUG_NUM_SLOTS (dreg_state.dbg_info); + if (loongarch_num_bp_regs > LOONGARCH_HBP_MAX_NUM) + { + warning (_("Unexpected number of hardware breakpoint registers" + " reported by ptrace, got %d, expected %d."), + loongarch_num_bp_regs, LOONGARCH_HBP_MAX_NUM); + loongarch_num_bp_regs = LOONGARCH_HBP_MAX_NUM; + } + } + else + { + warning (_("Unable to determine the number of hardware breakpoints" + " available.")); + loongarch_num_bp_regs = 0; + } } /* Return the debug register state for process PID. If no existing diff --git a/gdb/nat/loongarch-linux-hw-point.h b/gdb/nat/loongarch-linux-hw-point.h index 4086907e3c0..8d96443f5e0 100644 --- a/gdb/nat/loongarch-linux-hw-point.h +++ b/gdb/nat/loongarch-linux-hw-point.h @@ -93,6 +93,7 @@ struct arch_lwp_info /* When bit N is 1, it indicates the Nth hardware breakpoint or watchpoint register pair needs to be updated when the thread is resumed; see loongarch_linux_prepare_to_resume. */ + dr_changed_t dr_changed_bp; dr_changed_t dr_changed_wp; }; diff --git a/gdb/nat/loongarch-linux.c b/gdb/nat/loongarch-linux.c index 088d0fcbf34..03a0aaf9fd9 100644 --- a/gdb/nat/loongarch-linux.c +++ b/gdb/nat/loongarch-linux.c @@ -43,7 +43,8 @@ loongarch_linux_prepare_to_resume (struct lwp_info *lwp) if (info == NULL) return; - if (DR_HAS_CHANGED (info->dr_changed_wp)) + if (DR_HAS_CHANGED (info->dr_changed_bp) + || DR_HAS_CHANGED (info->dr_changed_wp)) { ptid_t ptid = ptid_of_lwp (lwp); int tid = ptid.lwp (); @@ -53,9 +54,19 @@ loongarch_linux_prepare_to_resume (struct lwp_info *lwp) if (show_debug_regs) debug_printf ("prepare_to_resume thread %d\n", tid); - loongarch_linux_set_debug_regs (state, tid, 1); - DR_CLEAR_CHANGED (info->dr_changed_wp); + /* Watchpoints. */ + if (DR_HAS_CHANGED (info->dr_changed_wp)) + { + loongarch_linux_set_debug_regs (state, tid, 1); + DR_CLEAR_CHANGED (info->dr_changed_wp); + } + /* Breakpoints. */ + if (DR_HAS_CHANGED (info->dr_changed_bp)) + { + loongarch_linux_set_debug_regs (state, tid, 0); + DR_CLEAR_CHANGED (info->dr_changed_bp); + } } } @@ -72,6 +83,8 @@ loongarch_linux_new_thread (struct lwp_info *lwp) /* If there are hardware breakpoints/watchpoints in the process then mark that all the hardware breakpoint/watchpoint register pairs for this thread need to be initialized (with data from arch_process_info.debug_reg_state). */ + if (loongarch_any_set_debug_regs_state (state, false)) + DR_MARK_ALL_CHANGED (info->dr_changed_bp, loongarch_num_bp_regs); if (loongarch_any_set_debug_regs_state (state, true)) DR_MARK_ALL_CHANGED (info->dr_changed_wp, loongarch_num_wp_regs); diff --git a/include/elf/common.h b/include/elf/common.h index 2d183342b49..c9920e7731a 100644 --- a/include/elf/common.h +++ b/include/elf/common.h @@ -751,6 +751,8 @@ /* note name must be "LINUX". */ #define NT_LARCH_LBT 0xa04 /* LoongArch Binary Translation registers */ /* note name must be "CORE". */ +#define NT_LOONGARCH_HW_BREAK 0xa05 /* LoongArch hardware breakpoint registers */ + /* note name must be "LINUX". */ #define NT_LOONGARCH_HW_WATCH 0xa06 /* LoongArch hardware watchpoint registers */ /* note name must be "LINUX". */ #define NT_RISCV_CSR 0x900 /* RISC-V Control and Status Registers */