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

classic Classic list List threaded Threaded
16 messages Options
Reply | Threaded
Open this post in threaded view
|

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

Maciej W. Rozycki-4
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  <[hidden email]>

        * 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  <[hidden email]>
            Maciej W. Rozycki  <[hidden email]>

        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  <[hidden email]>

        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)"
Reply | Threaded
Open this post in threaded view
|

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

Richard Sandiford-5
"Maciej W. Rozycki" <[hidden email]> writes:
> [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.]

Sounds like great work, thanks.  Hope it goes in.

I don't really have anything constructive to say, but just out of
curiosity: we "fixed" call/return stubs to have unwind information for
GCC 4.7.  Do you happen to know whether the test passes with that change?

Richard
Reply | Threaded
Open this post in threaded view
|

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

Maciej W. Rozycki-4
On Wed, 11 Apr 2012, Richard Sandiford wrote:

> > [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.]
>
> Sounds like great work, thanks.  Hope it goes in.

 Well, there's only this small issue of gdbarch_in_solib_return_trampoline
-- it shouldn't be too hard to overcome. :)

> I don't really have anything constructive to say, but just out of
> curiosity: we "fixed" call/return stubs to have unwind information for
> GCC 4.7.  Do you happen to know whether the test passes with that change?

 I am not completely sure as I haven't tried it/got to 4.7 yet (you may
remember I had troubles building the head of the tree; I saw you fixed
that at some point, thanks), but it looks to me the generic trampoline
stuff I rely on here is tangential to any particular frame unwinders (as
long as they get their details right, of course, which may not necessarily
be true in all cases, as it is with the heuristic unwinders), so it should
keep working.  I'll see if I can get to verifying this soon.

 Did you actually add this unwind information for both the mips16.S pieces
and the compiler generated bits (how about the PIC stubs produced by LD?)?  
Does that happen to address the problem I reported here:

http://gcc.gnu.org/ml/gcc-patches/2011-12/msg01067.html

?  Note that it's not a coincidence the function name there is the same as
one of those I used in the test case posted here -- this GDB test case
will fail without a fix for that GCC problem just like the test case I
posted there did.

  Maciej
Reply | Threaded
Open this post in threaded view
|

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

Richard Sandiford-5
"Maciej W. Rozycki" <[hidden email]> writes:

> On Wed, 11 Apr 2012, Richard Sandiford wrote:
>> > [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.]
>>
>> Sounds like great work, thanks.  Hope it goes in.
>
>  Well, there's only this small issue of gdbarch_in_solib_return_trampoline
> -- it shouldn't be too hard to overcome. :)
>
>> I don't really have anything constructive to say, but just out of
>> curiosity: we "fixed" call/return stubs to have unwind information for
>> GCC 4.7.  Do you happen to know whether the test passes with that change?
>
>  I am not completely sure as I haven't tried it/got to 4.7 yet (you may
> remember I had troubles building the head of the tree; I saw you fixed
> that at some point, thanks), but it looks to me the generic trampoline
> stuff I rely on here is tangential to any particular frame unwinders (as
> long as they get their details right, of course, which may not necessarily
> be true in all cases, as it is with the heuristic unwinders), so it should
> keep working.  I'll see if I can get to verifying this soon.
>
>  Did you actually add this unwind information for both the mips16.S pieces
> and the compiler generated bits

Yes.  But like I say, the fix was only (suppoed to be) for what you
called call/return stubs, i.e. those in which the stub JALs to the target,
fiddles with the return value afterwards, then JRs back to the caller.
It wasn't intended to touch pure "run this code first" stubs like...

> (how about the PIC stubs produced by LD?)?  

...these.

> Does that happen to address the problem I reported here:
>
> http://gcc.gnu.org/ml/gcc-patches/2011-12/msg01067.html

'Fraid not.  It was a separate problem.

Richard
Reply | Threaded
Open this post in threaded view
|

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

Maciej W. Rozycki-4
On Thu, 12 Apr 2012, Richard Sandiford wrote:

