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]

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


On Thu, 26 Apr 2012, Pedro Alves wrote:

> >  So I am going to apply the change with the update below shortly.  Any 
> > objections?
> 
> No objections.  Thanks!

 So this is the final version I have checked in.  Thanks to everybody 
involved!

2012-04-26  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-26  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-25 20:58:51.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/mips-tdep.c	2012-04-25 21:03:36.415570257 +0100
@@ -1035,6 +1035,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?  */
 
@@ -1052,12 +1091,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/2012-04-20: 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;
 }
 
@@ -5624,104 +5682,335 @@ 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
@@ -5784,21 +6073,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
@@ -6641,6 +6950,16 @@ mips_gdbarch_init (struct gdbarch_info i
 
   set_gdbarch_skip_trampoline_code (gdbarch, mips_skip_trampoline_code);
 
+  /* NOTE drow/2012-04-25: 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/2012-04-25: 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-25 20:58:51.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/mips-tdep.h	2012-04-25 21:03:36.415570257 +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-25 20:58:51.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/infrun.c	2012-04-25 21:03:36.425560611 +0100
@@ -4814,6 +4814,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-04-25: 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
@@ -5024,45 +5066,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-25 21:03:36.425560611 +0100
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   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/>.  */
+
+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-25 21:03:36.425560611 +0100
@@ -0,0 +1,24 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   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/>.  */
+
+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-25 21:03:36.425560611 +0100
@@ -0,0 +1,55 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   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/>.  */
+
+#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-25 21:03:36.425560611 +0100
@@ -0,0 +1,38 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   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/>.  */
+
+#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-25 21:03:36.425560611 +0100
@@ -0,0 +1,38 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   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/>.  */
+
+#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-25 21:03:36.425560611 +0100
@@ -0,0 +1,51 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   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/>.  */
+
+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-25 21:03:36.425560611 +0100
@@ -0,0 +1,62 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   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/>.  */
+
+#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-25 22:34:18.815561164 +0100
@@ -0,0 +1,543 @@
+# 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_test_multiple "" "check for MIPS16 support in the processor" {
+    -re "Breakpoint 1.*inmain .*$gdb_prompt $" {
+	gdb_test_multiple "finish" \
+	    "check for MIPS16 support in the processor" {
+	    -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_test_multiple "" "check for PIC support" {
+	-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 { $count > 0 } {
+	if { [gdb_test_multiple "backtrace" "$msg (backtrace)" {
+	    -re "${frames}$gdb_prompt $" {
+		if { [gdb_test_multiple "step" "$msg (step)" {
+		    -re "$gdb_prompt $" {
+			if { [gdb_test_multiple "frame" "$msg (frame)" {
+			    -re "${frame}.*$gdb_prompt $" {
+			    }
+			    -re "${anyframe}.*$gdb_prompt $" {
+				pass "$msg"
+				return $expect_out(1,string)
+			    }
+			}] != 0 } then {
+			    return ""
+			}
+		    }
+		}] != 0 } then {
+		    return ""
+		}
+	    }
+	}] != 0 } then {
+	    return ""
+	}
+	incr count -1
+    }
+    fail "$msg (too many steps)"
+    return ""
+}
+
+# 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)"
+
+    gdb_test_multiple "backtrace" "$msg (backtrace)" {
+	-re "${frames}$gdb_prompt $" {
+	    gdb_test_multiple "finish" "$msg (finish)" {
+		-re "Run till exit from ${frame}.*$gdb_prompt $" {
+		    gdb_test_multiple "frame" "$msg (frame)" {
+			-re "${anyframe}.*$gdb_prompt $" {
+			    pass "$msg"
+			    return $expect_out(1,string)
+			}
+		    }
+		}
+	    }
+	}
+    }
+    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]