This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[PATCH] gdb: Add native support for Linux SH
- From: John Paul Adrian Glaubitz <glaubitz at physik dot fu-berlin dot de>
- To: gdb-patches at sourceware dot org
- Cc: John Paul Adrian Glaubitz <glaubitz at physik dot fu-berlin dot de>
- Date: Fri, 10 Feb 2017 21:55:46 +0100
- Subject: [PATCH] gdb: Add native support for Linux SH
- Authentication-results: sourceware.org; auth=none
- References: <20170210205546.7982-1-glaubitz@physik.fu-berlin.de>
Signed-off-by: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
---
gdb/Makefile.in | 2 +
gdb/config/sh/linux.mh | 14 +
gdb/config/sh/nm-linux.h | 54 ++++
gdb/config/sh/xm-linux.h | 32 +++
gdb/configure.host | 1 +
gdb/sh-linux-nat.c | 268 ++++++++++++++++++
gdb/sh-linux-tdep.c | 516 +++++++++++++++++++++++++++++++++++
gdb/sh-tdep.c | 54 ++--
gdb/sh-tdep.h | 53 +++-
gdb/testsuite/gdb.asm/asm-source.exp | 5 +
gdb/testsuite/gdb.asm/sh-linux.inc | 78 ++++++
gdb/testsuite/gdb.asm/sh.inc | 3 +-
12 files changed, 1049 insertions(+), 31 deletions(-)
create mode 100644 gdb/config/sh/linux.mh
create mode 100644 gdb/config/sh/nm-linux.h
create mode 100644 gdb/config/sh/xm-linux.h
create mode 100644 gdb/sh-linux-nat.c
create mode 100644 gdb/testsuite/gdb.asm/sh-linux.inc
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index e0fe442938..0c7ef59add 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -2593,6 +2593,8 @@ ALLDEPFILES = \
ser-tcp.c \
sh-nbsd-nat.c \
sh-nbsd-tdep.c \
+ sh-linux-nat.c \
+ sh-linux-tdep.c \
sh-tdep.c \
sh64-tdep.c \
sol2-tdep.c \
diff --git a/gdb/config/sh/linux.mh b/gdb/config/sh/linux.mh
new file mode 100644
index 0000000000..9a1d332c14
--- /dev/null
+++ b/gdb/config/sh/linux.mh
@@ -0,0 +1,14 @@
+# Host: Renesas Super-H running GNU/Linux
+
+NAT_FILE= nm-linux.h
+NATDEPFILES= inf-ptrace.o fork-child.o \
+ sh-linux-nat.o \
+ proc-service.o linux-thread-db.o \
+ linux-nat.o linux-osdata.o linux-fork.o linux-procfs.o linux-ptrace.o \
+ linux-personality.o \
+ linux-waitpid.o linux-namespaces.o
+NAT_CDEPS = $(srcdir)/proc-service.list
+
+# The dynamically loaded libthread_db needs access to symbols in the
+# gdb executable.
+LOADLIBES = -ldl $(RDYNAMIC)
diff --git a/gdb/config/sh/nm-linux.h b/gdb/config/sh/nm-linux.h
new file mode 100644
index 0000000000..4e6fd5a9e2
--- /dev/null
+++ b/gdb/config/sh/nm-linux.h
@@ -0,0 +1,54 @@
+/* Native-dependent definitions for SuperH running Linux, for GDB.
+ Copyright 2004 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+
+#ifndef NM_LINUX_H
+#define NM_LINUX_H
+
+/* Get generic Linux native definitions. */
+#include "config/nm-linux.h"
+/* Support for the user area. */
+
+/* Return the size of the user struct. */
+extern int kernel_u_size (void);
+#define KERNEL_U_SIZE kernel_u_size()
+
+/* This is the amount to substract from u.u_ar0 to get the offset in
+ the core file of the register values. */
+#define KERNEL_U_ADDR 0
+
+#define U_REGS_OFFSET 0
+
+extern CORE_ADDR register_u_addr (CORE_ADDR blockend, int regnum);
+#define REGISTER_U_ADDR(addr, blockend, regnum) \
+ (addr) = register_u_addr (blockend, regnum)
+
+/* Override copies of {fetch,store}_inferior_registers in `infptrace.c'. */
+#define FETCH_INFERIOR_REGISTERS
+
+/* Nevertheless, define CANNOT_{FETCH,STORE}_REGISTER, because we
+ might fall back on the code `infptrace.c' (well a copy of that code
+ in `sh-linux-nat.c' for now) and we can access only the
+ general-purpose registers in that way. */
+extern int cannot_fetch_register (int regno);
+extern int cannot_store_register (int regno);
+#define CANNOT_FETCH_REGISTER(regno) cannot_fetch_register (regno)
+#define CANNOT_STORE_REGISTER(regno) cannot_store_register (regno)
+
+#endif /* NM_LINUX_H */
diff --git a/gdb/config/sh/xm-linux.h b/gdb/config/sh/xm-linux.h
new file mode 100644
index 0000000000..648209392f
--- /dev/null
+++ b/gdb/config/sh/xm-linux.h
@@ -0,0 +1,32 @@
+/* Native support for GNU/Linux, for GDB, the GNU debugger.
+ Copyright (C) 2000 Free Software Foundation, Inc.
+
+This file is part of GDB.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#ifndef XM_LINUX_H
+#define XM_LINUX_H
+
+#define HOST_BYTE_ORDER LITTLE_ENDIAN
+
+#define HAVE_TERMIOS
+
+#define NEED_POSIX_SETPGID
+
+/* Need R_OK etc, but USG isn't defined. */
+#include <unistd.h>
+
+#endif /* #ifndef XM_LINUX_H */
diff --git a/gdb/configure.host b/gdb/configure.host
index c45f61dbba..c56b184ec1 100644
--- a/gdb/configure.host
+++ b/gdb/configure.host
@@ -150,6 +150,7 @@ powerpc*-*-linux*) gdb_host=linux ;;
s390*-*-linux*) gdb_host=linux ;;
+sh*-*-linux*) gdb_host=linux ;;
sh*-*-netbsdelf* | sh*-*-knetbsd*-gnu)
gdb_host=nbsd ;;
sh*-*-openbsd*) gdb_host=nbsd ;;
diff --git a/gdb/sh-linux-nat.c b/gdb/sh-linux-nat.c
new file mode 100644
index 0000000000..53fbe1e8b0
--- /dev/null
+++ b/gdb/sh-linux-nat.c
@@ -0,0 +1,268 @@
+/* Low level SH interface to ptrace, for GDB when running native.
+ Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+
+This file is part of GDB.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include "defs.h"
+#include "inferior.h"
+#include "gdbcore.h"
+#include "regcache.h"
+#include "linux-nat.h"
+#include "target.h"
+#include "arch-utils.h"
+
+#include "gdb_assert.h"
+#include <sys/ptrace.h>
+#include <sys/user.h>
+#include <sys/procfs.h>
+#include <asm/ptrace.h>
+
+/* Prototypes for supply_gregset etc. */
+#include "gregset.h"
+#include "sh-tdep.h"
+
+/* Defines ps_err_e, struct ps_prochandle. */
+#include "gdb_proc_service.h"
+
+//#include <asm/elf.h>
+
+#define SH_LINUX_NUM_REGS 40
+/* This table must line up with REGISTER_NAME in "sh-tdep.c". */
+static const int regmap[] =
+{
+ /* general registers 0-15 */
+ REG_REG0 , REG_REG0+1 , REG_REG0+2 , REG_REG0+3,
+ REG_REG0+4 , REG_REG0+5 , REG_REG0+6 , REG_REG0+7,
+ REG_REG0+8 , REG_REG0+9 , REG_REG0+10, REG_REG0+11,
+ REG_REG0+12, REG_REG0+13, REG_REG0+14, REG_REG0+15,
+ /* 16 - 22 */
+ REG_PC, REG_PR, REG_GBR, -1, REG_MACH, REG_MACL, REG_SR,
+ /* 23, 24 */
+ REG_FPUL, REG_FPSCR,
+ /* floating point registers 25 - 40 */
+ REG_FPREG0 , REG_FPREG0+1 , REG_FPREG0+2 , REG_FPREG0+3 ,
+ REG_FPREG0+4 , REG_FPREG0+5 , REG_FPREG0+6 , REG_FPREG0+7 ,
+ REG_FPREG0+8 , REG_FPREG0+9 , REG_FPREG0+10, REG_FPREG0+11,
+ REG_FPREG0+12, REG_FPREG0+13, REG_FPREG0+14, REG_FPREG0+15,
+};
+
+CORE_ADDR
+register_u_addr (CORE_ADDR blockend, int regnum)
+{
+ if (regnum < 0 || regnum >= sizeof regmap/sizeof regmap[0])
+ return (CORE_ADDR)-1;
+ return (blockend + 4 * regmap[regnum]);
+}
+
+
+/* Return the address in the core dump or inferior of register REGNO.
+ BLOCKEND is the address of the end of the user structure. */
+
+CORE_ADDR
+register_addr (int regno, CORE_ADDR blockend)
+{
+ CORE_ADDR addr;
+
+ if (regno < 0 || regno >= SH_LINUX_NUM_REGS) {
+ internal_error (__FILE__, __LINE__,
+ _("Got request for bad register number %d."), regno);
+ }
+
+ REGISTER_U_ADDR (addr, blockend, regno);
+
+ return addr;
+}
+
+/* Fetch one register. */
+
+static void
+fetch_register (struct regcache *regcache, int tid, int regno)
+{
+ int val;
+
+ if (cannot_fetch_register (regno))
+ {
+ regcache_raw_supply (regcache, regno, NULL);
+ return;
+ }
+
+ errno = 0;
+ val = ptrace (PTRACE_PEEKUSER, tid, register_addr (regno, 0), 0);
+ if (errno != 0)
+ perror_with_name (_("Couldn't get registers"));
+
+ regcache_raw_supply (regcache, regno, &val);
+}
+
+/* Store one register. */
+
+static void
+store_register (struct regcache *regcache, int tid, int regno)
+{
+ int val;
+
+ if (cannot_store_register (regno))
+ return;
+
+ errno = 0;
+ regcache_raw_collect (regcache, regno, &val);
+ ptrace (PTRACE_POKEUSER, tid, register_addr (regno, 0), val);
+ if (errno != 0)
+ perror_with_name (_("Couldn't write registers"));
+}
+
+/* Transfering the general-purpose registers between GDB, inferiors
+ and core files. */
+
+/* Fill GDB's register array with the general-purpose register values
+ in *GREGSETP. */
+
+void
+supply_gregset (struct regcache *regcache, const elf_gregset_t *gregsetp)
+{
+ elf_greg_t *regp = (elf_greg_t *) gregsetp;
+ int i;
+
+ for (i = 0; i < 23; i++)
+ if (regmap[i] == -1)
+ regcache_raw_supply (regcache, i, NULL);
+ else
+ regcache_raw_supply (regcache, i, (char *) (regp + regmap[i]));
+}
+
+/* Fill register REGNO (if it is a general-purpose register) in
+ *GREGSETPS with the value in GDB's register array. If REGNO is -1,
+ do this for all registers. */
+
+void
+fill_gregset (const struct regcache *regcache, elf_gregset_t *gregsetp, int regno)
+{
+ elf_greg_t *regp = (elf_greg_t *) gregsetp;
+ int i;
+
+ for (i = 0; i < 23; i++)
+ if (regmap[i] != -1 && (regno == -1 || regno == i))
+ regcache_raw_collect (regcache, i, (char *) (regp + regmap[i]));
+}
+
+/* Transfering floating-point registers between GDB, inferiors and cores. */
+
+/* Fill GDB's register array with the floating-point register values in
+ *FPREGSETP. */
+
+void
+supply_fpregset (struct regcache *regcache, const elf_fpregset_t *fpregsetp)
+{
+ int i;
+ long *regp = (long *)fpregsetp;
+
+ for (i = 0; i < 16; i++)
+ regcache_raw_supply (regcache, 25 + i, (char *) (regp + i));
+ regcache_raw_supply (regcache, FPUL_REGNUM, (char *) (regp + REG_FPUL - REG_FPREG0));
+ regcache_raw_supply (regcache, FPSCR_REGNUM, (char *) (regp + REG_FPSCR - REG_FPREG0));
+}
+
+/* Fill register REGNO (if it is a floating-point register) in
+ *FPREGSETP with the value in GDB's register array. If REGNO is -1,
+ do this for all registers. */
+
+void
+fill_fpregset (const struct regcache *regcache, elf_fpregset_t *fpregsetp, int regno)
+{
+ int i;
+ long *regp = (long *)fpregsetp;
+
+ for (i = 0; i < 16; i++)
+ if ((regno == -1) || (regno == i))
+ regcache_raw_collect (regcache, 25 + i, (char *) (regp + i));
+ if ((regno == -1) || regno == FPSCR_REGNUM)
+ regcache_raw_collect (regcache, FPSCR_REGNUM, (char *) (regp + REG_FPSCR - REG_FPREG0));
+ if ((regno == -1) || regno == FPUL_REGNUM)
+ regcache_raw_collect (regcache, FPUL_REGNUM, (char *) (regp + REG_FPUL - REG_FPREG0));
+}
+
+/* Transferring arbitrary registers between GDB and inferior. */
+
+/* Check if register REGNO in the child process is accessible.
+ If we are accessing registers directly via the U area, only the
+ general-purpose registers are available.
+ All registers should be accessible if we have GETREGS support. */
+
+int
+cannot_fetch_register (int regno)
+{
+ return (regno < 0 || regno >= sizeof regmap / sizeof regmap[0] || regmap[regno] == -1);
+}
+
+int
+cannot_store_register (int regno)
+{
+ return (regno < 0 || regno >= sizeof regmap / sizeof regmap[0] || regmap[regno] == -1);
+}
+
+/* Fetch register values from the inferior.
+ If REGNO is negative, do this for all registers.
+ Otherwise, REGNO specifies which register (so we can save time). */
+
+static void
+sh_linux_fetch_inferior_registers (struct target_ops *ops, struct regcache *regcache, int regno)
+{
+ int i;
+ int tid;
+
+ /* GNU/Linux LWP ID's are process ID's. */
+ if ((tid = ptid_get_lwp (inferior_ptid)) == 0)
+ tid = ptid_get_pid (inferior_ptid); /* Not a threaded program. */
+
+ for (i = 0; i < SH_LINUX_NUM_REGS; i++)
+ if (regno == -1 || regno == i)
+ fetch_register (regcache, tid, i);
+}
+/* Store our register values back into the inferior.
+ If REGNO is negative, do this for all registers.
+ Otherwise, REGNO specifies which register (so we can save time). */
+
+static void
+sh_linux_store_inferior_registers (struct target_ops *ops, struct regcache *regcache, int regno)
+{
+ int i;
+ int tid;
+
+ /* GNU/Linux LWP ID's are process ID's. */
+ if ((tid = ptid_get_lwp (inferior_ptid)) == 0)
+ tid = ptid_get_pid (inferior_ptid); /* Not a threaded program. */
+
+ for (i = 0; i < SH_LINUX_NUM_REGS; i++)
+ if (regno == -1 || regno == i)
+ store_register (regcache, tid, i);
+}
+
+void
+_initialize_sh_linux_nat (void)
+{
+ struct target_ops *t;
+
+ /* Fill in the generic GNU/Linux methods. */
+ t = linux_target ();
+
+ /* Add our register access methods. */
+ t->to_fetch_registers = sh_linux_fetch_inferior_registers;
+ t->to_store_registers = sh_linux_store_inferior_registers;
+
+ /* Register the target. */
+ linux_nat_add_target (t);
+}
diff --git a/gdb/sh-linux-tdep.c b/gdb/sh-linux-tdep.c
index c5c745d218..28b0f56743 100644
--- a/gdb/sh-linux-tdep.c
+++ b/gdb/sh-linux-tdep.c
@@ -18,14 +18,36 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "defs.h"
+#include "gdbcore.h"
+#include "frame.h"
+#include "frame-base.h"
+#include "frame-unwind.h"
+#include "dwarf2-frame.h"
+#include "value.h"
+#include "regcache.h"
+#include "inferior.h"
#include "osabi.h"
+#include "reggroups.h"
+#include "arch-utils.h"
+#include "floatformat.h"
#include "solib-svr4.h"
#include "symtab.h"
+#include "command.h"
+#include "gdb_assert.h"
#include "trad-frame.h"
#include "tramp-frame.h"
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/user.h>
+#include <sys/syscall.h>
+
+#include <asm/ptrace.h>
+
+#include "regset.h"
#include "glibc-tdep.h"
#include "sh-tdep.h"
#include "linux-tdep.h"
@@ -180,11 +202,505 @@ static struct tramp_frame sh_linux_rt_sigreturn_tramp_frame = {
sh_linux_rt_sigreturn_init
};
+/* Recognizing signal handler frames. */
+
+/* GNU/Linux has two flavors of signals. Normal signal handlers, and
+ "realtime" (RT) signals. The RT signals can provide additional
+ information to the signal handler if the SA_SIGINFO flag is set
+ when establishing a signal handler using `sigaction'. It is not
+ unlikely that future versions of GNU/Linux will support SA_SIGINFO
+ for normal signals too. */
+
+/* When the SH Linux kernel calls a signal handler and the
+ SA_RESTORER flag isn't set, the return address points to a bit of
+ code on the stack. This function returns whether the PC appears to
+ be within this bit of code.
+
+ The instruction sequence for normal signals is
+ mov.w 1f,r3
+ trapa #16
+ or r0, r0
+ or r0, r0
+ or r0, r0
+ or r0, r0
+ or r0, r0
+ 1: .word __NR_sigreturn
+ or 0x9305 0xc310 0x200b 0x200b 0x200b 0x200b 0x200b 0x0077.
+
+ Checking for the code sequence should be somewhat reliable, because
+ the effect is to call the system call sigreturn. This is unlikely
+ to occur anywhere other than a signal trampoline.
+
+ It kind of sucks that we have to read memory from the process in
+ order to identify a signal trampoline, but there doesn't seem to be
+ any other way. The PC_IN_SIGTRAMP macro in tm-linux.h arranges to
+ only call us if no function name could be identified, which should
+ be the case since the code is on the stack.
+
+ Detection of signal trampolines for handlers that set the
+ SA_RESTORER flag is in general not possible. Unfortunately this is
+ what the GNU C Library has been doing for quite some time now.
+ However, as of version 2.1.2, the GNU C Library uses signal
+ trampolines (named __restore and __restore_rt) that are identical
+ to the ones used by the kernel. Therefore, these trampolines are
+ supported too. */
+
+#define MOVW(n) (0x9300|((n)-2)) /* Move mem word at PC+n to R3 */
+#define TRAP16 0xc310 /* Syscall w/no args (NR in R3) */
+#define OR_R0_R0 0x200b /* or r0,r0 (insert to avoid hardware bug) */
+
+#define LINUX_SIGTRAMP_INSN0 MOVW(7) /* Move mem word at PC+7 to R3 */
+#define LINUX_SIGTRAMP_INSN1 TRAP16 /* Syscall w/no args (NR in R3) */
+#define LINUX_SIGTRAMP_INSN2 OR_R0_R0 /* or r0,r0 (insert to avoid hardware bug) */
+
+static const unsigned short linux_sigtramp_code[] =
+{
+ LINUX_SIGTRAMP_INSN0,
+ LINUX_SIGTRAMP_INSN1,
+ LINUX_SIGTRAMP_INSN2,
+ LINUX_SIGTRAMP_INSN2,
+ LINUX_SIGTRAMP_INSN2,
+ LINUX_SIGTRAMP_INSN2,
+ LINUX_SIGTRAMP_INSN2,
+ __NR_sigreturn
+};
+
+#define LINUX_SIGTRAMP_LEN (sizeof linux_sigtramp_code)
+
+/* If PC is in a sigtramp routine, return the address of the start of
+ the routine. Otherwise, return 0. */
+
+static CORE_ADDR
+sh_linux_sigtramp_start (struct frame_info *next_frame)
+{
+ CORE_ADDR pc = get_frame_pc (next_frame);
+ gdb_byte buf[LINUX_SIGTRAMP_LEN];
+
+ /* We only recognize a signal trampoline if PC is at the start of
+ one of the three instructions. We optimize for finding the PC at
+ the start, as will be the case when the trampoline is not the
+ first frame on the stack. We assume that in the case where the
+ PC is not at the start of the instruction sequence, there will be
+ a few trailing readable bytes on the stack. */
+
+ if (!safe_frame_unwind_memory (next_frame, pc, buf, LINUX_SIGTRAMP_LEN))
+ return 0;
+
+ if (buf[0] != LINUX_SIGTRAMP_INSN0)
+ {
+ if (buf[0] != LINUX_SIGTRAMP_INSN1)
+ return 0;
+
+ pc -= 2;
+
+ if (!safe_frame_unwind_memory (next_frame, pc, buf, LINUX_SIGTRAMP_LEN))
+ return 0;
+ }
+
+ if (memcmp (buf, linux_sigtramp_code, LINUX_SIGTRAMP_LEN) != 0)
+ return 0;
+
+ return pc;
+}
+
+/* This function does the same for RT signals. Here the instruction
+ sequence is
+ mov.w 1f,r3
+ trapa #16
+ or r0, r0
+ or r0, r0
+ or r0, r0
+ or r0, r0
+ or r0, r0
+ 1: .word __NR_rt_sigreturn
+ or 0x9305 0xc310 0x200b 0x200b 0x200b 0x200b 0x200b 0x00ad.
+
+ The effect is to call the system call rt_sigreturn. */
+
+#define LINUX_RT_SIGTRAMP_INSN0 MOVW(7) /* Move mem word at PC+7 to R3 */
+#define LINUX_RT_SIGTRAMP_INSN1 TRAP16 /* Syscall w/no args (NR in R3) */
+#define LINUX_RT_SIGTRAMP_INSN2 OR_R0_R0 /* or r0,r0 (insert to avoid hardware bug) */
+
+static const unsigned short linux_rt_sigtramp_code[] =
+{
+ LINUX_RT_SIGTRAMP_INSN0,
+ LINUX_RT_SIGTRAMP_INSN1,
+ LINUX_RT_SIGTRAMP_INSN2,
+ LINUX_RT_SIGTRAMP_INSN2,
+ LINUX_RT_SIGTRAMP_INSN2,
+ LINUX_RT_SIGTRAMP_INSN2,
+ LINUX_RT_SIGTRAMP_INSN2,
+ __NR_rt_sigreturn
+};
+
+#define LINUX_RT_SIGTRAMP_LEN (sizeof linux_rt_sigtramp_code)
+
+/* If PC is in a RT sigtramp routine, return the address of the start
+ of the routine. Otherwise, return 0. */
+
+static CORE_ADDR
+sh_linux_rt_sigtramp_start (struct frame_info *next_frame)
+{
+ CORE_ADDR pc = get_frame_pc (next_frame);
+ gdb_byte buf[LINUX_RT_SIGTRAMP_LEN];
+
+ /* We only recognize a signal trampoline if PC is at the start of
+ one of the two instructions. We optimize for finding the PC at
+ the start, as will be the case when the trampoline is not the
+ first frame on the stack. We assume that in the case where the
+ PC is not at the start of the instruction sequence, there will be
+ a few trailing readable bytes on the stack. */
+
+ if (!safe_frame_unwind_memory (next_frame, pc, buf, LINUX_RT_SIGTRAMP_LEN))
+ return 0;
+
+ if (buf[0] != LINUX_RT_SIGTRAMP_INSN0)
+ {
+ if (buf[0] != LINUX_RT_SIGTRAMP_INSN1)
+ return 0;
+
+ pc -= 2;
+
+ if (!safe_frame_unwind_memory (next_frame, pc, buf,
+ LINUX_RT_SIGTRAMP_LEN))
+ return 0;
+ }
+
+ if (memcmp (buf, linux_rt_sigtramp_code, LINUX_RT_SIGTRAMP_LEN) != 0)
+ return 0;
+
+ return pc;
+}
+
+/* Return whether PC is in a GNU/Linux sigtramp routine. */
+
+static int
+sh_linux_sigtramp_p (struct frame_info *this_frame)
+{
+ CORE_ADDR pc = get_frame_pc (this_frame);
+ const char *name;
+
+ find_pc_partial_function (pc, &name, NULL, NULL);
+
+ /* If we have NAME, we can optimize the search. The trampolines are
+ named __restore and __restore_rt. However, they aren't dynamically
+ exported from the shared C library, so the trampoline may appear to
+ be part of the preceding function. This should always be sigaction,
+ __sigaction, or __libc_sigaction (all aliases to the same function). */
+ if (name == NULL || strstr (name, "sigaction") != NULL)
+ return (sh_linux_sigtramp_start (this_frame) != 0
+ || sh_linux_rt_sigtramp_start (this_frame) != 0);
+
+ return (strcmp ("__restore", name) == 0
+ || strcmp ("__restore_rt", name) == 0);
+}
+
+/* Offset to struct sigcontext in ucontext, from <asm/ucontext.h>. */
+#define SH_LINUX_UCONTEXT_SIGCONTEXT_OFFSET 12
+
+
+/* Assuming NEXT_FRAME is a frame following a GNU/Linux sigtramp
+ routine, return the address of the associated sigcontext structure. */
+
+static CORE_ADDR
+sh_linux_sigcontext_addr (struct frame_info *this_frame)
+{
+ CORE_ADDR pc;
+ CORE_ADDR sp;
+
+ sp = get_frame_register_unsigned (this_frame, SP_REGNUM);
+
+ pc = sh_linux_sigtramp_start (this_frame);
+ if (pc)
+ {
+ return sp;
+ }
+
+ pc = sh_linux_rt_sigtramp_start (this_frame);
+ if (pc)
+ {
+ CORE_ADDR ucontext_addr;
+
+ /* The sigcontext structure is part of the user context. A
+ pointer to the user context is passed as the third argument
+ to the signal handler. */
+ ucontext_addr = get_frame_register_unsigned (this_frame, ARG0_REGNUM+2);
+ return ucontext_addr + SH_LINUX_UCONTEXT_SIGCONTEXT_OFFSET;
+ }
+
+ error ("Couldn't recognize signal trampoline.");
+ return 0;
+}
+
+/* Signal trampolines. */
+extern struct sh_frame_cache *sh_alloc_frame_cache (void);
+
+static struct sh_frame_cache *
+sh_linux_sigtramp_frame_cache (struct frame_info *this_frame, void **this_cache)
+{
+ struct sh_frame_cache *cache;
+ struct gdbarch_tdep *tdep = gdbarch_tdep (get_current_arch ());
+ CORE_ADDR sigcontext_addr;
+
+ if (*this_cache)
+ return (struct sh_frame_cache *) *this_cache;
+
+ cache = sh_alloc_frame_cache ();
+
+ cache->base = get_frame_register_unsigned (this_frame, SP_REGNUM);
+ sigcontext_addr = tdep->sigcontext_addr (this_frame);
+ if (tdep->sc_reg_offset)
+ {
+ int i;
+
+ gdb_assert (tdep->sc_num_regs <= SH_NUM_REGS);
+
+ for (i = 0; i < tdep->sc_num_regs; i++)
+ if (tdep->sc_reg_offset[i] != -1)
+ cache->saved_regs[i] = sigcontext_addr + tdep->sc_reg_offset[i];
+ }
+
+ *this_cache = cache;
+ return cache;
+}
+
+static void
+sh_linux_sigtramp_frame_this_id (struct frame_info *this_frame, void **this_cache,
+ struct frame_id *this_id)
+{
+ struct sh_frame_cache *cache =
+ sh_linux_sigtramp_frame_cache (this_frame, this_cache);
+
+ (*this_id) = frame_id_build (cache->base + 64, cache->pc);
+}
+
+extern struct value * sh_frame_prev_register (struct frame_info *this_frame, void **this_cache, int regnum);
+static struct value *
+sh_linux_sigtramp_frame_prev_register (struct frame_info *this_frame,
+ void **this_cache, int regnum)
+{
+ sh_linux_sigtramp_frame_cache (this_frame, this_cache);
+
+ return sh_frame_prev_register (this_frame, this_cache, regnum);
+}
+
+static int
+sh_linux_sigtramp_frame_sniffer (const struct frame_unwind *self,
+ struct frame_info *this_frame,
+ void **this_prologue_cache)
+{
+ struct gdbarch_tdep *tdep = gdbarch_tdep (get_frame_arch (this_frame));
+
+ /* We shouldn't even bother if we don't have a sigcontext_addr
+ handler. */
+ if (tdep->sigcontext_addr == NULL)
+ return 0;
+
+ if (tdep->sigtramp_p != NULL)
+ {
+ if (tdep->sigtramp_p (this_frame))
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct frame_unwind sh_linux_sigtramp_frame_unwind =
+{
+ SIGTRAMP_FRAME,
+ default_frame_unwind_stop_reason,
+ sh_linux_sigtramp_frame_this_id,
+ sh_linux_sigtramp_frame_prev_register,
+ NULL,
+ sh_linux_sigtramp_frame_sniffer
+};
+
+/* Supply register REGNUM from the buffer specified by GREGS and LEN
+ in the general-purpose register set REGSET to register cache
+ REGCACHE. If REGNUM is -1, do this for all registers in REGSET. */
+
+void
+sh_linux_supply_gregset (const struct regset *regset, struct regcache *regcache,
+ int regnum, const void *gregs, size_t len)
+{
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ const gdb_byte *regs = (const gdb_byte *) gregs;
+ int i;
+
+ gdb_assert (len == tdep->sizeof_gregset);
+
+ for (i = 0; i < tdep->gregset_num_regs; i++)
+ {
+ if ((regnum == i || regnum == -1)
+ && tdep->gregset_reg_offset[i] != -1)
+ regcache_raw_supply (regcache, i, regs + tdep->gregset_reg_offset[i]);
+ }
+}
+
+/* Collect register REGNUM from the register cache REGCACHE and store
+ it in the buffer specified by GREGS and LEN as described by the
+ general-purpose register set REGSET. If REGNUM is -1, do this for
+ all registers in REGSET. */
+
+void
+sh_linux_collect_gregset (const struct regset *regset,
+ const struct regcache *regcache,
+ int regnum, void *gregs, size_t len)
+{
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ gdb_byte *regs = (gdb_byte *) gregs;
+ int i;
+
+ gdb_assert (len == tdep->sizeof_gregset);
+
+ for (i = 0; i < tdep->gregset_num_regs; i++)
+ {
+ if ((regnum == i || regnum == -1)
+ && tdep->gregset_reg_offset[i] != -1)
+ regcache_raw_collect (regcache, i, regs + tdep->gregset_reg_offset[i]);
+ }
+}
+
+/* Supply register REGNUM from the buffer specified by FPREGS and LEN
+ in the floating-point register set REGSET to register cache
+ REGCACHE. If REGNUM is -1, do this for all registers in REGSET. */
+
+static void
+sh_linux_supply_fpregset (const struct regset *regset, struct regcache *regcache,
+ int regnum, const void *fpregs, size_t len)
+{
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ const gdb_byte *regs = (const gdb_byte *) fpregs;
+ int i;
+
+ gdb_assert (len == tdep->sizeof_fpregset);
+ for (i = 0; i < 16; i++)
+ {
+ if (regnum == i+25 || regnum == -1)
+ regcache_raw_supply (regcache, i+25, regs + i*4);
+ }
+ if (regnum == FPSCR_REGNUM || regnum == -1)
+ regcache_raw_supply (regcache, FPSCR_REGNUM, regs + 32*4);
+ if (regnum == FPUL_REGNUM || regnum == -1)
+ regcache_raw_supply (regcache, FPUL_REGNUM, regs + 33*4);
+}
+
+/* Collect register REGNUM from the register cache REGCACHE and store
+ it in the buffer specified by FPREGS and LEN as described by the
+ floating-point register set REGSET. If REGNUM is -1, do this for
+ all registers in REGSET. */
+
+static void
+sh_linux_collect_fpregset (const struct regset *regset,
+ const struct regcache *regcache,
+ int regnum, void *fpregs, size_t len)
+{
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ gdb_byte *regs = (gdb_byte *) fpregs;
+ int i;
+
+ gdb_assert (len == tdep->sizeof_fpregset);
+ for (i = 0; i < 16; i++)
+ {
+ if (regnum == i+25 || regnum == -1)
+ regcache_raw_collect (regcache, i+25, regs + i*4);
+ }
+ if (regnum == FPSCR_REGNUM || regnum == -1)
+ regcache_raw_collect (regcache, FPSCR_REGNUM, regs + 32*4);
+ if (regnum == FPUL_REGNUM || regnum == -1)
+ regcache_raw_collect (regcache, FPUL_REGNUM, regs + 33*4);
+}
+
+static const struct regset sh_linux_gregset =
+ {
+ NULL, sh_linux_supply_gregset, sh_linux_collect_gregset
+ };
+
+static const struct regset sh_linux_fpregset =
+ {
+ NULL, sh_linux_supply_fpregset, sh_linux_collect_fpregset
+ };
+
+/* Iterate over core file register note sections. */
+
+static void
+sh_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
+ iterate_over_regset_sections_cb *cb,
+ void *cb_data,
+ const struct regcache *regcache)
+{
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ cb (".reg", tdep->sizeof_gregset, &sh_linux_gregset, NULL, cb_data);
+ cb (".reg2", tdep->sizeof_fpregset, &sh_linux_fpregset, NULL, cb_data);
+}
+
+/* The register sets used in GNU/Linux ELF core-dumps are identical to
+ the register sets in `struct user' that are used for a.out
+ core-dumps. These are also used by ptrace(2). The corresponding
+ types are `elf_gregset_t' for the general-purpose registers (with
+ `elf_greg_t' the type of a single GP register) and `elf_fpregset_t'
+ for the floating-point registers.
+
+ Those types used to be available under the names `gregset_t' and
+ `fpregset_t' too, and GDB used those names in the past. But those
+ names are now used for the register sets used in the `mcontext_t'
+ type, which have a different size and layout. */
+
+/* Mapping between the general-purpose registers in `struct user'
+ format and GDB's register cache layout. */
+
+/* From <sys/reg.h>. */
+static int sh_linux_gregset_reg_offset[] =
+{
+ 0, 4, 8, 12, 16, 20, 24, 28,
+ 32, 36, 40, 44, 48, 52, 56, 60,
+
+ REG_PC*4, REG_PR*4, REG_GBR*4, -1,
+ REG_MACH*4, REG_MACL*4, REG_SR*4,
+};
+
+/* Mapping between the general-purpose registers in `struct
+ sigcontext' format and GDB's register cache layout. */
+
+/* From <asm/sigcontext.h>. */
+static int sh_linux_sc_reg_offset[] =
+{
+ 4, 8, 12, 16, 20, 24, 28, 32,
+ 36, 40, 44, 48, 52, 56, 60, 64,
+ 68, 72, 80, -1,
+ 84, 88, 76
+};
+
+
static void
sh_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ bfd abfd;
+
linux_init_abi (info, gdbarch);
+ tdep->gregset_reg_offset = sh_linux_gregset_reg_offset;
+ tdep->gregset_num_regs = ARRAY_SIZE (sh_linux_gregset_reg_offset);
+ tdep->sizeof_gregset = 23 * 4;
+
+ tdep->jb_pc_offset = 32; /* From <bits/setjmp.h>. */
+
+ tdep->sigtramp_p = sh_linux_sigtramp_p;
+ tdep->sigcontext_addr = sh_linux_sigcontext_addr;
+ tdep->sc_reg_offset = sh_linux_sc_reg_offset;
+ tdep->sc_num_regs = ARRAY_SIZE (sh_linux_sc_reg_offset);
+
+ frame_unwind_append_unwinder(gdbarch, &sh_linux_sigtramp_frame_unwind);
+
+ /* Core file support. */
+ set_gdbarch_iterate_over_regset_sections
+ (gdbarch, sh_linux_iterate_over_regset_sections);
+
/* GNU/Linux uses SVR4-style shared libraries. */
set_gdbarch_skip_trampoline_code (gdbarch, find_solib_trampoline_target);
set_solib_svr4_fetch_link_map_offsets
diff --git a/gdb/sh-tdep.c b/gdb/sh-tdep.c
index 9b3692dc0c..bcc6f08503 100644
--- a/gdb/sh-tdep.c
+++ b/gdb/sh-tdep.c
@@ -21,6 +21,9 @@
sac@cygnus.com. */
#include "defs.h"
+#include "arch-utils.h"
+#include "command.h"
+#include "dummy-frame.h"
#include "frame.h"
#include "frame-base.h"
#include "frame-unwind.h"
@@ -35,6 +38,7 @@
#include "arch-utils.h"
#include "floatformat.h"
#include "regcache.h"
+#include "regset.h"
#include "doublest.h"
#include "osabi.h"
#include "reggroups.h"
@@ -68,23 +72,6 @@ static const char *const sh_cc_enum[] = {
static const char *sh_active_calling_convention = sh_cc_gcc;
-#define SH_NUM_REGS 67
-
-struct sh_frame_cache
-{
- /* Base address. */
- CORE_ADDR base;
- LONGEST sp_offset;
- CORE_ADDR pc;
-
- /* Flag showing that a frame has been created in the prologue code. */
- int uses_fp;
-
- /* Saved registers. */
- CORE_ADDR saved_regs[SH_NUM_REGS];
- CORE_ADDR saved_sp;
-};
-
static int
sh_is_renesas_calling_convention (struct type *func_type)
{
@@ -1052,7 +1039,7 @@ sh_treat_as_flt_p (struct type *type)
return 0;
/* Otherwise if the type of that member is float, the whole type is
treated as float. */
- if (TYPE_CODE (TYPE_FIELD_TYPE (type, 0)) == TYPE_CODE_FLT)
+ if (TYPE_CODE (check_typedef (TYPE_FIELD_TYPE (type, 0))) == TYPE_CODE_FLT)
return 1;
/* Otherwise it's not treated as float. */
return 0;
@@ -1102,7 +1089,7 @@ sh_push_dummy_call_fpu (struct gdbarch *gdbarch,
in four registers available. Loop thru args from first to last. */
for (argnum = 0; argnum < nargs; argnum++)
{
- type = value_type (args[argnum]);
+ type = check_typedef (value_type (args[argnum]));
len = TYPE_LENGTH (type);
val = sh_justify_value_in_reg (gdbarch, args[argnum], len);
@@ -1828,7 +1815,7 @@ sh_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum,
reg->how = DWARF2_FRAME_REG_UNDEFINED;
}
-static struct sh_frame_cache *
+struct sh_frame_cache *
sh_alloc_frame_cache (void)
{
struct sh_frame_cache *cache;
@@ -1855,7 +1842,7 @@ sh_alloc_frame_cache (void)
return cache;
}
-static struct sh_frame_cache *
+struct sh_frame_cache *
sh_frame_cache (struct frame_info *this_frame, void **this_cache)
{
struct gdbarch *gdbarch = get_frame_arch (this_frame);
@@ -1922,9 +1909,9 @@ sh_frame_cache (struct frame_info *this_frame, void **this_cache)
return cache;
}
-static struct value *
-sh_frame_prev_register (struct frame_info *this_frame,
- void **this_cache, int regnum)
+struct value *
+sh_frame_prev_register (struct frame_info *this_frame, void **this_cache,
+ int regnum)
{
struct gdbarch *gdbarch = get_frame_arch (this_frame);
struct sh_frame_cache *cache = sh_frame_cache (this_frame, this_cache);
@@ -1938,7 +1925,7 @@ sh_frame_prev_register (struct frame_info *this_frame,
the current frame. Frob regnum so that we pull the value from
the correct place. */
if (regnum == gdbarch_pc_regnum (gdbarch))
- regnum = PR_REGNUM;
+ regnum = PR_REGNUM; /* XXX: really? */
if (regnum < SH_NUM_REGS && cache->saved_regs[regnum] != -1)
return frame_unwind_got_memory (this_frame, regnum,
@@ -2247,8 +2234,8 @@ sh_return_in_first_hidden_param_p (struct gdbarch *gdbarch,
static struct gdbarch *
sh_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
{
- struct gdbarch *gdbarch;
struct gdbarch_tdep *tdep;
+ struct gdbarch *gdbarch;
/* SH5 is handled entirely in sh64-tdep.c. */
if (info.bfd_arch_info->mach == bfd_mach_sh5)
@@ -2264,6 +2251,18 @@ sh_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
tdep = XCNEW (struct gdbarch_tdep);
gdbarch = gdbarch_alloc (&info, tdep);
+ /* General-purpose registers. */
+ tdep->gregset = NULL;
+ tdep->gregset_reg_offset = NULL;
+ tdep->gregset_num_regs = 23;
+ tdep->sizeof_gregset = 0;
+
+ /* Floating-point registers. */
+ tdep->fpregset = NULL;
+ tdep->sizeof_fpregset = 34*4;
+
+ tdep->jb_pc_offset = -1;
+
set_gdbarch_short_bit (gdbarch, 2 * TARGET_CHAR_BIT);
set_gdbarch_int_bit (gdbarch, 4 * TARGET_CHAR_BIT);
set_gdbarch_long_bit (gdbarch, 4 * TARGET_CHAR_BIT);
@@ -2415,10 +2414,11 @@ sh_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
break;
}
+ dwarf2_append_unwinders (gdbarch);
+
/* Hook in ABI-specific overrides, if they have been registered. */
gdbarch_init_osabi (info, gdbarch);
- dwarf2_append_unwinders (gdbarch);
frame_unwind_append_unwinder (gdbarch, &sh_stub_unwind);
frame_unwind_append_unwinder (gdbarch, &sh_frame_unwind);
diff --git a/gdb/sh-tdep.h b/gdb/sh-tdep.h
index d15ef050e0..4d91f22a51 100644
--- a/gdb/sh-tdep.h
+++ b/gdb/sh-tdep.h
@@ -21,6 +21,12 @@
/* Contributed by Steve Chamberlain sac@cygnus.com. */
+struct frame_info;
+struct gdbarch;
+struct reggroup;
+struct regset;
+struct regcache;
+
/* Registers for all SH variants. Used also by sh3-rom.c. */
enum
{
@@ -29,6 +35,7 @@ enum
ARG0_REGNUM = 4,
ARGLAST_REGNUM = 7,
FP_REGNUM = 14,
+ SP_REGNUM = 15,
PC_REGNUM = 16,
PR_REGNUM = 17,
GBR_REGNUM = 18,
@@ -82,6 +89,25 @@ enum
FV_LAST_REGNUM = 79
};
+#define SH_NUM_REGS 67
+
+struct sh_frame_cache
+{
+ /* Base address. */
+ CORE_ADDR base;
+ LONGEST sp_offset;
+ CORE_ADDR pc;
+
+ /* Flag showing that a frame has been created in the prologue code. */
+ int uses_fp;
+
+ /* Saved registers. */
+ CORE_ADDR saved_regs[SH_NUM_REGS];
+ CORE_ADDR saved_sp;
+};
+
+extern struct sh_frame_cache *sh_frame_cache (struct frame_info *next_frame, void **this_cache);
+
/* This structure describes a register in a core-file. */
struct sh_corefile_regmap
{
@@ -89,18 +115,41 @@ struct sh_corefile_regmap
unsigned int offset;
};
+/* sh architecture specific information. */
struct gdbarch_tdep
{
+ /* General-purpose registers. */
+ struct regset *gregset;
+ int *gregset_reg_offset;
+ int gregset_num_regs;
+ size_t sizeof_gregset;
+
+ /* Floating-point registers. */
+ struct regset *fpregset;
+ size_t sizeof_fpregset;
+
+ /* Offset of saved PC in jmp_buf. */
+ int jb_pc_offset;
+
+ /* Detect sigtramp. */
+ int (*sigtramp_p) (struct frame_info *);
+
+ /* Get address of sigcontext for sigtramp. */
+ CORE_ADDR (*sigcontext_addr) (struct frame_info *);
+
+ /* Offset of registers in `struct sigcontext'. */
+ int *sc_reg_offset;
+ int sc_num_regs;
+
/* Non-NULL when debugging from a core file. Provides the offset
where each general-purpose register is stored inside the associated
core file section. */
struct sh_corefile_regmap *core_gregmap;
- int sizeof_gregset;
+
/* Non-NULL when debugging from a core file and when FP registers are
available. Provides the offset where each FP register is stored
inside the associated core file section. */
struct sh_corefile_regmap *core_fpregmap;
- int sizeof_fpregset;
};
extern const struct regset sh_corefile_gregset;
diff --git a/gdb/testsuite/gdb.asm/asm-source.exp b/gdb/testsuite/gdb.asm/asm-source.exp
index e07e5543f2..f5e60e1002 100644
--- a/gdb/testsuite/gdb.asm/asm-source.exp
+++ b/gdb/testsuite/gdb.asm/asm-source.exp
@@ -116,6 +116,11 @@ switch -glob -- [istarget] {
append link-flags " -m elf32ppc"
}
}
+ "sh*-linux*" {
+ set asm-arch sh-linux
+ set asm-flags "-I${srcdir}/${subdir} -I${objdir}/${subdir}"
+ set debug-flags "-gdwarf-2"
+ }
"sh*-*-*" {
set asm-arch sh
set debug-flags "-gdwarf-2"
diff --git a/gdb/testsuite/gdb.asm/sh-linux.inc b/gdb/testsuite/gdb.asm/sh-linux.inc
new file mode 100644
index 0000000000..4a0f6696f4
--- /dev/null
+++ b/gdb/testsuite/gdb.asm/sh-linux.inc
@@ -0,0 +1,78 @@
+# You'll find a bunch of nop opcodes in the below macros. They are
+# there to keep the code correctly aligned. Be careful to maintain
+# them when changing the code.
+
+ comment "subroutine declare"
+ .purgem gdbasm_declare
+ .macro gdbasm_declare name
+ .align 1
+ .global \name
+\name:
+ .endm
+
+ comment "subroutine prologue"
+ .macro gdbasm_enter
+ mov.l r14,@-r15
+ sts.l pr,@-r15
+ mov r15,r14
+ nop
+ .endm
+
+ comment "subroutine epilogue"
+ .macro gdbasm_leave
+ mov r14,r15
+ lds.l @r15+,pr
+ mov.l @r15+,r14
+ rts
+ nop
+ nop
+ .endm
+
+ comment "subroutine end"
+ .purgem gdbasm_end
+ .macro gdbasm_end name
+ .size \name, .-_foo1
+ .align 1
+ .endm
+
+ comment "subroutine call"
+ .macro gdbasm_call subr
+ mov.l .Lconst\@,r1
+ bra .Lafterconst\@
+ nop
+ .align 2
+.Lconst\@:
+ .long \subr
+.Lafterconst\@:
+ jsr @r1
+ nop
+ .endm
+
+ .macro gdbasm_several_nops
+ nop
+ nop
+ nop
+ nop
+ .endm
+
+ comment "exit (0)"
+ .macro gdbasm_exit0
+ sleep
+ nop
+ .endm
+
+ comment "crt0 startup"
+ .macro gdbasm_startup
+ mov #0,r14
+ .endm
+
+ comment "Declare a data variable"
+ .purgem gdbasm_datavar
+ .macro gdbasm_datavar name value
+ .data
+ .align 2
+ .type \name, @object
+ .size \name, 4
+\name:
+ .long \value
+ .endm
diff --git a/gdb/testsuite/gdb.asm/sh.inc b/gdb/testsuite/gdb.asm/sh.inc
index a4a5fc545e..89efed7795 100644
--- a/gdb/testsuite/gdb.asm/sh.inc
+++ b/gdb/testsuite/gdb.asm/sh.inc
@@ -40,9 +40,8 @@
mov.l .Lconst\@,r1
bra .Lafterconst\@
nop
- nop
-.Lconst\@:
.align 2
+.Lconst\@:
.long \subr
.align 1
.Lafterconst\@:
--
2.11.0