> >  Did you actually add this unwind information for both the mips16.S pieces
> > and the compiler generated bits
>
> Yes.  But like I say, the fix was only (suppoed to be) for what you
> called call/return stubs, i.e. those in which the stub JALs to the target,
> fiddles with the return value afterwards, then JRs back to the caller.

 OK, just wanted to be sure.

 BTW, do you also find the choice of "__call_stub_" and "__call_stub_fp_"
for function prefixes unfortunate?  You can't really tell them apart in
all cases except by interpreting code as you can have a valid user
function starting with "fp_" -- this is not a reserved namespace, unlike
anything starting with "_".  These should really be "__call_stub_" and
"__call_fp_stub_" or whatever, one just shouldn't be a substring of the
other, sigh...

> It wasn't intended to touch pure "run this code first" stubs like...
>
> > (how about the PIC stubs produced by LD?)?  
>
> ...these.

 Ack.  They're mostly handled by GDB already anyway.

> > Does that happen to address the problem I reported here:
> >
> > http://gcc.gnu.org/ml/gcc-patches/2011-12/msg01067.html
>
> 'Fraid not.  It was a separate problem.

 Any chance you'll be able to look into it anytime soon?  I can cope with
the patch I proposed there, but people may prefer an in-tree solution and
it doesn't look like I'll have time in the near future to work on anything
like what you've outlined instead of my simple solution.

  Maciej
Reply | Threaded
Open this post in threaded view
|

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

Pedro Alves-7
In reply to this post by Maciej W. Rozycki-4
Hi Maciej,

Some minor issues I noticed while browsing the patch.

On 04/10/2012 11:20 PM, Maciej W. Rozycki wrote:

