This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFA] MIPS/GDB: Fix the handling of MIPS16 thunks


Hi,

[Richard, I've cc-ed you as the MIPS port maintainer of GCC and binutils, 
the producers of MIPS16 and some other thunks covered here, in case you 
had anything to add, and just so that you know this issue is being 
addressed now.]

 This change fixes the long-standing problems with MIPS16 thunks -- small 
pieces of code used to translate between the standard MIPS and the MIPS16 
calling convention used for passing floating-point function arguments and 
function results on hard-float ABIs.  As floating-point registers are 
inaccessible from MIPS16 code, MIPS16 functions use integer registers 
where standard MIPS functions would use floating-point registers.  Thunks 
are therefore used in the process of passing control between functions to 
move floating-point values between registers as necessary.

 There are essentially three classes of MIPS16 thunks: call thunks, return 
thunks and call/return thunks.  I'll outline them briefly below for better 
understanding of what GDB has to do about them.

1. Call thunks are pieces of code that are called instead of the intended 
   function, for functions that take floating-point arguments, but return 
   no floating-point result.  They are also used for direct calls to 
   MIPS16 functions that both take floating-point arguments and return
   floating-point results.  These thunks move values between registers as 
   required and then make a jump (with no link) or a sibling call to the 
   originally intended function.  Therefore in the frame chain they appear 
   as a temporary empty frame that disappears as soon as the ultimate 
   function has been reached.

   There are two types of these thunks -- direct and indirect, used for 
   the respective types of function calls.  For the formers the target of 
   the jump (J) is hardcoded and the thunk itself is generated by GCC 
   individually on a per-function basis.  For the latters the target of 
   the jump is passed by the caller in a register ($v0), so a 
   jump-register (JR) operation is used to transfer control to the 
   intended function.  As a result the thunk itself can be reused for any 
   function that has the same register floating-point arguments and shared 
   thunks are pulled from libgcc on an as-needed basis.

   Even direct thunks need to load the address of the intended function 
   into a register and then make use of the jump-register instruction when 
   PIC code is used; GCC apparently pessimises some cases and emits a 
   load-address/jump-register instruction sequence even when producing 
   non-PIC code, where a direct jump would do (I have a patch somewhere 
   that needs regression-testing against GCC trunk to make things better, 
   that I'll try to squeeze in sometime).

2. Return thunks are pieces of code that are called by MIPS16 functions 
   that return floating-point results just as the respective function is 
   about to return.  These thunks move values between registers as 
   required and then return (with JR $ra) to their caller MIPS16 function.  
   This extra call is made between the function's body and its epilogue, 
   so in the frame chain it appears temporarily as an extra empty frame 
   inner to the original function's frame.  That frame disappears as soon 
   as the thunk returns to the caller's epilogue.

   These thunks can be reused for any function that has the same register 
   floating-point results and therefore shared thunksare used that are 
   pulled from libgcc on an as-needed basis.

3. Call/return thunks are pieces of code that are called instead of the 
   intended function for functions that both take floating-point arguments 
   and return floating-point results (except from direct calls to MIPS16 
   functions).  They move values between registers as required, store the
   return address in a call-saved register ($s2) and then make a call to 
   the originally intended function.  When that function returns, these 
   thunks move values between registers as required again, and then return
   (with JR $s2) to the original caller.  As a result in the frame chain 
   these thunks appear as a permanent extra empty frame between the 
   original caller and the ultimate callee throughout the life of the 
   callee.

   Like with call thunks there are two types of these thunks -- direct and 
   indirect with similar implications.

 These thunks are sometimes chained as the requirements of the ABI imply, 
for example all indirect calls have to standardise on a particular calling 
convention, for which the standard MIPS ABI has been chosen.  Therefore 
such a call between a pair of MIPS16 functions may involve an indirect 
call thunk that jumps to a direct call thunk that jumps to the ultimate 
callee.  Likewise an indirect call/return thunk may call a direct call 
thunk that jumps to the ultimate callee.  That callee may then call the 
return thunk before returning to the return part of the call/return thunk.

 Further non-MIPS16 trampolines may be generated as needed, for example 
PIC stubs may be prepended to some of these thunks when linking PIC code 
into non-PIC code, sometimes even unnecessarily such as in the case of 
return thunks that refer to no global symbols, but come from libgcc that 
is built as PIC code on targets that require it and the static linker is 
not smart enough to figure out that such a PIC stub is actually not 
needed.

 Similarly, there may be a PLT stub involved in a shared library call that 
resolves to a call thunk and which may itself be called by a MIPS16 thunk 
as the shared library ABI uses the standard MIPS calling convention.

 All these thunks are intended to be invisible to the user who debugs 
software at the high-level programming language level.  In practice this 
means that the thunks have to be silently stepped through when 
source-level single-stepping is requested and any auxiliary frames have to 
be removed from the view in backtraces.  Although some code has already 
been added to GDB to address these requirements, it does not handle all 
the cases and this change is intended to fix it.

 Here are the highlights of the changes made:

1. Intermediate frames of the call/return thunks are now skipped in 
   (silently removed from) backtraces.

2. Code to skip over MIPS16 thunks (trampolines), i.e. to calculate the 
   ultimate value of the PC once the respective thunk has executed, has 
   been updated to handle all the currently known thunk types, in 
   particular ones that handle complex floating-point types.  Changes
   were made to use macros and calculated values instead of hardcoded 
   numbers.  Cooked registers are now used for correct register retrieval 
   in outer frames.  Recursion into chained thunks has been removed from 
   here.  Finally, code to scan call thunks heuristically beyond "etext" 
   or "_etext" has been removed.

   The latter is probably the most controversial here as it seems to 
   remove a feature.  This feature however dates back to before the 
   history of our CVS repository and the mailing list, probably to the 
   original submission of this code, i.e.:

Thu Apr  3 10:31:12 1997  Mark Alexander  <marka@cygnus.com>

	* mips-tdep.c (mips_in_call_stub, mips_in_return_stub,
	mips_skip_stub, mips_ignore_helper): New functions for dealing
	with MIPS16 call/return thunks.

   (mips_skip_mips16_trampoline_code was called mips_skip_stub back then),
   and therefore I was unable to track down any explanation.  Myself, I 
   have never seen a case where a debug binary would have some thunks 
   placed beyond the end of recognised text and I don't really know how 
   this could happen.  Perhaps some old tools 15 years ago did something 
   weird about it.  Given that this code has been subject of a substantial 
   bitrot I assert that whoever required it originally no longer cares 
   about this corner case and I have decided to remove it to simplify the
   handling of the thunks.

   If however someone thinks this assertion is incorrect or has any other 
   reasons for this case to remain handled, then please speak out now!

   All these adjustments make single MIPS16 thunks be stepped-through
   correctly when single-stepping at the source level.

3. Arbitrary chains of trampolines are now skipped over until a piece of 
   code that is not a trampoline has been reached, fixing source-level 
   single-stepping where multiple trampolines are encountered.

4. A handler is now installed to skip over return thunks while 
   single-stepping at the source level, reusing the shared library return 
   trampoline hook.  As per the comment included, there is no overlap here 
   as no MIPS ABI uses shared library return trampolines.

   This modification, however, requires a change to generic code -- in
   handle_inferior_event (infrun.c).  This is because a return thunk is 
   called like an ordinary function and therefore once reached, things 
   looks as if a new frame has been created.  As a result code that checks 
   for subroutine calls triggers, at which point there is no way to back 
   out -- while the trampoline handler may still be used there (like for 
   call thunks) to skip over the return thunk, the subroutine call handler 
   insists on skipping over code further, to the next source line.  
   Which, given that we're about to return from a function, means the 
   point after the return instruction.  Now placing the single-stepping 
   breakpoint there -- doesn't quite work as expected.

   Therefore my proposal is to reverse the order of the checks made in 
   handle_inferior_event -- it looks to me there have been no particular 
   reason for why they have been put in the current order, except that one 
   must have been chosen.  Currently there are two users of the shared 
   library return trampoline hook: hppa-hpux-tdep.c and rs6000-tdep.c.  
   The latter is rather trivial and makes me almost sure that the order of 
   the checks does not matter.  The former is less obvious to me, but I 
   think it does not care either.  Input from people knowledgeable on 
   these platforms will be appreciated.

5. We've got a test case now! :)  I have carefully crafted a particularly 
   twisted piece of code that makes use of at least one MIPS16 thunk of 
   each kind.  As MIPS16 execution environment is not available 
   everywhere, but is an optional extension to the MIPS32 or MIPS64 
   architecture (and some legacy ISAs), some care had to be taken to get 
   coverage where available.  The test script therefore makes some checks 
   to make sure a MIPS16 app can be built and run, verifying both 
   toolchain support and support for the MIPS16 instruction set in the 
   target processor the test case is intended to run on.

   Additionally the test app has to be built from several sources, because
   mixing MIPS16 and standard MIPS code in a single source can be 
   problematic (there's a lot of hassle associated with the "mips16" GCC 
   attribute), as well as because we want to test MIPS16 thunks in the 
   context of PIC stubs where available as well.

   Finally, the test case makes an extensive use of single-stepping and in 
   some places the exact number of steps made is not necessarily known (or 
   cared about), so the test case needs to be prepared to make as many 
   steps as required until the designated place (a different frame, 
   sometimes one of two possible, depending on whether we have debug 
   information for the system library or not) is reached, checking every 
   time that the backtrace is correct.

   All of this made some of our standard higher-level ("cooked") TCL 
   procedures available for building executables or making individual 
   checks unsuitable for this case.  I have therefore used some of the 
   lower-level procedures as well as wrapped some repeated pieces into 
   local procedures.  I think it is the most complicated GDB test case I 
   have worked on and certainly the first one I wrote entirely from 
   scratch, so input on this code will be appreciated.

 I have regression tested this change with the mips-sde-elf and the 
mips-linux-gnu target, using a big-endian, o32 ABI configuration.  I have 
verified the new test case itself with the same two targets and lots of 
multilibs, including hard-float and soft-float variations, o32 and n64 
ABIs (the latter failing to build on Linux due to the current lack of 
support for n64 MIPS16 PIC code and scoring correctly as UNSUPPORTED), 
MIPS32, MIPS16 and microMIPS default instruction set (the latter again 
failing to build due to the incompatibility between the MIPS16 and 
microMIPS ASE) and target processors that do and do not support the MIPS16 
ASE (the latter scoring correctly as UNSUPPORTED, after the debuggee has 
gone astray).

 OK (for the generic part) to apply?

2012-04-10  Maciej W. Rozycki  <macro@mips.com>
            Maciej W. Rozycki  <macro@codesourcery.com>

	gdb/
	* infrun.c (handle_inferior_event): Move the check for return
	trampolines ahead of the check for function trampolines.
	* mips-tdep.h (MIPS_S2_REGNUM, MIPS_GP_REGNUM): New macros.
	* mips-tdep.c (mips_str_mips16_call_stub): New variable.
	(mips_str_mips16_ret_stub): Likewise.
	(mips_str_call_fp_stub): Likewise.
	(mips_str_call_stub): Likewise.
	(mips_str_fn_stub): Likewise.
	(mips_str_pic): Likewise.
	(mips_in_frame_stub): New function.
	(mips_unwind_pc): Return the return address rather than the PC
	if the PC of an intermediate frame is inside a call thunk.
	(mips_is_stub_suffix): New function.
	(mips_is_stub_mode): Likewise.
	(mips_get_mips16_fn_stub_pc): Likewise.
	(mips_skip_mips16_trampoline_code): Update to handle all the
	currently generated stub types.  Don't recurse into __fn_stub
	thunks.  Remove heuristics to handle stubs beyond etext/_etext.
	Use cooked register accesses.
	(mips_in_return_stub): Reintroduce function.
	(mips_skip_trampoline_code): Traverse trampolines recursively.
	(mips_gdbarch_init): Handle MIPS16 return trampolines.

2012-04-10  Maciej W. Rozycki  <macro@codesourcery.com>

	gdb/testsuite/
	* gdb.arch/mips16-thunks-inmain.c: New file.
	* gdb.arch/mips16-thunks-main.c: New file.
	* gdb.arch/mips16-thunks-sin.c: New file.
	* gdb.arch/mips16-thunks-sinfrob.c: New file.
	* gdb.arch/mips16-thunks-sinfrob16.c: New file.
	* gdb.arch/mips16-thunks-sinmain.c: New file.
	* gdb.arch/mips16-thunks-sinmips16.c: New file.
	* gdb.arch/mips16-thunks.exp: New file.

  Maciej

gdb-mips16-thunks.diff
Index: gdb-fsf-trunk-quilt/gdb/mips-tdep.c
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/mips-tdep.c	2012-04-04 12:05:40.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/mips-tdep.c	2012-04-05 22:29:33.445560104 +0100
@@ -1027,6 +1027,45 @@ mips_pc_is_mips16 (CORE_ADDR memaddr)
     return is_mips16_addr (memaddr);
 }
 
