[PowerPC] Fix debug register issues in ppc-linux-nat

This patch fixes some issues with debug register handling for the powerpc
linux native target.

Currently, the target methods for installing and removing hardware
breakpoints and watchpoints in ppc-linux-nat.c affect all threads known to
linux-nat, including threads of different processes.

This patch changes ppc-linux-nat.c so that only the process of
inferior_ptid is affected by these target methods, as GDB expects.

This is done in the same way as various other architectures.  The
install/remove target methods only register a hardware breakpoint or
watchpoint, and then send a stop signal to the threads.  The debug
registers are only changed with ptrace right before each thread is next
resumed, using low_prepare_to_resume.

There are two interfaces to modify debug registers for linux running on
powerpc, with different sets of ptrace requests:

- PPC_PTRACE_GETHWDBGINFO, PPC_PTRACE_SETHWDEBUG, and
  PPC_PTRACE_DELHWDEBUG.

   Or

- PTRACE_SET_DEBUGREG and PTRACE_GET_DEBUGREG

The first set (HWDEBUG) is the more flexible one and allows setting
watchpoints with a variable watched region length and, for certain
embedded processors, multiple types of debug registers (e.g. hardware
breakpoints and hardware-assisted conditions for watchpoints).
Currently, server processors only provide one watchpoint.  The second one
(DEBUGREG) only allows setting one debug register, a watchpoint, so we
only use it if the first one is not available.

The HWDEBUG interface handles debug registers with slot numbers.  Once a
hardware watchpoint or breakpoint is installed (with
PPC_PTRACE_SETHWDEBUG), ptrace returns a slot number.  This slot number
can then be used to remove the watchpoint or breakpoint from the inferior
(with PPC_PTRACE_DELHWDEBUG).  The first interface also provides a
bitmask of available debug register features, which can be obtained with
PPC_PTRACE_GETHWDBGINFO.

When GDB first tries to use debug registers, we try the first interface
with a ptrace call, and if it isn't available, we fall back to the second
one, if available.  We use EIO as an indicator that an interface is not
available in the kernel.  For simplicity, with any other error we
immediately assume no interface is available.  Unfortunately this means
that if a process is killed by a signal right before we try to detect the
interface, we might get an ESRCH, which would prevent debug registers to
be used in the GDB session.  However, it isn't clear that we can safely
raise an exception and try again in the future in all the contexts where
we try to detect the interface.

If the HWDEBUG interface works but provides no feature bits, the target
falls back to the DEBUGREG interface.  When the kernel is configured
without CONFIG_HW_BREAKPOINTS (selected by CONFIG_PERF_EVENTS), there is
a bug that causes watchpoints installed with the HWDEBUG interface not to
trigger.  When this is the case, the feature bits will be zero, which is
used as the indicator to fall back to the DEBUGREG interface.  This isn't
ideal, but has always been the behavior of GDB before this patch, so I
decided not to change it.

A flag indicates for each thread if its debug registers need to be
updated the next time it is resumed.  The flag is set whenever the upper
layers request or remove a hardware watchpoint or breakpoint, or when a
new thread is detected.  Because some kernel configurations disable
watchpoints after they are hit, we also use the last stop reason of the
LWP to determine whether we should update the debug registers.  It isn't
clear that this is also true of BookE hardware breakpoints, but we also
check their stop reason to be on the safe side, since it doesn't hurt.

A map from process numbers to hardware watchpoint or breakpoint objects
keeps track of what has been requested by the upper layers of GDB, since
for GDB installing a hardware watchpoint or breakpoint means doing so for
the whole process.

When using the HWDEBUG interface we also have to keep track of which
slots were last installed in each thread with a map from threads to the
slots, so that they can be removed when needed.  When resuming a thread,
we remove all the slots using this map, then we install all the hardware
watchpoints and breakpoints from the per-process map of requests, and
then update the per-thread map accordingly.

