X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=gdb%2Fgdbserver%2Flinux-arm-low.c;h=365f1c932227713d77c12dc1fd4eae29ee4972fc;hb=ed443b61e1f6e4eb7919fe9122dd947d1e87e767;hp=540807f6ab8d0f8a8e65a0a45d115f9d90810fa2;hpb=a9762ec78a53fbe9209fe1654db42df0cd328d50;p=binutils-gdb.git diff --git a/gdb/gdbserver/linux-arm-low.c b/gdb/gdbserver/linux-arm-low.c index 540807f6ab8..365f1c93222 100644 --- a/gdb/gdbserver/linux-arm-low.c +++ b/gdb/gdbserver/linux-arm-low.c @@ -1,6 +1,5 @@ /* GNU/Linux/ARM specific low level interface, for the remote server for GDB. - Copyright (C) 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, - 2006, 2007 Free Software Foundation, Inc. + Copyright (C) 1995-2016 Free Software Foundation, Inc. This file is part of GDB. @@ -19,10 +18,33 @@ #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 +#include +/* Don't include elf.h if linux/elf.h got included by gdb_proc_service.h. + On Bionic elf.h and linux/elf.h have conflicting definitions. */ +#ifndef ELFMAG0 +#include +#endif +#include "nat/gdb_ptrace.h" +#include +#include + +/* Defined in auto-generated files. */ +void init_registers_arm (void); +extern const struct target_desc *tdesc_arm; + +void init_registers_arm_with_iwmmxt (void); +extern const struct target_desc *tdesc_arm_with_iwmmxt; -#include "gdb_proc_service.h" +void init_registers_arm_with_vfpv2 (void); +extern const struct target_desc *tdesc_arm_with_vfpv2; + +void init_registers_arm_with_vfpv3 (void); +extern const struct target_desc *tdesc_arm_with_vfpv3; #ifndef PTRACE_GET_THREAD_AREA #define PTRACE_GET_THREAD_AREA 22 @@ -33,6 +55,77 @@ # define PTRACE_SETWMMXREGS 19 #endif +#ifndef PTRACE_GETVFPREGS +# define PTRACE_GETVFPREGS 27 +# define PTRACE_SETVFPREGS 28 +#endif + +#ifndef PTRACE_GETHBPREGS +#define PTRACE_GETHBPREGS 29 +#define PTRACE_SETHBPREGS 30 +#endif + +/* Information describing the hardware breakpoint capabilities. */ +static struct +{ + unsigned char arch; + unsigned char max_wp_length; + unsigned char wp_count; + unsigned char bp_count; +} arm_linux_hwbp_cap; + +/* Enum describing the different types of ARM hardware break-/watch-points. */ +typedef enum +{ + arm_hwbp_break = 0, + arm_hwbp_load = 1, + arm_hwbp_store = 2, + arm_hwbp_access = 3 +} arm_hwbp_type; + +/* Type describing an ARM Hardware Breakpoint Control register value. */ +typedef unsigned int arm_hwbp_control_t; + +/* Structure used to keep track of hardware break-/watch-points. */ +struct arm_linux_hw_breakpoint +{ + /* Address to break on, or being watched. */ + unsigned int address; + /* Control register for break-/watch- point. */ + arm_hwbp_control_t control; +}; + +/* Since we cannot dynamically allocate subfields of arch_process_info, + assume a maximum number of supported break-/watchpoints. */ +#define MAX_BPTS 32 +#define MAX_WPTS 32 + +/* Per-process arch-specific data we want to keep. */ +struct arch_process_info +{ + /* Hardware breakpoints for this process. */ + struct arm_linux_hw_breakpoint bpts[MAX_BPTS]; + /* Hardware watchpoints for this process. */ + struct arm_linux_hw_breakpoint wpts[MAX_WPTS]; +}; + +/* Per-thread arch-specific data we want to keep. */ +struct arch_lwp_info +{ + /* Non-zero if our copy differs from what's recorded in the thread. */ + char bpts_changed[MAX_BPTS]; + char wpts_changed[MAX_WPTS]; + /* Cached stopped data address. */ + CORE_ADDR stopped_data_address; +}; + +/* These are in in current kernels. */ +#define HWCAP_VFP 64 +#define HWCAP_IWMMXT 512 +#define HWCAP_NEON 4096 +#define HWCAP_VFPv3 8192 +#define HWCAP_VFPv3D16 16384 + #ifdef HAVE_SYS_REG_H #include #endif @@ -46,6 +139,28 @@ static int arm_regmap[] = { 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, + CORE_ADDR pc); + +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) { @@ -59,122 +174,104 @@ arm_cannot_fetch_register (int regno) } static void -arm_fill_gregset (void *buf) +arm_fill_wmmxregset (struct regcache *regcache, void *buf) { int i; - for (i = 0; i < arm_num_regs; i++) - if (arm_regmap[i] != -1) - collect_register (i, ((char *) buf) + arm_regmap[i]); -} + if (regcache->tdesc != tdesc_arm_with_iwmmxt) + return; -static void -arm_store_gregset (const void *buf) -{ - int i; - char zerobuf[8]; + for (i = 0; i < 16; i++) + collect_register (regcache, arm_num_regs + i, (char *) buf + i * 8); - memset (zerobuf, 0, 8); - for (i = 0; i < arm_num_regs; i++) - if (arm_regmap[i] != -1) - supply_register (i, ((char *) buf) + arm_regmap[i]); - else - supply_register (i, zerobuf); + /* We only have access to wcssf, wcasf, and wcgr0-wcgr3. */ + for (i = 0; i < 6; i++) + collect_register (regcache, arm_num_regs + i + 16, + (char *) buf + 16 * 8 + i * 4); } -#ifdef __IWMMXT__ - static void -arm_fill_wmmxregset (void *buf) +arm_store_wmmxregset (struct regcache *regcache, const void *buf) { int i; + if (regcache->tdesc != tdesc_arm_with_iwmmxt) + return; + for (i = 0; i < 16; i++) - collect_register (arm_num_regs + i, (char *) buf + i * 8); + supply_register (regcache, arm_num_regs + i, (char *) buf + i * 8); /* We only have access to wcssf, wcasf, and wcgr0-wcgr3. */ for (i = 0; i < 6; i++) - collect_register (arm_num_regs + i + 16, (char *) buf + 16 * 8 + i * 4); + supply_register (regcache, arm_num_regs + i + 16, + (char *) buf + 16 * 8 + i * 4); } static void -arm_store_wmmxregset (const void *buf) +arm_fill_vfpregset (struct regcache *regcache, void *buf) { - int i; + int num; - for (i = 0; i < 16; i++) - supply_register (arm_num_regs + i, (char *) buf + i * 8); + if (regcache->tdesc == tdesc_arm_with_neon + || regcache->tdesc == tdesc_arm_with_vfpv3) + num = 32; + else if (regcache->tdesc == tdesc_arm_with_vfpv2) + num = 16; + else + return; - /* We only have access to wcssf, wcasf, and wcgr0-wcgr3. */ - for (i = 0; i < 6; i++) - supply_register (arm_num_regs + i + 16, (char *) buf + 16 * 8 + i * 4); + arm_fill_vfpregset_num (regcache, buf, num); } -#endif /* __IWMMXT__ */ - -extern int debug_threads; - +/* Wrapper of UNMAKE_THUMB_ADDR for get_next_pcs. */ static CORE_ADDR -arm_get_pc () +get_next_pcs_addr_bits_remove (struct arm_get_next_pcs *self, CORE_ADDR val) { - unsigned long pc; - collect_register_by_name ("pc", &pc); - if (debug_threads) - fprintf (stderr, "stop pc is %08lx\n", pc); - return pc; + return UNMAKE_THUMB_ADDR (val); } static void -arm_set_pc (CORE_ADDR pc) +arm_store_vfpregset (struct regcache *regcache, const void *buf) { - unsigned long newpc = pc; - supply_register_by_name ("pc", &newpc); -} + int num; -/* Correct in either endianness. We do not support Thumb yet. */ -static const unsigned long arm_breakpoint = 0xef9f0001; -#define arm_breakpoint_len 4 + if (regcache->tdesc == tdesc_arm_with_neon + || regcache->tdesc == tdesc_arm_with_vfpv3) + num = 32; + else if (regcache->tdesc == tdesc_arm_with_vfpv2) + num = 16; + else + return; -/* 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. */ -static const unsigned long arm_eabi_breakpoint = 0xe7f001f0; + arm_store_vfpregset_num (regcache, buf, num); +} +/* Wrapper of arm_is_thumb_mode for get_next_pcs. */ static int -arm_breakpoint_at (CORE_ADDR where) +get_next_pcs_is_thumb (struct arm_get_next_pcs *self) { - unsigned long insn; - - (*the_target->read_memory) (where, (unsigned char *) &insn, 4); - if (insn == arm_breakpoint) - return 1; - - if (insn == arm_eabi_breakpoint) - return 1; - - /* If necessary, recognize more trap instructions here. GDB only uses the - two. */ - - return 0; + return arm_is_thumb_mode (); } -/* 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 () +/* 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) { - unsigned long pc; - collect_register_by_name ("lr", &pc); - return pc; + ULONGEST res; + + (*the_target->read_memory) (memaddr, (unsigned char *) &res, len); + return res; } /* Fetch the thread-local storage pointer for libthread_db. */ ps_err_e ps_get_thread_area (const struct ps_prochandle *ph, - lwpid_t lwpid, int idx, void **base) + lwpid_t lwpid, int idx, void **base) { if (ptrace (PTRACE_GET_THREAD_AREA, lwpid, NULL, base) != 0) return PS_ERR; @@ -187,32 +284,765 @@ ps_get_thread_area (const struct ps_prochandle *ph, return PS_OK; } -struct regset_info target_regsets[] = { - { PTRACE_GETREGS, PTRACE_SETREGS, 18 * 4, + +/* Query Hardware Breakpoint information for the target we are attached to + (using PID as ptrace argument) and set up arm_linux_hwbp_cap. */ +static void +arm_linux_init_hwbp_cap (int pid) +{ + unsigned int val; + + if (ptrace (PTRACE_GETHBPREGS, pid, 0, &val) < 0) + return; + + arm_linux_hwbp_cap.arch = (unsigned char)((val >> 24) & 0xff); + if (arm_linux_hwbp_cap.arch == 0) + return; + + arm_linux_hwbp_cap.max_wp_length = (unsigned char)((val >> 16) & 0xff); + arm_linux_hwbp_cap.wp_count = (unsigned char)((val >> 8) & 0xff); + arm_linux_hwbp_cap.bp_count = (unsigned char)(val & 0xff); + + if (arm_linux_hwbp_cap.wp_count > MAX_WPTS) + internal_error (__FILE__, __LINE__, "Unsupported number of watchpoints"); + if (arm_linux_hwbp_cap.bp_count > MAX_BPTS) + internal_error (__FILE__, __LINE__, "Unsupported number of breakpoints"); +} + +/* How many hardware breakpoints are available? */ +static int +arm_linux_get_hw_breakpoint_count (void) +{ + return arm_linux_hwbp_cap.bp_count; +} + +/* How many hardware watchpoints are available? */ +static int +arm_linux_get_hw_watchpoint_count (void) +{ + return arm_linux_hwbp_cap.wp_count; +} + +/* Maximum length of area watched by hardware watchpoint. */ +static int +arm_linux_get_hw_watchpoint_max_length (void) +{ + return arm_linux_hwbp_cap.max_wp_length; +} + +/* Initialize an ARM hardware break-/watch-point control register value. + BYTE_ADDRESS_SELECT is the mask of bytes to trigger on; HWBP_TYPE is the + type of break-/watch-point; ENABLE indicates whether the point is enabled. + */ +static arm_hwbp_control_t +arm_hwbp_control_initialize (unsigned byte_address_select, + arm_hwbp_type hwbp_type, + int enable) +{ + gdb_assert ((byte_address_select & ~0xffU) == 0); + gdb_assert (hwbp_type != arm_hwbp_break + || ((byte_address_select & 0xfU) != 0)); + + return (byte_address_select << 5) | (hwbp_type << 3) | (3 << 1) | enable; +} + +/* Does the breakpoint control value CONTROL have the enable bit set? */ +static int +arm_hwbp_control_is_enabled (arm_hwbp_control_t control) +{ + return control & 0x1; +} + +/* Is the breakpoint control value CONTROL initialized? */ +static int +arm_hwbp_control_is_initialized (arm_hwbp_control_t control) +{ + return control != 0; +} + +/* Change a breakpoint control word so that it is in the disabled state. */ +static arm_hwbp_control_t +arm_hwbp_control_disable (arm_hwbp_control_t control) +{ + return control & ~0x1; +} + +/* Are two break-/watch-points equal? */ +static int +arm_linux_hw_breakpoint_equal (const struct arm_linux_hw_breakpoint *p1, + const struct arm_linux_hw_breakpoint *p2) +{ + return p1->address == p2->address && p1->control == p2->control; +} + +/* Convert a raw breakpoint type to an enum arm_hwbp_type. */ + +static arm_hwbp_type +raw_bkpt_type_to_arm_hwbp_type (enum raw_bkpt_type raw_type) +{ + switch (raw_type) + { + case raw_bkpt_type_hw: + return arm_hwbp_break; + case raw_bkpt_type_write_wp: + return arm_hwbp_store; + case raw_bkpt_type_read_wp: + return arm_hwbp_load; + case raw_bkpt_type_access_wp: + return arm_hwbp_access; + default: + gdb_assert_not_reached ("unhandled raw type"); + } +} + +/* Initialize the hardware breakpoint structure P for a breakpoint or + watchpoint at ADDR to LEN. The type of watchpoint is given in TYPE. + Returns -1 if TYPE is unsupported, or -2 if the particular combination + of ADDR and LEN cannot be implemented. Otherwise, returns 0 if TYPE + represents a breakpoint and 1 if type represents a watchpoint. */ +static int +arm_linux_hw_point_initialize (enum raw_bkpt_type raw_type, CORE_ADDR addr, + int len, struct arm_linux_hw_breakpoint *p) +{ + arm_hwbp_type hwbp_type; + unsigned mask; + + hwbp_type = raw_bkpt_type_to_arm_hwbp_type (raw_type); + + if (hwbp_type == arm_hwbp_break) + { + /* For breakpoints, the length field encodes the mode. */ + switch (len) + { + case 2: /* 16-bit Thumb mode breakpoint */ + case 3: /* 32-bit Thumb mode breakpoint */ + mask = 0x3; + addr &= ~1; + break; + case 4: /* 32-bit ARM mode breakpoint */ + mask = 0xf; + addr &= ~3; + break; + default: + /* Unsupported. */ + return -2; + } + } + else + { + CORE_ADDR max_wp_length = arm_linux_get_hw_watchpoint_max_length (); + CORE_ADDR aligned_addr; + + /* Can not set watchpoints for zero or negative lengths. */ + if (len <= 0) + return -2; + /* The current ptrace interface can only handle watchpoints that are a + power of 2. */ + if ((len & (len - 1)) != 0) + return -2; + + /* Test that the range [ADDR, ADDR + LEN) fits into the largest address + range covered by a watchpoint. */ + aligned_addr = addr & ~(max_wp_length - 1); + if (aligned_addr + max_wp_length < addr + len) + return -2; + + mask = (1 << len) - 1; + } + + p->address = (unsigned int) addr; + p->control = arm_hwbp_control_initialize (mask, hwbp_type, 1); + + return hwbp_type != arm_hwbp_break; +} + +/* 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) +{ + 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); + } + + return 0; +} + +static int +arm_supports_z_point_type (char z_type) +{ + switch (z_type) + { + case Z_PACKET_SW_BP: + case Z_PACKET_HW_BP: + case Z_PACKET_WRITE_WP: + case Z_PACKET_READ_WP: + case Z_PACKET_ACCESS_WP: + return 1; + default: + /* Leave the handling of sw breakpoints with the gdb client. */ + return 0; + } +} + +/* Insert hardware break-/watchpoint. */ +static int +arm_insert_point (enum raw_bkpt_type type, CORE_ADDR addr, + int len, struct raw_breakpoint *bp) +{ + struct process_info *proc = current_process (); + struct arm_linux_hw_breakpoint p, *pts; + int watch, i, count; + + watch = arm_linux_hw_point_initialize (type, addr, len, &p); + if (watch < 0) + { + /* Unsupported. */ + return watch == -1 ? 1 : -1; + } + + if (watch) + { + count = arm_linux_get_hw_watchpoint_count (); + pts = proc->priv->arch_private->wpts; + } + else + { + count = arm_linux_get_hw_breakpoint_count (); + pts = proc->priv->arch_private->bpts; + } + + 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); + return 0; + } + + /* We're out of watchpoints. */ + return -1; +} + +/* Remove hardware break-/watchpoint. */ +static int +arm_remove_point (enum raw_bkpt_type type, CORE_ADDR addr, + int len, struct raw_breakpoint *bp) +{ + struct process_info *proc = current_process (); + struct arm_linux_hw_breakpoint p, *pts; + int watch, i, count; + + watch = arm_linux_hw_point_initialize (type, addr, len, &p); + if (watch < 0) + { + /* Unsupported. */ + return -1; + } + + if (watch) + { + count = arm_linux_get_hw_watchpoint_count (); + pts = proc->priv->arch_private->wpts; + } + else + { + count = arm_linux_get_hw_breakpoint_count (); + pts = proc->priv->arch_private->bpts; + } + + 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); + return 0; + } + + /* No watchpoint matched. */ + return -1; +} + +/* Return whether current thread is stopped due to a watchpoint. */ +static int +arm_stopped_by_watchpoint (void) +{ + struct lwp_info *lwp = get_thread_lwp (current_thread); + siginfo_t siginfo; + + /* We must be able to set hardware watchpoints. */ + if (arm_linux_get_hw_watchpoint_count () == 0) + return 0; + + /* Retrieve siginfo. */ + errno = 0; + ptrace (PTRACE_GETSIGINFO, lwpid_of (current_thread), 0, &siginfo); + if (errno != 0) + return 0; + + /* This must be a hardware breakpoint. */ + if (siginfo.si_signo != SIGTRAP + || (siginfo.si_code & 0xffff) != 0x0004 /* TRAP_HWBKPT */) + return 0; + + /* If we are in a positive slot then we're looking at a breakpoint and not + a watchpoint. */ + if (siginfo.si_errno >= 0) + return 0; + + /* Cache stopped data address for use by arm_stopped_data_address. */ + lwp->arch_private->stopped_data_address + = (CORE_ADDR) (uintptr_t) siginfo.si_addr; + + return 1; +} + +/* Return data address that triggered watchpoint. Called only if + arm_stopped_by_watchpoint returned true. */ +static CORE_ADDR +arm_stopped_data_address (void) +{ + struct lwp_info *lwp = get_thread_lwp (current_thread); + return lwp->arch_private->stopped_data_address; +} + +/* Called when a new process is created. */ +static struct arch_process_info * +arm_new_process (void) +{ + struct arch_process_info *info = XCNEW (struct arch_process_info); + return info; +} + +/* Called when a new thread is detected. */ +static void +arm_new_thread (struct lwp_info *lwp) +{ + struct arch_lwp_info *info = XCNEW (struct arch_lwp_info); + int i; + + for (i = 0; i < MAX_BPTS; i++) + info->bpts_changed[i] = 1; + for (i = 0; i < MAX_WPTS; i++) + info->wpts_changed[i] = 1; + + lwp->arch_private = info; +} + +static void +arm_new_fork (struct process_info *parent, struct process_info *child) +{ + struct arch_process_info *parent_proc_info; + struct arch_process_info *child_proc_info; + struct lwp_info *child_lwp; + struct arch_lwp_info *child_lwp_info; + int i; + + /* These are allocated by linux_add_process. */ + gdb_assert (parent->priv != NULL + && parent->priv->arch_private != NULL); + gdb_assert (child->priv != NULL + && child->priv->arch_private != NULL); + + parent_proc_info = parent->priv->arch_private; + child_proc_info = child->priv->arch_private; + + /* Linux kernel before 2.6.33 commit + 72f674d203cd230426437cdcf7dd6f681dad8b0d + will inherit hardware debug registers from parent + on fork/vfork/clone. Newer Linux kernels create such tasks with + zeroed debug registers. + + GDB core assumes the child inherits the watchpoints/hw + breakpoints of the parent, and will remove them all from the + forked off process. Copy the debug registers mirrors into the + new process so that all breakpoints and watchpoints can be + removed together. The debug registers mirror will become zeroed + in the end before detaching the forked off process, thus making + this compatible with older Linux kernels too. */ + + *child_proc_info = *parent_proc_info; + + /* 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_info = child_lwp->arch_private; + for (i = 0; i < MAX_BPTS; i++) + child_lwp_info->bpts_changed[i] = 1; + for (i = 0; i < MAX_WPTS; i++) + child_lwp_info->wpts_changed[i] = 1; +} + +/* Called when resuming a thread. + If the debug regs have changed, update the thread's copies. */ +static void +arm_prepare_to_resume (struct lwp_info *lwp) +{ + struct thread_info *thread = get_lwp_thread (lwp); + int pid = lwpid_of (thread); + struct process_info *proc = find_process_pid (pid_of (thread)); + struct arch_process_info *proc_info = proc->priv->arch_private; + struct arch_lwp_info *lwp_info = lwp->arch_private; + int i; + + for (i = 0; i < arm_linux_get_hw_breakpoint_count (); i++) + if (lwp_info->bpts_changed[i]) + { + errno = 0; + + if (arm_hwbp_control_is_enabled (proc_info->bpts[i].control)) + if (ptrace (PTRACE_SETHBPREGS, pid, + (PTRACE_TYPE_ARG3) ((i << 1) + 1), + &proc_info->bpts[i].address) < 0) + perror_with_name ("Unexpected error setting breakpoint address"); + + if (arm_hwbp_control_is_initialized (proc_info->bpts[i].control)) + if (ptrace (PTRACE_SETHBPREGS, pid, + (PTRACE_TYPE_ARG3) ((i << 1) + 2), + &proc_info->bpts[i].control) < 0) + perror_with_name ("Unexpected error setting breakpoint"); + + lwp_info->bpts_changed[i] = 0; + } + + for (i = 0; i < arm_linux_get_hw_watchpoint_count (); i++) + if (lwp_info->wpts_changed[i]) + { + errno = 0; + + if (arm_hwbp_control_is_enabled (proc_info->wpts[i].control)) + if (ptrace (PTRACE_SETHBPREGS, pid, + (PTRACE_TYPE_ARG3) -((i << 1) + 1), + &proc_info->wpts[i].address) < 0) + perror_with_name ("Unexpected error setting watchpoint address"); + + if (arm_hwbp_control_is_initialized (proc_info->wpts[i].control)) + if (ptrace (PTRACE_SETHBPREGS, pid, + (PTRACE_TYPE_ARG3) -((i << 1) + 2), + &proc_info->wpts[i].control) < 0) + perror_with_name ("Unexpected error setting watchpoint"); + + lwp_info->wpts_changed[i] = 0; + } +} + +/* 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; + CORE_ADDR 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 pc) +{ + CORE_ADDR next_pc = 0; + 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; + + (*the_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) +{ + unsigned char *data = (unsigned char *) alloca (8); + int offset = 0; + + while ((*the_target->read_auxv) (offset, data, 8) == 8) + { + unsigned int *data_p = (unsigned int *)data; + if (data_p[0] == AT_HWCAP) + { + *valp = data_p[1]; + return 1; + } + + offset += 8; + } + + *valp = 0; + return 0; +} + +static const struct target_desc * +arm_read_description (void) +{ + int pid = lwpid_of (current_thread); + unsigned long arm_hwcap = 0; + + /* Query hardware watchpoint/breakpoint capabilities. */ + arm_linux_init_hwbp_cap (pid); + + if (arm_get_hwcap (&arm_hwcap) == 0) + return tdesc_arm; + + if (arm_hwcap & HWCAP_IWMMXT) + return tdesc_arm_with_iwmmxt; + + if (arm_hwcap & HWCAP_VFP) + { + const struct target_desc *result; + char *buf; + + /* NEON implies either no VFP, or VFPv3-D32. We only support + it with VFP. */ + if (arm_hwcap & HWCAP_NEON) + result = tdesc_arm_with_neon; + else if ((arm_hwcap & (HWCAP_VFPv3 | HWCAP_VFPv3D16)) == HWCAP_VFPv3) + result = tdesc_arm_with_vfpv3; + else + result = tdesc_arm_with_vfpv2; + + /* Now make sure that the kernel supports reading these + registers. Support was added in 2.6.30. */ + errno = 0; + buf = (char *) xmalloc (32 * 8 + 4); + if (ptrace (PTRACE_GETVFPREGS, pid, 0, buf) < 0 + && errno == EIO) + result = tdesc_arm; + + free (buf); + + return result; + } + + /* The default configuration uses legacy FPA registers, probably + simulated. */ + return tdesc_arm; +} + +static void +arm_arch_setup (void) +{ + int tid = lwpid_of (current_thread); + int gpregs[18]; + struct iovec iov; + + current_process ()->tdesc = arm_read_description (); + + iov.iov_base = gpregs; + iov.iov_len = sizeof (gpregs); + + /* Check if PTRACE_GETREGSET works. */ + if (ptrace (PTRACE_GETREGSET, tid, NT_PRSTATUS, &iov) == 0) + have_ptrace_getregset = 1; + else + have_ptrace_getregset = 0; +} + +/* Fetch the next possible PCs after the current instruction executes. */ + +static VEC (CORE_ADDR) * +arm_gdbserver_get_next_pcs (struct regcache *regcache) +{ + struct arm_get_next_pcs next_pcs_ctx; + VEC (CORE_ADDR) *next_pcs = NULL; + + arm_get_next_pcs_ctor (&next_pcs_ctx, + &get_next_pcs_ops, + /* Byte order is ignored assumed as host. */ + 0, + 0, + 1, + regcache); + + next_pcs = arm_get_next_pcs (&next_pcs_ctx); + + return next_pcs; +} + +/* Support for hardware single step. */ + +static int +arm_supports_hardware_single_step (void) +{ + return 0; +} + +/* Register sets without using PTRACE_GETREGSET. */ + +static struct regset_info arm_regsets[] = { + { PTRACE_GETREGS, PTRACE_SETREGS, 0, 18 * 4, GENERAL_REGS, arm_fill_gregset, arm_store_gregset }, -#ifdef __IWMMXT__ - { PTRACE_GETWMMXREGS, PTRACE_SETWMMXREGS, 16 * 8 + 6 * 4, + { PTRACE_GETWMMXREGS, PTRACE_SETWMMXREGS, 0, 16 * 8 + 6 * 4, EXTENDED_REGS, arm_fill_wmmxregset, arm_store_wmmxregset }, -#endif - { 0, 0, -1, -1, NULL, NULL } + { PTRACE_GETVFPREGS, PTRACE_SETVFPREGS, 0, 32 * 8 + 4, + EXTENDED_REGS, + arm_fill_vfpregset, arm_store_vfpregset }, + NULL_REGSET }; +static struct regsets_info arm_regsets_info = + { + arm_regsets, /* regsets */ + 0, /* num_regsets */ + NULL, /* disabled_regsets */ + }; + +static struct usrregs_info arm_usrregs_info = + { + arm_num_regs, + arm_regmap, + }; + +static struct regs_info regs_info_arm = + { + NULL, /* regset_bitmap */ + &arm_usrregs_info, + &arm_regsets_info + }; + +static const struct regs_info * +arm_regs_info (void) +{ + const struct target_desc *tdesc = current_process ()->tdesc; + + if (have_ptrace_getregset == 1 + && (tdesc == tdesc_arm_with_neon || tdesc == tdesc_arm_with_vfpv3)) + return ®s_info_aarch32; + else + return ®s_info_arm; +} + struct linux_target_ops the_low_target = { - arm_num_regs, - arm_regmap, + arm_arch_setup, + arm_regs_info, arm_cannot_fetch_register, arm_cannot_store_register, - arm_get_pc, - arm_set_pc, -#ifndef __ARM_EABI__ - (const unsigned char *) &arm_breakpoint, -#else - (const unsigned char *) &arm_eabi_breakpoint, -#endif - arm_breakpoint_len, - arm_reinsert_addr, + NULL, /* fetch_register */ + linux_get_pc_32bit, + linux_set_pc_32bit, + arm_breakpoint_kind_from_pc, + arm_sw_breakpoint_from_kind, + arm_gdbserver_get_next_pcs, 0, arm_breakpoint_at, + arm_supports_z_point_type, + arm_insert_point, + arm_remove_point, + arm_stopped_by_watchpoint, + arm_stopped_data_address, + NULL, /* collect_ptrace_register */ + NULL, /* supply_ptrace_register */ + NULL, /* siginfo_fixup */ + arm_new_process, + arm_new_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 }; + +void +initialize_low_arch (void) +{ + /* Initialize the Linux target descriptions. */ + init_registers_arm (); + init_registers_arm_with_iwmmxt (); + init_registers_arm_with_vfpv2 (); + init_registers_arm_with_vfpv3 (); + + initialize_low_arch_aarch32 (); + + initialize_regsets_info (&arm_regsets_info); +}