858339f2b7
The necessity for this change has been revealed in the course of investigation related to proposed changes in the treatment of the ISA bit encoded in function symbols on the MIPS target. This change adds support for Linux signal trampolines encoded with the microMIPS instruction set. Such trampolines are used by the Linux kernel if compiled as a microMIPS binary (even if the binary run/debugged itself contains no microMIPS code at all). To see if we need to check whether the execution mode selected matches the given trampoline I have checked what the bit patterns of all the trampoline sequences decode to in the opposite instruction set. This produced useless or at least unusual code in most cases, for example: microMIPS/EB, o32 sigreturn, decoded as MIPS code: 30401017 andi zero,v0,0x1017 00008b7c dsll32 s1,zero,0xd MIPS/EL, o32 sigreturn, decoded as microMIPS code: 1017 2402 addi zero,s7,9218 000c 0000 sll zero,t0,0x0 However in some corner cases reasonable code can mimic a trampoline, for example: MIPS/EB, n32 rt_sigreturn, decoded as microMIPS code: 2402 sll s0,s0,1 1843 0000 sb v0,0(v1) 000c 0f3c jr t0 -- here the first instruction is a 16-bit one, making things nastier even as there are some other microMIPS instructions whose first 16-bit halfword is 0x000c and therefore matches this whole trampoline pattern. To overcome this problem I have decided the signal trampoline unwinder has to ask the platform backend whether it can apply a given trampoline pattern to the code location being concerned or not. Anticipating the acceptance of the ISA bit proposal I decided the handler not to merely be a predicate, but also to be able to provide an adjusted PC if required. I decided that returning zero will mean that the trampoline pattern is not applicable and any other value is the adjusted PC to use; a handler may return the value requested if the trampoline pattern and the PC requested as-is are both accepted. This changes the semantics of the trampoline unwinder a bit in that the zero PC now has a special value. I think this should be safe as a NULL pointer is generally supposed to be invalid. * tramp-frame.h (tramp_frame): Add `validate' member. * tramp-frame.c (tramp_frame_start): Validate trampoline before scanning. * mips-linux-tdep.c (MICROMIPS_INST_LI_V0): New macro. (MICROMIPS_INST_POOL32A, MICROMIPS_INST_SYSCALL): Likewise. (mips_linux_o32_sigframe): Initialize `validate' member. (mips_linux_o32_rt_sigframe): Likewise. (mips_linux_n32_rt_sigframe): Likewise. (mips_linux_n64_rt_sigframe): Likewise. (micromips_linux_o32_sigframe): New variable. (micromips_linux_o32_rt_sigframe): Likewise. (micromips_linux_n32_rt_sigframe): Likewise. (micromips_linux_n64_rt_sigframe): Likewise. (mips_linux_o32_sigframe_init): Handle microMIPS trampolines. (mips_linux_n32n64_sigframe_init): Likewise. (mips_linux_sigframe_validate): New function. (micromips_linux_sigframe_validate): Likewise. (mips_linux_init_abi): Install microMIPS trampoline unwinders.
173 lines
5.1 KiB
C
173 lines
5.1 KiB
C
/* Signal trampoline unwinder, for GDB the GNU Debugger.
|
|
|
|
Copyright (C) 2004-2014 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 "tramp-frame.h"
|
|
#include "frame-unwind.h"
|
|
#include "gdbcore.h"
|
|
#include "symtab.h"
|
|
#include "objfiles.h"
|
|
#include "target.h"
|
|
#include "trad-frame.h"
|
|
#include "frame-base.h"
|
|
|
|
struct frame_data
|
|
{
|
|
const struct tramp_frame *tramp_frame;
|
|
};
|
|
|
|
struct tramp_frame_cache
|
|
{
|
|
CORE_ADDR func;
|
|
const struct tramp_frame *tramp_frame;
|
|
struct trad_frame_cache *trad_cache;
|
|
};
|
|
|
|
static struct trad_frame_cache *
|
|
tramp_frame_cache (struct frame_info *this_frame,
|
|
void **this_cache)
|
|
{
|
|
struct tramp_frame_cache *tramp_cache = (*this_cache);
|
|
|
|
if (tramp_cache->trad_cache == NULL)
|
|
{
|
|
tramp_cache->trad_cache = trad_frame_cache_zalloc (this_frame);
|
|
tramp_cache->tramp_frame->init (tramp_cache->tramp_frame,
|
|
this_frame,
|
|
tramp_cache->trad_cache,
|
|
tramp_cache->func);
|
|
}
|
|
return tramp_cache->trad_cache;
|
|
}
|
|
|
|
static void
|
|
tramp_frame_this_id (struct frame_info *this_frame,
|
|
void **this_cache,
|
|
struct frame_id *this_id)
|
|
{
|
|
struct trad_frame_cache *trad_cache
|
|
= tramp_frame_cache (this_frame, this_cache);
|
|
|
|
trad_frame_get_id (trad_cache, this_id);
|
|
}
|
|
|
|
static struct value *
|
|
tramp_frame_prev_register (struct frame_info *this_frame,
|
|
void **this_cache,
|
|
int prev_regnum)
|
|
{
|
|
struct trad_frame_cache *trad_cache
|
|
= tramp_frame_cache (this_frame, this_cache);
|
|
|
|
return trad_frame_get_register (trad_cache, this_frame, prev_regnum);
|
|
}
|
|
|
|
static CORE_ADDR
|
|
tramp_frame_start (const struct tramp_frame *tramp,
|
|
struct frame_info *this_frame, CORE_ADDR pc)
|
|
{
|
|
struct gdbarch *gdbarch = get_frame_arch (this_frame);
|
|
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
|
int ti;
|
|
|
|
/* Check if we can use this trampoline. */
|
|
if (tramp->validate && !tramp->validate (tramp, this_frame, &pc))
|
|
return 0;
|
|
|
|
/* Search through the trampoline for one that matches the
|
|
instruction sequence around PC. */
|
|
for (ti = 0; tramp->insn[ti].bytes != TRAMP_SENTINEL_INSN; ti++)
|
|
{
|
|
CORE_ADDR func = pc - tramp->insn_size * ti;
|
|
int i;
|
|
|
|
for (i = 0; 1; i++)
|
|
{
|
|
gdb_byte buf[sizeof (tramp->insn[0])];
|
|
ULONGEST insn;
|
|
|
|
if (tramp->insn[i].bytes == TRAMP_SENTINEL_INSN)
|
|
return func;
|
|
if (!safe_frame_unwind_memory (this_frame,
|
|
func + i * tramp->insn_size,
|
|
buf, tramp->insn_size))
|
|
break;
|
|
insn = extract_unsigned_integer (buf, tramp->insn_size, byte_order);
|
|
if (tramp->insn[i].bytes != (insn & tramp->insn[i].mask))
|
|
break;
|
|
}
|
|
}
|
|
/* Trampoline doesn't match. */
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tramp_frame_sniffer (const struct frame_unwind *self,
|
|
struct frame_info *this_frame,
|
|
void **this_cache)
|
|
{
|
|
const struct tramp_frame *tramp = self->unwind_data->tramp_frame;
|
|
CORE_ADDR pc = get_frame_pc (this_frame);
|
|
CORE_ADDR func;
|
|
struct tramp_frame_cache *tramp_cache;
|
|
|
|
/* tausq/2004-12-12: We used to assume if pc has a name or is in a valid
|
|
section, then this is not a trampoline. However, this assumption is
|
|
false on HPUX which has a signal trampoline that has a name; it can
|
|
also be false when using an alternative signal stack. */
|
|
func = tramp_frame_start (tramp, this_frame, pc);
|
|
if (func == 0)
|
|
return 0;
|
|
tramp_cache = FRAME_OBSTACK_ZALLOC (struct tramp_frame_cache);
|
|
tramp_cache->func = func;
|
|
tramp_cache->tramp_frame = tramp;
|
|
(*this_cache) = tramp_cache;
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
tramp_frame_prepend_unwinder (struct gdbarch *gdbarch,
|
|
const struct tramp_frame *tramp_frame)
|
|
{
|
|
struct frame_data *data;
|
|
struct frame_unwind *unwinder;
|
|
int i;
|
|
|
|
/* Check that the instruction sequence contains a sentinel. */
|
|
for (i = 0; i < ARRAY_SIZE (tramp_frame->insn); i++)
|
|
{
|
|
if (tramp_frame->insn[i].bytes == TRAMP_SENTINEL_INSN)
|
|
break;
|
|
}
|
|
gdb_assert (i < ARRAY_SIZE (tramp_frame->insn));
|
|
gdb_assert (tramp_frame->insn_size <= sizeof (tramp_frame->insn[0].bytes));
|
|
|
|
data = GDBARCH_OBSTACK_ZALLOC (gdbarch, struct frame_data);
|
|
unwinder = GDBARCH_OBSTACK_ZALLOC (gdbarch, struct frame_unwind);
|
|
|
|
data->tramp_frame = tramp_frame;
|
|
unwinder->type = tramp_frame->frame_type;
|
|
unwinder->unwind_data = data;
|
|
unwinder->sniffer = tramp_frame_sniffer;
|
|
unwinder->stop_reason = default_frame_unwind_stop_reason;
|
|
unwinder->this_id = tramp_frame_this_id;
|
|
unwinder->prev_register = tramp_frame_prev_register;
|
|
frame_unwind_prepend_unwinder (gdbarch, unwinder);
|
|
}
|