This per-thread state is also used for copying the debug register state
after a fork or a clone is detected.  The kernel might do this depending
on the configuration.  Recent kernels running on server processors that
were configured with CONFIG_PERF_EVENTS (and therefore
CONFIG_HW_BREAKPOINTS) don't copy debug registers across forks and
clones.  Recent kernels without CONFIG_HW_BREAKPOINTS copy this state.  I
believe that on embedded processors (e.g. a ppc440) the debug register
state is copied, but I haven't been able to test this.  To handle both
cases, the per-thread state is always copied when forks and clones are
detected, and when we resume the thread and delete the debug register
slots before updating them, we ignore ENOENT errors.

We don't need to handle this when using the DEBUGREG interface since it
only allows one hardware watchpoint and doesn't return slot numbers, we
just set or clear this watchpoint when needed.

Since we signal running threads to stop after a request is processed, so
that we can update their debug registers when they are next resumed,
there will be a time between signalling the threads and their stop during
which the debug registers haven't been updated, even if the target
methods completed.

The tests in gdb.threads/watchpoint-fork.exp no longer fail with this
patch.

gdb/ChangeLog:
2020-03-30  Pedro Franco de Carvalho  <pedromfc@linux.ibm.com>

	* ppc-linux-nat.c: Include <algorithm>, <unordered_map>, and
	<list>.  Remove inclusion of observable.h.
	(PPC_DEBUG_CURRENT_VERSION): Move up define.
	(struct arch_lwp_info): New struct.
	(class ppc_linux_dreg_interface): New class.
	(struct ppc_linux_process_info): New struct.
	(struct ppc_linux_nat_target) <low_delete_thread, low_new_fork>
	<low_new_clone, low_forget_process, low_prepare_to_resume>
	<copy_thread_dreg_state, mark_thread_stale>
	<mark_debug_registers_changed, register_hw_breakpoint>
	<clear_hw_breakpoint, register_wp, clear_wp>
	<can_use_watchpoint_cond_accel, calculate_dvc, check_condition>
	<num_memory_accesses, get_trigger_type>
	<create_watchpoint_request, hwdebug_point_cmp>
	<init_arch_lwp_info, get_arch_lwp_info>
	<low_stopped_by_watchpoint, low_stopped_data_address>: Declare as
	methods.
	<struct ptid_hash>: New inner struct.
	<m_dreg_interface, m_process_info, m_installed_hw_bps>: Declare
	members.
	(saved_dabr_value, hwdebug_info, max_slots_number)
	(struct hw_break_tuple, struct thread_points, ppc_threads)
	(have_ptrace_hwdebug_interface)
	(hwdebug_find_thread_points_by_tid)
	(hwdebug_insert_point, hwdebug_remove_point): Remove.
	(ppc_linux_nat_target::can_use_hw_breakpoint): Use
	m_dreg_interface, remove call to PTRACE_SET_DEBUGREG.
	(ppc_linux_nat_target::region_ok_for_hw_watchpoint): Add comment,
	use m_dreg_interface.
	(hwdebug_point_cmp): Change to...
	(ppc_linux_nat_target::hwdebug_point_cmp): ...this method.  Use
	reference arguments instead of pointers.
	(ppc_linux_nat_target::ranged_break_num_registers): Use
	m_dreg_interface.
	(ppc_linux_nat_target::insert_hw_breakpoint): Add comment, use
	m_dreg_interface.  Call register_hw_breakpoint.
	(ppc_linux_nat_target::remove_hw_breakpoint): Add comment, use
	m_dreg_interface.  Call clear_hw_breakpoint.
	(get_trigger_type): Change to...
	(ppc_linux_nat_target::get_trigger_type): ...this method.  Add
	comment.
	(ppc_linux_nat_target::insert_mask_watchpoint): Update comment,
	use m_dreg_interface.  Call register_hw_breakpoint.
	(ppc_linux_nat_target::remove_mask_watchpoint): Update comment,
	use m_dreg_interface.  Call clear_hw_breakpoint.
	(can_use_watchpoint_cond_accel): Change to...
	(ppc_linux_nat_target::can_use_watchpoint_cond_accel): ...this
	method.  Update comment, use m_dreg_interface and
	m_process_info.
	(calculate_dvc): Change to...
	(ppc_linux_nat_target::calculate_dvc): ...this method.  Use
	m_dreg_interface.
	(num_memory_accesses): Change to...
	(ppc_linux_nat_target::num_memory_accesses): ...this method.
	(check_condition): Change to...
	(ppc_linux_nat_target::check_condition): ...this method.
	(ppc_linux_nat_target::can_accel_watchpoint_condition): Update
	comment, use m_dreg_interface.
	(create_watchpoint_request): Change to...
	(ppc_linux_nat_target::create_watchpoint_request): ...this
	method.  Use m_dreg_interface.
	(ppc_linux_nat_target::insert_watchpoint): Add comment, use
	m_dreg_interface.  Call register_hw_breakpoint or register_wp.
	(ppc_linux_nat_target::remove_watchpoint): Add comment, use
	m_dreg_interface.  Call clear_hw_breakpoint or clear_wp.
	(ppc_linux_nat_target::low_forget_process)
	(ppc_linux_nat_target::low_new_fork)
	(ppc_linux_nat_target::low_new_clone)
	(ppc_linux_nat_target::low_delete_thread)
	(ppc_linux_nat_target::low_prepare_to_resume): New methods.
	(ppc_linux_nat_target::low_new_thread): Remove previous logic,
	only call mark_thread_stale.
	(ppc_linux_thread_exit): Remove.
	(ppc_linux_nat_target::stopped_data_address): Change to...
	(ppc_linux_nat_target::low_stopped_data_address): This. Add
	comment, use m_dreg_interface and m_thread_hw_breakpoints.
	(ppc_linux_nat_target::stopped_by_watchpoint): Change to...
	(ppc_linux_nat_target::stopped_by_watchpoint): This.  Add
	comment.  Call low_stopped_data_address.
	(ppc_linux_nat_target::watchpoint_addr_within_range): Use
	m_dreg_interface.
	(ppc_linux_nat_target::masked_watch_num_registers): Use
	m_dreg_interface.
	(ppc_linux_nat_target::copy_thread_dreg_state)
	(ppc_linux_nat_target::mark_thread_stale)
	(ppc_linux_nat_target::mark_debug_registers_changed)
	(ppc_linux_nat_target::register_hw_breakpoint)
	(ppc_linux_nat_target::clear_hw_breakpoint)
	(ppc_linux_nat_target::register_wp)
	(ppc_linux_nat_target::clear_wp)
	(ppc_linux_nat_target::init_arch_lwp_info)
	(ppc_linux_nat_target::get_arch_lwp_info): New methods.
	(_initialize_ppc_linux_nat): Remove observer callback.