+/* Various MIPS16 thunk (aka stub or trampoline) names.  */
+
+static const char mips_str_mips16_call_stub[] = "__mips16_call_stub_";
+static const char mips_str_mips16_ret_stub[] = "__mips16_ret_";
+static const char mips_str_call_fp_stub[] = "__call_stub_fp_";
+static const char mips_str_call_stub[] = "__call_stub_";
+static const char mips_str_fn_stub[] = "__fn_stub_";
+
+/* This is used as a PIC thunk prefix.  */
+
+static const char mips_str_pic[] = ".pic.";
+
+/* Return non-zero if the PC is inside a call thunk (aka stub or
+   trampoline) that should be treated as a temporary frame.  */
+
+static int
+mips_in_frame_stub (CORE_ADDR pc)
+{
+  CORE_ADDR start_addr;
+  const char *name;
+
+  /* Find the starting address of the function containing the PC.  */
+  if (find_pc_partial_function (pc, &name, &start_addr, NULL) == 0)
+    return 0;
+
+  /* If the PC is in __mips16_call_stub_*, this is a call/return stub.  */
+  if (strncmp (name, mips_str_mips16_call_stub,
+	       strlen (mips_str_mips16_call_stub)) == 0)
+    return 1;
+  /* If the PC is in __call_stub_*, this is a call/return or a call stub.  */
+  if (strncmp (name, mips_str_call_stub, strlen (mips_str_call_stub)) == 0)
+    return 1;
+  /* If the PC is in __fn_stub_*, this is a call stub.  */
+  if (strncmp (name, mips_str_fn_stub, strlen (mips_str_fn_stub)) == 0)
+    return 1;
+
+  return 0;			/* Not a stub.  */
+}
+
 /* MIPS believes that the PC has a sign extended value.  Perhaps the
    all registers should be sign extended for simplicity?  */
 
