X-Git-Url: http://git.efficios.com/?a=blobdiff_plain;f=gdb%2Fnat%2Faarch64-linux-hw-point.c;h=7e1bb5c2ff0d6a41c79f94c7bb4d622560e722b0;hb=2301204a3b3cd6553f7490498b3adc5973157c1b;hp=ded4a197f3a293f870386857cbf3fdd7d573e71d;hpb=554717a3edce7e266d409cd2c9c76d92584ac156;p=deliverable%2Fbinutils-gdb.git diff --git a/gdb/nat/aarch64-linux-hw-point.c b/gdb/nat/aarch64-linux-hw-point.c index ded4a197f3..7e1bb5c2ff 100644 --- a/gdb/nat/aarch64-linux-hw-point.c +++ b/gdb/nat/aarch64-linux-hw-point.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2009-2015 Free Software Foundation, Inc. +/* Copyright (C) 2009-2020 Free Software Foundation, Inc. Contributed by ARM Ltd. This file is part of GDB. @@ -16,8 +16,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "common-defs.h" -#include "break-common.h" +#include "gdbsupport/common-defs.h" +#include "gdbsupport/break-common.h" +#include "gdbsupport/common-regcache.h" +#include "nat/linux-nat.h" #include "aarch64-linux-hw-point.h" #include @@ -32,29 +34,52 @@ int aarch64_num_bp_regs; int aarch64_num_wp_regs; +/* True if this kernel does not have the bug described by PR + external/20207 (Linux >= 4.10). A fixed kernel supports any + contiguous range of bits in 8-bit byte DR_CONTROL_MASK. A buggy + kernel supports only 0x01, 0x03, 0x0f and 0xff. We start by + assuming the bug is fixed, and then detect the bug at + PTRACE_SETREGSET time. */ +static bool kernel_supports_any_contiguous_range = true; + +/* Return starting byte 0..7 incl. of a watchpoint encoded by CTRL. */ + +unsigned int +aarch64_watchpoint_offset (unsigned int ctrl) +{ + uint8_t mask = DR_CONTROL_MASK (ctrl); + unsigned retval; + + /* Shift out bottom zeros. */ + for (retval = 0; mask && (mask & 1) == 0; ++retval) + mask >>= 1; + + return retval; +} + /* Utility function that returns the length in bytes of a watchpoint according to the content of a hardware debug control register CTRL. - Note that the kernel currently only supports the following Byte - Address Select (BAS) values: 0x1, 0x3, 0xf and 0xff, which means - that for a hardware watchpoint, its valid length can only be 1 - byte, 2 bytes, 4 bytes or 8 bytes. */ + Any contiguous range of bytes in CTRL is supported. The returned + value can be between 0..8 (inclusive). */ unsigned int aarch64_watchpoint_length (unsigned int ctrl) { - switch (DR_CONTROL_LENGTH (ctrl)) - { - case 0x01: - return 1; - case 0x03: - return 2; - case 0x0f: - return 4; - case 0xff: - return 8; - default: - return 0; - } + uint8_t mask = DR_CONTROL_MASK (ctrl); + unsigned retval; + + /* Shift out bottom zeros. */ + mask >>= aarch64_watchpoint_offset (ctrl); + + /* Count bottom ones. */ + for (retval = 0; (mask & 1) != 0; ++retval) + mask >>= 1; + + if (mask != 0) + error (_("Unexpected hardware watchpoint length register value 0x%x"), + DR_CONTROL_MASK (ctrl)); + + return retval; } /* Given the hardware breakpoint or watchpoint type TYPE and its @@ -62,10 +87,13 @@ aarch64_watchpoint_length (unsigned int ctrl) breakpoint/watchpoint control register. */ static unsigned int -aarch64_point_encode_ctrl_reg (enum target_hw_bp_type type, int len) +aarch64_point_encode_ctrl_reg (enum target_hw_bp_type type, int offset, int len) { unsigned int ctrl, ttype; + gdb_assert (offset == 0 || kernel_supports_any_contiguous_range); + gdb_assert (offset + len <= AARCH64_HWP_MAX_LEN_PER_REG); + /* type */ switch (type) { @@ -87,8 +115,8 @@ aarch64_point_encode_ctrl_reg (enum target_hw_bp_type type, int len) ctrl = ttype << 3; - /* length bitmask */ - ctrl |= ((1 << len) - 1) << 5; + /* offset and length bitmask */ + ctrl |= ((1 << len) - 1) << (5 + offset); /* enabled at el0 */ ctrl |= (2 << 1) | 1; @@ -111,64 +139,86 @@ aarch64_point_encode_ctrl_reg (enum target_hw_bp_type type, int len) static int aarch64_point_is_aligned (int is_watchpoint, CORE_ADDR addr, int len) { - unsigned int alignment = is_watchpoint ? AARCH64_HWP_ALIGNMENT - : AARCH64_HBP_ALIGNMENT; + unsigned int alignment = 0; + + if (is_watchpoint) + alignment = AARCH64_HWP_ALIGNMENT; + else + { + struct regcache *regcache + = get_thread_regcache_for_ptid (current_lwp_ptid ()); + + /* Set alignment to 2 only if the current process is 32-bit, + since thumb instruction can be 2-byte aligned. Otherwise, set + alignment to AARCH64_HBP_ALIGNMENT. */ + if (regcache_register_size (regcache, 0) == 8) + alignment = AARCH64_HBP_ALIGNMENT; + else + alignment = 2; + } if (addr & (alignment - 1)) return 0; - if (len != 8 && len != 4 && len != 2 && len != 1) + if ((!kernel_supports_any_contiguous_range + && len != 8 && len != 4 && len != 2 && len != 1) + || (kernel_supports_any_contiguous_range + && (len < 1 || len > 8))) return 0; return 1; } /* Given the (potentially unaligned) watchpoint address in ADDR and - length in LEN, return the aligned address and aligned length in - *ALIGNED_ADDR_P and *ALIGNED_LEN_P, respectively. The returned - aligned address and length will be valid values to write to the - hardware watchpoint value and control registers. + length in LEN, return the aligned address, offset from that base + address, and aligned length in *ALIGNED_ADDR_P, *ALIGNED_OFFSET_P + and *ALIGNED_LEN_P, respectively. The returned values will be + valid values to write to the hardware watchpoint value and control + registers. The given watchpoint may get truncated if more than one hardware register is needed to cover the watched region. *NEXT_ADDR_P and *NEXT_LEN_P, if non-NULL, will return the address and length of the remaining part of the watchpoint (which can be processed - by calling this routine again to generate another aligned address - and length pair. + by calling this routine again to generate another aligned address, + offset and length tuple. Essentially, unaligned watchpoint is achieved by minimally enlarging the watched area to meet the alignment requirement, and if necessary, splitting the watchpoint over several hardware - watchpoint registers. The trade-off is that there will be - false-positive hits for the read-type or the access-type hardware - watchpoints; for the write type, which is more commonly used, there - will be no such issues, as the higher-level breakpoint management - in gdb always examines the exact watched region for any content - change, and transparently resumes a thread from a watchpoint trap - if there is no change to the watched region. + watchpoint registers. + + On kernels that predate the support for Byte Address Select (BAS) + in the hardware watchpoint control register, the offset from the + base address is always zero, and so in that case the trade-off is + that there will be false-positive hits for the read-type or the + access-type hardware watchpoints; for the write type, which is more + commonly used, there will be no such issues, as the higher-level + breakpoint management in gdb always examines the exact watched + region for any content change, and transparently resumes a thread + from a watchpoint trap if there is no change to the watched region. Another limitation is that because the watched region is enlarged, - the watchpoint fault address returned by + the watchpoint fault address discovered by aarch64_stopped_data_address may be outside of the original watched region, especially when the triggering instruction is accessing a larger region. When the fault address is not within any known range, watchpoints_triggered in gdb will get confused, as the higher-level watchpoint management is only aware of original watched regions, and will think that some unknown watchpoint has - been triggered. In such a case, gdb may stop without displaying - any detailed information. - - Once the kernel provides the full support for Byte Address Select - (BAS) in the hardware watchpoint control register, these - limitations can be largely relaxed with some further work. */ + been triggered. To prevent such a case, + aarch64_stopped_data_address implementations in gdb and gdbserver + try to match the trapped address with a watched region, and return + an address within the latter. */ static void aarch64_align_watchpoint (CORE_ADDR addr, int len, CORE_ADDR *aligned_addr_p, - int *aligned_len_p, CORE_ADDR *next_addr_p, - int *next_len_p) + int *aligned_offset_p, int *aligned_len_p, + CORE_ADDR *next_addr_p, int *next_len_p, + CORE_ADDR *next_addr_orig_p) { int aligned_len; - unsigned int offset; + unsigned int offset, aligned_offset; CORE_ADDR aligned_addr; const unsigned int alignment = AARCH64_HWP_ALIGNMENT; const unsigned int max_wp_len = AARCH64_HWP_MAX_LEN_PER_REG; @@ -179,10 +229,12 @@ aarch64_align_watchpoint (CORE_ADDR addr, int len, CORE_ADDR *aligned_addr_p, if (len <= 0) return; - /* Address to be put into the hardware watchpoint value register - must be aligned. */ + /* The address put into the hardware watchpoint value register must + be aligned. */ offset = addr & (alignment - 1); aligned_addr = addr - offset; + aligned_offset + = kernel_supports_any_contiguous_range ? addr & (alignment - 1) : 0; gdb_assert (offset >= 0 && offset < alignment); gdb_assert (aligned_addr >= 0 && aligned_addr <= addr); @@ -190,9 +242,10 @@ aarch64_align_watchpoint (CORE_ADDR addr, int len, CORE_ADDR *aligned_addr_p, if (offset + len >= max_wp_len) { - /* Need more than one watchpoint registers; truncate it at the + /* Need more than one watchpoint register; truncate at the alignment boundary. */ - aligned_len = max_wp_len; + aligned_len + = max_wp_len - (kernel_supports_any_contiguous_range ? offset : 0); len -= (max_wp_len - offset); addr += (max_wp_len - offset); gdb_assert ((addr & (alignment - 1)) == 0); @@ -205,19 +258,157 @@ aarch64_align_watchpoint (CORE_ADDR addr, int len, CORE_ADDR *aligned_addr_p, aligned_len_array[AARCH64_HWP_MAX_LEN_PER_REG] = { 1, 2, 4, 4, 8, 8, 8, 8 }; - aligned_len = aligned_len_array[offset + len - 1]; + aligned_len = (kernel_supports_any_contiguous_range + ? len : aligned_len_array[offset + len - 1]); addr += len; len = 0; } if (aligned_addr_p) *aligned_addr_p = aligned_addr; + if (aligned_offset_p) + *aligned_offset_p = aligned_offset; if (aligned_len_p) *aligned_len_p = aligned_len; if (next_addr_p) *next_addr_p = addr; if (next_len_p) *next_len_p = len; + if (next_addr_orig_p) + *next_addr_orig_p = align_down (*next_addr_orig_p + alignment, alignment); +} + +/* Helper for aarch64_notify_debug_reg_change. Records the + information about the change of one hardware breakpoint/watchpoint + setting for the thread LWP. + N.B. The actual updating of hardware debug registers is not + carried out until the moment the thread is resumed. */ + +static int +debug_reg_change_callback (struct lwp_info *lwp, int is_watchpoint, + unsigned int idx) +{ + int tid = ptid_of_lwp (lwp).lwp (); + struct arch_lwp_info *info = lwp_arch_private_info (lwp); + dr_changed_t *dr_changed_ptr; + dr_changed_t dr_changed; + + if (info == NULL) + { + info = XCNEW (struct arch_lwp_info); + lwp_set_arch_private_info (lwp, info); + } + + if (show_debug_regs) + { + debug_printf ("debug_reg_change_callback: \n\tOn entry:\n"); + 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 = is_watchpoint ? &info->dr_changed_wp + : &info->dr_changed_bp; + dr_changed = *dr_changed_ptr; + + gdb_assert (idx >= 0 + && (idx <= (is_watchpoint ? aarch64_num_wp_regs + : aarch64_num_bp_regs))); + + /* The actual update is done later just before resuming the lwp, + we just mark that one register pair needs updating. */ + DR_MARK_N_CHANGED (dr_changed, idx); + *dr_changed_ptr = dr_changed; + + /* If the lwp isn't stopped, force it to momentarily pause, so + we can update its debug registers. */ + if (!lwp_is_stopped (lwp)) + linux_stop_lwp (lwp); + + if (show_debug_regs) + { + 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; +} + +/* Notify each thread that their IDXth breakpoint/watchpoint register + pair needs to be updated. The message will be recorded in each + thread's arch-specific data area, the actual updating will be done + when the thread is resumed. */ + +static void +aarch64_notify_debug_reg_change (const struct aarch64_debug_reg_state *state, + int is_watchpoint, unsigned int idx) +{ + ptid_t pid_ptid = ptid_t (current_lwp_ptid ().pid ()); + + iterate_over_lwps (pid_ptid, [=] (struct lwp_info *info) + { + return debug_reg_change_callback (info, + is_watchpoint, + idx); + }); +} + +/* Reconfigure STATE to be compatible with Linux kernels with the PR + external/20207 bug. This is called when + KERNEL_SUPPORTS_ANY_CONTIGUOUS_RANGE transitions to false. Note we + don't try to support combining watchpoints with matching (and thus + shared) masks, as it's too late when we get here. On buggy + kernels, GDB will try to first setup the perfect matching ranges, + which will run out of registers before this function can merge + them. It doesn't look like worth the effort to improve that, given + eventually buggy kernels will be phased out. */ + +static void +aarch64_downgrade_regs (struct aarch64_debug_reg_state *state) +{ + for (int i = 0; i < aarch64_num_wp_regs; ++i) + if ((state->dr_ctrl_wp[i] & 1) != 0) + { + gdb_assert (state->dr_ref_count_wp[i] != 0); + uint8_t mask_orig = (state->dr_ctrl_wp[i] >> 5) & 0xff; + gdb_assert (mask_orig != 0); + static const uint8_t old_valid[] = { 0x01, 0x03, 0x0f, 0xff }; + uint8_t mask = 0; + for (const uint8_t old_mask : old_valid) + if (mask_orig <= old_mask) + { + mask = old_mask; + break; + } + gdb_assert (mask != 0); + + /* No update needed for this watchpoint? */ + if (mask == mask_orig) + continue; + state->dr_ctrl_wp[i] |= mask << 5; + state->dr_addr_wp[i] + = align_down (state->dr_addr_wp[i], AARCH64_HWP_ALIGNMENT); + + /* Try to match duplicate entries. */ + for (int j = 0; j < i; ++j) + if ((state->dr_ctrl_wp[j] & 1) != 0 + && state->dr_addr_wp[j] == state->dr_addr_wp[i] + && state->dr_addr_orig_wp[j] == state->dr_addr_orig_wp[i] + && state->dr_ctrl_wp[j] == state->dr_ctrl_wp[i]) + { + state->dr_ref_count_wp[j] += state->dr_ref_count_wp[i]; + state->dr_ref_count_wp[i] = 0; + state->dr_addr_wp[i] = 0; + state->dr_addr_orig_wp[i] = 0; + state->dr_ctrl_wp[i] &= ~1; + break; + } + + aarch64_notify_debug_reg_change (state, 1 /* is_watchpoint */, i); + } } /* Record the insertion of one breakpoint/watchpoint, as represented @@ -226,11 +417,12 @@ aarch64_align_watchpoint (CORE_ADDR addr, int len, CORE_ADDR *aligned_addr_p, static int aarch64_dr_state_insert_one_point (struct aarch64_debug_reg_state *state, enum target_hw_bp_type type, - CORE_ADDR addr, int len) + CORE_ADDR addr, int offset, int len, + CORE_ADDR addr_orig) { int i, idx, num_regs, is_watchpoint; unsigned int ctrl, *dr_ctrl_p, *dr_ref_count; - CORE_ADDR *dr_addr_p; + CORE_ADDR *dr_addr_p, *dr_addr_orig_p; /* Set up state pointers. */ is_watchpoint = (type != hw_execute); @@ -239,6 +431,7 @@ aarch64_dr_state_insert_one_point (struct aarch64_debug_reg_state *state, { num_regs = aarch64_num_wp_regs; dr_addr_p = state->dr_addr_wp; + dr_addr_orig_p = state->dr_addr_orig_wp; dr_ctrl_p = state->dr_ctrl_wp; dr_ref_count = state->dr_ref_count_wp; } @@ -246,11 +439,12 @@ aarch64_dr_state_insert_one_point (struct aarch64_debug_reg_state *state, { num_regs = aarch64_num_bp_regs; dr_addr_p = state->dr_addr_bp; + dr_addr_orig_p = nullptr; dr_ctrl_p = state->dr_ctrl_bp; dr_ref_count = state->dr_ref_count_bp; } - ctrl = aarch64_point_encode_ctrl_reg (type, len); + ctrl = aarch64_point_encode_ctrl_reg (type, offset, len); /* Find an existing or free register in our cache. */ idx = -1; @@ -262,7 +456,9 @@ aarch64_dr_state_insert_one_point (struct aarch64_debug_reg_state *state, idx = i; /* no break; continue hunting for an exising one. */ } - else if (dr_addr_p[i] == addr && dr_ctrl_p[i] == ctrl) + else if (dr_addr_p[i] == addr + && (dr_addr_orig_p == nullptr || dr_addr_orig_p[i] == addr_orig) + && dr_ctrl_p[i] == ctrl) { gdb_assert (dr_ref_count[i] != 0); idx = i; @@ -279,6 +475,8 @@ aarch64_dr_state_insert_one_point (struct aarch64_debug_reg_state *state, { /* new entry */ dr_addr_p[idx] = addr; + if (dr_addr_orig_p != nullptr) + dr_addr_orig_p[idx] = addr_orig; dr_ctrl_p[idx] = ctrl; dr_ref_count[idx] = 1; /* Notify the change. */ @@ -299,19 +497,20 @@ aarch64_dr_state_insert_one_point (struct aarch64_debug_reg_state *state, static int aarch64_dr_state_remove_one_point (struct aarch64_debug_reg_state *state, enum target_hw_bp_type type, - CORE_ADDR addr, int len) + CORE_ADDR addr, int offset, int len, + CORE_ADDR addr_orig) { int i, num_regs, is_watchpoint; unsigned int ctrl, *dr_ctrl_p, *dr_ref_count; - CORE_ADDR *dr_addr_p; + CORE_ADDR *dr_addr_p, *dr_addr_orig_p; /* Set up state pointers. */ is_watchpoint = (type != hw_execute); - gdb_assert (aarch64_point_is_aligned (is_watchpoint, addr, len)); if (is_watchpoint) { num_regs = aarch64_num_wp_regs; dr_addr_p = state->dr_addr_wp; + dr_addr_orig_p = state->dr_addr_orig_wp; dr_ctrl_p = state->dr_ctrl_wp; dr_ref_count = state->dr_ref_count_wp; } @@ -319,15 +518,18 @@ aarch64_dr_state_remove_one_point (struct aarch64_debug_reg_state *state, { num_regs = aarch64_num_bp_regs; dr_addr_p = state->dr_addr_bp; + dr_addr_orig_p = nullptr; dr_ctrl_p = state->dr_ctrl_bp; dr_ref_count = state->dr_ref_count_bp; } - ctrl = aarch64_point_encode_ctrl_reg (type, len); + ctrl = aarch64_point_encode_ctrl_reg (type, offset, len); /* Find the entry that matches the ADDR and CTRL. */ for (i = 0; i < num_regs; ++i) - if (dr_addr_p[i] == addr && dr_ctrl_p[i] == ctrl) + if (dr_addr_p[i] == addr + && (dr_addr_orig_p == nullptr || dr_addr_orig_p[i] == addr_orig) + && dr_ctrl_p[i] == ctrl) { gdb_assert (dr_ref_count[i] != 0); break; @@ -343,6 +545,8 @@ aarch64_dr_state_remove_one_point (struct aarch64_debug_reg_state *state, /* Clear the enable bit. */ ctrl &= ~1; dr_addr_p[i] = 0; + if (dr_addr_orig_p != nullptr) + dr_addr_orig_p[i] = 0; dr_ctrl_p[i] = ctrl; /* Notify the change. */ aarch64_notify_debug_reg_change (state, is_watchpoint, i); @@ -356,15 +560,23 @@ aarch64_handle_breakpoint (enum target_hw_bp_type type, CORE_ADDR addr, int len, int is_insert, struct aarch64_debug_reg_state *state) { - /* The hardware breakpoint on AArch64 should always be 4-byte - aligned. */ - if (!aarch64_point_is_aligned (0 /* is_watchpoint */ , addr, len)) - return -1; - if (is_insert) - return aarch64_dr_state_insert_one_point (state, type, addr, len); + { + /* The hardware breakpoint on AArch64 should always be 4-byte + aligned, but on AArch32, it can be 2-byte aligned. Note that + we only check the alignment on inserting breakpoint because + aarch64_point_is_aligned needs the inferior_ptid inferior's + regcache to decide whether the inferior is 32-bit or 64-bit. + However when GDB follows the parent process and detach breakpoints + from child process, inferior_ptid is the child ptid, but the + child inferior doesn't exist in GDB's view yet. */ + if (!aarch64_point_is_aligned (0 /* is_watchpoint */ , addr, len)) + return -1; + + return aarch64_dr_state_insert_one_point (state, type, addr, 0, len, -1); + } else - return aarch64_dr_state_remove_one_point (state, type, addr, len); + return aarch64_dr_state_remove_one_point (state, type, addr, 0, len, -1); } /* This is essentially the same as aarch64_handle_breakpoint, apart @@ -376,9 +588,9 @@ aarch64_handle_aligned_watchpoint (enum target_hw_bp_type type, struct aarch64_debug_reg_state *state) { if (is_insert) - return aarch64_dr_state_insert_one_point (state, type, addr, len); + return aarch64_dr_state_insert_one_point (state, type, addr, 0, len, addr); else - return aarch64_dr_state_remove_one_point (state, type, addr, len); + return aarch64_dr_state_remove_one_point (state, type, addr, 0, len, addr); } /* Insert/remove unaligned watchpoint by calling @@ -393,28 +605,42 @@ aarch64_handle_unaligned_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr, int len, int is_insert, struct aarch64_debug_reg_state *state) { + CORE_ADDR addr_orig = addr; + while (len > 0) { CORE_ADDR aligned_addr; - int aligned_len, ret; + int aligned_offset, aligned_len, ret; + CORE_ADDR addr_orig_next = addr_orig; - aarch64_align_watchpoint (addr, len, &aligned_addr, &aligned_len, - &addr, &len); + aarch64_align_watchpoint (addr, len, &aligned_addr, &aligned_offset, + &aligned_len, &addr, &len, &addr_orig_next); if (is_insert) ret = aarch64_dr_state_insert_one_point (state, type, aligned_addr, - aligned_len); + aligned_offset, + aligned_len, addr_orig); else ret = aarch64_dr_state_remove_one_point (state, type, aligned_addr, - aligned_len); + aligned_offset, + aligned_len, addr_orig); if (show_debug_regs) - debug_printf ( -"handle_unaligned_watchpoint: is_insert: %d\n" -" aligned_addr: %s, aligned_len: %d\n" -" next_addr: %s, next_len: %d\n", -is_insert, core_addr_to_string_nz (aligned_addr), aligned_len, -core_addr_to_string_nz (addr), len); + debug_printf ("handle_unaligned_watchpoint: is_insert: %d\n" + " " + "aligned_addr: %s, aligned_len: %d\n" + " " + "addr_orig: %s\n" + " " + "next_addr: %s, next_len: %d\n" + " " + "addr_orig_next: %s\n", + is_insert, core_addr_to_string_nz (aligned_addr), + aligned_len, core_addr_to_string_nz (addr_orig), + core_addr_to_string_nz (addr), len, + core_addr_to_string_nz (addr_orig_next)); + + addr_orig = addr_orig_next; if (ret != 0) return ret; @@ -440,7 +666,7 @@ aarch64_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr, registers with data from *STATE. */ void -aarch64_linux_set_debug_regs (const struct aarch64_debug_reg_state *state, +aarch64_linux_set_debug_regs (struct aarch64_debug_reg_state *state, int tid, int watchpoint) { int i, count; @@ -456,8 +682,8 @@ aarch64_linux_set_debug_regs (const struct aarch64_debug_reg_state *state, ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp; if (count == 0) return; - iov.iov_len = (offsetof (struct user_hwdebug_state, dbg_regs[count - 1]) - + sizeof (regs.dbg_regs [count - 1])); + iov.iov_len = (offsetof (struct user_hwdebug_state, dbg_regs) + + count * sizeof (regs.dbg_regs[0])); for (i = 0; i < count; i++) { @@ -468,7 +694,38 @@ aarch64_linux_set_debug_regs (const struct aarch64_debug_reg_state *state, if (ptrace (PTRACE_SETREGSET, tid, watchpoint ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK, (void *) &iov)) - error (_("Unexpected error setting hardware debug registers")); + { + /* Handle Linux kernels with the PR external/20207 bug. */ + if (watchpoint && errno == EINVAL + && kernel_supports_any_contiguous_range) + { + kernel_supports_any_contiguous_range = false; + aarch64_downgrade_regs (state); + aarch64_linux_set_debug_regs (state, tid, watchpoint); + return; + } + error (_("Unexpected error setting hardware debug registers")); + } +} + +/* See nat/aarch64-linux-hw-point.h. */ + +bool +aarch64_linux_any_set_debug_regs_state (aarch64_debug_reg_state *state, + bool watchpoint) +{ + int count = watchpoint ? aarch64_num_wp_regs : aarch64_num_bp_regs; + if (count == 0) + return false; + + 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) + return true; + + return false; } /* Print the values of the cached breakpoint/watchpoint registers. */ @@ -499,7 +756,105 @@ aarch64_show_debug_reg_state (struct aarch64_debug_reg_state *state, debug_printf ("\tWATCHPOINTs:\n"); for (i = 0; i < aarch64_num_wp_regs; i++) - debug_printf ("\tWP%d: addr=%s, ctrl=0x%08x, ref.count=%d\n", + debug_printf ("\tWP%d: addr=%s (orig=%s), ctrl=0x%08x, ref.count=%d\n", i, core_addr_to_string_nz (state->dr_addr_wp[i]), + core_addr_to_string_nz (state->dr_addr_orig_wp[i]), state->dr_ctrl_wp[i], state->dr_ref_count_wp[i]); } + +/* Get the hardware debug register capacity information from the + process represented by TID. */ + +void +aarch64_linux_get_debug_reg_capacity (int tid) +{ + struct iovec iov; + struct user_hwdebug_state dreg_state; + + iov.iov_base = &dreg_state; + iov.iov_len = sizeof (dreg_state); + + /* Get hardware watchpoint register info. */ + if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_HW_WATCH, &iov) == 0 + && (AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8 + || AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8_1 + || AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8_2)) + { + aarch64_num_wp_regs = AARCH64_DEBUG_NUM_SLOTS (dreg_state.dbg_info); + if (aarch64_num_wp_regs > AARCH64_HWP_MAX_NUM) + { + warning (_("Unexpected number of hardware watchpoint registers" + " reported by ptrace, got %d, expected %d."), + aarch64_num_wp_regs, AARCH64_HWP_MAX_NUM); + aarch64_num_wp_regs = AARCH64_HWP_MAX_NUM; + } + } + else + { + warning (_("Unable to determine the number of hardware watchpoints" + " available.")); + aarch64_num_wp_regs = 0; + } + + /* Get hardware breakpoint register info. */ + if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_HW_BREAK, &iov) == 0 + && (AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8 + || AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8_1 + || AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8_2)) + { + aarch64_num_bp_regs = AARCH64_DEBUG_NUM_SLOTS (dreg_state.dbg_info); + if (aarch64_num_bp_regs > AARCH64_HBP_MAX_NUM) + { + warning (_("Unexpected number of hardware breakpoint registers" + " reported by ptrace, got %d, expected %d."), + aarch64_num_bp_regs, AARCH64_HBP_MAX_NUM); + aarch64_num_bp_regs = AARCH64_HBP_MAX_NUM; + } + } + else + { + warning (_("Unable to determine the number of hardware breakpoints" + " available.")); + aarch64_num_bp_regs = 0; + } +} + +/* Return true if we can watch a memory region that starts address + ADDR and whose length is LEN in bytes. */ + +int +aarch64_linux_region_ok_for_watchpoint (CORE_ADDR addr, int len) +{ + CORE_ADDR aligned_addr; + + /* Can not set watchpoints for zero or negative lengths. */ + if (len <= 0) + return 0; + + /* Must have hardware watchpoint debug register(s). */ + if (aarch64_num_wp_regs == 0) + return 0; + + /* We support unaligned watchpoint address and arbitrary length, + as long as the size of the whole watched area after alignment + doesn't exceed size of the total area that all watchpoint debug + registers can watch cooperatively. + + This is a very relaxed rule, but unfortunately there are + limitations, e.g. false-positive hits, due to limited support of + hardware debug registers in the kernel. See comment above + aarch64_align_watchpoint for more information. */ + + aligned_addr = addr & ~(AARCH64_HWP_MAX_LEN_PER_REG - 1); + if (aligned_addr + aarch64_num_wp_regs * AARCH64_HWP_MAX_LEN_PER_REG + < addr + len) + return 0; + + /* All tests passed so we are likely to be able to set the watchpoint. + The reason that it is 'likely' rather than 'must' is because + we don't check the current usage of the watchpoint registers, and + there may not be enough registers available for this watchpoint. + Ideally we should check the cached debug register state, however + the checking is costly. */ + return 1; +}