+2012-04-26 Maciej W. Rozycki <macro@mips.com>
+ Maciej W. Rozycki <macro@codesourcery.com>
+
+ * 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 Joel Brobecker <brobecker@adacore.com>
GDB 7.4.1 released.
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
}
}
- /* 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
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? */
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;
}
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.
-
- See the source code for the stubs in gcc/config/mips/mips16.S for
+ 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, 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 (strncmp (name, "__mips16_call_stub_", 19) == 0)
+ /* 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 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. */
-
- CORE_ADDR target_pc = get_frame_register_signed (frame, 2);
- int i;
-
- /* 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;
-
- /* 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;
-
- 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 */
- }
-
- /* Couldn't find the lui/addui pair, so return stub address. */
- return target_pc;
- }
+ /* 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 $r18. */
- return get_frame_register_signed (frame, 18);
+ address is in $18. */
+ return get_frame_register_signed
+ (frame, gdbarch_num_regs (gdbarch) + MIPS_S2_REGNUM);
}
+ else
+ return 0; /* Not a stub. */
+ }
+
+ /* 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);
}
- return 0; /* not a stub */
+
+ return 0; /* Not a stub. */
+}
+
+/* Return non-zero if the PC is inside a return thunk (aka stub or trampoline).
+ This implements the IN_SOLIB_RETURN_TRAMPOLINE macro. */
+
+static int
+mips_in_return_stub (struct gdbarch *gdbarch, CORE_ADDR pc, const char *name)
+{
+ CORE_ADDR start_addr;
+ size_t prefixlen;
+
+ /* 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
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;
+
+ do
+ {
+ target_pc = pc;
- target_pc = mips_skip_mips16_trampoline_code (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 = find_solib_trampoline_target (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);
+ }
- target_pc = mips_skip_pic_trampoline_code (frame, pc);
- if (target_pc)
- return target_pc;
+ 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 0;
+ return pc != requested_pc ? pc : 0;
}
/* Convert a dbx stab register number (from `r' declaration) to a GDB
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);
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. */
+2012-04-26 Maciej W. Rozycki <macro@codesourcery.com>
+
+ * 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.
+
2012-04-25 Doug Evans <dje@google.com>
* configure.ac: Create gdb.go/Makefile.
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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 ();
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+# 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)"