@@ -1044,12 +1083,31 @@ mips_read_pc (struct regcache *regcache)
 static CORE_ADDR
 mips_unwind_pc (struct gdbarch *gdbarch, struct frame_info *next_frame)
 {
-  ULONGEST pc;
+  CORE_ADDR pc;
 
   pc = frame_unwind_register_signed
 	 (next_frame, gdbarch_num_regs (gdbarch) + mips_regnum (gdbarch)->pc);
   if (is_mips16_addr (pc))
     pc = unmake_mips16_addr (pc);
+  /* macro/2005-03-31: This hack skips over MIPS16 call thunks as
+     intermediate frames.  In this case we can get the caller's address
+     from $ra, or if $ra contains an address within a thunk as well, then
+     it must be in the return path of __mips16_call_stub_{s,d}{f,c}_{0..10}
+     and thus the caller's address is in $s2.  */
+  if (frame_relative_level (next_frame) >= 0 && mips_in_frame_stub (pc))
+    {
+      pc = frame_unwind_register_signed
+	     (next_frame, gdbarch_num_regs (gdbarch) + MIPS_RA_REGNUM);
+      if (is_mips16_addr (pc))
+	pc = unmake_mips16_addr (pc);
+      if (mips_in_frame_stub (pc))
+	{
+	  pc = frame_unwind_register_signed
+		 (next_frame, gdbarch_num_regs (gdbarch) + MIPS_S2_REGNUM);
+	  if (is_mips16_addr (pc))
+	    pc = unmake_mips16_addr (pc);
+	}
+    }
   return pc;
 }
 
@@ -5653,104 +5711,333 @@ mips_adjust_breakpoint_address (struct g
   return bpaddr;
 }
 
