| /* Target-dependent code for OpenBSD/sparc64. | 
 |  | 
 |    Copyright (C) 2004-2023 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 3 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, see <http://www.gnu.org/licenses/>.  */ | 
 |  | 
 | #include "defs.h" | 
 | #include "frame.h" | 
 | #include "frame-unwind.h" | 
 | #include "gdbcore.h" | 
 | #include "osabi.h" | 
 | #include "regcache.h" | 
 | #include "regset.h" | 
 | #include "symtab.h" | 
 | #include "objfiles.h" | 
 | #include "trad-frame.h" | 
 | #include "inferior.h" | 
 |  | 
 | #include "obsd-tdep.h" | 
 | #include "sparc64-tdep.h" | 
 | #include "solib-svr4.h" | 
 | #include "bsd-uthread.h" | 
 |  | 
 | /* Older OpenBSD versions used the traditional NetBSD core file | 
 |    format, even for ports that use ELF.  These core files don't use | 
 |    multiple register sets.  Instead, the general-purpose and | 
 |    floating-point registers are lumped together in a single section. | 
 |    Unlike on NetBSD, OpenBSD uses a different layout for its | 
 |    general-purpose registers than the layout used for ptrace(2). | 
 |  | 
 |    Newer OpenBSD versions use ELF core files.  Here the register sets | 
 |    match the ptrace(2) layout.  */ | 
 |  | 
 | /* From <machine/reg.h>.  */ | 
 | const struct sparc_gregmap sparc64obsd_gregmap = | 
 | { | 
 |   0 * 8,			/* "tstate" */ | 
 |   1 * 8,			/* %pc */ | 
 |   2 * 8,			/* %npc */ | 
 |   3 * 8,			/* %y */ | 
 |   -1,				/* %fprs */ | 
 |   -1, | 
 |   5 * 8,			/* %g1 */ | 
 |   20 * 8,			/* %l0 */ | 
 |   4				/* sizeof (%y) */ | 
 | }; | 
 |  | 
 | const struct sparc_gregmap sparc64obsd_core_gregmap = | 
 | { | 
 |   0 * 8,			/* "tstate" */ | 
 |   1 * 8,			/* %pc */ | 
 |   2 * 8,			/* %npc */ | 
 |   3 * 8,			/* %y */ | 
 |   -1,				/* %fprs */ | 
 |   -1, | 
 |   7 * 8,			/* %g1 */ | 
 |   22 * 8,			/* %l0 */ | 
 |   4				/* sizeof (%y) */ | 
 | }; | 
 |  | 
 | static void | 
 | sparc64obsd_supply_gregset (const struct regset *regset, | 
 | 			    struct regcache *regcache, | 
 | 			    int regnum, const void *gregs, size_t len) | 
 | { | 
 |   const void *fpregs = (char *)gregs + 288; | 
 |  | 
 |   if (len < 832) | 
 |     { | 
 |       sparc64_supply_gregset (&sparc64obsd_gregmap, regcache, regnum, gregs); | 
 |       return; | 
 |     } | 
 |  | 
 |   sparc64_supply_gregset (&sparc64obsd_core_gregmap, regcache, regnum, gregs); | 
 |   sparc64_supply_fpregset (&sparc64_bsd_fpregmap, regcache, regnum, fpregs); | 
 | } | 
 |  | 
 | static void | 
 | sparc64obsd_supply_fpregset (const struct regset *regset, | 
 | 			     struct regcache *regcache, | 
 | 			     int regnum, const void *fpregs, size_t len) | 
 | { | 
 |   sparc64_supply_fpregset (&sparc64_bsd_fpregmap, regcache, regnum, fpregs); | 
 | } | 
 |  | 
 |  | 
 | /* Signal trampolines.  */ | 
 |  | 
 | /* Since OpenBSD 3.2, the sigtramp routine is mapped at a random page | 
 |    in virtual memory.  The randomness makes it somewhat tricky to | 
 |    detect it, but fortunately we can rely on the fact that the start | 
 |    of the sigtramp routine is page-aligned.  We recognize the | 
 |    trampoline by looking for the code that invokes the sigreturn | 
 |    system call.  The offset where we can find that code varies from | 
 |    release to release. | 
 |  | 
 |    By the way, the mapping mentioned above is read-only, so you cannot | 
 |    place a breakpoint in the signal trampoline.  */ | 
 |  | 
 | /* Default page size.  */ | 
 | static const int sparc64obsd_page_size = 8192; | 
 |  | 
 | /* Offset for sigreturn(2).  */ | 
 | static const int sparc64obsd_sigreturn_offset[] = { | 
 |   0xf0,				/* OpenBSD 3.8 */ | 
 |   0xec,				/* OpenBSD 3.6 */ | 
 |   0xe8,				/* OpenBSD 3.2 */ | 
 |   -1 | 
 | }; | 
 |  | 
 | static int | 
 | sparc64obsd_pc_in_sigtramp (CORE_ADDR pc, const char *name) | 
 | { | 
 |   CORE_ADDR start_pc = (pc & ~(sparc64obsd_page_size - 1)); | 
 |   unsigned long insn; | 
 |   const int *offset; | 
 |  | 
 |   if (name) | 
 |     return 0; | 
 |  | 
 |   for (offset = sparc64obsd_sigreturn_offset; *offset != -1; offset++) | 
 |     { | 
 |       /* Check for "restore %g0, SYS_sigreturn, %g1".  */ | 
 |       insn = sparc_fetch_instruction (start_pc + *offset); | 
 |       if (insn != 0x83e82067) | 
 | 	continue; | 
 |  | 
 |       /* Check for "t ST_SYSCALL".  */ | 
 |       insn = sparc_fetch_instruction (start_pc + *offset + 8); | 
 |       if (insn != 0x91d02000) | 
 | 	continue; | 
 |  | 
 |       return 1; | 
 |     } | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | static struct sparc_frame_cache * | 
 | sparc64obsd_frame_cache (frame_info_ptr this_frame, void **this_cache) | 
 | { | 
 |   struct sparc_frame_cache *cache; | 
 |   CORE_ADDR addr; | 
 |  | 
 |   if (*this_cache) | 
 |     return (struct sparc_frame_cache *) *this_cache; | 
 |  | 
 |   cache = sparc_frame_cache (this_frame, this_cache); | 
 |   gdb_assert (cache == *this_cache); | 
 |  | 
 |   /* If we couldn't find the frame's function, we're probably dealing | 
 |      with an on-stack signal trampoline.  */ | 
 |   if (cache->pc == 0) | 
 |     { | 
 |       cache->pc = get_frame_pc (this_frame); | 
 |       cache->pc &= ~(sparc64obsd_page_size - 1); | 
 |  | 
 |       /* Since we couldn't find the frame's function, the cache was | 
 | 	 initialized under the assumption that we're frameless.  */ | 
 |       sparc_record_save_insn (cache); | 
 |       addr = get_frame_register_unsigned (this_frame, SPARC_FP_REGNUM); | 
 |       if (addr & 1) | 
 | 	addr += BIAS; | 
 |       cache->base = addr; | 
 |     } | 
 |  | 
 |   /* We find the appropriate instance of `struct sigcontext' at a | 
 |      fixed offset in the signal frame.  */ | 
 |   addr = cache->base + 128 + 16; | 
 |   cache->saved_regs = sparc64nbsd_sigcontext_saved_regs (addr, this_frame); | 
 |  | 
 |   return cache; | 
 | } | 
 |  | 
 | static void | 
 | sparc64obsd_frame_this_id (frame_info_ptr this_frame, void **this_cache, | 
 | 			   struct frame_id *this_id) | 
 | { | 
 |   struct sparc_frame_cache *cache = | 
 |     sparc64obsd_frame_cache (this_frame, this_cache); | 
 |  | 
 |   (*this_id) = frame_id_build (cache->base, cache->pc); | 
 | } | 
 |  | 
 | static struct value * | 
 | sparc64obsd_frame_prev_register (frame_info_ptr this_frame, | 
 | 				 void **this_cache, int regnum) | 
 | { | 
 |   struct sparc_frame_cache *cache = | 
 |     sparc64obsd_frame_cache (this_frame, this_cache); | 
 |  | 
 |   return trad_frame_get_prev_register (this_frame, cache->saved_regs, regnum); | 
 | } | 
 |  | 
 | static int | 
 | sparc64obsd_sigtramp_frame_sniffer (const struct frame_unwind *self, | 
 | 				    frame_info_ptr this_frame, | 
 | 				    void **this_cache) | 
 | { | 
 |   CORE_ADDR pc = get_frame_pc (this_frame); | 
 |   const char *name; | 
 |  | 
 |   find_pc_partial_function (pc, &name, NULL, NULL); | 
 |   if (sparc64obsd_pc_in_sigtramp (pc, name)) | 
 |     return 1; | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | static const struct frame_unwind sparc64obsd_frame_unwind = | 
 | { | 
 |   "sparc64 openbsd sigtramp", | 
 |   SIGTRAMP_FRAME, | 
 |   default_frame_unwind_stop_reason, | 
 |   sparc64obsd_frame_this_id, | 
 |   sparc64obsd_frame_prev_register, | 
 |   NULL, | 
 |   sparc64obsd_sigtramp_frame_sniffer | 
 | }; | 
 |  | 
 | /* Kernel debugging support.  */ | 
 |  | 
 | static struct sparc_frame_cache * | 
 | sparc64obsd_trapframe_cache (frame_info_ptr this_frame, void **this_cache) | 
 | { | 
 |   struct sparc_frame_cache *cache; | 
 |   CORE_ADDR sp, trapframe_addr; | 
 |   int regnum; | 
 |  | 
 |   if (*this_cache) | 
 |     return (struct sparc_frame_cache *) *this_cache; | 
 |  | 
 |   cache = sparc_frame_cache (this_frame, this_cache); | 
 |   gdb_assert (cache == *this_cache); | 
 |  | 
 |   sp = get_frame_register_unsigned (this_frame, SPARC_SP_REGNUM); | 
 |   trapframe_addr = sp + BIAS + 176; | 
 |  | 
 |   cache->saved_regs = trad_frame_alloc_saved_regs (this_frame); | 
 |  | 
 |   cache->saved_regs[SPARC64_STATE_REGNUM].set_addr (trapframe_addr); | 
 |   cache->saved_regs[SPARC64_PC_REGNUM].set_addr (trapframe_addr + 8); | 
 |   cache->saved_regs[SPARC64_NPC_REGNUM].set_addr (trapframe_addr + 16); | 
 |  | 
 |   for (regnum = SPARC_G0_REGNUM; regnum <= SPARC_I7_REGNUM; regnum++) | 
 |     cache->saved_regs[regnum].set_addr (trapframe_addr + 48 | 
 | 					+ (regnum - SPARC_G0_REGNUM) * 8); | 
 |  | 
 |   return cache; | 
 | } | 
 |  | 
 | static void | 
 | sparc64obsd_trapframe_this_id (frame_info_ptr this_frame, | 
 | 			       void **this_cache, struct frame_id *this_id) | 
 | { | 
 |   struct sparc_frame_cache *cache = | 
 |     sparc64obsd_trapframe_cache (this_frame, this_cache); | 
 |  | 
 |   (*this_id) = frame_id_build (cache->base, cache->pc); | 
 | } | 
 |  | 
 | static struct value * | 
 | sparc64obsd_trapframe_prev_register (frame_info_ptr this_frame, | 
 | 				     void **this_cache, int regnum) | 
 | { | 
 |   struct sparc_frame_cache *cache = | 
 |     sparc64obsd_trapframe_cache (this_frame, this_cache); | 
 |  | 
 |   return trad_frame_get_prev_register (this_frame, cache->saved_regs, regnum); | 
 | } | 
 |  | 
 | static int | 
 | sparc64obsd_trapframe_sniffer (const struct frame_unwind *self, | 
 | 			       frame_info_ptr this_frame, | 
 | 			       void **this_cache) | 
 | { | 
 |   CORE_ADDR pc; | 
 |   ULONGEST pstate; | 
 |   const char *name; | 
 |  | 
 |   /* Check whether we are in privileged mode, and bail out if we're not.  */ | 
 |   pstate = get_frame_register_unsigned (this_frame, SPARC64_PSTATE_REGNUM); | 
 |   if ((pstate & SPARC64_PSTATE_PRIV) == 0) | 
 |     return 0; | 
 |  | 
 |   pc = get_frame_address_in_block (this_frame); | 
 |   find_pc_partial_function (pc, &name, NULL, NULL); | 
 |   if (name && strcmp (name, "Lslowtrap_reenter") == 0) | 
 |     return 1; | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | static const struct frame_unwind sparc64obsd_trapframe_unwind = | 
 | { | 
 |   "sparc64 openbsd trap", | 
 |   NORMAL_FRAME, | 
 |   default_frame_unwind_stop_reason, | 
 |   sparc64obsd_trapframe_this_id, | 
 |   sparc64obsd_trapframe_prev_register, | 
 |   NULL, | 
 |   sparc64obsd_trapframe_sniffer | 
 | }; | 
 |  | 
 |  | 
 | /* Threads support.  */ | 
 |  | 
 | /* Offset wthin the thread structure where we can find %fp and %i7.  */ | 
 | #define SPARC64OBSD_UTHREAD_FP_OFFSET	232 | 
 | #define SPARC64OBSD_UTHREAD_PC_OFFSET	240 | 
 |  | 
 | static void | 
 | sparc64obsd_supply_uthread (struct regcache *regcache, | 
 | 			    int regnum, CORE_ADDR addr) | 
 | { | 
 |   struct gdbarch *gdbarch = regcache->arch (); | 
 |   enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); | 
 |   CORE_ADDR fp, fp_addr = addr + SPARC64OBSD_UTHREAD_FP_OFFSET; | 
 |   gdb_byte buf[8]; | 
 |  | 
 |   /* This function calls functions that depend on the global current thread.  */ | 
 |   gdb_assert (regcache->ptid () == inferior_ptid); | 
 |  | 
 |   gdb_assert (regnum >= -1); | 
 |  | 
 |   fp = read_memory_unsigned_integer (fp_addr, 8, byte_order); | 
 |   if (regnum == SPARC_SP_REGNUM || regnum == -1) | 
 |     { | 
 |       store_unsigned_integer (buf, 8, byte_order, fp); | 
 |       regcache->raw_supply (SPARC_SP_REGNUM, buf); | 
 |  | 
 |       if (regnum == SPARC_SP_REGNUM) | 
 | 	return; | 
 |     } | 
 |  | 
 |   if (regnum == SPARC64_PC_REGNUM || regnum == SPARC64_NPC_REGNUM | 
 |       || regnum == -1) | 
 |     { | 
 |       CORE_ADDR i7, i7_addr = addr + SPARC64OBSD_UTHREAD_PC_OFFSET; | 
 |  | 
 |       i7 = read_memory_unsigned_integer (i7_addr, 8, byte_order); | 
 |       if (regnum == SPARC64_PC_REGNUM || regnum == -1) | 
 | 	{ | 
 | 	  store_unsigned_integer (buf, 8, byte_order, i7 + 8); | 
 | 	  regcache->raw_supply (SPARC64_PC_REGNUM, buf); | 
 | 	} | 
 |       if (regnum == SPARC64_NPC_REGNUM || regnum == -1) | 
 | 	{ | 
 | 	  store_unsigned_integer (buf, 8, byte_order, i7 + 12); | 
 | 	  regcache->raw_supply (SPARC64_NPC_REGNUM, buf); | 
 | 	} | 
 |  | 
 |       if (regnum == SPARC64_PC_REGNUM || regnum == SPARC64_NPC_REGNUM) | 
 | 	return; | 
 |     } | 
 |  | 
 |   sparc_supply_rwindow (regcache, fp, regnum); | 
 | } | 
 |  | 
 | static void | 
 | sparc64obsd_collect_uthread(const struct regcache *regcache, | 
 | 			    int regnum, CORE_ADDR addr) | 
 | { | 
 |   struct gdbarch *gdbarch = regcache->arch (); | 
 |   enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); | 
 |   CORE_ADDR sp; | 
 |   gdb_byte buf[8]; | 
 |  | 
 |   /* This function calls functions that depend on the global current thread.  */ | 
 |   gdb_assert (regcache->ptid () == inferior_ptid); | 
 |  | 
 |   gdb_assert (regnum >= -1); | 
 |  | 
 |   if (regnum == SPARC_SP_REGNUM || regnum == -1) | 
 |     { | 
 |       CORE_ADDR fp_addr = addr + SPARC64OBSD_UTHREAD_FP_OFFSET; | 
 |  | 
 |       regcache->raw_collect (SPARC_SP_REGNUM, buf); | 
 |       write_memory (fp_addr,buf, 8); | 
 |     } | 
 |  | 
 |   if (regnum == SPARC64_PC_REGNUM || regnum == -1) | 
 |     { | 
 |       CORE_ADDR i7, i7_addr = addr + SPARC64OBSD_UTHREAD_PC_OFFSET; | 
 |  | 
 |       regcache->raw_collect (SPARC64_PC_REGNUM, buf); | 
 |       i7 = extract_unsigned_integer (buf, 8, byte_order) - 8; | 
 |       write_memory_unsigned_integer (i7_addr, 8, byte_order, i7); | 
 |  | 
 |       if (regnum == SPARC64_PC_REGNUM) | 
 | 	return; | 
 |     } | 
 |  | 
 |   regcache->raw_collect (SPARC_SP_REGNUM, buf); | 
 |   sp = extract_unsigned_integer (buf, 8, byte_order); | 
 |   sparc_collect_rwindow (regcache, sp, regnum); | 
 | } | 
 |  | 
 |  | 
 | static const struct regset sparc64obsd_gregset = | 
 |   { | 
 |     NULL, sparc64obsd_supply_gregset, NULL | 
 |   }; | 
 |  | 
 | static const struct regset sparc64obsd_fpregset = | 
 |   { | 
 |     NULL, sparc64obsd_supply_fpregset, NULL | 
 |   }; | 
 |  | 
 | static void | 
 | sparc64obsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) | 
 | { | 
 |   sparc_gdbarch_tdep *tdep = gdbarch_tdep<sparc_gdbarch_tdep> (gdbarch); | 
 |  | 
 |   tdep->gregset = &sparc64obsd_gregset; | 
 |   tdep->sizeof_gregset = 288; | 
 |   tdep->fpregset = &sparc64obsd_fpregset; | 
 |   tdep->sizeof_fpregset = 272; | 
 |  | 
 |   /* Make sure we can single-step "new" syscalls.  */ | 
 |   tdep->step_trap = sparcnbsd_step_trap; | 
 |  | 
 |   frame_unwind_append_unwinder (gdbarch, &sparc64obsd_frame_unwind); | 
 |   frame_unwind_append_unwinder (gdbarch, &sparc64obsd_trapframe_unwind); | 
 |  | 
 |   sparc64_init_abi (info, gdbarch); | 
 |   obsd_init_abi (info, gdbarch); | 
 |  | 
 |   /* OpenBSD/sparc64 has SVR4-style shared libraries.  */ | 
 |   set_solib_svr4_fetch_link_map_offsets | 
 |     (gdbarch, svr4_lp64_fetch_link_map_offsets); | 
 |   set_gdbarch_skip_solib_resolver (gdbarch, obsd_skip_solib_resolver); | 
 |  | 
 |   /* OpenBSD provides a user-level threads implementation.  */ | 
 |   bsd_uthread_set_supply_uthread (gdbarch, sparc64obsd_supply_uthread); | 
 |   bsd_uthread_set_collect_uthread (gdbarch, sparc64obsd_collect_uthread); | 
 | } | 
 |  | 
 | void _initialize_sparc64obsd_tdep (); | 
 | void | 
 | _initialize_sparc64obsd_tdep () | 
 | { | 
 |   gdbarch_register_osabi (bfd_arch_sparc, bfd_mach_sparc_v9, | 
 | 			  GDB_OSABI_OPENBSD, sparc64obsd_init_abi); | 
 | } |