/* GNU/Linux/ARM specific low level interface, for the remote server for GDB.
- Copyright (C) 1995-2015 Free Software Foundation, Inc.
+ Copyright (C) 1995-2018 Free Software Foundation, Inc.
This file is part of GDB.
#include "server.h"
#include "linux-low.h"
#include "arch/arm.h"
+#include "arch/arm-linux.h"
+#include "arch/arm-get-next-pcs.h"
#include "linux-aarch32-low.h"
#include <sys/uio.h>
#endif
#include "nat/gdb_ptrace.h"
#include <signal.h>
-
-#include "arch/arm.h"
+#include <sys/syscall.h>
/* Defined in auto-generated files. */
void init_registers_arm (void);
arm_hwbp_access = 3
} arm_hwbp_type;
-/* Enum describing the different kinds of breakpoints. */
-enum arm_breakpoint_kinds
-{
- ARM_BP_KIND_THUMB = 2,
- ARM_BP_KIND_THUMB2 = 3,
- ARM_BP_KIND_ARM = 4,
-};
-
/* Type describing an ARM Hardware Breakpoint Control register value. */
typedef unsigned int arm_hwbp_control_t;
64
};
+/* Forward declarations needed for get_next_pcs ops. */
+static ULONGEST get_next_pcs_read_memory_unsigned_integer (CORE_ADDR memaddr,
+ int len,
+ int byte_order);
+
+static CORE_ADDR get_next_pcs_addr_bits_remove (struct arm_get_next_pcs *self,
+ CORE_ADDR val);
+
+static CORE_ADDR get_next_pcs_syscall_next_pc (struct arm_get_next_pcs *self);
+
+static int get_next_pcs_is_thumb (struct arm_get_next_pcs *self);
+
+/* get_next_pcs operations. */
+static struct arm_get_next_pcs_ops get_next_pcs_ops = {
+ get_next_pcs_read_memory_unsigned_integer,
+ get_next_pcs_syscall_next_pc,
+ get_next_pcs_addr_bits_remove,
+ get_next_pcs_is_thumb,
+ arm_linux_get_next_pcs_fixup,
+};
+
static int
arm_cannot_store_register (int regno)
{
arm_fill_vfpregset_num (regcache, buf, num);
}
+/* Wrapper of UNMAKE_THUMB_ADDR for get_next_pcs. */
+static CORE_ADDR
+get_next_pcs_addr_bits_remove (struct arm_get_next_pcs *self, CORE_ADDR val)
+{
+ return UNMAKE_THUMB_ADDR (val);
+}
+
static void
arm_store_vfpregset (struct regcache *regcache, const void *buf)
{
arm_store_vfpregset_num (regcache, buf, num);
}
-extern int debug_threads;
-
-static CORE_ADDR
-arm_get_pc (struct regcache *regcache)
-{
- unsigned long pc;
- collect_register_by_name (regcache, "pc", &pc);
- if (debug_threads)
- debug_printf ("stop pc is %08lx\n", pc);
- return pc;
-}
-
-static void
-arm_set_pc (struct regcache *regcache, CORE_ADDR pc)
+/* Wrapper of arm_is_thumb_mode for get_next_pcs. */
+static int
+get_next_pcs_is_thumb (struct arm_get_next_pcs *self)
{
- unsigned long newpc = pc;
- supply_register_by_name (regcache, "pc", &newpc);
+ return arm_is_thumb_mode ();
}
-/* Correct in either endianness. */
-#define arm_abi_breakpoint 0xef9f0001UL
-
-/* For new EABI binaries. We recognize it regardless of which ABI
- is used for gdbserver, so single threaded debugging should work
- OK, but for multi-threaded debugging we only insert the current
- ABI's breakpoint instruction. For now at least. */
-#define arm_eabi_breakpoint 0xe7f001f0UL
-
-#ifndef __ARM_EABI__
-static const unsigned long arm_breakpoint = arm_abi_breakpoint;
-#else
-static const unsigned long arm_breakpoint = arm_eabi_breakpoint;
-#endif
-
-#define arm_breakpoint_len 4
-static const unsigned short thumb_breakpoint = 0xde01;
-#define thumb_breakpoint_len 2
-static const unsigned short thumb2_breakpoint[] = { 0xf7f0, 0xa000 };
-#define thumb2_breakpoint_len 4
-
-static int
-arm_breakpoint_at (CORE_ADDR where)
+/* Read memory from the inferiror.
+ BYTE_ORDER is ignored and there to keep compatiblity with GDB's
+ read_memory_unsigned_integer. */
+static ULONGEST
+get_next_pcs_read_memory_unsigned_integer (CORE_ADDR memaddr,
+ int len,
+ int byte_order)
{
- struct regcache *regcache = get_thread_regcache (current_thread, 1);
- unsigned long cpsr;
-
- collect_register_by_name (regcache, "cpsr", &cpsr);
+ ULONGEST res;
- if (cpsr & 0x20)
- {
- /* Thumb mode. */
- unsigned short insn;
-
- (*the_target->read_memory) (where, (unsigned char *) &insn, 2);
- if (insn == thumb_breakpoint)
- return 1;
-
- if (insn == thumb2_breakpoint[0])
- {
- (*the_target->read_memory) (where + 2, (unsigned char *) &insn, 2);
- if (insn == thumb2_breakpoint[1])
- return 1;
- }
- }
- else
- {
- /* ARM mode. */
- unsigned long insn;
+ res = 0;
+ target_read_memory (memaddr, (unsigned char *) &res, len);
- (*the_target->read_memory) (where, (unsigned char *) &insn, 4);
- if (insn == arm_abi_breakpoint)
- return 1;
-
- if (insn == arm_eabi_breakpoint)
- return 1;
- }
-
- return 0;
-}
-
-/* We only place breakpoints in empty marker functions, and thread locking
- is outside of the function. So rather than importing software single-step,
- we can just run until exit. */
-static CORE_ADDR
-arm_reinsert_addr (void)
-{
- struct regcache *regcache = get_thread_regcache (current_thread, 1);
- unsigned long pc;
- collect_register_by_name (regcache, "lr", &pc);
- return pc;
+ return res;
}
/* Fetch the thread-local storage pointer for libthread_db. */
ps_err_e
-ps_get_thread_area (const struct ps_prochandle *ph,
+ps_get_thread_area (struct ps_prochandle *ph,
lwpid_t lwpid, int idx, void **base)
{
if (ptrace (PTRACE_GET_THREAD_AREA, lwpid, NULL, base) != 0)
/* Callback to mark a watch-/breakpoint to be updated in all threads of
the current process. */
-struct update_registers_data
-{
- int watch;
- int i;
-};
-
-static int
-update_registers_callback (struct inferior_list_entry *entry, void *arg)
+static void
+update_registers_callback (thread_info *thread, int watch, int i)
{
- struct thread_info *thread = (struct thread_info *) entry;
struct lwp_info *lwp = get_thread_lwp (thread);
- struct update_registers_data *data = (struct update_registers_data *) arg;
- /* Only update the threads of the current process. */
- if (pid_of (thread) == pid_of (current_thread))
- {
- /* The actual update is done later just before resuming the lwp,
- we just mark that the registers need updating. */
- if (data->watch)
- lwp->arch_private->wpts_changed[data->i] = 1;
- else
- lwp->arch_private->bpts_changed[data->i] = 1;
-
- /* If the lwp isn't stopped, force it to momentarily pause, so
- we can update its breakpoint registers. */
- if (!lwp->stopped)
- linux_stop_lwp (lwp);
- }
+ /* The actual update is done later just before resuming the lwp,
+ we just mark that the registers need updating. */
+ if (watch)
+ lwp->arch_private->wpts_changed[i] = 1;
+ else
+ lwp->arch_private->bpts_changed[i] = 1;
- return 0;
+ /* If the lwp isn't stopped, force it to momentarily pause, so
+ we can update its breakpoint registers. */
+ if (!lwp->stopped)
+ linux_stop_lwp (lwp);
}
static int
for (i = 0; i < count; i++)
if (!arm_hwbp_control_is_enabled (pts[i].control))
{
- struct update_registers_data data = { watch, i };
pts[i] = p;
- find_inferior (&all_threads, update_registers_callback, &data);
+
+ /* Only update the threads of the current process. */
+ for_each_thread (current_thread->id.pid (), [&] (thread_info *thread)
+ {
+ update_registers_callback (thread, watch, i);
+ });
+
return 0;
}
for (i = 0; i < count; i++)
if (arm_linux_hw_breakpoint_equal (&p, pts + i))
{
- struct update_registers_data data = { watch, i };
pts[i].control = arm_hwbp_control_disable (pts[i].control);
- find_inferior (&all_threads, update_registers_callback, &data);
+
+ /* Only update the threads of the current process. */
+ for_each_thread (current_thread->id.pid (), [&] (thread_info *thread)
+ {
+ update_registers_callback (thread, watch, i);
+ });
+
return 0;
}
return info;
}
+/* Called when a process is being deleted. */
+
+static void
+arm_delete_process (struct arch_process_info *info)
+{
+ xfree (info);
+}
+
/* Called when a new thread is detected. */
static void
arm_new_thread (struct lwp_info *lwp)
lwp->arch_private = info;
}
+/* Function to call when a thread is being deleted. */
+
+static void
+arm_delete_thread (struct arch_lwp_info *arch_lwp)
+{
+ xfree (arch_lwp);
+}
+
static void
arm_new_fork (struct process_info *parent, struct process_info *child)
{
/* Mark all the hardware breakpoints and watchpoints as changed to
make sure that the registers will be updated. */
- child_lwp = find_lwp_pid (ptid_of (child));
+ child_lwp = find_lwp_pid (ptid_t (child->pid));
child_lwp_info = child_lwp->arch_private;
for (i = 0; i < MAX_BPTS; i++)
child_lwp_info->bpts_changed[i] = 1;
}
}
+/* Find the next pc for a sigreturn or rt_sigreturn syscall. In
+ addition, set IS_THUMB depending on whether we will return to ARM
+ or Thumb code.
+ See arm-linux.h for stack layout details. */
+static CORE_ADDR
+arm_sigreturn_next_pc (struct regcache *regcache, int svc_number,
+ int *is_thumb)
+{
+ unsigned long sp;
+ unsigned long sp_data;
+ /* Offset of PC register. */
+ int pc_offset = 0;
+ CORE_ADDR next_pc = 0;
+ uint32_t cpsr;
+
+ gdb_assert (svc_number == __NR_sigreturn || svc_number == __NR_rt_sigreturn);
+
+ collect_register_by_name (regcache, "sp", &sp);
+ (*the_target->read_memory) (sp, (unsigned char *) &sp_data, 4);
+
+ pc_offset = arm_linux_sigreturn_next_pc_offset
+ (sp, sp_data, svc_number, __NR_sigreturn == svc_number ? 1 : 0);
+
+ (*the_target->read_memory) (sp + pc_offset, (unsigned char *) &next_pc, 4);
+
+ /* Set IS_THUMB according the CPSR saved on the stack. */
+ (*the_target->read_memory) (sp + pc_offset + 4, (unsigned char *) &cpsr, 4);
+ *is_thumb = ((cpsr & CPSR_T) != 0);
+
+ return next_pc;
+}
+
+/* When PC is at a syscall instruction, return the PC of the next
+ instruction to be executed. */
+static CORE_ADDR
+get_next_pcs_syscall_next_pc (struct arm_get_next_pcs *self)
+{
+ CORE_ADDR next_pc = 0;
+ CORE_ADDR pc = regcache_read_pc (self->regcache);
+ int is_thumb = arm_is_thumb_mode ();
+ ULONGEST svc_number = 0;
+ struct regcache *regcache = self->regcache;
+
+ if (is_thumb)
+ {
+ collect_register (regcache, 7, &svc_number);
+ next_pc = pc + 2;
+ }
+ else
+ {
+ unsigned long this_instr;
+ unsigned long svc_operand;
+
+ target_read_memory (pc, (unsigned char *) &this_instr, 4);
+ svc_operand = (0x00ffffff & this_instr);
+
+ if (svc_operand) /* OABI. */
+ {
+ svc_number = svc_operand - 0x900000;
+ }
+ else /* EABI. */
+ {
+ collect_register (regcache, 7, &svc_number);
+ }
+
+ next_pc = pc + 4;
+ }
+
+ /* This is a sigreturn or sigreturn_rt syscall. */
+ if (svc_number == __NR_sigreturn || svc_number == __NR_rt_sigreturn)
+ {
+ /* SIGRETURN or RT_SIGRETURN may affect the arm thumb mode, so
+ update IS_THUMB. */
+ next_pc = arm_sigreturn_next_pc (regcache, svc_number, &is_thumb);
+ }
+
+ /* Addresses for calling Thumb functions have the bit 0 set. */
+ if (is_thumb)
+ next_pc = MAKE_THUMB_ADDR (next_pc);
+
+ return next_pc;
+}
static int
arm_get_hwcap (unsigned long *valp)
have_ptrace_getregset = 0;
}
+/* Fetch the next possible PCs after the current instruction executes. */
+
+static std::vector<CORE_ADDR>
+arm_gdbserver_get_next_pcs (struct regcache *regcache)
+{
+ struct arm_get_next_pcs next_pcs_ctx;
+
+ arm_get_next_pcs_ctor (&next_pcs_ctx,
+ &get_next_pcs_ops,
+ /* Byte order is ignored assumed as host. */
+ 0,
+ 0,
+ 1,
+ regcache);
+
+ return arm_get_next_pcs (&next_pcs_ctx);
+}
+
+/* Support for hardware single step. */
+
+static int
+arm_supports_hardware_single_step (void)
+{
+ return 0;
+}
+
+/* Implementation of linux_target_ops method "get_syscall_trapinfo". */
+
+static void
+arm_get_syscall_trapinfo (struct regcache *regcache, int *sysno)
+{
+ if (arm_is_thumb_mode ())
+ collect_register_by_name (regcache, "r7", sysno);
+ else
+ {
+ unsigned long pc;
+ unsigned long insn;
+
+ collect_register_by_name (regcache, "pc", &pc);
+
+ if ((*the_target->read_memory) (pc - 4, (unsigned char *) &insn, 4))
+ *sysno = UNKNOWN_SYSCALL;
+ else
+ {
+ unsigned long svc_operand = (0x00ffffff & insn);
+
+ if (svc_operand)
+ {
+ /* OABI */
+ *sysno = svc_operand - 0x900000;
+ }
+ else
+ {
+ /* EABI */
+ collect_register_by_name (regcache, "r7", sysno);
+ }
+ }
+ }
+}
+
/* Register sets without using PTRACE_GETREGSET. */
static struct regset_info arm_regsets[] = {
return ®s_info_arm;
}
-/* Implementation of linux_target_ops method "breakpoint_kind_from_pc".
-
- Determine the type and size of breakpoint to insert at PCPTR. Uses the
- program counter value to determine whether a 16-bit or 32-bit breakpoint
- should be used. It returns the breakpoint's kind, and adjusts the program
- counter (if necessary) to point to the actual memory location where the
- breakpoint should be inserted. */
-
-static int
-arm_breakpoint_kind_from_pc (CORE_ADDR *pcptr)
-{
- if (IS_THUMB_ADDR (*pcptr))
- {
- gdb_byte buf[2];
-
- *pcptr = UNMAKE_THUMB_ADDR (*pcptr);
-
- /* Check whether we are replacing a thumb2 32-bit instruction. */
- if ((*the_target->read_memory) (*pcptr, buf, 2) == 0)
- {
- unsigned short inst1 = 0;
-
- (*the_target->read_memory) (*pcptr, (gdb_byte *) &inst1, 2);
- if (thumb_insn_size (inst1) == 4)
- return ARM_BP_KIND_THUMB2;
- }
- return ARM_BP_KIND_THUMB;
- }
- else
- return ARM_BP_KIND_ARM;
-}
-
-/* Implementation of the linux_target_ops method "sw_breakpoint_from_kind". */
-
-static const gdb_byte *
-arm_sw_breakpoint_from_kind (int kind , int *size)
-{
- *size = arm_breakpoint_len;
- /* Define an ARM-mode breakpoint; we only set breakpoints in the C
- library, which is most likely to be ARM. If the kernel supports
- clone events, we will never insert a breakpoint, so even a Thumb
- C library will work; so will mixing EABI/non-EABI gdbserver and
- application. */
- switch (kind)
- {
- case ARM_BP_KIND_THUMB:
- *size = thumb_breakpoint_len;
- return (gdb_byte *) &thumb_breakpoint;
- case ARM_BP_KIND_THUMB2:
- *size = thumb2_breakpoint_len;
- return (gdb_byte *) &thumb2_breakpoint;
- case ARM_BP_KIND_ARM:
- *size = arm_breakpoint_len;
- return (const gdb_byte *) &arm_breakpoint;
- default:
- return NULL;
- }
- return NULL;
-}
-
struct linux_target_ops the_low_target = {
arm_arch_setup,
arm_regs_info,
arm_cannot_fetch_register,
arm_cannot_store_register,
NULL, /* fetch_register */
- arm_get_pc,
- arm_set_pc,
+ linux_get_pc_32bit,
+ linux_set_pc_32bit,
arm_breakpoint_kind_from_pc,
arm_sw_breakpoint_from_kind,
- arm_reinsert_addr,
+ arm_gdbserver_get_next_pcs,
0,
arm_breakpoint_at,
arm_supports_z_point_type,
NULL, /* supply_ptrace_register */
NULL, /* siginfo_fixup */
arm_new_process,
+ arm_delete_process,
arm_new_thread,
+ arm_delete_thread,
arm_new_fork,
arm_prepare_to_resume,
+ NULL, /* process_qsupported */
+ NULL, /* supports_tracepoints */
+ NULL, /* get_thread_area */
+ NULL, /* install_fast_tracepoint_jump_pad */
+ NULL, /* emit_ops */
+ NULL, /* get_min_fast_tracepoint_insn_len */
+ NULL, /* supports_range_stepping */
+ arm_breakpoint_kind_from_current_state,
+ arm_supports_hardware_single_step,
+ arm_get_syscall_trapinfo,
};
void