-/* If PC is in a mips16 call or return stub, return the address of the target
-   PC, which is either the callee or the caller.  There are several
+/* Return non-zero if SUFFIX is one of the numeric suffixes used for MIPS16
+   call stubs, one of 1, 2, 5, 6, 9, 10, or, if ZERO is non-zero, also 0.  */
+
+static int mips_is_stub_suffix (const char *suffix, int zero)
+{
+  switch (suffix[0])
+   {
+   case '0':
+     return zero && suffix[1] == '\0';
+   case '1':
+     return suffix[1] == '\0' || (suffix[1] == '0' && suffix[2] == '\0');
+   case '2':
+   case '5':
+   case '6':
+   case '9':
+     return suffix[1] == '\0';
+   default:
+     return 0;
+   }
+}
+
+/* Return non-zero if MODE is one of the mode infixes used for MIPS16
+   call stubs, one of sf, df, sc, or dc.  */
+
+static int mips_is_stub_mode (const char *mode)
+{
+  return ((mode[0] == 's' || mode[0] == 'd')
+	  && (mode[1] == 'f' || mode[1] == 'c'));
+}
+
+/* Code at PC is a compiler-generated stub.  Such a stub for a function
+   bar might have a name like __fn_stub_bar, and might look like this:
+
+      mfc1    $4, $f13
+      mfc1    $5, $f12
+      mfc1    $6, $f15
+      mfc1    $7, $f14
+
+   followed by (or interspersed with):
+
+      j       bar
+
+   or:
+
+      lui     $25, %hi(bar)
+      addiu   $25, $25, %lo(bar)
+      jr      $25
+
+   ($1 may be used in old code; for robustness we accept any register)
+   or, in PIC code:
+
+      lui     $28, %hi(_gp_disp)
+      addiu   $28, $28, %lo(_gp_disp)
+      addu    $28, $28, $25
+      lw      $25, %got(bar)
+      addiu   $25, $25, %lo(bar)
+      jr      $25
+
+   In the case of a __call_stub_bar stub, the sequence to set up
+   arguments might look like this:
+
+      mtc1    $4, $f13
+      mtc1    $5, $f12
+      mtc1    $6, $f15
+      mtc1    $7, $f14
+
+   followed by (or interspersed with) one of the jump sequences above.
+
+   In the case of a __call_stub_fp_bar stub, JAL or JALR is used instead
+   of J or JR, respectively, followed by:
+
+      mfc1    $2, $f0
+      mfc1    $3, $f1
+      jr      $18
+
+   We are at the beginning of the stub here, and scan down and extract
+   the target address from the jump immediate instruction or, if a jump
+   register instruction is used, from the register referred.  Return
+   the value of PC calculated or 0 if inconclusive.
+
+   The limit on the search is arbitrarily set to 20 instructions.  FIXME.  */
+
+static CORE_ADDR
+mips_get_mips16_fn_stub_pc (struct frame_info *frame, CORE_ADDR pc)
+{
+  struct gdbarch *gdbarch = get_frame_arch (frame);
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  int addrreg = MIPS_ZERO_REGNUM;
+  CORE_ADDR start_pc = pc;
+  CORE_ADDR target_pc = 0;
+  CORE_ADDR addr = 0;
+  CORE_ADDR gp = 0;
+  int status = 0;
+  int i;
+
+  for (i = 0;
+       status == 0 && target_pc == 0 && i < 20;
+       i++, pc += MIPS_INSN32_SIZE)
+    {
+      ULONGEST inst = mips_fetch_instruction (gdbarch, pc);
+      CORE_ADDR imm;
+      int rt;
+      int rs;
+      int rd;
+
+      switch (itype_op (inst))
+	{
+	case 0:		/* SPECIAL */
+	  switch (rtype_funct (inst))
+	    {
+	    case 8:		/* JR */
+	    case 9:		/* JALR */
+	      rs = rtype_rs (inst);
+	      if (rs == MIPS_GP_REGNUM)
+		target_pc = gp;				/* Hmm...  */
+	      else if (rs == addrreg)
+		target_pc = addr;
+	      break;
+
+	    case 0x21:		/* ADDU */
+	      rt = rtype_rt (inst);
+	      rs = rtype_rs (inst);
+	      rd = rtype_rd (inst);
+	      if (rd == MIPS_GP_REGNUM
+		  && ((rs == MIPS_GP_REGNUM && rt == MIPS_T9_REGNUM)
+		      || (rs == MIPS_T9_REGNUM && rt == MIPS_GP_REGNUM)))
+		gp += start_pc;
+	      break;
+	    }
+	  break;
+
+	case 2:		/* J */
+	case 3:		/* JAL */
+	  target_pc = jtype_target (inst) << 2;
+	  target_pc += ((pc + 4) & ~(CORE_ADDR) 0x0fffffff);
+	  break;
+
+	case 9:		/* ADDIU */
+	  rt = itype_rt (inst);
+	  rs = itype_rs (inst);
+	  if (rt == rs)
+	    {
+	      imm = (itype_immediate (inst) ^ 0x8000) - 0x8000;
+	      if (rt == MIPS_GP_REGNUM)
+		gp += imm;
+	      else if (rt == addrreg)
+		addr += imm;
+	    }
+	  break;
+
+	case 0xf:	/* LUI */
+	  rt = itype_rt (inst);
+	  imm = ((itype_immediate (inst) ^ 0x8000) - 0x8000) << 16;
+	  if (rt == MIPS_GP_REGNUM)
+	    gp = imm;
+	  else if (rt != MIPS_ZERO_REGNUM)
+	    {
+	      addrreg = rt;
+	      addr = imm;
+	    }
+	  break;
+
+	case 0x23:	/* LW */
+	  rt = itype_rt (inst);
+	  rs = itype_rs (inst);
+	  imm = (itype_immediate (inst) ^ 0x8000) - 0x8000;
+	  if (gp != 0 && rs == MIPS_GP_REGNUM)
+	    {
+	      gdb_byte buf[4];
+
+	      memset (buf, 0, sizeof (buf));
+	      status = target_read_memory (gp + imm, buf, sizeof (buf));
+	      addrreg = rt;
+	      addr = extract_signed_integer (buf, sizeof (buf), byte_order);
+	    }
+	  break;
+	}
+    }
+
+  return target_pc;
+}
+
+/* If PC is in a MIPS16 call or return stub, return the address of the
+   target PC, which is either the callee or the caller.  There are several
    cases which must be handled:
 
-   * If the PC is in __mips16_ret_{d,s}f, this is a return stub and the
-   target PC is in $31 ($ra).
+   * If the PC is in __mips16_ret_{d,s}{f,c}, this is a return stub
+     and the target PC is in $31 ($ra).
    * If the PC is in __mips16_call_stub_{1..10}, this is a call stub
-   and the target PC is in $2.
-   * If the PC at the start of __mips16_call_stub_{s,d}f_{0..10}, i.e.
-   before the jal instruction, this is effectively a call stub
-   and the target PC is in $2.  Otherwise this is effectively
-   a return stub and the target PC is in $18.
+     and the target PC is in $2.
+   * If the PC at the start of __mips16_call_stub_{s,d}{f,c}_{0..10},
+     i.e. before the JALR instruction, this is effectively a call stub
+     and the target PC is in $2.  Otherwise this is effectively
+     a return stub and the target PC is in $18.
+   * If the PC is at the start of __call_stub_fp_*, i.e. before the
+     JAL or JALR instruction, this is effectively a call stub and the
+     target PC is buried in the instruction stream.  Otherwise this
+     is effectively a return stub and the target PC is in $18.
+   * If the PC is in __call_stub_* or in __fn_stub_*, this is a call
+     stub and the target PC is buried in the instruction stream.
 
-   See the source code for the stubs in gcc/config/mips/mips16.S for
+   See the source code for the stubs in gcc/config/mips/mips16.S, or the
+   stub builder in gcc/config/mips/mips.c (mips16_build_call_stub) for the
    gory details.  */
 
 static CORE_ADDR
 mips_skip_mips16_trampoline_code (struct frame_info *frame, CORE_ADDR pc)
 {
   struct gdbarch *gdbarch = get_frame_arch (frame);
-  const char *name;
   CORE_ADDR start_addr;
+  const char *name;
+  size_t prefixlen;
 
   /* Find the starting address and name of the function containing the PC.  */
   if (find_pc_partial_function (pc, &name, &start_addr, NULL) == 0)
     return 0;
 
-  /* If the PC is in __mips16_ret_{d,s}f, this is a return stub and the
-     target PC is in $31 ($ra).  */
-  if (strcmp (name, "__mips16_ret_sf") == 0
-      || strcmp (name, "__mips16_ret_df") == 0)
-    return get_frame_register_signed (frame, MIPS_RA_REGNUM);
+  /* If the PC is in __mips16_ret_{d,s}{f,c}, this is a return stub
+     and the target PC is in $31 ($ra).  */
+  prefixlen = strlen (mips_str_mips16_ret_stub);
+  if (strncmp (name, mips_str_mips16_ret_stub, prefixlen) == 0
+      && mips_is_stub_mode (name + prefixlen)
+      && name[prefixlen + 2] == '\0')
+    return get_frame_register_signed
+	     (frame, gdbarch_num_regs (gdbarch) + MIPS_RA_REGNUM);
 
-  if (strncmp (name, "__mips16_call_stub_", 19) == 0)
+  /* If the PC is in __mips16_call_stub_*, this is one of the call
+     call/return stubs.  */
+  prefixlen = strlen (mips_str_mips16_call_stub);
+  if (strncmp (name, mips_str_mips16_call_stub, prefixlen) == 0)
     {
       /* If the PC is in __mips16_call_stub_{1..10}, this is a call stub
          and the target PC is in $2.  */
-      if (name[19] >= '0' && name[19] <= '9')
-	return get_frame_register_signed (frame, 2);
+      if (mips_is_stub_suffix (name + prefixlen, 0))
+	return get_frame_register_signed
+		 (frame, gdbarch_num_regs (gdbarch) + MIPS_V0_REGNUM);
 
-      /* If the PC at the start of __mips16_call_stub_{s,d}f_{0..10}, i.e.
-         before the jal instruction, this is effectively a call stub
+      /* If the PC at the start of __mips16_call_stub_{s,d}{f,c}_{0..10},
+         i.e. before the JALR instruction, this is effectively a call stub
          and the target PC is in $2.  Otherwise this is effectively
          a return stub and the target PC is in $18.  */
-      else if (name[19] == 's' || name[19] == 'd')
+      else if (mips_is_stub_mode (name + prefixlen)
+	       && name[prefixlen + 2] == '_'
+	       && mips_is_stub_suffix (name + prefixlen + 3, 0))
 	{
 	  if (pc == start_addr)
-	    {
-	      /* Check if the target of the stub is a compiler-generated
-	         stub.  Such a stub for a function bar might have a name
-	         like __fn_stub_bar, and might look like this:
-	         mfc1    $4,$f13
-	         mfc1    $5,$f12
-	         mfc1    $6,$f15
-	         mfc1    $7,$f14
-	         la      $1,bar   (becomes a lui/addiu pair)
-	         jr      $1
-	         So scan down to the lui/addi and extract the target
-	         address from those two instructions.  */
+	    /* This is the 'call' part of a call stub.  The return
+	       address is in $2.  */
+	    return get_frame_register_signed
+		     (frame, gdbarch_num_regs (gdbarch) + MIPS_V0_REGNUM);
+	  else
+	    /* This is the 'return' part of a call stub.  The return
+	       address is in $18.  */
+	    return get_frame_register_signed
+		     (frame, gdbarch_num_regs (gdbarch) + MIPS_S2_REGNUM);
+	}
+      else
+	return 0;		/* Not a stub.  */
+    }
 
-	      CORE_ADDR target_pc = get_frame_register_signed (frame, 2);
-	      int i;
+  /* If the PC is in __call_stub_* or __fn_stub*, this is one of the
+     compiler-generated call or call/return stubs.  */
+  if (strncmp (name, mips_str_fn_stub, strlen (mips_str_fn_stub)) == 0
+      || strncmp (name, mips_str_call_stub, strlen (mips_str_call_stub)) == 0)
+    {
+      if (pc == start_addr)
+	/* This is the 'call' part of a call stub.  Call this helper
+	   to scan through this code for interesting instructions
+	   and determine the final PC.  */
+	return mips_get_mips16_fn_stub_pc (frame, pc);
+      else
+	/* This is the 'return' part of a call stub.  The return address
+	   is in $18.  */
+	return get_frame_register_signed
+		 (frame, gdbarch_num_regs (gdbarch) + MIPS_S2_REGNUM);
+    }
 
-	      /* See if the name of the target function is  __fn_stub_*.  */
-	      if (find_pc_partial_function (target_pc, &name, NULL, NULL) ==
-		  0)
-		return target_pc;
-	      if (strncmp (name, "__fn_stub_", 10) != 0
-		  && strcmp (name, "etext") != 0
-		  && strcmp (name, "_etext") != 0)
-		return target_pc;
+  return 0;			/* Not a stub.  */
+}
 
-	      /* Scan through this _fn_stub_ code for the lui/addiu pair.
-	         The limit on the search is arbitrarily set to 20
-	         instructions.  FIXME.  */
-	      for (i = 0, pc = 0; i < 20; i++, target_pc += MIPS_INSN32_SIZE)
-		{
-		  ULONGEST inst = mips_fetch_instruction (gdbarch, target_pc);
-		  CORE_ADDR addr = inst;
+/* Return non-zero if the PC is inside a return thunk (aka stub or trampoline).
+   This implements the IN_SOLIB_RETURN_TRAMPOLINE macro.  */
 
-		  if ((inst & 0xffff0000) == 0x3c010000)	/* lui $at */
-		    pc = (((addr & 0xffff) ^ 0x8000) - 0x8000) << 16;
-								/* high word */
-		  else if ((inst & 0xffff0000) == 0x24210000)	/* addiu $at */
-		    return pc + ((addr & 0xffff) ^ 0x8000) - 0x8000;
-								/* low word */
-		}
+static int
+mips_in_return_stub (struct gdbarch *gdbarch, CORE_ADDR pc, const char *name)
+{
+  CORE_ADDR start_addr;
+  size_t prefixlen;
 
-	      /* Couldn't find the lui/addui pair, so return stub address.  */
-	      return target_pc;
-	    }
-	  else
-	    /* This is the 'return' part of a call stub.  The return
-	       address is in $r18.  */
-	    return get_frame_register_signed (frame, 18);
-	}
-    }
-  return 0;			/* not a stub */
+  /* Find the starting address of the function containing the PC.  */
+  if (find_pc_partial_function (pc, NULL, &start_addr, NULL) == 0)
+    return 0;
+
+  /* If the PC is in __mips16_call_stub_{s,d}{f,c}_{0..10} but not at
+     the start, i.e. after the JALR instruction, this is effectively
+     a return stub.  */
+  prefixlen = strlen (mips_str_mips16_call_stub);
+  if (pc != start_addr
+      && strncmp (name, mips_str_mips16_call_stub, prefixlen) == 0
+      && mips_is_stub_mode (name + prefixlen)
+      && name[prefixlen + 2] == '_'
+      && mips_is_stub_suffix (name + prefixlen + 3, 1))
+    return 1;
+
+  /* If the PC is in __call_stub_fp_* but not at the start, i.e. after
+     the JAL or JALR instruction, this is effectively a return stub.  */
+  prefixlen = strlen (mips_str_call_fp_stub);
+  if (pc != start_addr
+      && strncmp (name, mips_str_call_fp_stub, prefixlen) == 0)
+    return 1;
+
+  /* Consume the .pic. prefix of any PIC stub, this function must return
+     true when the PC is in a PIC stub of a __mips16_ret_{d,s}{f,c} stub
+     or the call stub path will trigger in handle_inferior_event causing
+     it to go astray.  */
+  prefixlen = strlen (mips_str_pic);
+  if (strncmp (name, mips_str_pic, prefixlen) == 0)
+    name += prefixlen;
+
+  /* If the PC is in __mips16_ret_{d,s}{f,c}, this is a return stub.  */
+  prefixlen = strlen (mips_str_mips16_ret_stub);
+  if (strncmp (name, mips_str_mips16_ret_stub, prefixlen) == 0
+      && mips_is_stub_mode (name + prefixlen)
+      && name[prefixlen + 2] == '\0')
+    return 1;
+
+  return 0;			/* Not a stub.  */
 }
 
 /* If the current PC is the start of a non-PIC-to-PIC stub, return the
@@ -5813,21 +6100,41 @@ mips_skip_pic_trampoline_code (struct fr
 static CORE_ADDR
 mips_skip_trampoline_code (struct frame_info *frame, CORE_ADDR pc)
 {
+  CORE_ADDR requested_pc = pc;
   CORE_ADDR target_pc;
+  CORE_ADDR new_pc;
 
-  target_pc = mips_skip_mips16_trampoline_code (frame, pc);
-  if (target_pc)
-    return target_pc;
+  do
+    {
+      target_pc = pc;
 
-  target_pc = find_solib_trampoline_target (frame, pc);
-  if (target_pc)
-    return target_pc;
+      new_pc = mips_skip_mips16_trampoline_code (frame, pc);
+      if (new_pc)
+	{
+	  pc = new_pc;
+	  if (is_mips16_addr (pc))
+	    pc = unmake_mips16_addr (pc);
+	}
 
-  target_pc = mips_skip_pic_trampoline_code (frame, pc);
-  if (target_pc)
-    return target_pc;
+      new_pc = find_solib_trampoline_target (frame, pc);
+      if (new_pc)
+	{
+	  pc = new_pc;
+	  if (is_mips16_addr (pc))
+	    pc = unmake_mips16_addr (pc);
+	}
 
-  return 0;
+      new_pc = mips_skip_pic_trampoline_code (frame, pc);
+      if (new_pc)
+	{
+	  pc = new_pc;
+	  if (is_mips16_addr (pc))
+	    pc = unmake_mips16_addr (pc);
+	}
+    }
+  while (pc != target_pc);
+
+  return pc != requested_pc ? pc : 0;
 }
 
 /* Convert a dbx stab register number (from `r' declaration) to a GDB
@@ -6670,6 +6977,16 @@ mips_gdbarch_init (struct gdbarch_info i
 
   set_gdbarch_skip_trampoline_code (gdbarch, mips_skip_trampoline_code);
 
+  /* NOTE drow/2004-02-11: We overload the core solib trampoline code
+     to support MIPS16.  This is a bad thing.  Make sure not to do it
+     if we have an OS ABI that actually supports shared libraries, since
+     shared library support is more important.  If we have an OS someday
+     that supports both shared libraries and MIPS16, we'll have to find
+     a better place for these.
+     macro/2011-12-08: But that applies to return trampolines only and
+     currently no MIPS OS ABI uses shared libraries that have them.  */
+  set_gdbarch_in_solib_return_trampoline (gdbarch, mips_in_return_stub);
+
   set_gdbarch_single_step_through_delay (gdbarch,
 					 mips_single_step_through_delay);
 
