From d4af727286e3a9f177ba11677fbd3a012d36558a Mon Sep 17 00:00:00 2001 From: Anton Kolesov Date: Thu, 22 Dec 2016 21:52:16 +0300 Subject: [PATCH] arc: Add support for signal frames for Linux targets Implement functions needed to unwind signal frames on ARC Linux targets. gdb/ChangeLog * arc-linux-tdep.c (arc_linux_sc_reg_offsets): New static variable. (arc_linux_is_sigtramp): New function. (arc_linux_sigcontext_addr): Likewise. (arc_linux_init_osabi): Use them. --- gdb/ChangeLog | 7 ++ gdb/arc-linux-tdep.c | 181 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index ba574d7fd8b..05c82f60888 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,10 @@ +2020-12-22 Anton Kolesov + + * arc-linux-tdep.c (arc_linux_sc_reg_offsets): New static variable. + (arc_linux_is_sigtramp): New function. + (arc_linux_sigcontext_addr): Likewise. + (arc_linux_init_osabi): Use them. + 2020-12-22 Anton Kolesov * arc-tdep.c (arc_make_sigtramp_frame_cache): New function. diff --git a/gdb/arc-linux-tdep.c b/gdb/arc-linux-tdep.c index 2bdeaaf0614..c86bd601c9b 100644 --- a/gdb/arc-linux-tdep.c +++ b/gdb/arc-linux-tdep.c @@ -33,6 +33,60 @@ #define REGOFF(offset) (offset * ARC_REGISTER_SIZE) +/* arc_linux_sc_reg_offsets[i] is the offset of register i in the `struct + sigcontext'. Array index is an internal GDB register number, as defined in + arc-tdep.h:arc_regnum. + + From and . + + The layout of this struct is tightly bound to "arc_regnum" enum + in arc-tdep.h. Any change of order in there, must be reflected + here as well. */ +static const int arc_linux_sc_reg_offsets[] = { + /* R0 - R12. */ + REGOFF (22), REGOFF (21), REGOFF (20), REGOFF (19), + REGOFF (18), REGOFF (17), REGOFF (16), REGOFF (15), + REGOFF (14), REGOFF (13), REGOFF (12), REGOFF (11), + REGOFF (10), + + /* R13 - R25. */ + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, + + REGOFF (9), /* R26 (GP) */ + REGOFF (8), /* FP */ + REGOFF (23), /* SP */ + ARC_OFFSET_NO_REGISTER, /* ILINK */ + ARC_OFFSET_NO_REGISTER, /* R30 */ + REGOFF (7), /* BLINK */ + + /* R32 - R59. */ + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, ARC_OFFSET_NO_REGISTER, + ARC_OFFSET_NO_REGISTER, + + REGOFF (4), /* LP_COUNT */ + ARC_OFFSET_NO_REGISTER, /* RESERVED */ + ARC_OFFSET_NO_REGISTER, /* LIMM */ + ARC_OFFSET_NO_REGISTER, /* PCL */ + + REGOFF (6), /* PC */ + REGOFF (5), /* STATUS32 */ + REGOFF (2), /* LP_START */ + REGOFF (3), /* LP_END */ + REGOFF (1), /* BTA */ +}; + /* arc_linux_core_reg_offsets[i] is the offset in the .reg section of GDB regnum i. Array index is an internal GDB register number, as defined in arc-tdep.h:arc_regnum. @@ -87,6 +141,127 @@ static const int arc_linux_core_reg_offsets[] = { REGOFF (6) /* ERET */ }; +/* Is THIS_FRAME a sigtramp function - the function that returns from + signal handler into normal execution flow? This is the case if the PC is + either at the start of, or in the middle of the two instructions: + + mov r8, __NR_rt_sigreturn ; __NR_rt_sigreturn == 139 + trap_s 0 ; `swi' for ARC700 + + On ARC uClibc Linux this function is called __default_rt_sa_restorer. + + Returns TRUE if this is a sigtramp frame. */ + +static bool +arc_linux_is_sigtramp (struct frame_info *this_frame) +{ + struct gdbarch *gdbarch = get_frame_arch (this_frame); + CORE_ADDR pc = get_frame_pc (this_frame); + + if (arc_debug) + { + debug_printf ("arc-linux: arc_linux_is_sigtramp, pc=%s\n", + paddress(gdbarch, pc)); + } + + static const gdb_byte insns_be_hs[] = { + 0x20, 0x8a, 0x12, 0xc2, /* mov r8,nr_rt_sigreturn */ + 0x78, 0x1e /* trap_s 0 */ + }; + static const gdb_byte insns_be_700[] = { + 0x20, 0x8a, 0x12, 0xc2, /* mov r8,nr_rt_sigreturn */ + 0x22, 0x6f, 0x00, 0x3f /* swi */ + }; + + gdb_byte arc_sigtramp_insns[sizeof (insns_be_700)]; + size_t insns_sz; + if (arc_mach_is_arcv2 (gdbarch)) + { + insns_sz = sizeof (insns_be_hs); + memcpy (arc_sigtramp_insns, insns_be_hs, insns_sz); + } + else + { + insns_sz = sizeof (insns_be_700); + memcpy (arc_sigtramp_insns, insns_be_700, insns_sz); + } + if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_LITTLE) + { + /* On little endian targets, ARC code section is in what is called + "middle endian", where half-words are in the big-endian order, + only bytes inside the halfwords are in the little endian order. + As a result it is very easy to convert big endian instruction to + little endian, since it is needed to swap bytes in the halfwords, + so there is no need to have information on whether that is a + 4-byte instruction or 2-byte. */ + gdb_assert ((insns_sz % 2) == 0); + for (int i = 0; i < insns_sz; i += 2) + std::swap (arc_sigtramp_insns[i], arc_sigtramp_insns[i+1]); + } + + gdb_byte buf[insns_sz]; + + /* Read the memory at the PC. Since we are stopped, any breakpoint must + have been removed. */ + if (!safe_frame_unwind_memory (this_frame, pc, buf, insns_sz)) + { + /* Failed to unwind frame. */ + return FALSE; + } + + /* Is that code the sigtramp instruction sequence? */ + if (memcmp (buf, arc_sigtramp_insns, insns_sz) == 0) + return TRUE; + + /* No - look one instruction earlier in the code... */ + if (!safe_frame_unwind_memory (this_frame, pc - 4, buf, insns_sz)) + { + /* Failed to unwind frame. */ + return FALSE; + } + + return (memcmp (buf, arc_sigtramp_insns, insns_sz) == 0); +} + +/* Get sigcontext structure of sigtramp frame - it contains saved + registers of interrupted frame. + + Stack pointer points to the rt_sigframe structure, and sigcontext can + be found as in: + + struct rt_sigframe { + struct siginfo info; + struct ucontext uc; + ... + }; + + struct ucontext { + unsigned long uc_flags; + struct ucontext *uc_link; + stack_t uc_stack; + struct sigcontext uc_mcontext; + sigset_t uc_sigmask; + }; + + sizeof (struct siginfo) == 0x80 + offsetof (struct ucontext, uc_mcontext) == 0x14 + + GDB cannot include linux headers and use offsetof () because those are + target headers and GDB might be built for a different run host. There + doesn't seem to be an established mechanism to figure out those offsets + via gdbserver, so the only way is to hardcode values in the GDB, + meaning that GDB will be broken if values will change. That seems to + be a very unlikely scenario and other arches (aarch64, alpha, amd64, + etc) in GDB hardcode values. */ + +static CORE_ADDR +arc_linux_sigcontext_addr (struct frame_info *this_frame) +{ + const int ucontext_offset = 0x80; + const int sigcontext_offset = 0x14; + return get_frame_sp (this_frame) + ucontext_offset + sigcontext_offset; +} + /* Implement the "cannot_fetch_register" gdbarch method. */ static int @@ -430,6 +605,12 @@ arc_linux_init_osabi (struct gdbarch_info info, struct gdbarch *gdbarch) if (arc_debug) debug_printf ("arc-linux: GNU/Linux OS/ABI initialization.\n"); + /* Fill in target-dependent info in ARC-private structure. */ + tdep->is_sigtramp = arc_linux_is_sigtramp; + tdep->sigcontext_addr = arc_linux_sigcontext_addr; + tdep->sc_reg_offset = arc_linux_sc_reg_offsets; + tdep->sc_num_regs = ARRAY_SIZE (arc_linux_sc_reg_offsets); + /* If we are using Linux, we have in uClibc (libc/sysdeps/linux/arc/bits/setjmp.h): -- 2.30.2