From 45a816d93ec86fb4aec161713ec6b3deaac68872 Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sat, 4 Mar 2000 23:37:33 +0000 Subject: 2000-03-04 Mark Kettenis Fix support for Linux/i386 signal trampolines. The old approach didn't work for Linux 2.2 and beyond, and didn't work with recent versions of the GNU C library. * i386-tdep.c (LINUX_RT_SIGTRAMP_INSN0, LINUX_RT_SIGTRAMP_OFFSET0, LINUX_RT_SIGTRAMP_INSN1, LINUX_RT_SIGTRAMP_OFFSET1): New defines. (linux_rt_sigtramp_code): New variable. (LINUX_RT_SIGTRAMP_LEN): New define. (i386_linux_rt_sigtramp_start): New function. Detect start of signal trampolines for RT signals. (i386_linux_sigtramp): Removed. (i386_linux_in_sigtramp): New function. (i386_linux_sigcontext_addr): New function. Recognize the names of the signal tranmpolines used by recent versions of the GNU C library, and add support for RT signals. (LINUX_SIGCONTEXT_PC_OFFSET, LINUX_SIGCONTEXT_SP_OFFSET): New defines. Moved here from config/i386/tm-linux.h. (i386_linux_sigtramp_saved_pc, i386_linux_sigtramp_saved_sp): Reimplement in terms of i386_linux_sigcontext_addr. * config/i386/tm-linux.h (LINUX_SIGCONTEXT_SIZE): Removed. (LINUX_SIGCONTEXT_PC_OFFSET, LINUX_SIGCONTEXT_SP_OFFSET): Moved to i386-tdep.c. (IN_SIGTRAMP): Redefine to call i386_linux_in_sigtramp. --- gdb/ChangeLog | 25 +++++++ gdb/config/i386/tm-linux.h | 18 +---- gdb/i386-tdep.c | 181 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 176 insertions(+), 48 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 15bf236..ed438a9a 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,28 @@ +2000-03-04 Mark Kettenis + + Fix support for Linux/i386 signal trampolines. The old approach + didn't work for Linux 2.2 and beyond, and didn't work with recent + versions of the GNU C library. + * i386-tdep.c (LINUX_RT_SIGTRAMP_INSN0, LINUX_RT_SIGTRAMP_OFFSET0, + LINUX_RT_SIGTRAMP_INSN1, LINUX_RT_SIGTRAMP_OFFSET1): New defines. + (linux_rt_sigtramp_code): New variable. + (LINUX_RT_SIGTRAMP_LEN): New define. + (i386_linux_rt_sigtramp_start): New function. Detect start of + signal trampolines for RT signals. + (i386_linux_sigtramp): Removed. + (i386_linux_in_sigtramp): New function. + (i386_linux_sigcontext_addr): New function. Recognize the names + of the signal tranmpolines used by recent versions of the GNU C + library, and add support for RT signals. + (LINUX_SIGCONTEXT_PC_OFFSET, LINUX_SIGCONTEXT_SP_OFFSET): New + defines. Moved here from config/i386/tm-linux.h. + (i386_linux_sigtramp_saved_pc, i386_linux_sigtramp_saved_sp): + Reimplement in terms of i386_linux_sigcontext_addr. + * config/i386/tm-linux.h (LINUX_SIGCONTEXT_SIZE): Removed. + (LINUX_SIGCONTEXT_PC_OFFSET, LINUX_SIGCONTEXT_SP_OFFSET): + Moved to i386-tdep.c. + (IN_SIGTRAMP): Redefine to call i386_linux_in_sigtramp. + Sat Mar 4 19:38:11 2000 Andrew Cagney By: Sat Mar 4 04:08:58 2000 Alexandre Oliva diff --git a/gdb/config/i386/tm-linux.h b/gdb/config/i386/tm-linux.h index a043489..391d5c0 100644 --- a/gdb/config/i386/tm-linux.h +++ b/gdb/config/i386/tm-linux.h @@ -30,15 +30,6 @@ #include "i386/tm-i386.h" #include "tm-linux.h" -/* Size of sigcontext, from . */ -#define LINUX_SIGCONTEXT_SIZE (88) - -/* Offset to saved PC in sigcontext, from . */ -#define LINUX_SIGCONTEXT_PC_OFFSET (56) - -/* Offset to saved SP in sigcontext, from . */ -#define LINUX_SIGCONTEXT_SP_OFFSET (28) - #define LOW_RETURN_REGNUM 0 /* holds low four bytes of result */ #define HIGH_RETURN_REGNUM 2 /* holds high four bytes of result */ @@ -108,15 +99,14 @@ extern int i387_store_floating (PTR addr, int len, long double val); order to support backtracing through calls to signal handlers. */ #define I386_LINUX_SIGTRAMP -#define IN_SIGTRAMP(pc, name) ((name) == NULL && i386_linux_sigtramp (pc)) - -extern int i386_linux_sigtramp PARAMS ((CORE_ADDR)); +#define IN_SIGTRAMP(pc, name) i386_linux_in_sigtramp (pc, name) +extern int i386_linux_in_sigtramp (CORE_ADDR, char *); /* We need our own version of sigtramp_saved_pc to get the saved PC in a sigtramp routine. */ #define sigtramp_saved_pc i386_linux_sigtramp_saved_pc -extern CORE_ADDR i386_linux_sigtramp_saved_pc PARAMS ((struct frame_info *)); +extern CORE_ADDR i386_linux_sigtramp_saved_pc (struct frame_info *); /* Signal trampolines don't have a meaningful frame. As in tm-i386.h, the frame pointer value we use is actually the frame pointer of the @@ -162,7 +152,7 @@ extern CORE_ADDR i386_linux_sigtramp_saved_pc PARAMS ((struct frame_info *)); ? read_memory_integer (i386_linux_sigtramp_saved_sp ((FRAME)->next), 4) \ : read_memory_integer ((FRAME)->frame + 4, 4))) -extern CORE_ADDR i386_linux_sigtramp_saved_sp PARAMS ((struct frame_info *)); +extern CORE_ADDR i386_linux_sigtramp_saved_sp (struct frame_info *); /* When we call a function in a shared library, and the PLT sends us into the dynamic linker to find the function's real address, we diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c index c381961..10011f6 100644 --- a/gdb/i386-tdep.c +++ b/gdb/i386-tdep.c @@ -781,11 +781,19 @@ i386v4_sigtramp_saved_pc (frame) #ifdef I386_LINUX_SIGTRAMP -/* When the i386 Linux kernel calls a signal handler, 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 is +/* 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 Linux will support SA_SIGINFO for + normal signals too. */ + +/* When the i386 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 pop %eax mov $0x77,%eax int $0x80 @@ -799,7 +807,15 @@ i386v4_sigtramp_saved_pc (frame) order to identify a signal trampoline, but there doesn't seem to be any other way. The 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. */ + 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 LINUX_SIGTRAMP_INSN0 (0x58) /* pop %eax */ #define LINUX_SIGTRAMP_OFFSET0 (0) @@ -821,8 +837,7 @@ static const unsigned char linux_sigtramp_code[] = the routine. Otherwise, return 0. */ static CORE_ADDR -i386_linux_sigtramp_start (pc) - CORE_ADDR pc; +i386_linux_sigtramp_start (CORE_ADDR pc) { unsigned char buf[LINUX_SIGTRAMP_LEN]; @@ -864,51 +879,149 @@ i386_linux_sigtramp_start (pc) return pc; } +/* This function does the same for RT signals. Here the instruction + sequence is + mov $0xad,%eax + int $0x80 + or 0xb8 0xad 0x00 0x00 0x00 0xcd 0x80. + + The effect is to call the system call rt_sigreturn. */ + +#define LINUX_RT_SIGTRAMP_INSN0 (0xb8) /* mov $NNNN,%eax */ +#define LINUX_RT_SIGTRAMP_OFFSET0 (0) +#define LINUX_RT_SIGTRAMP_INSN1 (0xcd) /* int */ +#define LINUX_RT_SIGTRAMP_OFFSET1 (5) + +static const unsigned char linux_rt_sigtramp_code[] = +{ + LINUX_RT_SIGTRAMP_INSN0, 0xad, 0x00, 0x00, 0x00, /* mov $0xad,%eax */ + LINUX_RT_SIGTRAMP_INSN1, 0x80 /* int $0x80 */ +}; + +#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 +i386_linux_rt_sigtramp_start (CORE_ADDR pc) +{ + unsigned char 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 (read_memory_nobpt (pc, (char *) buf, LINUX_RT_SIGTRAMP_LEN) != 0) + return 0; + + if (buf[0] != LINUX_RT_SIGTRAMP_INSN0) + { + if (buf[0] != LINUX_RT_SIGTRAMP_INSN1) + return 0; + + pc -= LINUX_RT_SIGTRAMP_OFFSET1; + + if (read_memory_nobpt (pc, (char *) buf, LINUX_RT_SIGTRAMP_LEN) != 0) + return 0; + } + + if (memcmp (buf, linux_rt_sigtramp_code, LINUX_RT_SIGTRAMP_LEN) != 0) + return 0; + + return pc; +} + /* Return whether PC is in a Linux sigtramp routine. */ int -i386_linux_sigtramp (pc) - CORE_ADDR pc; +i386_linux_in_sigtramp (CORE_ADDR pc, char *name) { - return i386_linux_sigtramp_start (pc) != 0; + if (name) + return STREQ ("__restore", name) || STREQ ("__restore_rt", name); + + return (i386_linux_sigtramp_start (pc) != 0 + || i386_linux_rt_sigtramp_start (pc) != 0); } -/* Assuming FRAME is for a Linux sigtramp routine, return the saved - program counter. The Linux kernel will set up a sigcontext - structure immediately before the sigtramp routine on the stack. */ +/* Assuming FRAME is for a Linux sigtramp routine, return the address + of the associated sigcontext structure. */ CORE_ADDR -i386_linux_sigtramp_saved_pc (frame) - struct frame_info *frame; +i386_linux_sigcontext_addr (struct frame_info *frame) { CORE_ADDR pc; pc = i386_linux_sigtramp_start (frame->pc); - if (pc == 0) - error ("i386_linux_sigtramp_saved_pc called when no sigtramp"); - return read_memory_integer ((pc - - LINUX_SIGCONTEXT_SIZE - + LINUX_SIGCONTEXT_PC_OFFSET), - 4); + if (pc) + { + CORE_ADDR sp; + + if (frame->next) + /* If this isn't the top frame, the next frame must be for the + signal handler itself. The sigcontext structure lives on + the stack, right after the signum argument. */ + return frame->next->frame + 12; + + /* This is the top frame. We'll have to find the address of the + sigcontext structure by looking at the stack pointer. Keep + in mind that the first instruction of the sigtramp code is + "pop %eax". If the PC is at this instruction, adjust the + returned value accordingly. */ + sp = read_register (SP_REGNUM); + if (pc == frame->pc) + return sp + 4; + return sp; + } + + pc = i386_linux_rt_sigtramp_start (frame->pc); + if (pc) + { + if (frame->next) + /* If this isn't the top frame, the next frame must be for the + signal handler itself. 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. */ + return read_memory_integer (frame->next->frame + 16, 4) + 20; + + /* This is the top frame. Again, use the stack pointer to find + the address of the sigcontext structure. */ + return read_memory_integer (read_register (SP_REGNUM) + 8, 4) + 20; + } + + error ("Couldn't recognize signal trampoline."); + return 0; } +/* Offset to saved PC in sigcontext, from . */ +#define LINUX_SIGCONTEXT_PC_OFFSET (56) + /* Assuming FRAME is for a Linux sigtramp routine, return the saved - stack pointer. The Linux kernel will set up a sigcontext structure - immediately before the sigtramp routine on the stack. */ + program counter. */ CORE_ADDR -i386_linux_sigtramp_saved_sp (frame) - struct frame_info *frame; +i386_linux_sigtramp_saved_pc (struct frame_info *frame) { - CORE_ADDR pc; + CORE_ADDR addr; + addr = i386_linux_sigcontext_addr (frame); + return read_memory_integer (addr + LINUX_SIGCONTEXT_PC_OFFSET, 4); +} - pc = i386_linux_sigtramp_start (frame->pc); - if (pc == 0) - error ("i386_linux_sigtramp_saved_sp called when no sigtramp"); - return read_memory_integer ((pc - - LINUX_SIGCONTEXT_SIZE - + LINUX_SIGCONTEXT_SP_OFFSET), - 4); +/* Offset to saved SP in sigcontext, from . */ +#define LINUX_SIGCONTEXT_SP_OFFSET (28) + +/* Assuming FRAME is for a Linux sigtramp routine, return the saved + stack pointer. */ + +CORE_ADDR +i386_linux_sigtramp_saved_sp (struct frame_info *frame) +{ + CORE_ADDR addr; + addr = i386_linux_sigcontext_addr (frame); + return read_memory_integer (addr + LINUX_SIGCONTEXT_SP_OFFSET, 4); } #endif /* I386_LINUX_SIGTRAMP */ -- cgit v1.1