This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[RFA] MIPS/GDB: Fix the handling of MIPS16 thunks
- From: "Maciej W. Rozycki" <macro at codesourcery dot com>
- To: <gdb-patches at sourceware dot org>
- Cc: Richard Sandiford <rdsandiford at googlemail dot com>
- Date: Tue, 10 Apr 2012 23:20:19 +0100
- Subject: [RFA] MIPS/GDB: Fix the handling of MIPS16 thunks
Hi,
[Richard, I've cc-ed you as the MIPS port maintainer of GCC and binutils,
the producers of MIPS16 and some other thunks covered here, in case you
had anything to add, and just so that you know this issue is being
addressed now.]
This change fixes the long-standing problems with MIPS16 thunks -- small
pieces of code used to translate between the standard MIPS and the MIPS16
calling convention used for passing floating-point function arguments and
function results on hard-float ABIs. As floating-point registers are
inaccessible from MIPS16 code, MIPS16 functions use integer registers
where standard MIPS functions would use floating-point registers. Thunks
are therefore used in the process of passing control between functions to
move floating-point values between registers as necessary.
There are essentially three classes of MIPS16 thunks: call thunks, return
thunks and call/return thunks. I'll outline them briefly below for better
understanding of what GDB has to do about them.
1. Call thunks are pieces of code that are called instead of the intended
function, for functions that take floating-point arguments, but return
no floating-point result. They are also used for direct calls to
MIPS16 functions that both take floating-point arguments and return
floating-point results. These thunks move values between registers as
required and then make a jump (with no link) or a sibling call to the
originally intended function. Therefore in the frame chain they appear
as a temporary empty frame that disappears as soon as the ultimate
function has been reached.
There are two types of these thunks -- direct and indirect, used for
the respective types of function calls. For the formers the target of
the jump (J) is hardcoded and the thunk itself is generated by GCC
individually on a per-function basis. For the latters the target of
the jump is passed by the caller in a register ($v0), so a
jump-register (JR) operation is used to transfer control to the
intended function. As a result the thunk itself can be reused for any
function that has the same register floating-point arguments and shared
thunks are pulled from libgcc on an as-needed basis.
Even direct thunks need to load the address of the intended function
into a register and then make use of the jump-register instruction when
PIC code is used; GCC apparently pessimises some cases and emits a
load-address/jump-register instruction sequence even when producing
non-PIC code, where a direct jump would do (I have a patch somewhere
that needs regression-testing against GCC trunk to make things better,
that I'll try to squeeze in sometime).
2. Return thunks are pieces of code that are called by MIPS16 functions
that return floating-point results just as the respective function is
about to return. These thunks move values between registers as
required and then return (with JR $ra) to their caller MIPS16 function.
This extra call is made between the function's body and its epilogue,
so in the frame chain it appears temporarily as an extra empty frame
inner to the original function's frame. That frame disappears as soon
as the thunk returns to the caller's epilogue.
These thunks can be reused for any function that has the same register
floating-point results and therefore shared thunksare used that are
pulled from libgcc on an as-needed basis.
3. Call/return thunks are pieces of code that are called instead of the
intended function for functions that both take floating-point arguments
and return floating-point results (except from direct calls to MIPS16
functions). They move values between registers as required, store the
return address in a call-saved register ($s2) and then make a call to
the originally intended function. When that function returns, these
thunks move values between registers as required again, and then return
(with JR $s2) to the original caller. As a result in the frame chain
these thunks appear as a permanent extra empty frame between the
original caller and the ultimate callee throughout the life of the
callee.
Like with call thunks there are two types of these thunks -- direct and
indirect with similar implications.
These thunks are sometimes chained as the requirements of the ABI imply,
for example all indirect calls have to standardise on a particular calling
convention, for which the standard MIPS ABI has been chosen. Therefore
such a call between a pair of MIPS16 functions may involve an indirect
call thunk that jumps to a direct call thunk that jumps to the ultimate
callee. Likewise an indirect call/return thunk may call a direct call
thunk that jumps to the ultimate callee. That callee may then call the
return thunk before returning to the return part of the call/return thunk.
Further non-MIPS16 trampolines may be generated as needed, for example
PIC stubs may be prepended to some of these thunks when linking PIC code
into non-PIC code, sometimes even unnecessarily such as in the case of
return thunks that refer to no global symbols, but come from libgcc that
is built as PIC code on targets that require it and the static linker is
not smart enough to figure out that such a PIC stub is actually not
needed.
Similarly, there may be a PLT stub involved in a shared library call that
resolves to a call thunk and which may itself be called by a MIPS16 thunk
as the shared library ABI uses the standard MIPS calling convention.
All these thunks are intended to be invisible to the user who debugs
software at the high-level programming language level. In practice this
means that the thunks have to be silently stepped through when
source-level single-stepping is requested and any auxiliary frames have to
be removed from the view in backtraces. Although some code has already
been added to GDB to address these requirements, it does not handle all
the cases and this change is intended to fix it.
Here are the highlights of the changes made:
1. Intermediate frames of the call/return thunks are now skipped in
(silently removed from) backtraces.
2. Code to skip over MIPS16 thunks (trampolines), i.e. to calculate the
ultimate value of the PC once the respective thunk has executed, has
been updated to handle all the currently known thunk types, in
particular ones that handle complex floating-point types. Changes
were made to use macros and calculated values instead of hardcoded
numbers. Cooked registers are now used for correct register retrieval
in outer frames. Recursion into chained thunks has been removed from
here. Finally, code to scan call thunks heuristically beyond "etext"
or "_etext" has been removed.
The latter is probably the most controversial here as it seems to
remove a feature. This feature however dates back to before the
history of our CVS repository and the mailing list, probably to the
original submission of this code, i.e.:
Thu Apr 3 10:31:12 1997 Mark Alexander <marka@cygnus.com>
* mips-tdep.c (mips_in_call_stub, mips_in_return_stub,
mips_skip_stub, mips_ignore_helper): New functions for dealing
with MIPS16 call/return thunks.
(mips_skip_mips16_trampoline_code was called mips_skip_stub back then),
and therefore I was unable to track down any explanation. Myself, I
have never seen a case where a debug binary would have some thunks
placed beyond the end of recognised text and I don't really know how
this could happen. Perhaps some old tools 15 years ago did something
weird about it. Given that this code has been subject of a substantial
bitrot I assert that whoever required it originally no longer cares
about this corner case and I have decided to remove it to simplify the
handling of the thunks.
If however someone thinks this assertion is incorrect or has any other
reasons for this case to remain handled, then please speak out now!
All these adjustments make single MIPS16 thunks be stepped-through
correctly when single-stepping at the source level.
3. Arbitrary chains of trampolines are now skipped over until a piece of
code that is not a trampoline has been reached, fixing source-level
single-stepping where multiple trampolines are encountered.
4. A handler is now installed to skip over return thunks while
single-stepping at the source level, reusing the shared library return
trampoline hook. As per the comment included, there is no overlap here
as no MIPS ABI uses shared library return trampolines.
This modification, however, requires a change to generic code -- in
handle_inferior_event (infrun.c). This is because a return thunk is
called like an ordinary function and therefore once reached, things
looks as if a new frame has been created. As a result code that checks
for subroutine calls triggers, at which point there is no way to back
out -- while the trampoline handler may still be used there (like for
call thunks) to skip over the return thunk, the subroutine call handler
insists on skipping over code further, to the next source line.
Which, given that we're about to return from a function, means the
point after the return instruction. Now placing the single-stepping
breakpoint there -- doesn't quite work as expected.
Therefore my proposal is to reverse the order of the checks made in
handle_inferior_event -- it looks to me there have been no particular
reason for why they have been put in the current order, except that one
must have been chosen. Currently there are two users of the shared
library return trampoline hook: hppa-hpux-tdep.c and rs6000-tdep.c.
The latter is rather trivial and makes me almost sure that the order of
the checks does not matter. The former is less obvious to me, but I
think it does not care either. Input from people knowledgeable on
these platforms will be appreciated.
5. We've got a test case now! :) I have carefully crafted a particularly
twisted piece of code that makes use of at least one MIPS16 thunk of
each kind. As MIPS16 execution environment is not available
everywhere, but is an optional extension to the MIPS32 or MIPS64
architecture (and some legacy ISAs), some care had to be taken to get
coverage where available. The test script therefore makes some checks
to make sure a MIPS16 app can be built and run, verifying both
toolchain support and support for the MIPS16 instruction set in the
target processor the test case is intended to run on.
Additionally the test app has to be built from several sources, because
mixing MIPS16 and standard MIPS code in a single source can be
problematic (there's a lot of hassle associated with the "mips16" GCC
attribute), as well as because we want to test MIPS16 thunks in the
context of PIC stubs where available as well.
Finally, the test case makes an extensive use of single-stepping and in
some places the exact number of steps made is not necessarily known (or
cared about), so the test case needs to be prepared to make as many
steps as required until the designated place (a different frame,
sometimes one of two possible, depending on whether we have debug
information for the system library or not) is reached, checking every
time that the backtrace is correct.
All of this made some of our standard higher-level ("cooked") TCL
procedures available for building executables or making individual
checks unsuitable for this case. I have therefore used some of the
lower-level procedures as well as wrapped some repeated pieces into
local procedures. I think it is the most complicated GDB test case I
have worked on and certainly the first one I wrote entirely from
scratch, so input on this code will be appreciated.
I have regression tested this change with the mips-sde-elf and the
mips-linux-gnu target, using a big-endian, o32 ABI configuration. I have
verified the new test case itself with the same two targets and lots of
multilibs, including hard-float and soft-float variations, o32 and n64
ABIs (the latter failing to build on Linux due to the current lack of
support for n64 MIPS16 PIC code and scoring correctly as UNSUPPORTED),
MIPS32, MIPS16 and microMIPS default instruction set (the latter again
failing to build due to the incompatibility between the MIPS16 and
microMIPS ASE) and target processors that do and do not support the MIPS16
ASE (the latter scoring correctly as UNSUPPORTED, after the debuggee has
gone astray).
OK (for the generic part) to apply?
2012-04-10 Maciej W. Rozycki <macro@mips.com>
Maciej W. Rozycki <macro@codesourcery.com>
gdb/
* infrun.c (handle_inferior_event): Move the check for return
trampolines ahead of the check for function trampolines.
* mips-tdep.h (MIPS_S2_REGNUM, MIPS_GP_REGNUM): New macros.
* mips-tdep.c (mips_str_mips16_call_stub): New variable.
(mips_str_mips16_ret_stub): Likewise.
(mips_str_call_fp_stub): Likewise.
(mips_str_call_stub): Likewise.
(mips_str_fn_stub): Likewise.
(mips_str_pic): Likewise.
(mips_in_frame_stub): New function.
(mips_unwind_pc): Return the return address rather than the PC
if the PC of an intermediate frame is inside a call thunk.
(mips_is_stub_suffix): New function.
(mips_is_stub_mode): Likewise.
(mips_get_mips16_fn_stub_pc): Likewise.
(mips_skip_mips16_trampoline_code): Update to handle all the
currently generated stub types. Don't recurse into __fn_stub
thunks. Remove heuristics to handle stubs beyond etext/_etext.
Use cooked register accesses.
(mips_in_return_stub): Reintroduce function.
(mips_skip_trampoline_code): Traverse trampolines recursively.
(mips_gdbarch_init): Handle MIPS16 return trampolines.
2012-04-10 Maciej W. Rozycki <macro@codesourcery.com>
gdb/testsuite/
* gdb.arch/mips16-thunks-inmain.c: New file.
* gdb.arch/mips16-thunks-main.c: New file.
* gdb.arch/mips16-thunks-sin.c: New file.
* gdb.arch/mips16-thunks-sinfrob.c: New file.
* gdb.arch/mips16-thunks-sinfrob16.c: New file.
* gdb.arch/mips16-thunks-sinmain.c: New file.
* gdb.arch/mips16-thunks-sinmips16.c: New file.
* gdb.arch/mips16-thunks.exp: New file.
Maciej
gdb-mips16-thunks.diff
Index: gdb-fsf-trunk-quilt/gdb/mips-tdep.c
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/mips-tdep.c 2012-04-04 12:05:40.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/mips-tdep.c 2012-04-05 22:29:33.445560104 +0100
@@ -1027,6 +1027,45 @@ mips_pc_is_mips16 (CORE_ADDR memaddr)
return is_mips16_addr (memaddr);
}
+/* Various MIPS16 thunk (aka stub or trampoline) names. */
+
+static const char mips_str_mips16_call_stub[] = "__mips16_call_stub_";
+static const char mips_str_mips16_ret_stub[] = "__mips16_ret_";
+static const char mips_str_call_fp_stub[] = "__call_stub_fp_";
+static const char mips_str_call_stub[] = "__call_stub_";
+static const char mips_str_fn_stub[] = "__fn_stub_";
+
+/* This is used as a PIC thunk prefix. */
+
+static const char mips_str_pic[] = ".pic.";
+
+/* Return non-zero if the PC is inside a call thunk (aka stub or
+ trampoline) that should be treated as a temporary frame. */
+
+static int
+mips_in_frame_stub (CORE_ADDR pc)
+{
+ CORE_ADDR start_addr;
+ const char *name;
+
+ /* Find the starting address of the function containing the PC. */
+ if (find_pc_partial_function (pc, &name, &start_addr, NULL) == 0)
+ return 0;
+
+ /* If the PC is in __mips16_call_stub_*, this is a call/return stub. */
+ if (strncmp (name, mips_str_mips16_call_stub,
+ strlen (mips_str_mips16_call_stub)) == 0)
+ return 1;
+ /* If the PC is in __call_stub_*, this is a call/return or a call stub. */
+ if (strncmp (name, mips_str_call_stub, strlen (mips_str_call_stub)) == 0)
+ return 1;
+ /* If the PC is in __fn_stub_*, this is a call stub. */
+ if (strncmp (name, mips_str_fn_stub, strlen (mips_str_fn_stub)) == 0)
+ return 1;
+
+ return 0; /* Not a stub. */
+}
+
/* MIPS believes that the PC has a sign extended value. Perhaps the
all registers should be sign extended for simplicity? */
@@ -1044,12 +1083,31 @@ mips_read_pc (struct regcache *regcache)
static CORE_ADDR
mips_unwind_pc (struct gdbarch *gdbarch, struct frame_info *next_frame)
{
- ULONGEST pc;
+ CORE_ADDR pc;
pc = frame_unwind_register_signed
(next_frame, gdbarch_num_regs (gdbarch) + mips_regnum (gdbarch)->pc);
if (is_mips16_addr (pc))
pc = unmake_mips16_addr (pc);
+ /* macro/2005-03-31: This hack skips over MIPS16 call thunks as
+ intermediate frames. In this case we can get the caller's address
+ from $ra, or if $ra contains an address within a thunk as well, then
+ it must be in the return path of __mips16_call_stub_{s,d}{f,c}_{0..10}
+ and thus the caller's address is in $s2. */
+ if (frame_relative_level (next_frame) >= 0 && mips_in_frame_stub (pc))
+ {
+ pc = frame_unwind_register_signed
+ (next_frame, gdbarch_num_regs (gdbarch) + MIPS_RA_REGNUM);
+ if (is_mips16_addr (pc))
+ pc = unmake_mips16_addr (pc);
+ if (mips_in_frame_stub (pc))
+ {
+ pc = frame_unwind_register_signed
+ (next_frame, gdbarch_num_regs (gdbarch) + MIPS_S2_REGNUM);
+ if (is_mips16_addr (pc))
+ pc = unmake_mips16_addr (pc);
+ }
+ }
return pc;
}
@@ -5653,104 +5711,333 @@ mips_adjust_breakpoint_address (struct g
return bpaddr;
}
-/* If PC is in a mips16 call or return stub, return the address of the target
- PC, which is either the callee or the caller. There are several
+/* Return non-zero if SUFFIX is one of the numeric suffixes used for MIPS16
+ call stubs, one of 1, 2, 5, 6, 9, 10, or, if ZERO is non-zero, also 0. */
+
+static int mips_is_stub_suffix (const char *suffix, int zero)
+{
+ switch (suffix[0])
+ {
+ case '0':
+ return zero && suffix[1] == '\0';
+ case '1':
+ return suffix[1] == '\0' || (suffix[1] == '0' && suffix[2] == '\0');
+ case '2':
+ case '5':
+ case '6':
+ case '9':
+ return suffix[1] == '\0';
+ default:
+ return 0;
+ }
+}
+
+/* Return non-zero if MODE is one of the mode infixes used for MIPS16
+ call stubs, one of sf, df, sc, or dc. */
+
+static int mips_is_stub_mode (const char *mode)
+{
+ return ((mode[0] == 's' || mode[0] == 'd')
+ && (mode[1] == 'f' || mode[1] == 'c'));
+}
+
+/* Code at PC is a compiler-generated stub. Such a stub for a function
+ bar might have a name like __fn_stub_bar, and might look like this:
+
+ mfc1 $4, $f13
+ mfc1 $5, $f12
+ mfc1 $6, $f15
+ mfc1 $7, $f14
+
+ followed by (or interspersed with):
+
+ j bar
+
+ or:
+
+ lui $25, %hi(bar)
+ addiu $25, $25, %lo(bar)
+ jr $25
+
+ ($1 may be used in old code; for robustness we accept any register)
+ or, in PIC code:
+
+ lui $28, %hi(_gp_disp)
+ addiu $28, $28, %lo(_gp_disp)
+ addu $28, $28, $25
+ lw $25, %got(bar)
+ addiu $25, $25, %lo(bar)
+ jr $25
+
+ In the case of a __call_stub_bar stub, the sequence to set up
+ arguments might look like this:
+
+ mtc1 $4, $f13
+ mtc1 $5, $f12
+ mtc1 $6, $f15
+ mtc1 $7, $f14
+
+ followed by (or interspersed with) one of the jump sequences above.
+
+ In the case of a __call_stub_fp_bar stub, JAL or JALR is used instead
+ of J or JR, respectively, followed by:
+
+ mfc1 $2, $f0
+ mfc1 $3, $f1
+ jr $18
+
+ We are at the beginning of the stub here, and scan down and extract
+ the target address from the jump immediate instruction or, if a jump
+ register instruction is used, from the register referred. Return
+ the value of PC calculated or 0 if inconclusive.
+
+ The limit on the search is arbitrarily set to 20 instructions. FIXME. */
+
+static CORE_ADDR
+mips_get_mips16_fn_stub_pc (struct frame_info *frame, CORE_ADDR pc)
+{
+ struct gdbarch *gdbarch = get_frame_arch (frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ int addrreg = MIPS_ZERO_REGNUM;
+ CORE_ADDR start_pc = pc;
+ CORE_ADDR target_pc = 0;
+ CORE_ADDR addr = 0;
+ CORE_ADDR gp = 0;
+ int status = 0;
+ int i;
+
+ for (i = 0;
+ status == 0 && target_pc == 0 && i < 20;
+ i++, pc += MIPS_INSN32_SIZE)
+ {
+ ULONGEST inst = mips_fetch_instruction (gdbarch, pc);
+ CORE_ADDR imm;
+ int rt;
+ int rs;
+ int rd;
+
+ switch (itype_op (inst))
+ {
+ case 0: /* SPECIAL */
+ switch (rtype_funct (inst))
+ {
+ case 8: /* JR */
+ case 9: /* JALR */
+ rs = rtype_rs (inst);
+ if (rs == MIPS_GP_REGNUM)
+ target_pc = gp; /* Hmm... */
+ else if (rs == addrreg)
+ target_pc = addr;
+ break;
+
+ case 0x21: /* ADDU */
+ rt = rtype_rt (inst);
+ rs = rtype_rs (inst);
+ rd = rtype_rd (inst);
+ if (rd == MIPS_GP_REGNUM
+ && ((rs == MIPS_GP_REGNUM && rt == MIPS_T9_REGNUM)
+ || (rs == MIPS_T9_REGNUM && rt == MIPS_GP_REGNUM)))
+ gp += start_pc;
+ break;
+ }
+ break;
+
+ case 2: /* J */
+ case 3: /* JAL */
+ target_pc = jtype_target (inst) << 2;
+ target_pc += ((pc + 4) & ~(CORE_ADDR) 0x0fffffff);
+ break;
+
+ case 9: /* ADDIU */
+ rt = itype_rt (inst);
+ rs = itype_rs (inst);
+ if (rt == rs)
+ {
+ imm = (itype_immediate (inst) ^ 0x8000) - 0x8000;
+ if (rt == MIPS_GP_REGNUM)
+ gp += imm;
+ else if (rt == addrreg)
+ addr += imm;
+ }
+ break;
+
+ case 0xf: /* LUI */
+ rt = itype_rt (inst);
+ imm = ((itype_immediate (inst) ^ 0x8000) - 0x8000) << 16;
+ if (rt == MIPS_GP_REGNUM)
+ gp = imm;
+ else if (rt != MIPS_ZERO_REGNUM)
+ {
+ addrreg = rt;
+ addr = imm;
+ }
+ break;
+
+ case 0x23: /* LW */
+ rt = itype_rt (inst);
+ rs = itype_rs (inst);
+ imm = (itype_immediate (inst) ^ 0x8000) - 0x8000;
+ if (gp != 0 && rs == MIPS_GP_REGNUM)
+ {
+ gdb_byte buf[4];
+
+ memset (buf, 0, sizeof (buf));
+ status = target_read_memory (gp + imm, buf, sizeof (buf));
+ addrreg = rt;
+ addr = extract_signed_integer (buf, sizeof (buf), byte_order);
+ }
+ break;
+ }
+ }
+
+ return target_pc;
+}
+
+/* If PC is in a MIPS16 call or return stub, return the address of the
+ target PC, which is either the callee or the caller. There are several
cases which must be handled:
- * If the PC is in __mips16_ret_{d,s}f, this is a return stub and the
- target PC is in $31 ($ra).
+ * If the PC is in __mips16_ret_{d,s}{f,c}, this is a return stub
+ and the target PC is in $31 ($ra).
* If the PC is in __mips16_call_stub_{1..10}, this is a call stub
- and the target PC is in $2.
- * If the PC at the start of __mips16_call_stub_{s,d}f_{0..10}, i.e.
- before the jal instruction, this is effectively a call stub
- and the target PC is in $2. Otherwise this is effectively
- a return stub and the target PC is in $18.
+ and the target PC is in $2.
+ * If the PC at the start of __mips16_call_stub_{s,d}{f,c}_{0..10},
+ i.e. before the JALR instruction, this is effectively a call stub
+ and the target PC is in $2. Otherwise this is effectively
+ a return stub and the target PC is in $18.
+ * If the PC is at the start of __call_stub_fp_*, i.e. before the
+ JAL or JALR instruction, this is effectively a call stub and the
+ target PC is buried in the instruction stream. Otherwise this
+ is effectively a return stub and the target PC is in $18.
+ * If the PC is in __call_stub_* or in __fn_stub_*, this is a call
+ stub and the target PC is buried in the instruction stream.
- See the source code for the stubs in gcc/config/mips/mips16.S for
+ See the source code for the stubs in gcc/config/mips/mips16.S, or the
+ stub builder in gcc/config/mips/mips.c (mips16_build_call_stub) for the
gory details. */
static CORE_ADDR
mips_skip_mips16_trampoline_code (struct frame_info *frame, CORE_ADDR pc)
{
struct gdbarch *gdbarch = get_frame_arch (frame);
- const char *name;
CORE_ADDR start_addr;
+ const char *name;
+ size_t prefixlen;
/* Find the starting address and name of the function containing the PC. */
if (find_pc_partial_function (pc, &name, &start_addr, NULL) == 0)
return 0;
- /* If the PC is in __mips16_ret_{d,s}f, this is a return stub and the
- target PC is in $31 ($ra). */
- if (strcmp (name, "__mips16_ret_sf") == 0
- || strcmp (name, "__mips16_ret_df") == 0)
- return get_frame_register_signed (frame, MIPS_RA_REGNUM);
+ /* If the PC is in __mips16_ret_{d,s}{f,c}, this is a return stub
+ and the target PC is in $31 ($ra). */
+ prefixlen = strlen (mips_str_mips16_ret_stub);
+ if (strncmp (name, mips_str_mips16_ret_stub, prefixlen) == 0
+ && mips_is_stub_mode (name + prefixlen)
+ && name[prefixlen + 2] == '\0')
+ return get_frame_register_signed
+ (frame, gdbarch_num_regs (gdbarch) + MIPS_RA_REGNUM);
- if (strncmp (name, "__mips16_call_stub_", 19) == 0)
+ /* If the PC is in __mips16_call_stub_*, this is one of the call
+ call/return stubs. */
+ prefixlen = strlen (mips_str_mips16_call_stub);
+ if (strncmp (name, mips_str_mips16_call_stub, prefixlen) == 0)
{
/* If the PC is in __mips16_call_stub_{1..10}, this is a call stub
and the target PC is in $2. */
- if (name[19] >= '0' && name[19] <= '9')
- return get_frame_register_signed (frame, 2);
+ if (mips_is_stub_suffix (name + prefixlen, 0))
+ return get_frame_register_signed
+ (frame, gdbarch_num_regs (gdbarch) + MIPS_V0_REGNUM);
- /* If the PC at the start of __mips16_call_stub_{s,d}f_{0..10}, i.e.
- before the jal instruction, this is effectively a call stub
+ /* If the PC at the start of __mips16_call_stub_{s,d}{f,c}_{0..10},
+ i.e. before the JALR instruction, this is effectively a call stub
and the target PC is in $2. Otherwise this is effectively
a return stub and the target PC is in $18. */
- else if (name[19] == 's' || name[19] == 'd')
+ else if (mips_is_stub_mode (name + prefixlen)
+ && name[prefixlen + 2] == '_'
+ && mips_is_stub_suffix (name + prefixlen + 3, 0))
{
if (pc == start_addr)
- {
- /* Check if the target of the stub is a compiler-generated
- stub. Such a stub for a function bar might have a name
- like __fn_stub_bar, and might look like this:
- mfc1 $4,$f13
- mfc1 $5,$f12
- mfc1 $6,$f15
- mfc1 $7,$f14
- la $1,bar (becomes a lui/addiu pair)
- jr $1
- So scan down to the lui/addi and extract the target
- address from those two instructions. */
+ /* This is the 'call' part of a call stub. The return
+ address is in $2. */
+ return get_frame_register_signed
+ (frame, gdbarch_num_regs (gdbarch) + MIPS_V0_REGNUM);
+ else
+ /* This is the 'return' part of a call stub. The return
+ address is in $18. */
+ return get_frame_register_signed
+ (frame, gdbarch_num_regs (gdbarch) + MIPS_S2_REGNUM);
+ }
+ else
+ return 0; /* Not a stub. */
+ }
- CORE_ADDR target_pc = get_frame_register_signed (frame, 2);
- int i;
+ /* If the PC is in __call_stub_* or __fn_stub*, this is one of the
+ compiler-generated call or call/return stubs. */
+ if (strncmp (name, mips_str_fn_stub, strlen (mips_str_fn_stub)) == 0
+ || strncmp (name, mips_str_call_stub, strlen (mips_str_call_stub)) == 0)
+ {
+ if (pc == start_addr)
+ /* This is the 'call' part of a call stub. Call this helper
+ to scan through this code for interesting instructions
+ and determine the final PC. */
+ return mips_get_mips16_fn_stub_pc (frame, pc);
+ else
+ /* This is the 'return' part of a call stub. The return address
+ is in $18. */
+ return get_frame_register_signed
+ (frame, gdbarch_num_regs (gdbarch) + MIPS_S2_REGNUM);
+ }
- /* See if the name of the target function is __fn_stub_*. */
- if (find_pc_partial_function (target_pc, &name, NULL, NULL) ==
- 0)
- return target_pc;
- if (strncmp (name, "__fn_stub_", 10) != 0
- && strcmp (name, "etext") != 0
- && strcmp (name, "_etext") != 0)
- return target_pc;
+ return 0; /* Not a stub. */
+}
- /* Scan through this _fn_stub_ code for the lui/addiu pair.
- The limit on the search is arbitrarily set to 20
- instructions. FIXME. */
- for (i = 0, pc = 0; i < 20; i++, target_pc += MIPS_INSN32_SIZE)
- {
- ULONGEST inst = mips_fetch_instruction (gdbarch, target_pc);
- CORE_ADDR addr = inst;
+/* Return non-zero if the PC is inside a return thunk (aka stub or trampoline).
+ This implements the IN_SOLIB_RETURN_TRAMPOLINE macro. */
- if ((inst & 0xffff0000) == 0x3c010000) /* lui $at */
- pc = (((addr & 0xffff) ^ 0x8000) - 0x8000) << 16;
- /* high word */
- else if ((inst & 0xffff0000) == 0x24210000) /* addiu $at */
- return pc + ((addr & 0xffff) ^ 0x8000) - 0x8000;
- /* low word */
- }
+static int
+mips_in_return_stub (struct gdbarch *gdbarch, CORE_ADDR pc, const char *name)
+{
+ CORE_ADDR start_addr;
+ size_t prefixlen;
- /* Couldn't find the lui/addui pair, so return stub address. */
- return target_pc;
- }
- else
- /* This is the 'return' part of a call stub. The return
- address is in $r18. */
- return get_frame_register_signed (frame, 18);
- }
- }
- return 0; /* not a stub */
+ /* Find the starting address of the function containing the PC. */
+ if (find_pc_partial_function (pc, NULL, &start_addr, NULL) == 0)
+ return 0;
+
+ /* If the PC is in __mips16_call_stub_{s,d}{f,c}_{0..10} but not at
+ the start, i.e. after the JALR instruction, this is effectively
+ a return stub. */
+ prefixlen = strlen (mips_str_mips16_call_stub);
+ if (pc != start_addr
+ && strncmp (name, mips_str_mips16_call_stub, prefixlen) == 0
+ && mips_is_stub_mode (name + prefixlen)
+ && name[prefixlen + 2] == '_'
+ && mips_is_stub_suffix (name + prefixlen + 3, 1))
+ return 1;
+
+ /* If the PC is in __call_stub_fp_* but not at the start, i.e. after
+ the JAL or JALR instruction, this is effectively a return stub. */
+ prefixlen = strlen (mips_str_call_fp_stub);
+ if (pc != start_addr
+ && strncmp (name, mips_str_call_fp_stub, prefixlen) == 0)
+ return 1;
+
+ /* Consume the .pic. prefix of any PIC stub, this function must return
+ true when the PC is in a PIC stub of a __mips16_ret_{d,s}{f,c} stub
+ or the call stub path will trigger in handle_inferior_event causing
+ it to go astray. */
+ prefixlen = strlen (mips_str_pic);
+ if (strncmp (name, mips_str_pic, prefixlen) == 0)
+ name += prefixlen;
+
+ /* If the PC is in __mips16_ret_{d,s}{f,c}, this is a return stub. */
+ prefixlen = strlen (mips_str_mips16_ret_stub);
+ if (strncmp (name, mips_str_mips16_ret_stub, prefixlen) == 0
+ && mips_is_stub_mode (name + prefixlen)
+ && name[prefixlen + 2] == '\0')
+ return 1;
+
+ return 0; /* Not a stub. */
}
/* If the current PC is the start of a non-PIC-to-PIC stub, return the
@@ -5813,21 +6100,41 @@ mips_skip_pic_trampoline_code (struct fr
static CORE_ADDR
mips_skip_trampoline_code (struct frame_info *frame, CORE_ADDR pc)
{
+ CORE_ADDR requested_pc = pc;
CORE_ADDR target_pc;
+ CORE_ADDR new_pc;
- target_pc = mips_skip_mips16_trampoline_code (frame, pc);
- if (target_pc)
- return target_pc;
+ do
+ {
+ target_pc = pc;
- target_pc = find_solib_trampoline_target (frame, pc);
- if (target_pc)
- return target_pc;
+ new_pc = mips_skip_mips16_trampoline_code (frame, pc);
+ if (new_pc)
+ {
+ pc = new_pc;
+ if (is_mips16_addr (pc))
+ pc = unmake_mips16_addr (pc);
+ }
- target_pc = mips_skip_pic_trampoline_code (frame, pc);
- if (target_pc)
- return target_pc;
+ new_pc = find_solib_trampoline_target (frame, pc);
+ if (new_pc)
+ {
+ pc = new_pc;
+ if (is_mips16_addr (pc))
+ pc = unmake_mips16_addr (pc);
+ }
- return 0;
+ new_pc = mips_skip_pic_trampoline_code (frame, pc);
+ if (new_pc)
+ {
+ pc = new_pc;
+ if (is_mips16_addr (pc))
+ pc = unmake_mips16_addr (pc);
+ }
+ }
+ while (pc != target_pc);
+
+ return pc != requested_pc ? pc : 0;
}
/* Convert a dbx stab register number (from `r' declaration) to a GDB
@@ -6670,6 +6977,16 @@ mips_gdbarch_init (struct gdbarch_info i
set_gdbarch_skip_trampoline_code (gdbarch, mips_skip_trampoline_code);
+ /* NOTE drow/2004-02-11: We overload the core solib trampoline code
+ to support MIPS16. This is a bad thing. Make sure not to do it
+ if we have an OS ABI that actually supports shared libraries, since
+ shared library support is more important. If we have an OS someday
+ that supports both shared libraries and MIPS16, we'll have to find
+ a better place for these.
+ macro/2011-12-08: But that applies to return trampolines only and
+ currently no MIPS OS ABI uses shared libraries that have them. */
+ set_gdbarch_in_solib_return_trampoline (gdbarch, mips_in_return_stub);
+
set_gdbarch_single_step_through_delay (gdbarch,
mips_single_step_through_delay);
Index: gdb-fsf-trunk-quilt/gdb/mips-tdep.h
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/mips-tdep.h 2012-04-04 12:05:40.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/mips-tdep.h 2012-04-05 21:42:24.995424172 +0100
@@ -119,7 +119,9 @@ enum
MIPS_AT_REGNUM = 1,
MIPS_V0_REGNUM = 2, /* Function integer return value. */
MIPS_A0_REGNUM = 4, /* Loc of first arg during a subr call. */
+ MIPS_S2_REGNUM = 18, /* Contains return address in MIPS16 thunks. */
MIPS_T9_REGNUM = 25, /* Contains address of callee in PIC. */
+ MIPS_GP_REGNUM = 28,
MIPS_SP_REGNUM = 29,
MIPS_RA_REGNUM = 31,
MIPS_PS_REGNUM = 32, /* Contains processor status. */
Index: gdb-fsf-trunk-quilt/gdb/infrun.c
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/infrun.c 2012-04-05 21:42:09.000000000 +0100
+++ gdb-fsf-trunk-quilt/gdb/infrun.c 2012-04-05 22:29:33.405561491 +0100
@@ -4804,6 +4804,48 @@ handle_inferior_event (struct execution_
return;
}
+ /* If we're in the return path from a shared library trampoline,
+ we want to proceed through the trampoline when stepping. */
+ /* macro/2012-03-29: This needs to come before the subroutine
+ call check below as on some targets return trampolines look
+ like subroutine calls (MIPS16 return thunks). */
+ if (gdbarch_in_solib_return_trampoline (gdbarch,
+ stop_pc, ecs->stop_func_name)
+ && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
+ {
+ /* Determine where this trampoline returns. */
+ CORE_ADDR real_stop_pc;
+
+ real_stop_pc = gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc);
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepped into solib return tramp\n");
+
+ /* Only proceed through if we know where it's going. */
+ if (real_stop_pc)
+ {
+ /* And put the step-breakpoint there and go until there. */
+ struct symtab_and_line sr_sal;
+
+ init_sal (&sr_sal); /* initialize to zeroes */
+ sr_sal.pc = real_stop_pc;
+ sr_sal.section = find_pc_overlay (sr_sal.pc);
+ sr_sal.pspace = get_frame_program_space (frame);
+
+ /* Do not specify what the fp should be when we stop since
+ on some machines the prologue is where the new fp value
+ is established. */
+ insert_step_resume_breakpoint_at_sal (gdbarch,
+ sr_sal, null_frame_id);
+
+ /* Restart without fiddling with the step ranges or
+ other state. */
+ keep_going (ecs);
+ return;
+ }
+ }
+
/* Check for subroutine calls. The check for the current frame
equalling the step ID is not necessary - the check of the
previous frame's ID is sufficient - but it is a common case and
@@ -5014,45 +5056,6 @@ handle_inferior_event (struct execution_
}
}
- /* If we're in the return path from a shared library trampoline,
- we want to proceed through the trampoline when stepping. */
- if (gdbarch_in_solib_return_trampoline (gdbarch,
- stop_pc, ecs->stop_func_name)
- && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
- {
- /* Determine where this trampoline returns. */
- CORE_ADDR real_stop_pc;
-
- real_stop_pc = gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc);
-
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog,
- "infrun: stepped into solib return tramp\n");
-
- /* Only proceed through if we know where it's going. */
- if (real_stop_pc)
- {
- /* And put the step-breakpoint there and go until there. */
- struct symtab_and_line sr_sal;
-
- init_sal (&sr_sal); /* initialize to zeroes */
- sr_sal.pc = real_stop_pc;
- sr_sal.section = find_pc_overlay (sr_sal.pc);
- sr_sal.pspace = get_frame_program_space (frame);
-
- /* Do not specify what the fp should be when we stop since
- on some machines the prologue is where the new fp value
- is established. */
- insert_step_resume_breakpoint_at_sal (gdbarch,
- sr_sal, null_frame_id);
-
- /* Restart without fiddling with the step ranges or
- other state. */
- keep_going (ecs);
- return;
- }
- }
-
stop_pc_sal = find_pc_line (stop_pc, 0);
/* NOTE: tausq/2004-05-24: This if block used to be done before all
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-inmain.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-inmain.c 2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,5 @@
+int
+inmain (void)
+{
+ return 0;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-main.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-main.c 2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,7 @@
+int inmain (void);
+
+int
+main (void)
+{
+ return inmain ();
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sin.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sin.c 2012-04-05 21:42:24.995424172 +0100
@@ -0,0 +1,38 @@
+#include <math.h>
+
+double sinfrob (double d);
+double sinfrob16 (double d);
+
+double sinblah (double d);
+double sinblah16 (double d);
+
+double sinmips16 (double d);
+long lsinmips16 (double d);
+
+extern long i;
+
+double
+sinhelper (double d)
+{
+ i++;
+ d = sin (d);
+ d = sinfrob16 (d);
+ d = sinfrob (d);
+ d = sinmips16 (d);
+ i++;
+ return d;
+}
+
+long
+lsinhelper (double d)
+{
+ long l;
+
+ i++;
+ d = sin (d);
+ d = sinblah (d);
+ d = sinblah16 (d);
+ l = lsinmips16 (d);
+ i++;
+ return l;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob.c 2012-04-05 21:42:24.995424172 +0100
@@ -0,0 +1,21 @@
+#include <math.h>
+
+extern long i;
+
+double
+sinfrob (double d)
+{
+ i++;
+ d = sin (d);
+ i++;
+ return d;
+}
+
+double
+sinblah (double d)
+{
+ i++;
+ d = sin (d);
+ i++;
+ return d;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob16.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinfrob16.c 2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,21 @@
+#include <math.h>
+
+extern long i;
+
+double
+sinfrob16 (double d)
+{
+ i++;
+ d = sin (d);
+ i++;
+ return d;
+}
+
+double
+sinblah16 (double d)
+{
+ i++;
+ d = sin (d);
+ i++;
+ return d;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmain.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmain.c 2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,34 @@
+double sinfrob (double d);
+double sinfrob16 (double d);
+
+double sinblah (double d);
+double sinblah16 (double d);
+
+double sinhelper (double);
+long lsinhelper (double);
+
+double (*sinfunc) (double) = sinfrob;
+double (*sinfunc16) (double) = sinfrob16;
+
+double f = 1.0;
+long i = 1;
+
+int
+main (void)
+{
+ double d = f;
+ long l = i;
+
+ d = sinfrob16 (d);
+ d = sinfrob (d);
+ d = sinhelper (d);
+
+ sinfunc = sinblah;
+ sinfunc16 = sinblah16;
+
+ d = sinblah (d);
+ d = sinblah16 (d);
+ l = lsinhelper (d);
+
+ return l + i;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmips16.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks-sinmips16.c 2012-04-05 21:42:25.005559959 +0100
@@ -0,0 +1,45 @@
+#include <math.h>
+
+double sinfrob (double d);
+double sinfrob16 (double d);
+
+double sinblah (double d);
+double sinblah16 (double d);
+
+extern double (*sinfunc) (double);
+extern double (*sinfunc16) (double);
+
+extern long i;
+
+double
+sinmips16 (double d)
+{
+ i++;
+ d = sin (d);
+ d = sinfrob16 (d);
+ d = sinfrob (d);
+ d = sinfunc16 (d);
+ d = sinfunc (d);
+ i++;
+ return d;
+}
+
+long
+lsinmips16 (double d)
+{
+ union
+ {
+ double d;
+ long l[2];
+ }
+ u;
+
+ i++;
+ d = sin (d);
+ d = sinblah (d);
+ d = sinblah16 (d);
+ d = sinfunc (d);
+ u.d = sinfunc16 (d);
+ i++;
+ return u.l[0] == 0 && u.l[1] == 0;
+}
Index: gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks.exp
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/testsuite/gdb.arch/mips16-thunks.exp 2012-04-05 22:53:33.955629283 +0100
@@ -0,0 +1,585 @@
+# Copyright 2012 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Contributed by Mentor Graphics, written by Maciej W. Rozycki.
+
+# Test MIPS16 thunk support.
+
+# This should work on any targets that support MIPS16 execution, including
+# Linux and bare-iron ones, but not all of them do, for example MIPS16
+# support has been added to Linux relatively late in the game. Also besides
+# environment support, the target processor has to support the MIPS16 ASE.
+# Finally as of this writing MIPS16 support has only been implemented in the
+# toolchain for a subset of ABIs, so we need to check that a MIPS16
+# executable can be built and run at all before we attempt the actual test.
+
+if { ![istarget "mips*-*-*"] } then {
+ verbose "Skipping MIPS16 thunk support tests."
+ return
+}
+
+# A helper to set caller's SRCFILE and OBJFILE based on FILENAME and SUFFIX.
+proc set_src_and_obj { filename { suffix "" } } {
+ upvar srcfile srcfile
+ upvar objfile objfile
+ global srcdir
+ global objdir
+ global subdir
+
+ if ![string equal "$suffix" ""] then {
+ set suffix "-$suffix"
+ }
+ set srcfile ${srcdir}/${subdir}/${filename}.c
+ set objfile ${objdir}/${subdir}/${filename}${suffix}.o
+}
+
+# First check if a trivial MIPS16 program can be built and debugged. This
+# verifies environment and processor support, any failure here must be
+# classed as the lack of support.
+set testname mips16-thunks-main
+
+set_src_and_obj mips16-thunks-inmain
+set options [list debug nowarnings additional_flags=-mips16]
+set objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-main
+set options [list debug nowarnings additional_flags=-mips16]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set binfile ${objdir}/${subdir}/${testname}
+set options [list debug nowarnings]
+if { [gdb_compile ${objfiles} ${binfile} executable ${options}] != "" } then {
+ unsupported "No MIPS16 support in the toolchain."
+ return
+}
+clean_restart ${testname}
+gdb_breakpoint inmain
+gdb_run_cmd
+gdb_expect 30 {
+ -re "Breakpoint 1.*inmain .*$gdb_prompt $" {
+ send_gdb "finish\n"
+ gdb_expect {
+ -re "Value returned is \\\$\[0-9\]+ = 0\[^0-9\].*$gdb_prompt $" {
+ verbose "MIPS16 support check successful."
+ }
+ -re "$gdb_prompt $" {
+ unsupported "No MIPS16 support in the processor."
+ return
+ }
+ default {
+ unsupported "No MIPS16 support in the processor."
+ return
+ }
+ }
+ }
+ -re "$gdb_prompt $" {
+ unsupported "No MIPS16 support in the processor."
+ return
+ }
+ default {
+ unsupported "No MIPS16 support in the processor."
+ return
+ }
+}
+
+# Check if MIPS16 PIC code can be built and debugged. We want to check
+# PIC and MIPS16 thunks are handled correctly together if possible, but
+# on targets that do not support PIC code, e.g. bare iron, we still want
+# to test the rest of functionality.
+set testname mips16-thunks-pic
+set picflag ""
+
+set_src_and_obj mips16-thunks-inmain pic
+set options [list \
+ debug nowarnings additional_flags=-mips16 additional_flags=-fPIC]
+set objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-main pic
+set options [list \
+ debug nowarnings additional_flags=-mips16 additional_flags=-fPIC]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set binfile ${objdir}/${subdir}/${testname}
+set options [list debug nowarnings additional_flags=-fPIC]
+if { [gdb_compile ${objfiles} ${binfile} executable ${options}] == "" } then {
+ clean_restart ${testname}
+ gdb_breakpoint inmain
+ gdb_run_cmd
+ gdb_expect 30 {
+ -re "Breakpoint 1.*inmain .*$gdb_prompt $" {
+ note "PIC support present, will make additional PIC thunk checks."
+ set picflag additional_flags=-fPIC
+ }
+ -re "$gdb_prompt $" {
+ note "No PIC support, skipping additional PIC thunk checks."
+ }
+ default {
+ note "No PIC support, skipping additional PIC thunk checks."
+ }
+ }
+} else {
+ note "No PIC support, skipping additional PIC thunk checks."
+}
+
+# OK, build the twisted executable. This program contains the following
+# MIPS16 thunks:
+# - __call_stub_fp_sin,
+# - __call_stub_fp_sinblah,
+# - __call_stub_fp_sinfrob,
+# - __call_stub_fp_sinhelper,
+# - __call_stub_lsinhelper,
+# - __fn_stub_lsinmips16,
+# - __fn_stub_sinblah16,
+# - __fn_stub_sinfrob16,
+# - __fn_stub_sinmips16,
+# - __mips16_call_stub_df_2,
+# - __mips16_ret_df.
+# Additionally, if PIC code is supported, it contains the following PIC thunks:
+# - .pic.__mips16_call_stub_df_2,
+# - .pic.__mips16_ret_df,
+# - .pic.sinblah,
+# - .pic.sinblah16,
+# - .pic.sinfrob,
+# - .pic.sinfrob16.
+set testname mips16-thunks-sin
+
+set_src_and_obj mips16-thunks-sinmain
+set options [list debug nowarnings additional_flags=-mips16]
+set objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sin
+set options [list debug nowarnings additional_flags=-mno-mips16]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sinmips16
+set options [list debug nowarnings additional_flags=-mips16]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sinfrob
+set options [list \
+ debug nowarnings additional_flags=-mno-mips16 ${picflag}]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set_src_and_obj mips16-thunks-sinfrob16
+set options [list \
+ debug nowarnings additional_flags=-mips16 ${picflag}]
+lappend objfiles ${objfile}
+gdb_compile ${srcfile} ${objfile} object ${options}
+
+set binfile ${objdir}/${subdir}/${testname}
+set options [list debug nowarnings]
+gdb_compile ${objfiles} ${binfile} executable ${options}
+clean_restart ${testname}
+if ![runto_main] then {
+ fail "running test program, MIPS16 thunk tests aborted"
+ return
+}
+
+# Build some useful regular expressions out of a list of functions FUNCS
+# to be used to match against backtraces.
+proc build_frames_re { funcs } {
+ upvar anyframe anyframe
+ upvar frames frames
+ upvar frame frame
+ upvar func func
+
+ set fid 0
+ set argsandsource " +\\\(.*\\\) +at +\[^\r\n\]+\r\n"
+ set addrin "(?:\[^ \]+ +in +)?"
+ set anyframe "#${fid} +${addrin}(\[^ \]+)${argsandsource}"
+ set frame "#${fid} +${addrin}${func}${argsandsource}"
+ set frames "$frame"
+ foreach f [lrange $funcs 1 end] {
+ incr fid
+ append frames "#${fid} +${addrin}${f}${argsandsource}"
+ }
+}
+
+# Single-step through the function that is at the head of function list
+# FUNCS until a different function (frame) is reached. Before each step
+# check the backtrace against FUNCS. ID is used for reporting, to tell
+# apart different calls to this procedure for the same function. If
+# successful, then return the name of the function we have stopped in.
+proc step_through { id funcs } {
+ global gdb_prompt
+
+ set func [lindex $funcs 0]
+ build_frames_re "$funcs"
+
+ set msg "single-stepping through \"${func}\" ($id)"
+
+ # Arbitrarily limit the maximium number of steps made to avoid looping
+ # indefinitely in the case something goes wrong, increase as (if)
+ # necessary.
+ set count 8
+ while [expr $count > 0] {
+ send_gdb "backtrace\n"
+ gdb_expect {
+ -re "${frames}$gdb_prompt $" {
+ send_gdb "step\n"
+ gdb_expect {
+ -re "$gdb_prompt $" {
+ send_gdb "frame\n"
+ gdb_expect {
+ -re "${frame}.*$gdb_prompt $" {
+ }
+ -re "${anyframe}.*$gdb_prompt $" {
+ pass "$msg"
+ return $expect_out(1,string)
+ }
+ -re "$gdb_prompt $" {
+ fail "$msg (frame)"
+ return ""
+ }
+ default {
+ fail "$msg (frame)"
+ return ""
+ }
+ }
+ }
+ default {
+ fail "$msg (step)"
+ return ""
+ }
+ }
+ }
+ -re "$gdb_prompt $" {
+ fail "$msg (backtrace)"
+ return ""
+ }
+ default {
+ fail "$msg (backtrace)"
+ return
+ }
+ }
+ incr count -1
+ }
+ fail "$msg (too many steps)"
+}
+
+# Finish the current function that must be one that is at the head of
+# function list FUNCS. Before that check the backtrace against FUNCS.
+# ID is used for reporting, to tell apart different calls to this
+# procedure for the same function. If successful, then return the name
+# of the function we have stopped in.
+proc finish_through { id funcs } {
+ global gdb_prompt
+
+ set func [lindex $funcs 0]
+ build_frames_re "$funcs"
+
+ set msg "finishing \"${func}\" ($id)"
+
+ send_gdb "backtrace\n"
+ gdb_expect {
+ -re "${frames}$gdb_prompt $" {
+ send_gdb "finish\n"
+ gdb_expect {
+ -re "Run till exit from ${frame}.*$gdb_prompt $" {
+ send_gdb "frame\n"
+ gdb_expect {
+ -re "${anyframe}.*$gdb_prompt $" {
+ pass "$msg"
+ return $expect_out(1,string)
+ }
+ -re "$gdb_prompt $" {
+ fail "$msg (frame)"
+ return ""
+ }
+ default {
+ fail "$msg (frame)"
+ return ""
+ }
+ }
+ }
+ -re "$gdb_prompt $" {
+ fail "$msg (finish)"
+ return ""
+ }
+ default {
+ fail "$msg (finish)"
+ return ""
+ }
+ }
+ }
+ -re "$gdb_prompt $" {
+ fail "$msg (backtrace)"
+ return ""
+ }
+ default {
+ fail "$msg (backtrace)"
+ return ""
+ }
+ }
+}
+
+# Report PASS if VAL is equal to EXP, otherwise report FAIL, using MSG.
+proc pass_if_eq { val exp msg } {
+ if [string equal "$val" "$exp"] then {
+ pass "$msg"
+ } else {
+ fail "$msg"
+ }
+}
+
+# Check if FUNC is equal to WANT. If not, then assume that we have stepped
+# into a library call. In this case finish it, then step out of the caller.
+# ID is used for reporting, to tell apart different calls to this procedure
+# for the same function. If successful, then return the name of the
+# function we have stopped in.
+proc finish_if_ne { id func want funcs } {
+ if ![string equal "$func" "$want"] then {
+ set call "$func"
+ set want [lindex $funcs 0]
+ set func [finish_through "$id" [linsert $funcs 0 "$func"]]
+ pass_if_eq "$func" "$want" "\"${call}\" finishing to \"${want}\" ($id)"
+ set func [step_through "$id" $funcs]
+ }
+ return "$func"
+}
+
+# Now single-step through the program, making sure all thunks are correctly
+# stepped over and omitted from backtraces.
+
+set id 1
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinfrob16 "stepping from \"main\" into \"sinfrob16\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 main]]
+set func [finish_if_ne $id "$func" main [list sinfrob16 main]]
+pass_if_eq "$func" main "stepping from \"sinfrob16\" back to \"main\" ($id)"
+
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinfrob "stepping from \"main\" into \"sinfrob\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob main]]
+set func [finish_if_ne $id "$func" main [list sinfrob main]]
+pass_if_eq "$func" main "stepping from \"sinfrob\" back to \"main\" ($id)"
+
+# 5
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinhelper "stepping from \"main\" into \"sinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list sinhelper main]]
+set func [finish_if_ne $id "$func" sinfrob16 [list sinhelper main]]
+pass_if_eq "$func" sinfrob16 \
+ "stepping from \"sinhelper\" into \"sinfrob16\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper [list sinfrob16 sinhelper main]]
+pass_if_eq "$func" sinhelper \
+ "stepping from \"sinfrob16\" back to \"sinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list sinhelper main]]
+pass_if_eq "$func" sinfrob "stepping from \"sinhelper\" into \"sinfrob\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper [list sinfrob sinhelper main]]
+pass_if_eq "$func" sinhelper \
+ "stepping from \"sinfrob\" back to \"sinhelper\" ($id)"
+
+# 10
+incr id
+set func [step_through $id [list sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+ "stepping from \"sinhelper\" into \"sinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinfrob16 [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob16 \
+ "stepping from \"sinmips16\" into \"sinfrob16\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinmips16 \
+ [list sinfrob16 sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+ "stepping from \"sinfrob16\" back to \"sinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob "stepping from \"sinmips16\" into \"sinfrob\" ($id)"
+
+incr id
+set func [step_through $id [list sinfrob sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper \
+ [list sinfrob sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+ "stepping from \"sinfrob\" back to \"sinmips16\" ($id)"
+
+# 15
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob16 \
+ "stepping from \"sinmips16\" into \"sinfrob16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinfrob16 sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinmips16 \
+ [list sinfrob16 sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+ "stepping from \"sinfrob16\" back to \"sinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinfrob \
+ "stepping from \"sinmips16\" into \"sinfrob\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinfrob sinmips16 sinhelper main]]
+set func [finish_if_ne $id "$func" sinhelper \
+ [list sinfrob sinmips16 sinhelper main]]
+pass_if_eq "$func" sinmips16 \
+ "stepping from \"sinfrob\" back to \"sinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinmips16 sinhelper main]]
+pass_if_eq "$func" sinhelper \
+ "stepping from \"sinmips16\" back to \"sinhelper\" ($id)"
+
+# 20
+incr id
+set func [step_through $id [list sinhelper main]]
+pass_if_eq "$func" main "stepping from \"sinhelper\" back to \"main\" ($id)"
+
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinblah "stepping from \"main\" into \"sinblah\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah main]]
+set func [finish_if_ne $id "$func" main [list sinblah main]]
+pass_if_eq "$func" main "stepping from \"sinblah\" back to \"main\" ($id)"
+
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" sinblah16 "stepping from \"main\" into \"sinblah16\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 main]]
+set func [finish_if_ne $id "$func" main [list sinblah16 main]]
+pass_if_eq "$func" main "stepping from \"sinblah16\" back to \"main\" ($id)"
+
+# 25
+incr id
+set func [step_through $id [list main]]
+pass_if_eq "$func" lsinhelper \
+ "stepping from \"main\" into \"lsinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list lsinhelper main]]
+set func [finish_if_ne $id "$func" sinblah [list lsinhelper main]]
+pass_if_eq "$func" sinblah \
+ "stepping from \"lsinhelper\" into \"sinblah\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper [list sinblah lsinhelper main]]
+pass_if_eq "$func" lsinhelper \
+ "stepping from \"sinblah\" back to \"lsinhelper\" ($id)"
+
+incr id
+set func [step_through $id [list lsinhelper main]]
+pass_if_eq "$func" sinblah16 \
+ "stepping from \"lsinhelper\" into \"sinblah16\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper [list sinblah16 lsinhelper main]]
+pass_if_eq "$func" lsinhelper \
+ "stepping from \"sinblah16\" back to \"lsinhelper\" ($id)"
+
+# 30
+incr id
+set func [step_through $id [list lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+ "stepping from \"lsinhelper\" into \"lsinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" sinblah [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah \
+ "stepping from \"lsinmips16\" into \"sinblah\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinmips16 \
+ [list sinblah lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+ "stepping from \"sinblah\" back to \"lsinmips16\" ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah16 \
+ "stepping from \"lsinmips16\" into \"sinblah16\" ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper \
+ [list sinblah16 lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+ "stepping from \"sinblah16\" back to \"lsinmips16\" ($id)"
+
+# 35
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah \
+ "stepping from \"lsinmips16\" into \"sinblah\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinblah lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinmips16 \
+ [list sinblah lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+ "stepping from \"sinblah\" back to \"lsinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" sinblah16 \
+ "stepping from \"lsinmips16\" into \"sinblah16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list sinblah16 lsinmips16 lsinhelper main]]
+set func [finish_if_ne $id "$func" lsinhelper \
+ [list sinblah16 lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinmips16 \
+ "stepping from \"sinblah16\" back to \"lsinmips16\" (indirectly) ($id)"
+
+incr id
+set func [step_through $id [list lsinmips16 lsinhelper main]]
+pass_if_eq "$func" lsinhelper \
+ "stepping from \"lsinmips16\" back to \"lsinhelper\" ($id)"
+
+# 40
+incr id
+set func [step_through $id [list lsinhelper main]]
+pass_if_eq "$func" main "stepping from \"lsinhelper\" back to \"main\" ($id)"