* 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.

	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.
This commit is contained in:
Maciej W. Rozycki 2012-04-26 16:56:18 +00:00
parent 518f0db5cf
commit 14132e8924
13 changed files with 1313 additions and 119 deletions

View File

@ -1,3 +1,29 @@
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.

View File

@ -4814,6 +4814,48 @@ process_event_stop_test:
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 @@ process_event_stop_test:
}
}
/* 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

View File

@ -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 gdbarch *gdbarch, CORE_ADDR bpaddr)
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. */
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. */
}
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 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
@ -5784,21 +6073,41 @@ mips_skip_pic_trampoline_code (struct frame_info *frame, CORE_ADDR pc)
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 info, struct gdbarch_list *arches)
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);

View File

@ -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. */

View File

@ -1,3 +1,14 @@
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.

View File

@ -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;
}

View File

@ -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 ();
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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)"