This commit is contained in:
Pedro Franco de Carvalho 2020-03-30 12:04:25 -03:00
parent 4db10d8f49
commit 227c0bf4b3
2 changed files with 1026 additions and 402 deletions

View File

@ -1,3 +1,99 @@
2020-03-30 Pedro Franco de Carvalho <pedromfc@linux.ibm.com>
* ppc-linux-nat.c: Include <algorithm>, <unordered_map>, and
<list>. Remove inclusion of observable.h.
(PPC_DEBUG_CURRENT_VERSION): Move up define.
(struct arch_lwp_info): New struct.
(class ppc_linux_dreg_interface): New class.
(struct ppc_linux_process_info): New struct.
(struct ppc_linux_nat_target) <low_delete_thread, low_new_fork>
<low_new_clone, low_forget_process, low_prepare_to_resume>
<copy_thread_dreg_state, mark_thread_stale>
<mark_debug_registers_changed, register_hw_breakpoint>
<clear_hw_breakpoint, register_wp, clear_wp>
<can_use_watchpoint_cond_accel, calculate_dvc, check_condition>
<num_memory_accesses, get_trigger_type>
<create_watchpoint_request, hwdebug_point_cmp>
<init_arch_lwp_info, get_arch_lwp_info>
<low_stopped_by_watchpoint, low_stopped_data_address>: Declare as
methods.
<struct ptid_hash>: New inner struct.
<m_dreg_interface, m_process_info, m_installed_hw_bps>: Declare
members.
(saved_dabr_value, hwdebug_info, max_slots_number)
(struct hw_break_tuple, struct thread_points, ppc_threads)
(have_ptrace_hwdebug_interface)
(hwdebug_find_thread_points_by_tid)
(hwdebug_insert_point, hwdebug_remove_point): Remove.
(ppc_linux_nat_target::can_use_hw_breakpoint): Use
m_dreg_interface, remove call to PTRACE_SET_DEBUGREG.
(ppc_linux_nat_target::region_ok_for_hw_watchpoint): Add comment,
use m_dreg_interface.
(hwdebug_point_cmp): Change to...
(ppc_linux_nat_target::hwdebug_point_cmp): ...this method. Use
reference arguments instead of pointers.
(ppc_linux_nat_target::ranged_break_num_registers): Use
m_dreg_interface.
(ppc_linux_nat_target::insert_hw_breakpoint): Add comment, use
m_dreg_interface. Call register_hw_breakpoint.
(ppc_linux_nat_target::remove_hw_breakpoint): Add comment, use
m_dreg_interface. Call clear_hw_breakpoint.
(get_trigger_type): Change to...
(ppc_linux_nat_target::get_trigger_type): ...this method. Add
comment.
(ppc_linux_nat_target::insert_mask_watchpoint): Update comment,
use m_dreg_interface. Call register_hw_breakpoint.
(ppc_linux_nat_target::remove_mask_watchpoint): Update comment,
use m_dreg_interface. Call clear_hw_breakpoint.
(can_use_watchpoint_cond_accel): Change to...
(ppc_linux_nat_target::can_use_watchpoint_cond_accel): ...this
method. Update comment, use m_dreg_interface and
m_process_info.
(calculate_dvc): Change to...
(ppc_linux_nat_target::calculate_dvc): ...this method. Use
m_dreg_interface.
(num_memory_accesses): Change to...
(ppc_linux_nat_target::num_memory_accesses): ...this method.
(check_condition): Change to...
(ppc_linux_nat_target::check_condition): ...this method.
(ppc_linux_nat_target::can_accel_watchpoint_condition): Update
comment, use m_dreg_interface.
(create_watchpoint_request): Change to...
(ppc_linux_nat_target::create_watchpoint_request): ...this
method. Use m_dreg_interface.
(ppc_linux_nat_target::insert_watchpoint): Add comment, use
m_dreg_interface. Call register_hw_breakpoint or register_wp.
(ppc_linux_nat_target::remove_watchpoint): Add comment, use
m_dreg_interface. Call clear_hw_breakpoint or clear_wp.
(ppc_linux_nat_target::low_forget_process)
(ppc_linux_nat_target::low_new_fork)
(ppc_linux_nat_target::low_new_clone)
(ppc_linux_nat_target::low_delete_thread)
(ppc_linux_nat_target::low_prepare_to_resume): New methods.
(ppc_linux_nat_target::low_new_thread): Remove previous logic,
only call mark_thread_stale.
(ppc_linux_thread_exit): Remove.
(ppc_linux_nat_target::stopped_data_address): Change to...
(ppc_linux_nat_target::low_stopped_data_address): This. Add
comment, use m_dreg_interface and m_thread_hw_breakpoints.
(ppc_linux_nat_target::stopped_by_watchpoint): Change to...
(ppc_linux_nat_target::stopped_by_watchpoint): This. Add
comment. Call low_stopped_data_address.
(ppc_linux_nat_target::watchpoint_addr_within_range): Use
m_dreg_interface.
(ppc_linux_nat_target::masked_watch_num_registers): Use
m_dreg_interface.
(ppc_linux_nat_target::copy_thread_dreg_state)
(ppc_linux_nat_target::mark_thread_stale)
(ppc_linux_nat_target::mark_debug_registers_changed)
(ppc_linux_nat_target::register_hw_breakpoint)
(ppc_linux_nat_target::clear_hw_breakpoint)
(ppc_linux_nat_target::register_wp)
(ppc_linux_nat_target::clear_wp)
(ppc_linux_nat_target::init_arch_lwp_info)
(ppc_linux_nat_target::get_arch_lwp_info): New methods.
(_initialize_ppc_linux_nat): Remove observer callback.
2020-03-30 Pedro Franco de Carvalho <pedromfc@linux.ibm.com>
* ppc-linux-nat.c (ppc_linux_nat_target::store_registers)

File diff suppressed because it is too large Load Diff