> +  /* macro/2005-03-31: This hack skips over MIPS16 call thunks as


Eh, that long?  Might want to update that date to the check in date,
or drop it.  Otherwise it's just useless, IMO.

> +
> +static int mips_is_stub_suffix (const char *suffix, int zero)

Function name at column 0.

> +
> +static int mips_is_stub_mode (const char *mode)
> +{

Ditto.

> 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>
> +

I've noticed the tests miss copyright headers.

> +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 $" {

Can we use gdb_test_multiple (catching internal errors etc., and dropping
the default cases) ?  (other instances)

> +# 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 } {

Not sure it'd be useful here, but note the new with_test_prefix routine,
which we now use as a convenient way to make sure there are no
duplicate messages in gdb.sum (, as in
<http://sourceware.org/gdb/wiki/GDBTestcaseCookbook#Make_sure_test_messages_are_unique>).

--
Pedro Alves
Reply | Threaded
Open this post in threaded view
|

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

Maciej W. Rozycki-4
Hi Pedro,

[Cc-ing Daniel for the question below.]

> > +  /* macro/2005-03-31: This hack skips over MIPS16 call thunks as
>
>
> Eh, that long?  Might want to update that date to the check in date,
> or drop it.  Otherwise it's just useless, IMO.

 Yes, sigh...  And fair enough :) -- I'll tweak the date.  The comment
might need updating once new GCC's unwind information Richard mentioned
has been tested.

 There's another note, that is even older, by Daniel -- Daniel, would you
mind if I updated the date on your note to the check in date too -- IOW,
do you continue to stand by it?

> > +
> > +static int mips_is_stub_suffix (const char *suffix, int zero)
>
> Function name at column 0.
>
> > +
> > +static int mips_is_stub_mode (const char *mode)
> > +{
>
> Ditto.

 Fixed, thanks.

> > 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>
> > +
>
> I've noticed the tests miss copyright headers.

 Fixed.  I wasn't sure, some existing tests don't have either.  Not that
this program is any useful code ;) -- although it does certainly compute
"something".

> > +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 $" {
>
> Can we use gdb_test_multiple (catching internal errors etc., and dropping
> the default cases) ?  (other instances)

 I'm not sure -- I looked at gdb_test_multiple and it appeared to me you
can't avoid scoring failures for cases the test considers to be just
symptoms of an unsupported feature that we want to silently ignore; I'll
recheck.

> > +# 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 } {
>
> Not sure it'd be useful here, but note the new with_test_prefix routine,
> which we now use as a convenient way to make sure there are no
> duplicate messages in gdb.sum (, as in
> <http://sourceware.org/gdb/wiki/GDBTestcaseCookbook#Make_sure_test_messages_are_unique>).

 ISTR looking at it; I decided to go for a monotonically increasing ID
instead that notes the stage of the test as there are too many repeating
test messages IMO.  You can refer to the place in the .exp script easily
by the ID as I noted these IDs thoroughout (which I consider valuable as
chasing failures I get frequently lost in the maze of some tests that are
not simply a linear flow of gdb_test or suchlike calls).

 I think getting prefixes right would be difficult and error-prone; note
for example that some messages may appear more or fewer times depending on
whether your system library has debugging enabled or not.  And this is
actually intentional -- to check if PLT or MIPS SVR4 stubs are handled
correctly in the context of MIPS16 thunks; I decided not to build a shared
library as a part of this test at this stage not to overcomplicate what is
already quite complex -- I might revisit it in the future, but I think it
better was a separate test case then.  As a side-effect this arrangement
triggered the MIPS32 JALX decoding bug I fixed a couple days ago, so it
seems to be doing its work quite well.

 Thanks for your feedback.  I'll wait for a while for any further notes
from other people on the infrun.c change.

  Maciej
Reply | Threaded
Open this post in threaded view
|

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

Daniel Jacobowitz-2
On Fri, Apr 20, 2012 at 11:40 AM, Maciej W. Rozycki
<[hidden email]> wrote:

> Hi Pedro,
>
> [Cc-ing Daniel for the question below.]
>
>> > +  /* macro/2005-03-31: This hack skips over MIPS16 call thunks as
>>
>>
>> Eh, that long?  Might want to update that date to the check in date,
>> or drop it.  Otherwise it's just useless, IMO.
>
>  Yes, sigh...  And fair enough :) -- I'll tweak the date.  The comment
> might need updating once new GCC's unwind information Richard mentioned
> has been tested.
>
>  There's another note, that is even older, by Daniel -- Daniel, would you
> mind if I updated the date on your note to the check in date too -- IOW,
> do you continue to stand by it?

It's all the same to me.  I haven't thought about that note since I
wrote it in 2004... I wasn't even at CodeSourcery yet.

--
Thanks,
Daniel
Reply | Threaded
Open this post in threaded view
|

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

Maciej W. Rozycki-4
In reply to this post by Maciej W. Rozycki-4
Hi,

>  There's another note, that is even older, by Daniel -- Daniel, would you
> mind if I updated the date on your note to the check in date too -- IOW,
> do you continue to stand by it?

 Daniel, thanks for your reply.  I'll use the check-in date for your
comment then.

> > > +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 $" {
> >
> > Can we use gdb_test_multiple (catching internal errors etc., and dropping
> > the default cases) ?  (other instances)
>
>  I'm not sure -- I looked at gdb_test_multiple and it appeared to me you
> can't avoid scoring failures for cases the test considers to be just
> symptoms of an unsupported feature that we want to silently ignore; I'll
> recheck.

 Thanks for your persistence.  And good news!  I've looked at
gdb_test_multiple again and came with the update below.  As timeouts and
some other odd messages are legitimate for a target (either the processor
or e.g. a remote debug stub) that does not support the MIPS16 mode -- the
debuggee could go astray and loop indefinitely by chance or do all kinds
of weird stuff (SIGSEGV or SIGILL is the usual case however) -- I did keep
the override expression clauses in the detection tests such as one you
quoted above.

 This will handle problems with sending a command correctly -- it
shouldn't trigger false negatives (I think -- I wonder if a forked process
or `gdbserver' would ever terminate prematurely because of an OS kernel
refusing to run a binary with the MIPS16 flag set in the ELF header; I
think Linux never does that and no other OS supports MIPS16 executables).  
If it ever does, then we'll have to revert to send_gdb or tweak
gdb_test_multiple -- a failure to run an unsupported binary shouldn't
score a test failure here.

 For the test proper I did as you suggested; while at it fixing a problem
with a while loop expression where a variable got evaluated at the wrong
point (oh joys of TCL!).

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

 Out of curiosity gdb_test_multiple contains this:

    if { $message == "" } {
        set message $command
    }

at the very beginning, and then lots of pieces like this:

         -re "\\*\\*\\* DOSEXIT code.*" {
             if { $message != "" } {
                 fail "$message";
             }
             gdb_suppress_entire_file "GDB died";
             set result -1;
         }

(this has weird indentation too, more cases elsewhere), or this:

            if ![string match "" $message] then {
                set errmsg "$message (the program exited)"
            } else {
                set errmsg "$command (the program exited)"
            }

or this:

        timeout {
            if ![string match "" $message] then {
                fail "$message (timeout)"
            }
            set result 1
        }

Are these meant to trigger where both the command and the message supplied
are empty?  Or are they just a leftover from past time?  Shouldn't the
conditions be made consistent throughout, i.e. no fail invocation where
$message is ultimately still empty?  Or just removed?

  Maciej

gdb-mips16-thunks-test-fix.diff
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks.exp
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/testsuite/gdb.arch/mips16-thunks.exp 2012-04-25 22:34:45.535560326 +0100
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks.exp 2012-04-25 22:34:18.815561164 +0100
@@ -69,10 +69,10 @@ if { [gdb_compile ${objfiles} ${binfile}
 clean_restart ${testname}
 gdb_breakpoint inmain
 gdb_run_cmd
-gdb_expect 30 {
+gdb_test_multiple "" "check for MIPS16 support in the processor" {
     -re "Breakpoint 1.*inmain .*$gdb_prompt $" {
- send_gdb "finish\n"
- gdb_expect {
+ 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."
     }
@@ -121,7 +121,7 @@ if { [gdb_compile ${objfiles} ${binfile}
     clean_restart ${testname}
     gdb_breakpoint inmain
     gdb_run_cmd
-    gdb_expect 30 {
+    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
@@ -232,49 +232,33 @@ proc step_through { id funcs } {
     # indefinitely in the case something goes wrong, increase as (if)
     # necessary.
     set count 8
-    while [expr $count > 0] {
- send_gdb "backtrace\n"
- gdb_expect {
+    while { $count > 0 } {
+ if { [gdb_test_multiple "backtrace" "$msg (backtrace)" {
     -re "${frames}$gdb_prompt $" {
- send_gdb "step\n"
- gdb_expect {
+ if { [gdb_test_multiple "step" "$msg (step)" {
     -re "$gdb_prompt $" {
- send_gdb "frame\n"
- gdb_expect {
+ if { [gdb_test_multiple "frame" "$msg (frame)" {
     -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 ""
-    }
+ }] != 0 } then {
+    return ""
  }
     }
-    default {
- fail "$msg (step)"
- return ""
-    }
+ }] != 0 } then {
+    return ""
  }
     }
-    -re "$gdb_prompt $" {
- fail "$msg (backtrace)"
- return ""
-    }
-    default {
- fail "$msg (backtrace)"
- 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
@@ -290,47 +274,21 @@ proc finish_through { id funcs } {
 
     set msg "finishing \"${func}\" ($id)"
 
-    send_gdb "backtrace\n"
-    gdb_expect {
+    gdb_test_multiple "backtrace" "$msg (backtrace)" {
  -re "${frames}$gdb_prompt $" {
-    send_gdb "finish\n"
-    gdb_expect {
+    gdb_test_multiple "finish" "$msg (finish)" {
  -re "Run till exit from ${frame}.*$gdb_prompt $" {
-    send_gdb "frame\n"
-    gdb_expect {
+    gdb_test_multiple "frame" "$msg (frame)" {
  -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 ""
- }
     }
+    return ""
 }
 
 # Report PASS if VAL is equal to EXP, otherwise report FAIL, using MSG.
Reply | Threaded
Open this post in threaded view
|

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

Pedro Alves-7
On 04/25/2012 11:01 PM, Maciej W. Rozycki wrote:

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


No objections.  Thanks!

--
Pedro Alves
Reply | Threaded
Open this post in threaded view
|

gdb_test_multiple and empty $message

Pedro Alves-7
In reply to this post by Maciej W. Rozycki-4
On 04/25/2012 11:01 PM, Maciej W. Rozycki wrote:

>  Out of curiosity gdb_test_multiple contains this:
>
>     if { $message == "" } {
> set message $command
>     }
>
> at the very beginning, and then lots of pieces like this:
>
> -re "\\*\\*\\* DOSEXIT code.*" {
>     if { $message != "" } {
> fail "$message";
>     }
>     gdb_suppress_entire_file "GDB died";
>              set result -1;
> }
>
> (this has weird indentation too, more cases elsewhere), or this:
>
>    if ![string match "" $message] then {
> set errmsg "$message (the program exited)"
>    } else {
> set errmsg "$command (the program exited)"
>    }
>
> or this:
>
> timeout {
>    if ![string match "" $message] then {
> fail "$message (timeout)"
>    }
>    set result 1
> }
>
> Are these meant to trigger where both the command and the message supplied
> are empty?  Or are they just a leftover from past time?


Looking at the history, gdb_test_multiple was introduced as a split out of
gdb_test.   And it seems gdb_test already supported hidding a PASS by passing down
no message string even before gdb_test_multiple's introduction.  But this comment:

# MESSAGE is an optional message to be printed.  If this is
#   omitted, then the pass/fail messages use the command string as the
#   message.  (If this is the empty string, then sometimes we don't
#   call pass or fail at all; I don't understand this at all.)

already existed back then.

> Shouldn't the conditions be made consistent throughout, i.e. no fail invocation where
> $message is ultimately still empty?  Or just removed?


My impression gdb_test without a message string is used at places we're
sending some commands that just prepare the real test.  It's a bit
arguable whether we should do that, but there you go.  But I think that hiding
an internal fail in such preparation steps, which are never ever expected to
fail (otherwise you'd pass down a message string to begin with) would be
actively harmful, and make it harder to grok and debug testsuite results.
For gdb_test_multiple itself, which never issues a "pass" itself, I think the
empty-message-means-command-string-is-the-message is really what people that
call it directly always expect, and likewise hiding an internal fail
for gdb_test_multiple direct call would be actively harmful, IMO.

So I suggest just removing the dead empty string tests from gdb_test_multiple,
making the non-empty paths unconditional.

--
Pedro Alves
Reply | Threaded
Open this post in threaded view
|

Re: gdb_test_multiple and empty $message

Pedro Alves-7
On 04/26/2012 02:09 PM, Pedro Alves wrote:

>> Are these meant to trigger where both the command and the message supplied
>> are empty?


I meant to say that I don't think I ever saw such usage, and indeed I can't
find any now.  Thinking a bit more, it could be conceivable to hide
a fail call in that case, and handle the error at the caller, by
inspecting gdb_test_multiple's result, but I'm not really sure that's
useful.  So I'd also be fine with making all the fails consistent and
documenting this, if people want it.

--
Pedro Alves
Reply | Threaded
Open this post in threaded view
|

Re: gdb_test_multiple and empty $message

Tom Tromey
In reply to this post by Pedro Alves-7
>>>>> "Pedro" == Pedro Alves <[hidden email]> writes:

Pedro> My impression gdb_test without a message string is used at places
Pedro> we're sending some commands that just prepare the real test.
Pedro> It's a bit arguable whether we should do that, but there you go.
Pedro> But I think that hiding an internal fail in such preparation
Pedro> steps, which are never ever expected to fail (otherwise you'd
Pedro> pass down a message string to begin with) would be actively
Pedro> harmful, and make it harder to grok and debug testsuite results.

Yeah, I agree.

I remember thinking sometimes that it would be nice to have something
like gdb_test_multiple that just returns a status instead of also
logging a pass/fail as a side effect.  I can't remember my scenario now.

Pedro> So I suggest just removing the dead empty string tests from
Pedro> gdb_test_multiple, making the non-empty paths unconditional.

Yes please.

Tom
Reply | Threaded
Open this post in threaded view
|

Re: gdb_test_multiple and empty $message

Joel Brobecker
> Pedro> My impression gdb_test without a message string is used at places
> Pedro> we're sending some commands that just prepare the real test.
> Pedro> It's a bit arguable whether we should do that, but there you go.
> Pedro> But I think that hiding an internal fail in such preparation
> Pedro> steps, which are never ever expected to fail (otherwise you'd
> Pedro> pass down a message string to begin with) would be actively
> Pedro> harmful, and make it harder to grok and debug testsuite results.
>
> Yeah, I agree.

Same here.

--
Joel
Reply | Threaded
Open this post in threaded view
|

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

Maciej W. Rozycki-4
In reply to this post by Pedro Alves-7
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  <[hidden email]>
            Maciej W. Rozycki  <[hidden email]>

        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  <[hidden email]>

        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)"
Reply | Threaded
Open this post in threaded view
|

Re: gdb_test_multiple and empty $message

Maciej W. Rozycki-4
In reply to this post by Tom Tromey
On Thu, 26 Apr 2012, Tom Tromey wrote:

> Pedro> My impression gdb_test without a message string is used at places
> Pedro> we're sending some commands that just prepare the real test.
> Pedro> It's a bit arguable whether we should do that, but there you go.
> Pedro> But I think that hiding an internal fail in such preparation
> Pedro> steps, which are never ever expected to fail (otherwise you'd
> Pedro> pass down a message string to begin with) would be actively
> Pedro> harmful, and make it harder to grok and debug testsuite results.
>
> Yeah, I agree.
>
> I remember thinking sometimes that it would be nice to have something
> like gdb_test_multiple that just returns a status instead of also
> logging a pass/fail as a side effect.  I can't remember my scenario now.

 Actually I thought it could be useful for my mips16-thunks.exp test, but
there is some uncertainty about what should be considered an internal
error for this purpose.  As you can see the test already makes use of the
return code from gdb_test_multiple; the test could be extended to tell
internal errors apart from other failures in the preparatory steps where
it wants to ignore most responses that would normally score as failures.  

 And:

".*A problem internal to GDB has been detected"

is undoubtedly a bug in GDB the test would rather not ignore (but it does
now), as is the EOF condition, but:

"Ending remote debugging.*$gdb_prompt $"

may or may not be -- if it's `gdbserver' being used, then it's our bug, if
it's some other stub, then it may be they just do not support MIPS16
debugging.

 So for this to work for my case I think gdb_test_multiple would have to
define a list of distinct return codes covering individual cases of
suspected internal errors (these could be short strings rather than
numbers for easier handling, it's TCL not C after all) alongside the
message gdb_test_multiple would print should the caller have not requested
it to stay silent.  And then the caller could decide on a case-by-case
basis if to output this message or to ignore the potential failure.

 Currently the test overrides most of gdb_test_multiple's predefined
patterns by supplying "$gdb_prompt $" (and caller's code is prepended so
takes precedence).  That may be good enough though as it won't let an EOF
or "problem internal to GDB" slip through.

> Pedro> So I suggest just removing the dead empty string tests from
> Pedro> gdb_test_multiple, making the non-empty paths unconditional.
>
> Yes please.

 Erm, they're not dead as I noted, merely inconsistent as only applied
selectively to some cases, as calling gdb_test_multiple with empty command
and message both at a time is valid and perhaps useful sometimes.  That's
not an objection of any kind though, just an observation for the avoidance
of doubt.

  Maciej