From: Maciej W. Rozycki Date: Thu, 26 Apr 2012 16:56:18 +0000 (+0000) Subject: gdb/ X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=14132e8924cd988218b4b2c4c6aaea45af951c59;p=binutils-gdb.git gdb/ * infrun.c (handle_inferior_event): Move the check for return trampolines ahead of the check for function trampolines. * mips-tdep.h (MIPS_S2_REGNUM, MIPS_GP_REGNUM): New macros. * mips-tdep.c (mips_str_mips16_call_stub): New variable. (mips_str_mips16_ret_stub): Likewise. (mips_str_call_fp_stub): Likewise. (mips_str_call_stub): Likewise. (mips_str_fn_stub): Likewise. (mips_str_pic): Likewise. (mips_in_frame_stub): New function. (mips_unwind_pc): Return the return address rather than the PC if the PC of an intermediate frame is inside a call thunk. (mips_is_stub_suffix): New function. (mips_is_stub_mode): Likewise. (mips_get_mips16_fn_stub_pc): Likewise. (mips_skip_mips16_trampoline_code): Update to handle all the currently generated stub types. Don't recurse into __fn_stub thunks. Remove heuristics to handle stubs beyond etext/_etext. Use cooked register accesses. (mips_in_return_stub): Reintroduce function. (mips_skip_trampoline_code): Traverse trampolines recursively. (mips_gdbarch_init): Handle MIPS16 return trampolines. 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. --- diff --git a/gdb/ChangeLog b/gdb/ChangeLog index c3a2aae19c4..05073a10032 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,29 @@ +2012-04-26 Maciej W. Rozycki + Maciej W. Rozycki + + * 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 GDB 7.4.1 released. diff --git a/gdb/infrun.c b/gdb/infrun.c index a7d6c3c2bb2..854ab01f06a 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -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 diff --git a/gdb/mips-tdep.c b/gdb/mips-tdep.c index 8973ff79be5..9a3c7fb0813 100644 --- a/gdb/mips-tdep.c +++ b/gdb/mips-tdep.c @@ -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. - - 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 @@ -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; + + 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 @@ -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); diff --git a/gdb/mips-tdep.h b/gdb/mips-tdep.h index b264257a14b..72cc49adf99 100644 --- a/gdb/mips-tdep.h +++ b/gdb/mips-tdep.h @@ -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. */ diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 6c2ca581cb5..ba84133cd3e 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,14 @@ +2012-04-26 Maciej W. Rozycki + + * 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 * configure.ac: Create gdb.go/Makefile. diff --git a/gdb/testsuite/gdb.arch/mips16-thunks-inmain.c b/gdb/testsuite/gdb.arch/mips16-thunks-inmain.c new file mode 100644 index 00000000000..86ff4bc9cab --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks-inmain.c @@ -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 . */ + +int +inmain (void) +{ + return 0; +} diff --git a/gdb/testsuite/gdb.arch/mips16-thunks-main.c b/gdb/testsuite/gdb.arch/mips16-thunks-main.c new file mode 100644 index 00000000000..37239b1a3eb --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks-main.c @@ -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 . */ + +int inmain (void); + +int +main (void) +{ + return inmain (); +} diff --git a/gdb/testsuite/gdb.arch/mips16-thunks-sin.c b/gdb/testsuite/gdb.arch/mips16-thunks-sin.c new file mode 100644 index 00000000000..3e2d1bd18d4 --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks-sin.c @@ -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 . */ + +#include + +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; +} diff --git a/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob.c b/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob.c new file mode 100644 index 00000000000..cad50c5663d --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob.c @@ -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 . */ + +#include + +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; +} diff --git a/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob16.c b/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob16.c new file mode 100644 index 00000000000..1c7773b9566 --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob16.c @@ -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 . */ + +#include + +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; +} diff --git a/gdb/testsuite/gdb.arch/mips16-thunks-sinmain.c b/gdb/testsuite/gdb.arch/mips16-thunks-sinmain.c new file mode 100644 index 00000000000..2cbc471b723 --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks-sinmain.c @@ -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 . */ + +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; +} diff --git a/gdb/testsuite/gdb.arch/mips16-thunks-sinmips16.c b/gdb/testsuite/gdb.arch/mips16-thunks-sinmips16.c new file mode 100644 index 00000000000..5d223f69e03 --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks-sinmips16.c @@ -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 . */ + +#include + +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; +} diff --git a/gdb/testsuite/gdb.arch/mips16-thunks.exp b/gdb/testsuite/gdb.arch/mips16-thunks.exp new file mode 100644 index 00000000000..79c079ae787 --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips16-thunks.exp @@ -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 . + +# 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)"