Index: gdb-fsf-trunk-quilt/gdb/mips-tdep.h
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/mips-tdep.h	2012-04-04 12:05:40.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/mips-tdep.h	2012-04-05 21:42:24.995424172 +0100
@@ -119,7 +119,9 @@ enum
   MIPS_AT_REGNUM = 1,
   MIPS_V0_REGNUM = 2,		/* Function integer return value.  */
   MIPS_A0_REGNUM = 4,		/* Loc of first arg during a subr call.  */
+  MIPS_S2_REGNUM = 18,		/* Contains return address in MIPS16 thunks. */
   MIPS_T9_REGNUM = 25,		/* Contains address of callee in PIC.  */
+  MIPS_GP_REGNUM = 28,
   MIPS_SP_REGNUM = 29,
   MIPS_RA_REGNUM = 31,
   MIPS_PS_REGNUM = 32,		/* Contains processor status.  */
Index: gdb-fsf-trunk-quilt/gdb/infrun.c
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/infrun.c	2012-04-05 21:42:09.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/infrun.c	2012-04-05 22:29:33.405561491 +0100
@@ -4804,6 +4804,48 @@ handle_inferior_event (struct execution_
       return;
     }
 
+  /* If we're in the return path from a shared library trampoline,
+     we want to proceed through the trampoline when stepping.  */
+  /* macro/2012-03-29: This needs to come before the subroutine
+     call check below as on some targets return trampolines look
+     like subroutine calls (MIPS16 return thunks).  */
+  if (gdbarch_in_solib_return_trampoline (gdbarch,
+					  stop_pc, ecs->stop_func_name)
+      && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
+    {
+      /* Determine where this trampoline returns.  */
+      CORE_ADDR real_stop_pc;
+
+      real_stop_pc = gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc);
+
+      if (debug_infrun)
+	 fprintf_unfiltered (gdb_stdlog,
+			     "infrun: stepped into solib return tramp\n");
+
+      /* Only proceed through if we know where it's going.  */
+      if (real_stop_pc)
+	{
+	  /* And put the step-breakpoint there and go until there.  */
+	  struct symtab_and_line sr_sal;
+
+	  init_sal (&sr_sal);	/* initialize to zeroes */
+	  sr_sal.pc = real_stop_pc;
+	  sr_sal.section = find_pc_overlay (sr_sal.pc);
+	  sr_sal.pspace = get_frame_program_space (frame);
+
+	  /* Do not specify what the fp should be when we stop since
+	     on some machines the prologue is where the new fp value
+	     is established.  */
+	  insert_step_resume_breakpoint_at_sal (gdbarch,
+						sr_sal, null_frame_id);
+
+	  /* Restart without fiddling with the step ranges or
+	     other state.  */
+	  keep_going (ecs);
+	  return;
+	}
+    }
+
   /* Check for subroutine calls.  The check for the current frame
      equalling the step ID is not necessary - the check of the
      previous frame's ID is sufficient - but it is a common case and
@@ -5014,45 +5056,6 @@ handle_inferior_event (struct execution_
 	}
     }
 
-  /* If we're in the return path from a shared library trampoline,
-     we want to proceed through the trampoline when stepping.  */
-  if (gdbarch_in_solib_return_trampoline (gdbarch,
-					  stop_pc, ecs->stop_func_name)
-      && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
-    {
-      /* Determine where this trampoline returns.  */
-      CORE_ADDR real_stop_pc;
-
-      real_stop_pc = gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc);
-
-      if (debug_infrun)
-	 fprintf_unfiltered (gdb_stdlog,
-			     "infrun: stepped into solib return tramp\n");
-
-      /* Only proceed through if we know where it's going.  */
-      if (real_stop_pc)
-	{
-	  /* And put the step-breakpoint there and go until there.  */
-	  struct symtab_and_line sr_sal;
-
-	  init_sal (&sr_sal);	/* initialize to zeroes */
-	  sr_sal.pc = real_stop_pc;
-	  sr_sal.section = find_pc_overlay (sr_sal.pc);
-	  sr_sal.pspace = get_frame_program_space (frame);
-
-	  /* Do not specify what the fp should be when we stop since
-	     on some machines the prologue is where the new fp value
-	     is established.  */
-	  insert_step_resume_breakpoint_at_sal (gdbarch,
-						sr_sal, null_frame_id);
-
-	  /* Restart without fiddling with the step ranges or
-	     other state.  */
-	  keep_going (ecs);
-	  return;
-	}
-    }
-
   stop_pc_sal = find_pc_line (stop_pc, 0);
 
   /* NOTE: tausq/2004-05-24: This if block used to be done before all
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-inmain.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-inmain.c	2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,5 @@
+int
+inmain (void)
+{
+  return 0;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-main.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-main.c	2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,7 @@
+int inmain (void);
+
+int
+main (void)
+{
+  return inmain ();
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sin.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sin.c	2012-04-05 21:42:24.995424172 +0100
@@ -0,0 +1,38 @@
+#include <math.h>
+
+double sinfrob (double d);
+double sinfrob16 (double d);
+
+double sinblah (double d);
+double sinblah16 (double d);
+
+double sinmips16 (double d);
+long lsinmips16 (double d);
+
+extern long i;
+
+double
+sinhelper (double d)
+{
+  i++;
+  d = sin (d);
+  d = sinfrob16 (d);
+  d = sinfrob (d);
+  d = sinmips16 (d);
+  i++;
+  return d;
+}
+
+long
+lsinhelper (double d)
+{
+  long l;
+
+  i++;
+  d = sin (d);
+  d = sinblah (d);
+  d = sinblah16 (d);
+  l = lsinmips16 (d);
+  i++;
+  return l;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob.c	2012-04-05 21:42:24.995424172 +0100
@@ -0,0 +1,21 @@
+#include <math.h>
+
+extern long i;
+
+double
+sinfrob (double d)
+{
+  i++;
+  d = sin (d);
+  i++;
+  return d;
+}
+
+double
+sinblah (double d)
+{
+  i++;
+  d = sin (d);
+  i++;
+  return d;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob16.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob16.c	2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,21 @@
+#include <math.h>
+
+extern long i;
+
+double
+sinfrob16 (double d)
+{
+  i++;
+  d = sin (d);
+  i++;
+  return d;
+}
+
+double
+sinblah16 (double d)
+{
+  i++;
+  d = sin (d);
+  i++;
+  return d;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmain.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmain.c	2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,34 @@
+double sinfrob (double d);
+double sinfrob16 (double d);
+
+double sinblah (double d);
+double sinblah16 (double d);
+
+double sinhelper (double);
+long lsinhelper (double);
+
+double (*sinfunc) (double) = sinfrob;
+double (*sinfunc16) (double) = sinfrob16;
+
+double f = 1.0;
+long i = 1;
+
+int
+main (void)
+{
+  double d = f;
+  long l = i;
+
+  d = sinfrob16 (d);
+  d = sinfrob (d);
+  d = sinhelper (d);
+
+  sinfunc = sinblah;
+  sinfunc16 = sinblah16;
+
+  d = sinblah (d);
+  d = sinblah16 (d);
+  l = lsinhelper (d);
+
+  return l + i;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmips16.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmips16.c	2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,45 @@
+#include <math.h>
+
+double sinfrob (double d);
+double sinfrob16 (double d);
+
+double sinblah (double d);
+double sinblah16 (double d);
+
+extern double (*sinfunc) (double);
+extern double (*sinfunc16) (double);
+
+extern long i;
+
+double
+sinmips16 (double d)
+{
+  i++;
+  d = sin (d);
+  d = sinfrob16 (d);
+  d = sinfrob (d);
+  d = sinfunc16 (d);
+  d = sinfunc (d);
+  i++;
+  return d;
+}
+
+long
+lsinmips16 (double d)
+{
+  union
+    {
+      double d;
+      long l[2];
+    }
+  u;
+
+  i++;
+  d = sin (d);
+  d = sinblah (d);
+  d = sinblah16 (d);
+  d = sinfunc (d);
+  u.d = sinfunc16 (d);
+  i++;
+  return u.l[0] == 0 && u.l[1] == 0;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks.exp
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks.exp	2012-04-05 22:53:33.955629283 +0100
@@ -0,0 +1,585 @@
+# Copyright 2012 Free Software Foundation, Inc.
+
+# 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/>.
+
+# Contributed by Mentor Graphics, written by Maciej W. Rozycki.
+
+# Test MIPS16 thunk support.
+
+# This should work on any targets that support MIPS16 execution, including
+# Linux and bare-iron ones, but not all of them do, for example MIPS16
+# support has been added to Linux relatively late in the game.  Also besides
+# environment support, the target processor has to support the MIPS16 ASE.
+# Finally as of this writing MIPS16 support has only been implemented in the
+# toolchain for a subset of ABIs, so we need to check that a MIPS16
+# executable can be built and run at all before we attempt the actual test.
+
+if { ![istarget "mips*-*-*"] } then {
+    verbose "Skipping MIPS16 thunk support tests."
+    return
+}
+
+# A helper to set caller's SRCFILE and OBJFILE based on FILENAME and SUFFIX.
+proc set_src_and_obj { filename { suffix "" } } {
+    upvar srcfile srcfile
+    upvar objfile objfile
+    global srcdir
+    global objdir
+    global subdir
+
+    if ![string equal "$suffix" ""] then {
+	set suffix "-$suffix"
+    }
+    set srcfile ${srcdir}/${subdir}/${filename}.c
+    set objfile ${objdir}/${subdir}/${filename}${suffix}.o
+}
+
+# First check if a trivial MIPS16 program can be built and debugged.  This
+# verifies environment and processor support, any failure here must be
+# classed as the lack of support.
+set testname mips16-thunks-main
+
+set_src_and_obj mips16-thunks-inmain
+set options [list debug nowarnings additional_flags=-mips16]
+set objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-main
+set options [list debug nowarnings additional_flags=-mips16]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set binfile ${objdir}/${subdir}/${testname}
+set options [list debug nowarnings]
+if { [gdb_compile ${objfiles} ${binfile} executable ${options}] != "" } then {
+    unsupported "No MIPS16 support in the toolchain."
+    return
+}
+clean_restart ${testname}
+gdb_breakpoint inmain
+gdb_run_cmd
+gdb_expect 30 {
+    -re "Breakpoint 1.*inmain .*$gdb_prompt $" {
+	send_gdb "finish\n"
+	gdb_expect {
+	    -re "Value returned is \\\$\[0-9\]+ = 0\[^0-9\].*$gdb_prompt $" {
+		verbose "MIPS16 support check successful."
+	    }
+	    -re "$gdb_prompt $" {
+		unsupported "No MIPS16 support in the processor."
+		return
+	    }
+	    default {
+		unsupported "No MIPS16 support in the processor."
+		return
+	    }
+	}
+    }
+    -re "$gdb_prompt $" {
+	unsupported "No MIPS16 support in the processor."
+	return
+    }
+    default {
+	unsupported "No MIPS16 support in the processor."
+	return
+    }
+}
+
+# Check if MIPS16 PIC code can be built and debugged.  We want to check
+# PIC and MIPS16 thunks are handled correctly together if possible, but
+# on targets that do not support PIC code, e.g. bare iron, we still want
+# to test the rest of functionality.
+set testname mips16-thunks-pic
+set picflag ""
+
+set_src_and_obj mips16-thunks-inmain pic
+set options [list \
+    debug nowarnings additional_flags=-mips16 additional_flags=-fPIC]
+set objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-main pic
+set options [list \
+    debug nowarnings additional_flags=-mips16 additional_flags=-fPIC]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set binfile ${objdir}/${subdir}/${testname}
+set options [list debug nowarnings additional_flags=-fPIC]
+if { [gdb_compile ${objfiles} ${binfile} executable ${options}] == "" } then {
+    clean_restart ${testname}
+    gdb_breakpoint inmain
+    gdb_run_cmd
+    gdb_expect 30 {
+	-re "Breakpoint 1.*inmain .*$gdb_prompt $" {
+	    note "PIC support present, will make additional PIC thunk checks."
+	    set picflag additional_flags=-fPIC
+	}
+	-re "$gdb_prompt $" {
+	    note "No PIC support, skipping additional PIC thunk checks."
+	}
+	default {
+	    note "No PIC support, skipping additional PIC thunk checks."
+	}
+    }
+} else {
+    note "No PIC support, skipping additional PIC thunk checks."
+}
+
+# OK, build the twisted executable.  This program contains the following
+# MIPS16 thunks:
+# - __call_stub_fp_sin,
+# - __call_stub_fp_sinblah,
+# - __call_stub_fp_sinfrob,
+# - __call_stub_fp_sinhelper,
+# - __call_stub_lsinhelper,
+# - __fn_stub_lsinmips16,
+# - __fn_stub_sinblah16,
+# - __fn_stub_sinfrob16,
+# - __fn_stub_sinmips16,
+# - __mips16_call_stub_df_2,
+# - __mips16_ret_df.
+# Additionally, if PIC code is supported, it contains the following PIC thunks:
+# - .pic.__mips16_call_stub_df_2,
+# - .pic.__mips16_ret_df,
+# - .pic.sinblah,
+# - .pic.sinblah16,
+# - .pic.sinfrob,
+# - .pic.sinfrob16.
+set testname mips16-thunks-sin
+
+set_src_and_obj mips16-thunks-sinmain
+set options [list debug nowarnings additional_flags=-mips16]
+set objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sin
+set options [list debug nowarnings additional_flags=-mno-mips16]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sinmips16
+set options [list debug nowarnings additional_flags=-mips16]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sinfrob
+set options [list \
+    debug nowarnings additional_flags=-mno-mips16 ${picflag}]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sinfrob16
+set options [list \
+    debug nowarnings additional_flags=-mips16 ${picflag}]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set binfile ${objdir}/${subdir}/${testname}
+set options [list debug nowarnings]
+gdb_compile ${objfiles} ${binfile} executable ${options}
+clean_restart ${testname}
+if ![runto_main] then {
+    fail "running test program, MIPS16 thunk tests aborted"
+    return
+}
+
+# Build some useful regular expressions out of a list of functions FUNCS
+# to be used to match against backtraces.
+proc build_frames_re { funcs } {
+    upvar anyframe anyframe
+    upvar frames frames
+    upvar frame frame
+    upvar func func
+
+    set fid 0
+    set argsandsource " +\\\(.*\\\) +at +\[^\r\n\]+\r\n"
+    set addrin "(?:\[^ \]+ +in +)?"
+    set anyframe "#${fid} +${addrin}(\[^ \]+)${argsandsource}"
+    set frame "#${fid} +${addrin}${func}${argsandsource}"
+    set frames "$frame"
+    foreach f [lrange $funcs 1 end] {
+	incr fid
+	append frames "#${fid} +${addrin}${f}${argsandsource}"
+    }
+}
+
+# Single-step through the function that is at the head of function list
+# FUNCS until a different function (frame) is reached.  Before each step
+# check the backtrace against FUNCS.  ID is used for reporting, to tell
+# apart different calls to this procedure for the same function.  If
+# successful, then return the name of the function we have stopped in.
+proc step_through { id funcs } {
+    global gdb_prompt
+
+    set func [lindex $funcs 0]
+    build_frames_re "$funcs"
+
+    set msg "single-stepping through \"${func}\" ($id)"
+
+    # Arbitrarily limit the maximium number of steps made to avoid looping
+    # indefinitely in the case something goes wrong, increase as (if)
+    # necessary.
+    set count 8
+    while [expr $count > 0] {
+	send_gdb "backtrace\n"
+	gdb_expect {
+	    -re "${frames}$gdb_prompt $" {
+		send_gdb "step\n"
+		gdb_expect {
+		    -re "$gdb_prompt $" {
+			send_gdb "frame\n"
+			gdb_expect {
+			    -re "${frame}.*$gdb_prompt $" {
+			    }
+			    -re "${anyframe}.*$gdb_prompt $" {
+				pass "$msg"
+				return $expect_out(1,string)
+			    }
+			    -re "$gdb_prompt $" {
+				fail "$msg (frame)"
+				return ""
+			    }
+			    default {
+				fail "$msg (frame)"
+				return ""
+			    }
+			}
+		    }
+		    default {
+			fail "$msg (step)"
+			return ""
+		    }
+		}
+	    }
+	    -re "$gdb_prompt $" {
+		fail "$msg (backtrace)"
+		return ""
+	    }
+	    default {
+		fail "$msg (backtrace)"
+		return
+	    }
+	}
+	incr count -1
+    }
+    fail "$msg (too many steps)"
+}
+
+# Finish the current function that must be one that is at the head of
+# function list FUNCS.  Before that check the backtrace against FUNCS.
+# ID is used for reporting, to tell apart different calls to this
+# procedure for the same function.  If successful, then return the name
+# of the function we have stopped in.
+proc finish_through { id funcs } {
+    global gdb_prompt
+
+    set func [lindex $funcs 0]
+    build_frames_re "$funcs"
+
+    set msg "finishing \"${func}\" ($id)"
+
+    send_gdb "backtrace\n"
+    gdb_expect {
+	-re "${frames}$gdb_prompt $" {
+	    send_gdb "finish\n"
+	    gdb_expect {
+		-re "Run till exit from ${frame}.*$gdb_prompt $" {
+		    send_gdb "frame\n"
+		    gdb_expect {
+			-re "${anyframe}.*$gdb_prompt $" {
+			    pass "$msg"
+			    return $expect_out(1,string)
+			}
+			-re "$gdb_prompt $" {
+			    fail "$msg (frame)"
+			    return ""
+			}
+			default {
+			    fail "$msg (frame)"
+			    return ""
+			}
+		    }
+		}
+		-re "$gdb_prompt $" {
+		    fail "$msg (finish)"
+		    return ""
+		}
+		default {
+		    fail "$msg (finish)"
+		    return ""
+		}
+	    }
+	}
+	-re "$gdb_prompt $" {
+	    fail "$msg (backtrace)"
+	    return ""
+	}
+	default {
+	    fail "$msg (backtrace)"
+	    return ""
+	}
+    }
+}
+
+# Report PASS if VAL is equal to EXP, otherwise report FAIL, using MSG.
+proc pass_if_eq { val exp msg } {
+    if [string equal "$val" "$exp"] then {
+	pass "$msg"
+    } else {
+	fail "$msg"
+    }
+}
+
+# Check if FUNC is equal to WANT.  If not, then assume that we have stepped
+# into a library call.  In this case finish it, then step out of the caller.
+# ID is used for reporting, to tell apart different calls to this procedure
+# for the same function.  If successful, then return the name of the
+# function we have stopped in.
+proc finish_if_ne { id func want funcs } {
+    if ![string equal "$func" "$want"] then {
+	set call "$func"
+	set want [lindex $funcs 0]
+	set func [finish_through "$id" [linsert $funcs 0 "$func"]]
+	pass_if_eq "$func" "$want" "\"${call}\" finishing to \"${want}\" ($id)"
+	set func [step_through "$id" $funcs]
+    }
+    return "$func"
+}
+
+# Now single-step through the program, making sure all thunks are correctly
+# stepped over and omitted from backtraces.
+
+set id 1
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinfrob16 "stepping from \"main\" into \"sinfrob16\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 main]]
+set func [finish_if_ne $id "$func" main [list sinfrob16 main]]
+pass_if_eq "$func" main "stepping from \"sinfrob16\" back to \"main\" ($id)"
+
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinfrob "stepping from \"main\" into \"sinfrob\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob main]]
+set func [finish_if_ne $id "$func" main [list sinfrob main]]
+pass_if_eq "$func" main "stepping from \"sinfrob\" back to \"main\" ($id)"
+
+# 5
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinhelper "stepping from \"main\" into \"sinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list sinhelper main]]
+set func [finish_if_ne $id "$func" sinfrob16 [list sinhelper main]]
+pass_if_eq "$func" sinfrob16 \
+    "stepping from \"sinhelper\" into \"sinfrob16\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper [list sinfrob16 sinhelper main]]
+pass_if_eq "$func" sinhelper \
+    "stepping from \"sinfrob16\" back to \"sinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list sinhelper main]]
+pass_if_eq "$func" sinfrob "stepping from \"sinhelper\" into \"sinfrob\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper [list sinfrob sinhelper main]]
+pass_if_eq "$func" sinhelper \
+    "stepping from \"sinfrob\" back to \"sinhelper\" ($id)"
+
+# 10
+incr id
+set func [step_through $id [list sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+    "stepping from \"sinhelper\" into \"sinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinfrob16 [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob16 \
+    "stepping from \"sinmips16\" into \"sinfrob16\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinmips16 \
+	      [list sinfrob16 sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+    "stepping from \"sinfrob16\" back to \"sinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob "stepping from \"sinmips16\" into \"sinfrob\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper \
+	      [list sinfrob sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+    "stepping from \"sinfrob\" back to \"sinmips16\" ($id)"
+
+# 15
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob16 \
+    "stepping from \"sinmips16\" into \"sinfrob16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinmips16 \
+	      [list sinfrob16 sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+    "stepping from \"sinfrob16\" back to \"sinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob \
+    "stepping from \"sinmips16\" into \"sinfrob\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinfrob sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper \
+	      [list sinfrob sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+    "stepping from \"sinfrob\" back to \"sinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinhelper \
+    "stepping from \"sinmips16\" back to \"sinhelper\" ($id)"
+
+# 20
+incr id
+set func [step_through $id [list sinhelper main]]
+pass_if_eq "$func" main "stepping from \"sinhelper\" back to \"main\" ($id)"
+
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinblah "stepping from \"main\" into \"sinblah\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah main]]
+set func [finish_if_ne $id "$func" main [list sinblah main]]
+pass_if_eq "$func" main "stepping from \"sinblah\" back to \"main\" ($id)"
+
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinblah16 "stepping from \"main\" into \"sinblah16\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 main]]
+set func [finish_if_ne $id "$func" main [list sinblah16 main]]
+pass_if_eq "$func" main "stepping from \"sinblah16\" back to \"main\" ($id)"
+
+# 25
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" lsinhelper \
+    "stepping from \"main\" into \"lsinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list lsinhelper main]]
+set func [finish_if_ne $id "$func" sinblah [list lsinhelper main]]
+pass_if_eq "$func" sinblah \
+    "stepping from \"lsinhelper\" into \"sinblah\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper [list sinblah lsinhelper main]]
+pass_if_eq "$func" lsinhelper \
+    "stepping from \"sinblah\" back to \"lsinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list lsinhelper main]]
+pass_if_eq "$func" sinblah16 \
+    "stepping from \"lsinhelper\" into \"sinblah16\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper [list sinblah16 lsinhelper main]]
+pass_if_eq "$func" lsinhelper \
+    "stepping from \"sinblah16\" back to \"lsinhelper\" ($id)"
+
+# 30
+incr id
+set func [step_through $id [list lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+    "stepping from \"lsinhelper\" into \"lsinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" sinblah [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah \
+    "stepping from \"lsinmips16\" into \"sinblah\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinmips16 \
+	      [list sinblah lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+    "stepping from \"sinblah\" back to \"lsinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah16 \
+    "stepping from \"lsinmips16\" into \"sinblah16\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper \
+	      [list sinblah16 lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+    "stepping from \"sinblah16\" back to \"lsinmips16\" ($id)"
+
+# 35
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah \
+    "stepping from \"lsinmips16\" into \"sinblah\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinblah lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinmips16 \
+	      [list sinblah lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+    "stepping from \"sinblah\" back to \"lsinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah16 \
+    "stepping from \"lsinmips16\" into \"sinblah16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper \
+	      [list sinblah16 lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+    "stepping from \"sinblah16\" back to \"lsinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinhelper \
+    "stepping from \"lsinmips16\" back to \"lsinhelper\" ($id)"
+
+# 40
+incr id
+set func [step_through $id [list lsinhelper main]]
+pass_if_eq "$func" main "stepping from \"lsinhelper\" back to \"main\" ($id)"


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]