arc: Add prologue analysis
Add a prologue analysis that recognizes all instructions that may happen in compiler-generated prologue, including various stores, core register moves, subtraction and ENTER_S instruction that does a lot of prologue actions through microcode. Testcases cover various prologue scenarios, including instructions that are spread across multiple 16-bit encodings (for example there are 7 encodings of store instruction). gdb/ChangeLog: yyyy-mm-dd Anton Kolesov <anton.kolesov@synopsys.com> * arc-tdep.c (arc_frame_cache): Add support for prologue analysis. (arc_skip_prologue): Likewise. (arc_make_frame_cache): Likewise. (arc_pv_get_operand): New function. (arc_is_in_prologue): Likewise. (arc_analyze_prologue): Likewise. (arc_print_frame_cache): Likewise. (MAX_PROLOGUE_LENGTH): New constant. gdb/doc/ChangeLog: yyyy-mm-dd Anton Kolesov <anton.kolesov@synopsys.com> * gdb.texinfo (Synopsys ARC): Document "set debug arc 2". gdb/testsuite/ChangeLog: yyyy-mm-dd Anton Kolesov <anton.kolesov@synopsys.com> * gdb.arch/arc-analyze-prologue.S: New file. * gdb.arch/arc-analyze-prologue.exp: Likewise.
This commit is contained in:
parent
eea787570f
commit
fe5f7374be
|
@ -1,3 +1,14 @@
|
|||
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
|
||||
|
||||
* arc-tdep.c (arc_frame_cache): Add support for prologue analysis.
|
||||
(arc_skip_prologue): Likewise.
|
||||
(arc_make_frame_cache): Likewise.
|
||||
(arc_pv_get_operand): New function.
|
||||
(arc_is_in_prologue): Likewise.
|
||||
(arc_analyze_prologue): Likewise.
|
||||
(arc_print_frame_cache): Likewise.
|
||||
(MAX_PROLOGUE_LENGTH): New constant.
|
||||
|
||||
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
|
||||
|
||||
* configure.tgt: Add arc-insn.o.
|
||||
|
|
498
gdb/arc-tdep.c
498
gdb/arc-tdep.c
|
@ -28,6 +28,7 @@
|
|||
#include "gdbcore.h"
|
||||
#include "gdbcmd.h"
|
||||
#include "objfiles.h"
|
||||
#include "prologue-value.h"
|
||||
#include "trad-frame.h"
|
||||
|
||||
/* ARC header files. */
|
||||
|
@ -42,8 +43,7 @@
|
|||
#include "features/arc-v2.c"
|
||||
#include "features/arc-arcompact.c"
|
||||
|
||||
/* The frame unwind cache for the ARC. Current structure is a stub, because
|
||||
it should be filled in during the prologue analysis. */
|
||||
/* The frame unwind cache for ARC. */
|
||||
|
||||
struct arc_frame_cache
|
||||
{
|
||||
|
@ -52,7 +52,35 @@ struct arc_frame_cache
|
|||
frame. */
|
||||
CORE_ADDR prev_sp;
|
||||
|
||||
/* Store addresses for registers saved in prologue. */
|
||||
/* Register that is a base for this frame - FP for normal frame, SP for
|
||||
non-FP frames. */
|
||||
int frame_base_reg;
|
||||
|
||||
/* Offset from the previous SP to the current frame base. If GCC uses
|
||||
`SUB SP,SP,offset` to allocate space for local variables, then it will be
|
||||
done after setting up a frame pointer, but it still will be considered
|
||||
part of prologue, therefore SP will be lesser than FP at the end of the
|
||||
prologue analysis. In this case that would be an offset from old SP to a
|
||||
new FP. But in case of non-FP frames, frame base is an SP and thus that
|
||||
would be an offset from old SP to new SP. What is important is that this
|
||||
is an offset from old SP to a known register, so it can be used to find
|
||||
old SP.
|
||||
|
||||
Using FP is preferable, when possible, because SP can change in function
|
||||
body after prologue due to alloca, variadic arguments or other shenanigans.
|
||||
If that is the case in the caller frame, then PREV_SP will point to SP at
|
||||
the moment of function call, but it will be different from SP value at the
|
||||
end of the caller prologue. As a result it will not be possible to
|
||||
reconstruct caller's frame and go past it in the backtrace. Those things
|
||||
are unlikely to happen to FP - FP value at the moment of function call (as
|
||||
stored on stack in callee prologue) is also an FP value at the end of the
|
||||
caller's prologue. */
|
||||
|
||||
LONGEST frame_base_offset;
|
||||
|
||||
/* Store addresses for registers saved in prologue. During prologue analysis
|
||||
GDB stores offsets relatively to "old SP", then after old SP is evaluated,
|
||||
offsets are replaced with absolute addresses. */
|
||||
struct trad_frame_saved_reg *saved_regs;
|
||||
};
|
||||
|
||||
|
@ -117,6 +145,10 @@ static const char *const core_arcompact_register_names[] = {
|
|||
"lp_count", "reserved", "limm", "pcl",
|
||||
};
|
||||
|
||||
/* Functions are sorted in the order as they are used in the
|
||||
_initialize_arc_tdep (), which uses the same order as gdbarch.h. Static
|
||||
functions are defined before the first invocation. */
|
||||
|
||||
/* Returns an unsigned value of OPERAND_NUM in instruction INSN.
|
||||
For relative branch instructions returned value is an offset, not an actual
|
||||
branch target. */
|
||||
|
@ -227,10 +259,6 @@ arc_insn_get_memory_offset (const struct arc_instruction &insn)
|
|||
return value;
|
||||
}
|
||||
|
||||
/* Functions are sorted in the order as they are used in the
|
||||
_initialize_arc_tdep (), which uses the same order as gdbarch.h. Static
|
||||
functions are defined before the first invocation. */
|
||||
|
||||
CORE_ADDR
|
||||
arc_insn_get_branch_target (const struct arc_instruction &insn)
|
||||
{
|
||||
|
@ -913,6 +941,247 @@ arc_frame_base_address (struct frame_info *this_frame, void **prologue_cache)
|
|||
return (CORE_ADDR) get_frame_register_unsigned (this_frame, ARC_FP_REGNUM);
|
||||
}
|
||||
|
||||
/* Helper function that returns valid pv_t for an instruction operand:
|
||||
either a register or a constant. */
|
||||
|
||||
static pv_t
|
||||
arc_pv_get_operand (pv_t *regs, const struct arc_instruction &insn, int operand)
|
||||
{
|
||||
if (insn.operands[operand].kind == ARC_OPERAND_KIND_REG)
|
||||
return regs[insn.operands[operand].value];
|
||||
else
|
||||
return pv_constant (arc_insn_get_operand_value (insn, operand));
|
||||
}
|
||||
|
||||
/* Determine whether the given disassembled instruction may be part of a
|
||||
function prologue. If it is, the information in the frame unwind cache will
|
||||
be updated. */
|
||||
|
||||
static bool
|
||||
arc_is_in_prologue (struct gdbarch *gdbarch, const struct arc_instruction &insn,
|
||||
pv_t *regs, struct pv_area *stack)
|
||||
{
|
||||
/* It might be that currently analyzed address doesn't contain an
|
||||
instruction, hence INSN is not valid. It likely means that address points
|
||||
to a data, non-initialized memory, or middle of a 32-bit instruction. In
|
||||
practice this may happen if GDB connects to a remote target that has
|
||||
non-zeroed memory. GDB would read PC value and would try to analyze
|
||||
prologue, but there is no guarantee that memory contents at the address
|
||||
specified in PC is address is a valid instruction. There is not much that
|
||||
that can be done about that. */
|
||||
if (!insn.valid)
|
||||
return false;
|
||||
|
||||
/* Branch/jump or a predicated instruction. */
|
||||
if (insn.is_control_flow || insn.condition_code != ARC_CC_AL)
|
||||
return false;
|
||||
|
||||
/* Store of some register. May or may not update base address register. */
|
||||
if (insn.insn_class == STORE || insn.insn_class == PUSH)
|
||||
{
|
||||
/* There is definetely at least one operand - register/value being
|
||||
stored. */
|
||||
gdb_assert (insn.operands_count > 0);
|
||||
|
||||
/* Store at some constant address. */
|
||||
if (insn.operands_count > 1
|
||||
&& insn.operands[1].kind != ARC_OPERAND_KIND_REG)
|
||||
return false;
|
||||
|
||||
/* Writeback modes:
|
||||
Mode Address used Writeback value
|
||||
--------------------------------------------------
|
||||
No reg + offset no
|
||||
A/AW reg + offset reg + offset
|
||||
AB reg reg + offset
|
||||
AS reg + (offset << scaling) no
|
||||
|
||||
"PUSH reg" is an alias to "ST.AW reg, [SP, -4]" encoding. However
|
||||
16-bit PUSH_S is a distinct instruction encoding, where offset and
|
||||
base register are implied through opcode. */
|
||||
|
||||
/* Register with base memory address. */
|
||||
int base_reg = arc_insn_get_memory_base_reg (insn);
|
||||
|
||||
/* Address where to write. arc_insn_get_memory_offset returns scaled
|
||||
value for ARC_WRITEBACK_AS. */
|
||||
pv_t addr;
|
||||
if (insn.writeback_mode == ARC_WRITEBACK_AB)
|
||||
addr = regs[base_reg];
|
||||
else
|
||||
addr = pv_add_constant (regs[base_reg],
|
||||
arc_insn_get_memory_offset (insn));
|
||||
|
||||
if (pv_area_store_would_trash (stack, addr))
|
||||
return false;
|
||||
|
||||
if (insn.data_size_mode != ARC_SCALING_D)
|
||||
{
|
||||
/* Find the value being stored. */
|
||||
pv_t store_value = arc_pv_get_operand (regs, insn, 0);
|
||||
|
||||
/* What is the size of a the stored value? */
|
||||
CORE_ADDR size;
|
||||
if (insn.data_size_mode == ARC_SCALING_B)
|
||||
size = 1;
|
||||
else if (insn.data_size_mode == ARC_SCALING_H)
|
||||
size = 2;
|
||||
else
|
||||
size = ARC_REGISTER_SIZE;
|
||||
|
||||
pv_area_store (stack, addr, size, store_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (insn.operands[0].kind == ARC_OPERAND_KIND_REG)
|
||||
{
|
||||
/* If this is a double store, than write N+1 register as well. */
|
||||
pv_t store_value1 = regs[insn.operands[0].value];
|
||||
pv_t store_value2 = regs[insn.operands[0].value + 1];
|
||||
pv_area_store (stack, addr, ARC_REGISTER_SIZE, store_value1);
|
||||
pv_area_store (stack,
|
||||
pv_add_constant (addr, ARC_REGISTER_SIZE),
|
||||
ARC_REGISTER_SIZE, store_value2);
|
||||
}
|
||||
else
|
||||
{
|
||||
pv_t store_value
|
||||
= pv_constant (arc_insn_get_operand_value (insn, 0));
|
||||
pv_area_store (stack, addr, ARC_REGISTER_SIZE * 2, store_value);
|
||||
}
|
||||
}
|
||||
|
||||
/* Is base register updated? */
|
||||
if (insn.writeback_mode == ARC_WRITEBACK_A
|
||||
|| insn.writeback_mode == ARC_WRITEBACK_AB)
|
||||
regs[base_reg] = pv_add_constant (regs[base_reg],
|
||||
arc_insn_get_memory_offset (insn));
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (insn.insn_class == MOVE)
|
||||
{
|
||||
gdb_assert (insn.operands_count == 2);
|
||||
|
||||
/* Destination argument can be "0", so nothing will happen. */
|
||||
if (insn.operands[0].kind == ARC_OPERAND_KIND_REG)
|
||||
{
|
||||
int dst_regnum = insn.operands[0].value;
|
||||
regs[dst_regnum] = arc_pv_get_operand (regs, insn, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (insn.insn_class == SUB)
|
||||
{
|
||||
gdb_assert (insn.operands_count == 3);
|
||||
|
||||
/* SUB 0,b,c. */
|
||||
if (insn.operands[0].kind != ARC_OPERAND_KIND_REG)
|
||||
return true;
|
||||
|
||||
int dst_regnum = insn.operands[0].value;
|
||||
regs[dst_regnum] = pv_subtract (arc_pv_get_operand (regs, insn, 1),
|
||||
arc_pv_get_operand (regs, insn, 2));
|
||||
return true;
|
||||
}
|
||||
else if (insn.insn_class == ENTER)
|
||||
{
|
||||
/* ENTER_S is a prologue-in-instruction - it saves all callee-saved
|
||||
registers according to given arguments thus greatly reducing code
|
||||
size. Which registers will be actually saved depends on arguments.
|
||||
|
||||
ENTER_S {R13-...,FP,BLINK} stores registers in following order:
|
||||
|
||||
new SP ->
|
||||
BLINK
|
||||
R13
|
||||
R14
|
||||
R15
|
||||
...
|
||||
FP
|
||||
old SP ->
|
||||
|
||||
There are up to three arguments for this opcode, as presented by ARC
|
||||
disassembler:
|
||||
1) amount of general-purpose registers to be saved - this argument is
|
||||
always present even when it is 0;
|
||||
2) FP register number (27) if FP has to be stored, otherwise argument
|
||||
is not present;
|
||||
3) BLINK register number (31) if BLINK has to be stored, otherwise
|
||||
argument is not present. If both FP and BLINK are stored, then FP
|
||||
is present before BLINK in argument list. */
|
||||
gdb_assert (insn.operands_count > 0);
|
||||
|
||||
int regs_saved = arc_insn_get_operand_value (insn, 0);
|
||||
|
||||
bool is_fp_saved;
|
||||
if (insn.operands_count > 1)
|
||||
is_fp_saved = (insn.operands[1].value == ARC_FP_REGNUM);
|
||||
else
|
||||
is_fp_saved = false;
|
||||
|
||||
bool is_blink_saved;
|
||||
if (insn.operands_count > 1)
|
||||
is_blink_saved = (insn.operands[insn.operands_count - 1].value
|
||||
== ARC_BLINK_REGNUM);
|
||||
else
|
||||
is_blink_saved = false;
|
||||
|
||||
/* Amount of bytes to be allocated to store specified registers. */
|
||||
CORE_ADDR st_size = ((regs_saved + is_fp_saved + is_blink_saved)
|
||||
* ARC_REGISTER_SIZE);
|
||||
pv_t new_sp = pv_add_constant (regs[ARC_SP_REGNUM], -st_size);
|
||||
|
||||
/* Assume that if the last register (closest to new SP) can be written,
|
||||
then it is possible to write all of them. */
|
||||
if (pv_area_store_would_trash (stack, new_sp))
|
||||
return false;
|
||||
|
||||
/* Current store address. */
|
||||
pv_t addr = regs[ARC_SP_REGNUM];
|
||||
|
||||
if (is_fp_saved)
|
||||
{
|
||||
addr = pv_add_constant (addr, -ARC_REGISTER_SIZE);
|
||||
pv_area_store (stack, addr, ARC_REGISTER_SIZE, regs[ARC_FP_REGNUM]);
|
||||
}
|
||||
|
||||
/* Registers are stored in backward order: from GP (R26) to R13. */
|
||||
for (int i = ARC_R13_REGNUM + regs_saved - 1; i >= ARC_R13_REGNUM; i--)
|
||||
{
|
||||
addr = pv_add_constant (addr, -ARC_REGISTER_SIZE);
|
||||
pv_area_store (stack, addr, ARC_REGISTER_SIZE, regs[i]);
|
||||
}
|
||||
|
||||
if (is_blink_saved)
|
||||
{
|
||||
addr = pv_add_constant (addr, -ARC_REGISTER_SIZE);
|
||||
pv_area_store (stack, addr, ARC_REGISTER_SIZE,
|
||||
regs[ARC_BLINK_REGNUM]);
|
||||
}
|
||||
|
||||
gdb_assert (pv_is_identical (addr, new_sp));
|
||||
|
||||
regs[ARC_SP_REGNUM] = new_sp;
|
||||
|
||||
if (is_fp_saved)
|
||||
regs[ARC_FP_REGNUM] = regs[ARC_SP_REGNUM];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Some other architectures, like nds32 or arm, try to continue as far as
|
||||
possible when building a prologue cache (as opposed to when skipping
|
||||
prologue), so that cache will be as full as possible. However current
|
||||
code for ARC doesn't recognize some instructions that may modify SP, like
|
||||
ADD, AND, OR, etc, hence there is no way to guarantee that SP wasn't
|
||||
clobbered by the skipped instruction. Potential existence of extension
|
||||
instruction, which may do anything they want makes this even more complex,
|
||||
so it is just better to halt on a first unrecognized instruction. */
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy of gdb_buffered_insn_length_fprintf from disasm.c. */
|
||||
|
||||
static int ATTRIBUTE_PRINTF (2, 3)
|
||||
|
@ -937,6 +1206,146 @@ arc_disassemble_info (struct gdbarch *gdbarch)
|
|||
return di;
|
||||
}
|
||||
|
||||
/* Analyze the prologue and update the corresponding frame cache for the frame
|
||||
unwinder for unwinding frames that doesn't have debug info. In such
|
||||
situation GDB attempts to parse instructions in the prologue to understand
|
||||
where each register is saved.
|
||||
|
||||
If CACHE is not NULL, then it will be filled with information about saved
|
||||
registers.
|
||||
|
||||
There are several variations of prologue which GDB may encouter. "Full"
|
||||
prologue looks like this:
|
||||
|
||||
sub sp,sp,<imm> ; Space for variadic arguments.
|
||||
push blink ; Store return address.
|
||||
push r13 ; Store callee saved registers (up to R26/GP).
|
||||
push r14
|
||||
push fp ; Store frame pointer.
|
||||
mov fp,sp ; Update frame pointer.
|
||||
sub sp,sp,<imm> ; Create space for local vars on the stack.
|
||||
|
||||
Depending on compiler options lots of things may change:
|
||||
|
||||
1) BLINK is not saved in leaf functions.
|
||||
2) Frame pointer is not saved and updated if -fomit-frame-pointer is used.
|
||||
3) 16-bit versions of those instructions may be used.
|
||||
4) Instead of a sequence of several push'es, compiler may instead prefer to
|
||||
do one subtract on stack pointer and then store registers using normal
|
||||
store, that doesn't update SP. Like this:
|
||||
|
||||
|
||||
sub sp,sp,8 ; Create space for calee-saved registers.
|
||||
st r13,[sp,4] ; Store callee saved registers (up to R26/GP).
|
||||
st r14,[sp,0]
|
||||
|
||||
5) ENTER_S instruction can encode most of prologue sequence in one
|
||||
instruction (except for those subtracts for variadic arguments and local
|
||||
variables).
|
||||
6) GCC may use "millicode" functions from libgcc to store callee-saved
|
||||
registers with minimal code-size requirements. This function currently
|
||||
doesn't support this.
|
||||
|
||||
ENTRYPOINT is a function entry point where prologue starts.
|
||||
|
||||
LIMIT_PC is a maximum possible end address of prologue (meaning address
|
||||
of first instruction after the prologue). It might also point to the middle
|
||||
of prologue if execution has been stopped by the breakpoint at this address
|
||||
- in this case debugger should analyze prologue only up to this address,
|
||||
because further instructions haven't been executed yet.
|
||||
|
||||
Returns address of the first instruction after the prologue. */
|
||||
|
||||
static CORE_ADDR
|
||||
arc_analyze_prologue (struct gdbarch *gdbarch, const CORE_ADDR entrypoint,
|
||||
const CORE_ADDR limit_pc, struct arc_frame_cache *cache)
|
||||
{
|
||||
if (arc_debug)
|
||||
debug_printf ("arc: analyze_prologue (entrypoint=%s, limit_pc=%s)\n",
|
||||
paddress (gdbarch, entrypoint),
|
||||
paddress (gdbarch, limit_pc));
|
||||
|
||||
/* Prologue values. Only core registers can be stored. */
|
||||
pv_t regs[ARC_LAST_CORE_REGNUM + 1];
|
||||
for (int i = 0; i <= ARC_LAST_CORE_REGNUM; i++)
|
||||
regs[i] = pv_register (i, 0);
|
||||
struct pv_area *stack = make_pv_area (ARC_SP_REGNUM,
|
||||
gdbarch_addr_bit (gdbarch));
|
||||
struct cleanup *back_to = make_cleanup_free_pv_area (stack);
|
||||
|
||||
CORE_ADDR current_prologue_end = entrypoint;
|
||||
|
||||
/* Look at each instruction in the prologue. */
|
||||
while (current_prologue_end < limit_pc)
|
||||
{
|
||||
struct arc_instruction insn;
|
||||
struct disassemble_info di = arc_disassemble_info (gdbarch);
|
||||
arc_insn_decode (current_prologue_end, &di, arc_delayed_print_insn,
|
||||
&insn);
|
||||
|
||||
if (arc_debug >= 2)
|
||||
arc_insn_dump (insn);
|
||||
|
||||
/* If this instruction is in the prologue, fields in the cache will be
|
||||
updated, and the saved registers mask may be updated. */
|
||||
if (!arc_is_in_prologue (gdbarch, insn, regs, stack))
|
||||
{
|
||||
/* Found an instruction that is not in the prologue. */
|
||||
if (arc_debug)
|
||||
debug_printf ("arc: End of prologue reached at address %s\n",
|
||||
paddress (gdbarch, insn.address));
|
||||
break;
|
||||
}
|
||||
|
||||
current_prologue_end = arc_insn_get_linear_next_pc (insn);
|
||||
}
|
||||
|
||||
if (cache != NULL)
|
||||
{
|
||||
/* Figure out if it is a frame pointer or just a stack pointer. */
|
||||
if (pv_is_register (regs[ARC_FP_REGNUM], ARC_SP_REGNUM))
|
||||
{
|
||||
cache->frame_base_reg = ARC_FP_REGNUM;
|
||||
cache->frame_base_offset = -regs[ARC_FP_REGNUM].k;
|
||||
}
|
||||
else
|
||||
{
|
||||
cache->frame_base_reg = ARC_SP_REGNUM;
|
||||
cache->frame_base_offset = -regs[ARC_SP_REGNUM].k;
|
||||
}
|
||||
|
||||
/* Assign offset from old SP to all saved registers. */
|
||||
for (int i = 0; i <= ARC_LAST_CORE_REGNUM; i++)
|
||||
{
|
||||
CORE_ADDR offset;
|
||||
if (pv_area_find_reg (stack, gdbarch, i, &offset))
|
||||
cache->saved_regs[i].addr = offset;
|
||||
}
|
||||
}
|
||||
|
||||
do_cleanups (back_to);
|
||||
return current_prologue_end;
|
||||
}
|
||||
|
||||
/* Estimated maximum prologue length in bytes. This should include:
|
||||
1) Store instruction for each callee-saved register (R25 - R13 + 1)
|
||||
2) Two instructions for FP
|
||||
3) One for BLINK
|
||||
4) Three substract instructions for SP (for variadic args, for
|
||||
callee saved regs and for local vars) and assuming that those SUB use
|
||||
long-immediate (hence double length).
|
||||
5) Stores of arguments registers are considered part of prologue too
|
||||
(R7 - R1 + 1).
|
||||
This is quite an extreme case, because even with -O0 GCC will collapse first
|
||||
two SUBs into one and long immediate values are quite unlikely to appear in
|
||||
this case, but still better to overshoot a bit - prologue analysis will
|
||||
anyway stop at the first instruction that doesn't fit prologue, so this
|
||||
limit will be rarely reached. */
|
||||
|
||||
const static int MAX_PROLOGUE_LENGTH
|
||||
= 4 * (ARC_R25_REGNUM - ARC_R13_REGNUM + 1 + 2 + 1 + 6
|
||||
+ ARC_LAST_ARG_REGNUM - ARC_FIRST_ARG_REGNUM + 1);
|
||||
|
||||
/* Implement the "skip_prologue" gdbarch method.
|
||||
|
||||
Skip the prologue for the function at PC. This is done by checking from
|
||||
|
@ -966,15 +1375,19 @@ arc_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
|
|||
/* No prologue info in symbol table, have to analyze prologue. */
|
||||
|
||||
/* Find an upper limit on the function prologue using the debug
|
||||
information. If the debug information could not be used to provide that
|
||||
bound, then pass 0 and arc_scan_prologue will estimate value itself. */
|
||||
information. If there is no debug information about prologue end, then
|
||||
skip_prologue_using_sal will return 0. */
|
||||
CORE_ADDR limit_pc = skip_prologue_using_sal (gdbarch, pc);
|
||||
/* We don't have a proper analyze_prologue function yet, but its result
|
||||
should be returned here. Currently GDB will just stop at the first
|
||||
instruction of function if debug information doesn't have prologue info;
|
||||
and if there is a debug info about prologue - this code path will not be
|
||||
taken at all. */
|
||||
return (limit_pc == 0 ? pc : limit_pc);
|
||||
|
||||
/* If there is no debug information at all, it is required to give some
|
||||
semi-arbitrary hard limit on amount of bytes to scan during prologue
|
||||
analysis. */
|
||||
if (limit_pc == 0)
|
||||
limit_pc = pc + MAX_PROLOGUE_LENGTH;
|
||||
|
||||
/* Find the address of the first instruction after the prologue by scanning
|
||||
through it - no other information is needed, so pass NULL as a cache. */
|
||||
return arc_analyze_prologue (gdbarch, pc, limit_pc, NULL);
|
||||
}
|
||||
|
||||
/* Implement the "print_insn" gdbarch method.
|
||||
|
@ -1114,6 +1527,28 @@ arc_frame_align (struct gdbarch *gdbarch, CORE_ADDR sp)
|
|||
return align_down (sp, 4);
|
||||
}
|
||||
|
||||
/* Dump the frame info. Used for internal debugging only. */
|
||||
|
||||
static void
|
||||
arc_print_frame_cache (struct gdbarch *gdbarch, char *message,
|
||||
struct arc_frame_cache *cache, int addresses_known)
|
||||
{
|
||||
debug_printf ("arc: frame_info %s\n", message);
|
||||
debug_printf ("arc: prev_sp = %s\n", paddress (gdbarch, cache->prev_sp));
|
||||
debug_printf ("arc: frame_base_reg = %i\n", cache->frame_base_reg);
|
||||
debug_printf ("arc: frame_base_offset = %s\n",
|
||||
plongest (cache->frame_base_offset));
|
||||
|
||||
for (int i = 0; i <= ARC_BLINK_REGNUM; i++)
|
||||
{
|
||||
if (trad_frame_addr_p (cache->saved_regs, i))
|
||||
debug_printf ("arc: saved register %s at %s %s\n",
|
||||
gdbarch_register_name (gdbarch, i),
|
||||
(addresses_known) ? "address" : "offset",
|
||||
paddress (gdbarch, cache->saved_regs[i].addr));
|
||||
}
|
||||
}
|
||||
|
||||
/* Frame unwinder for normal frames. */
|
||||
|
||||
static struct arc_frame_cache *
|
||||
|
@ -1125,12 +1560,11 @@ arc_make_frame_cache (struct frame_info *this_frame)
|
|||
struct gdbarch *gdbarch = get_frame_arch (this_frame);
|
||||
|
||||
CORE_ADDR block_addr = get_frame_address_in_block (this_frame);
|
||||
CORE_ADDR prev_pc = get_frame_pc (this_frame);
|
||||
|
||||
CORE_ADDR entrypoint, prologue_end;
|
||||
if (find_pc_partial_function (block_addr, NULL, &entrypoint, &prologue_end))
|
||||
{
|
||||
struct symtab_and_line sal = find_pc_line (entrypoint, 0);
|
||||
CORE_ADDR prev_pc = get_frame_pc (this_frame);
|
||||
if (sal.line == 0)
|
||||
/* No line info so use current PC. */
|
||||
prologue_end = prev_pc;
|
||||
|
@ -1142,18 +1576,42 @@ arc_make_frame_cache (struct frame_info *this_frame)
|
|||
}
|
||||
else
|
||||
{
|
||||
/* If find_pc_partial_function returned nothing then there is no symbol
|
||||
information at all for this PC. Currently it is assumed in this case
|
||||
that current PC is entrypoint to function and try to construct the
|
||||
frame from that. This is, probably, suboptimal, for example ARM
|
||||
assumes in this case that program is inside the normal frame (with
|
||||
frame pointer). ARC, perhaps, should try to do the same. */
|
||||
entrypoint = get_frame_register_unsigned (this_frame,
|
||||
gdbarch_pc_regnum (gdbarch));
|
||||
prologue_end = 0;
|
||||
prologue_end = entrypoint + MAX_PROLOGUE_LENGTH;
|
||||
}
|
||||
|
||||
/* Allocate new frame cache instance and space for saved register info.
|
||||
* FRAME_OBSTACK_ZALLOC will initialize fields to zeroes. */
|
||||
FRAME_OBSTACK_ZALLOC will initialize fields to zeroes. */
|
||||
struct arc_frame_cache *cache
|
||||
= FRAME_OBSTACK_ZALLOC (struct arc_frame_cache);
|
||||
cache->saved_regs = trad_frame_alloc_saved_regs (this_frame);
|
||||
|
||||
/* Should call analyze_prologue here, when it will be implemented. */
|
||||
arc_analyze_prologue (gdbarch, entrypoint, prologue_end, cache);
|
||||
|
||||
if (arc_debug)
|
||||
arc_print_frame_cache (gdbarch, "after prologue", cache, false);
|
||||
|
||||
CORE_ADDR unwound_fb = get_frame_register_unsigned (this_frame,
|
||||
cache->frame_base_reg);
|
||||
if (unwound_fb == 0)
|
||||
return cache;
|
||||
cache->prev_sp = unwound_fb + cache->frame_base_offset;
|
||||
|
||||
for (int i = 0; i <= ARC_LAST_CORE_REGNUM; i++)
|
||||
{
|
||||
if (trad_frame_addr_p (cache->saved_regs, i))
|
||||
cache->saved_regs[i].addr += cache->prev_sp;
|
||||
}
|
||||
|
||||
if (arc_debug)
|
||||
arc_print_frame_cache (gdbarch, "after previous SP found", cache, true);
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
|
||||
|
||||
* gdb.texinfo (Synopsys ARC): Document "set debug arc 2".
|
||||
|
||||
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
|
||||
|
||||
* gdb.texinfo (Synopsys ARC): Add "maint print arc arc-instruction".
|
||||
|
|
|
@ -22098,8 +22098,7 @@ acceptable commands.
|
|||
@item set debug arc
|
||||
@kindex set debug arc
|
||||
Control the level of ARC specific debug messages. Use 0 for no messages (the
|
||||
default) and 1 for debug messages. At present higher values offer no further
|
||||
messages.
|
||||
default), 1 for debug messages, and 2 for even more debug messages.
|
||||
|
||||
@item show debug arc
|
||||
@kindex show debug arc
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
|
||||
|
||||
* gdb.arch/arc-analyze-prologue.S: New file.
|
||||
* gdb.arch/arc-analyze-prologue.exp: Likewise.
|
||||
|
||||
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
|
||||
|
||||
* gdb.arch/arc-decode-insn.S: New file.
|
||||
|
|
|
@ -0,0 +1,903 @@
|
|||
; This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
; Copyright 2017 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/>.
|
||||
|
||||
.section .data
|
||||
some_variable:
|
||||
.long 0xdeadbeef
|
||||
|
||||
.section .text
|
||||
.global main
|
||||
.type main, @function
|
||||
|
||||
; Standard prologue.
|
||||
|
||||
.align 4
|
||||
standard_prologue:
|
||||
push blink
|
||||
sub sp,sp,12
|
||||
st r13, [sp, 0]
|
||||
st r14, [sp, 4]
|
||||
st r18, [sp, 8]
|
||||
add r0, r1, r2
|
||||
ld r18, [sp, 8]
|
||||
ld r14, [sp, 4]
|
||||
ld r13, [sp, 0]
|
||||
add sp,sp,12
|
||||
pop blink
|
||||
j [blink]
|
||||
|
||||
; Standard prologue using short instructions.
|
||||
|
||||
.align 4
|
||||
mini_prologue:
|
||||
push_s blink
|
||||
sub_s sp,sp,12
|
||||
; ST_S can store only some of the core registers.
|
||||
st_s r13, [sp, 0]
|
||||
st_s r15, [sp, 4]
|
||||
st_s r14, [sp, 8]
|
||||
add r0, r1, r2
|
||||
add sp,sp,16
|
||||
j [blink]
|
||||
|
||||
; Standard prologue without `sub sp,sp,INTEGER`.
|
||||
|
||||
.align 4
|
||||
no_subsp_prologue:
|
||||
push blink
|
||||
push r13
|
||||
push r20
|
||||
push r25
|
||||
add r0, r1, r2
|
||||
pop r25
|
||||
pop r20
|
||||
pop r13
|
||||
pop blink
|
||||
j [blink]
|
||||
|
||||
; Standard prologue of leaf function.
|
||||
|
||||
.align 4
|
||||
leaf_prologue:
|
||||
sub sp,sp,8
|
||||
st r13, [sp, 0]
|
||||
st r15, [sp, 4]
|
||||
add r0, r1, r2
|
||||
ld r13, [sp, 0]
|
||||
ld r15, [sp, 4]
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
; Prologue with `push fp`.
|
||||
|
||||
.align 4
|
||||
pushfp_prologue:
|
||||
push r13
|
||||
push r14
|
||||
push fp
|
||||
; mov fp,sp is part of prologue, but this test will not verify that.
|
||||
; It will be checked later in the "arg_regs_fp" test.
|
||||
mov fp, sp
|
||||
add r0, r1, r2
|
||||
pop fp
|
||||
pop r14
|
||||
pop r13
|
||||
j [blink]
|
||||
|
||||
; Prologue with frame pointer and store relative to FP.
|
||||
|
||||
.align 4
|
||||
fp_prologue_with_store:
|
||||
push r13
|
||||
push r14
|
||||
push fp
|
||||
mov fp, sp
|
||||
sub_s sp,sp,4
|
||||
st r15,[fp,-4]
|
||||
add r0, r1, r2
|
||||
pop r15
|
||||
pop fp
|
||||
pop r14
|
||||
pop r13
|
||||
j [blink]
|
||||
|
||||
; Verify that store of the non-callee saved registers is not part of prologue.
|
||||
; Repeat this test for multiple registers, to check boundaries. Also check
|
||||
; with both ST and PUSH (aka ST.AW). We have to use multiple functions for
|
||||
; this, because GDB would stop analisys at the first instruction that is not
|
||||
; part of prologue.
|
||||
|
||||
.align 4
|
||||
noncallee_saved_regs_r12_st:
|
||||
sub sp,sp,8
|
||||
st r13, [sp, 4]
|
||||
st r12, [sp, 0]
|
||||
add r0, r1, r2
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
.align 4
|
||||
noncallee_saved_regs_r12_push:
|
||||
push r13
|
||||
push r12
|
||||
add r0, r1, r2
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
.align 4
|
||||
noncallee_saved_regs_r2_push:
|
||||
push r13
|
||||
push r2
|
||||
add r0, r1, r2
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
.align 4
|
||||
noncallee_saved_regs_gp_push:
|
||||
push r25
|
||||
push gp
|
||||
add r0, r1, r2
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
; LP_COUNT is treated like a normal register.
|
||||
|
||||
.align 4
|
||||
noncallee_saved_regs_lp_count:
|
||||
push r25
|
||||
push lp_count
|
||||
add r0, r1, r2
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
; BLINK is saved, but after an instruction that is not part of prologue.
|
||||
; Currently arc_analyze_prologue stops analisys at the first intstruction
|
||||
; that is not a part of prologue. This might be not the best way, but it is
|
||||
; what it is right now, so this test confirms this.
|
||||
|
||||
.align 4
|
||||
noncallee_saved_regs_blink_out_of_prologue:
|
||||
push r25
|
||||
push gp
|
||||
push blink
|
||||
add r0, r1, r2
|
||||
j.d [blink]
|
||||
add sp,sp,12
|
||||
|
||||
; Saving arguments register via FP.
|
||||
|
||||
.align 4
|
||||
arg_regs_fp:
|
||||
push fp
|
||||
mov fp, sp
|
||||
sub sp, sp, 16
|
||||
st r0, [fp, -4]
|
||||
st r1, [fp, -8]
|
||||
st r7, [fp, -12]
|
||||
st r8, [fp, -16]
|
||||
add r0, r1, r2
|
||||
add sp,sp,16
|
||||
pop fp
|
||||
j [blink]
|
||||
|
||||
; Like the previous, but with mov_s.
|
||||
|
||||
.align 4
|
||||
arg_regs_fp_mov_s:
|
||||
push fp
|
||||
mov_s fp, sp
|
||||
sub sp, sp, 8
|
||||
st r0, [fp, -4]
|
||||
; Not part of the prologue.
|
||||
st r8, [fp, -8]
|
||||
add r0, r1, r2
|
||||
add sp,sp,8
|
||||
pop fp
|
||||
j [blink]
|
||||
|
||||
; Saving arguments register without FP.
|
||||
|
||||
.align 4
|
||||
arg_regs_sp:
|
||||
sub sp, sp, 24
|
||||
st r0, [sp, 0]
|
||||
st r1, [sp, 4]
|
||||
st r7, [sp, 8]
|
||||
; Normally that would be done before saving args, but it is used as a
|
||||
; marker that saving arguments relatively to SP is considered part of
|
||||
; prologue.
|
||||
st r13, [sp, 16]
|
||||
; Not part of the prologue.
|
||||
st r8, [sp, 12]
|
||||
st r14, [sp, 20]
|
||||
add r0, r1, r2
|
||||
j.d [blink]
|
||||
add sp,sp,24
|
||||
|
||||
; ENTER_S that does nothing.
|
||||
|
||||
.align 4
|
||||
enter_s_nop:
|
||||
; Effectively a nop.
|
||||
enter_s 0
|
||||
add r0,r1,r2
|
||||
j [blink]
|
||||
|
||||
; ENTER_S that stores BLINK.
|
||||
|
||||
.align 4
|
||||
enter_s_blink:
|
||||
enter_s 32
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,4
|
||||
|
||||
; ENTER_S that stores FP.
|
||||
|
||||
.align 4
|
||||
enter_s_fp:
|
||||
enter_s 16
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,4
|
||||
|
||||
; ENTER_S that stores R13, FP and BLINK.
|
||||
|
||||
.align 4
|
||||
enter_s_r13:
|
||||
enter_s (32 + 16 + 1)
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,12
|
||||
|
||||
; ENTER_S that stores R13-R15
|
||||
|
||||
.align 4
|
||||
enter_s_r15:
|
||||
enter_s 3
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,12
|
||||
|
||||
; ENTER_S that stores everything it could.
|
||||
|
||||
.align 4
|
||||
enter_s_all:
|
||||
enter_s (32 + 16 + 14)
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,64
|
||||
|
||||
; Deeper nesting.
|
||||
|
||||
.align 4
|
||||
nested_prologue_inner:
|
||||
sub sp,sp,8
|
||||
st r18, [sp, 4]
|
||||
st r13, [sp, 0]
|
||||
add r0, r1, r2
|
||||
ld r18, [sp, 4]
|
||||
ld r13, [sp, 0]
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
.align 4
|
||||
nested_prologue_outer:
|
||||
push blink
|
||||
sub sp,sp,8
|
||||
st r14, [sp, 0]
|
||||
st r15, [sp, 4]
|
||||
bl @nested_prologue_inner
|
||||
add r0, r1, r2
|
||||
ld r14, [sp, 0]
|
||||
ld r15, [sp, 4]
|
||||
add sp,sp,8
|
||||
pop blink
|
||||
j [blink]
|
||||
|
||||
; Prologue with maximum length.
|
||||
; Expressions like (0xFFFFFFFF + 25) force assembler to use long immediate
|
||||
; even for values that don't need it, thus letting us test maksimum prologue
|
||||
; length without having huge frames.
|
||||
.align 4
|
||||
max_length_prologue:
|
||||
; Variadic args
|
||||
sub sp,sp,(0xFFFFFFFF + 25) ; 24 bytes
|
||||
push blink
|
||||
; Allocate space for 13 callee-saved and 8 arg regs.
|
||||
sub sp,sp,(0xFFFFFFFF + 1 + 21 * 4)
|
||||
st r13, [sp, 0]
|
||||
st r14, [sp, 4]
|
||||
st r15, [sp, 8]
|
||||
st r16, [sp, 12]
|
||||
st r17, [sp, 16]
|
||||
st r18, [sp, 20]
|
||||
st r19, [sp, 24]
|
||||
st r20, [sp, 28]
|
||||
st r21, [sp, 32]
|
||||
st r22, [sp, 36]
|
||||
st r23, [sp, 40]
|
||||
st r24, [sp, 44]
|
||||
st r25, [sp, 48]
|
||||
st r0, [sp, 52]
|
||||
st r1, [sp, 56]
|
||||
st r2, [sp, 60]
|
||||
st r3, [sp, 64]
|
||||
st r4, [sp, 68]
|
||||
st r5, [sp, 72]
|
||||
st r6, [sp, 76]
|
||||
st r7, [sp, 80]
|
||||
push fp
|
||||
mov fp,sp
|
||||
sub sp,sp,(0xFFFFFFFF + 1 + 16) ; Space for local variables.
|
||||
; End of prologue.
|
||||
add sp,sp,24 + 21 * 4 + 16
|
||||
j [blink]
|
||||
|
||||
; Few tests that test that prologue analysis stops at branch. There are four
|
||||
; types of "branches": conditional and non-conditional, relative branches and
|
||||
; absolute jumps.
|
||||
|
||||
.align 4
|
||||
branch_in_prologue:
|
||||
push r13
|
||||
b @.L1
|
||||
; This store on stack is not a prologue.
|
||||
push r14
|
||||
.L1:
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,4
|
||||
|
||||
.align 4
|
||||
cond_branch_in_prologue:
|
||||
sub_s sp,sp,8
|
||||
st_s r13,[sp,4]
|
||||
; Doesn't matter if branch is taken or not.
|
||||
breq r0,r1,@.L2
|
||||
; This store on stack is not a prologue.
|
||||
st_s r14,[sp,0]
|
||||
.L2:
|
||||
add r0,r1,r2
|
||||
pop fp
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
.align 4
|
||||
jump_in_prologue:
|
||||
push r13
|
||||
j @.L3
|
||||
; This store on stack is not a prologue.
|
||||
push r14
|
||||
.L3:
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,4
|
||||
|
||||
.align 4
|
||||
cond_jump_in_prologue:
|
||||
sub_s sp,sp,8
|
||||
st_s r13,[sp,4]
|
||||
; It doesn't matter if jump is taken or not - prologue analysis has to
|
||||
; stop before `jeq` in any case.
|
||||
jeq @.L4
|
||||
; This store on stack is not a prologue.
|
||||
st_s r14,[sp,0]
|
||||
.L4:
|
||||
add r0,r1,r2
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
.align 4
|
||||
predicated_insn:
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,8]
|
||||
st_s r15,[sp,0]
|
||||
; Use SUB SP,SP,0 because it is otherwise a valid instruction for
|
||||
; prologue, so it will halt analysis purely because of its predicate.
|
||||
sub.eq sp,sp,0 ; This is not a prologue anymore.
|
||||
st_s r14,[sp,4]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Loops should halt prologue analysis.
|
||||
|
||||
.align 4
|
||||
loop_in_prologue:
|
||||
push r25
|
||||
push lp_count
|
||||
mov lp_count, 4
|
||||
lp @.Lloop_end1
|
||||
push r26 ; Not part of prologue.
|
||||
add r0, r1, r2
|
||||
.Lloop_end1:
|
||||
add r1, r1, r2
|
||||
pop r26
|
||||
add sp,sp,8
|
||||
pop r25
|
||||
j [blink]
|
||||
|
||||
; Store of a constant value (not a register).
|
||||
|
||||
.align 4
|
||||
store_constant:
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,8]
|
||||
st 0xdeadbeef,[sp,0]
|
||||
st_s r14,[sp,4]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Test that store to immediate address halts prologue analysis.
|
||||
.align 4
|
||||
st_c_limm:
|
||||
push r15
|
||||
st r14,[@some_variable]
|
||||
push r13
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Store with AB writeback mode.
|
||||
|
||||
.align 4
|
||||
st_ab_writeback:
|
||||
sub sp,sp,8
|
||||
st r13,[sp,4]
|
||||
st.ab r14,[sp,-4]
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of a word with AS writeback mode.
|
||||
|
||||
.align 4
|
||||
st_as_writeback:
|
||||
sub sp,sp,12
|
||||
st r13,[sp,8]
|
||||
st.as r14,[sp,1] ; ST.AS, hence address is (offset << 2).
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of a halfword with AS writeback mode.
|
||||
|
||||
.align 4
|
||||
sth_as_writeback:
|
||||
sub sp,sp,12
|
||||
st r13,[sp,8]
|
||||
sth.as r14,[sp,2] ; STH.AS, hence address is (offset << 1).
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of a double word with AS writeback mode. Shift is still 2, like ST!
|
||||
|
||||
.align 4
|
||||
std_as_writeback:
|
||||
sub sp,sp,16
|
||||
st r13,[sp,12]
|
||||
#ifdef __ARC_LL64__
|
||||
std.as r14,[sp,1] ; STD.AS, hence address is (offset << 2).
|
||||
#else
|
||||
st.as r14,[sp,1] ; STD.AS, hence address is (offset << 2).
|
||||
st.as r15,[sp,2] ; STD.AS, hence address is (offset << 2).
|
||||
#endif
|
||||
st r16,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of the halfword. R14 will not be reported as "saved".
|
||||
|
||||
.align 4
|
||||
st_halfword:
|
||||
sub sp,sp,12
|
||||
st r13,[sp,8]
|
||||
sth r14,[sp,4]
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of the halfword. R14 will not be reported as "saved".
|
||||
|
||||
.align 4
|
||||
sts_halfword:
|
||||
sub sp,sp,12
|
||||
st r13,[sp,8]
|
||||
mov r13,sp
|
||||
sth_s r14,[r13,4]
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of the byte. R14 will not be reported as "saved".
|
||||
|
||||
.align 4
|
||||
st_byte:
|
||||
sub sp,sp,12
|
||||
st r13,[sp,8]
|
||||
stb r14,[sp,4]
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of the byte. R14 will not be reported as "saved".
|
||||
|
||||
.align 4
|
||||
sts_byte:
|
||||
sub sp,sp,12
|
||||
st r13,[sp,8]
|
||||
mov r13,sp
|
||||
stb_s r14,[r13,4]
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store of the byte. R14 will not be reported as "saved".
|
||||
|
||||
.align 4
|
||||
sts_byte_sp:
|
||||
sub sp,sp,12
|
||||
st r13,[sp,8]
|
||||
stb_s r14,[sp,4]
|
||||
st r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Double word store, optionally available for ARC HS.
|
||||
|
||||
.align 4
|
||||
st_double:
|
||||
sub sp,sp,8
|
||||
#ifdef __ARC_LL64__
|
||||
std r14,[sp,0]
|
||||
std.aw r18,[sp,-8]
|
||||
std.aw 0xdeadbeef,[sp,-8]
|
||||
#else
|
||||
st r14,[sp,0]
|
||||
st r15,[sp,4]
|
||||
st.aw r19,[sp,-4]
|
||||
st.aw r18,[sp,-4]
|
||||
sub sp,sp,8
|
||||
#endif
|
||||
add sp,sp,24
|
||||
j [blink]
|
||||
|
||||
; Store relative to some register with a known value.
|
||||
|
||||
.align 4
|
||||
r_relative_store:
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,8]
|
||||
mov r13,sp
|
||||
; Check for both mov and mov_s in one testcase.
|
||||
mov_s r12,r13
|
||||
st r15,[r12,0]
|
||||
st_s r14,[sp,4]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store relative to some register with a known value using sub.
|
||||
; Like a previous test, but register is assigned via sub, instead of mov.
|
||||
|
||||
.align 4
|
||||
r_relative_sub_store:
|
||||
; Following is a complicated way to construct frame like this:
|
||||
; sub_s sp,sp,12
|
||||
; st_s r13,[sp,8]
|
||||
; st_s r14,[sp,4]
|
||||
; st_s r15,[sp,0]
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,8]
|
||||
sub r13,sp,4
|
||||
st r14,[r13,8]
|
||||
st_s r15,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Like r_relative_store, but using st_s c,[b,u7] which has different opcode.
|
||||
|
||||
.align 4
|
||||
r_relative_store_st_s:
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,8]
|
||||
mov r13,sp
|
||||
st_s r15,[r13,4]
|
||||
st_s r14,[sp,0]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store relative to some register with a unknown value.
|
||||
|
||||
.align 4
|
||||
r_relative_store_unknown:
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,8]
|
||||
st r15,[gp,0] ; GP value is not relative to SP.
|
||||
st_s r14,[sp,4]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Store relative to some register with a unknown value, using st_s r0,[gp,s11].
|
||||
|
||||
.align 4
|
||||
st_s_r0gp:
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,8]
|
||||
st_s r0,[gp,0] ; GP value is not relative to SP.
|
||||
st_s r14,[sp,4]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Check prologue that uses `push_s RR` instructions. `push_s b` and `push_s
|
||||
; blink` use slightly different subopcodes.
|
||||
|
||||
.align 4
|
||||
push_s_prologue:
|
||||
push_s r12
|
||||
push_s r0
|
||||
push_s r3
|
||||
push_s r13
|
||||
push_s r1
|
||||
push_s r14
|
||||
push_s r15
|
||||
push_s r2
|
||||
push_s blink ; Also tested in mini_prologue ().
|
||||
add sp,sp,(4 * 9)
|
||||
j [blink]
|
||||
|
||||
; Check for SUB_S c,b,u3 presence - it doesn't affect prologue.
|
||||
|
||||
.align 4
|
||||
sub_s_cbu3:
|
||||
push_s r13
|
||||
sub_s r0,r1,3
|
||||
push_s r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Check for SUB_S b,b,c presence - it doesn't affect prologue.
|
||||
|
||||
.align 4
|
||||
sub_s_bbc:
|
||||
push_s r13
|
||||
sub_s r0,r0,r1
|
||||
push_s r0
|
||||
push_s r1
|
||||
push_s r14
|
||||
add sp,sp,16
|
||||
j [blink]
|
||||
|
||||
; Check for SUB_S b,b,u5.
|
||||
|
||||
.align 4
|
||||
sub_s_bbu5:
|
||||
push_s r13
|
||||
sub_s r2,r2,14
|
||||
push_s r2
|
||||
push_s r14
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Check for SUB 0,b,c, which is effectively a noop (but it can set status
|
||||
; flags). It shouldn't stop prologue analysis.
|
||||
|
||||
.align 4
|
||||
sub_0bc:
|
||||
push_s r13
|
||||
sub 0,r1,r2
|
||||
sub.f 0,r3,r4
|
||||
push_s r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Check for SUB a,limm,c.
|
||||
|
||||
.align 4
|
||||
sub_alimmb:
|
||||
push_s r13
|
||||
sub r13,0xdeadbeef,r14
|
||||
push_s r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Check for sub_s.ne b,b,b. Has a condition code, hence should halt prologue.
|
||||
|
||||
.align 4
|
||||
sub_s_ne_bbb:
|
||||
push_s r13
|
||||
sub_s.ne r13,r13,r13
|
||||
push_s r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Check MOV that uses LIMM values.
|
||||
|
||||
.align 4
|
||||
mov_limm:
|
||||
push_s r13
|
||||
mov r13,0xdeadbeef
|
||||
push_s r14
|
||||
add sp,sp,4
|
||||
pop_s r13
|
||||
j [blink]
|
||||
|
||||
; Check MOV 0,c.
|
||||
|
||||
.align 4
|
||||
mov0c_limm:
|
||||
push_s r13
|
||||
mov 0,r13
|
||||
push_s r14
|
||||
add sp,sp,4
|
||||
pop_s r13
|
||||
j [blink]
|
||||
|
||||
; Check that MOV_S h,s3 doesn't prevent prologue analysis.
|
||||
|
||||
.align 4
|
||||
mov_s_hs3:
|
||||
push_s r13
|
||||
mov_s r5,1
|
||||
push_s r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Check that MOV_S b,u8 doesn't prevent prologue analysis.
|
||||
|
||||
.align 4
|
||||
mov_s_bu8:
|
||||
push_s r13
|
||||
mov_s r12,250
|
||||
push_s r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Check that `mov_s.ne b,h` halts prologue analysis.
|
||||
|
||||
.align 4
|
||||
mov_s_ne_bh:
|
||||
push_s r13
|
||||
mov_s.ne r13,r5
|
||||
push_s r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
; Check that register R12 which original value is not stored will not pop-up in
|
||||
; the "Saved registers" list.
|
||||
|
||||
.align 4
|
||||
unstored_reg:
|
||||
sub_s sp,sp,12
|
||||
st_s r13,[sp,0]
|
||||
st_s r14,[sp,4]
|
||||
mov r12,0x42
|
||||
st_s r12,[sp,8]
|
||||
add sp,sp,12
|
||||
j [blink]
|
||||
|
||||
; Two stores at the same adddress. GDB should report only the R14.
|
||||
|
||||
.align 4
|
||||
double_store:
|
||||
sub_s sp,sp,4
|
||||
st_s r13,[sp,0]
|
||||
st_s r14,[sp,0]
|
||||
add sp,sp,4
|
||||
j [blink]
|
||||
|
||||
; Test for a case where callee has an alloca or anything else that might
|
||||
; modify stack dynamically in the function body - after the prologue.
|
||||
; This assumes that FP is set properly, so that GDB can use it - this holds
|
||||
; true for frames generated by GCC.
|
||||
|
||||
.align 4
|
||||
alloca_outer:
|
||||
sub sp,sp,8
|
||||
st blink,[sp,4]
|
||||
st fp,[sp,0]
|
||||
mov fp,sp
|
||||
add r0,r1,r2 ; Not a prologue anymore.
|
||||
sub sp,sp,8
|
||||
bl @alloca_inner
|
||||
add sp,sp,8
|
||||
ld fp,[sp,0]
|
||||
ld blink,[sp,4]
|
||||
j.d [blink]
|
||||
add sp,sp,8
|
||||
|
||||
.align 4
|
||||
alloca_inner:
|
||||
push r13
|
||||
push r14
|
||||
add sp,sp,8
|
||||
j [blink]
|
||||
|
||||
|
||||
.align 4
|
||||
main:
|
||||
push blink
|
||||
# Create small section for GP-relative accesses.
|
||||
push gp
|
||||
sub sp,sp,16
|
||||
add gp,sp,8
|
||||
bl @standard_prologue
|
||||
bl @mini_prologue
|
||||
bl @no_subsp_prologue
|
||||
bl @leaf_prologue
|
||||
bl @pushfp_prologue
|
||||
bl @fp_prologue_with_store
|
||||
bl @noncallee_saved_regs_r12_st
|
||||
bl @noncallee_saved_regs_r12_push
|
||||
bl @noncallee_saved_regs_r2_push
|
||||
bl @noncallee_saved_regs_gp_push
|
||||
bl @noncallee_saved_regs_lp_count
|
||||
bl @noncallee_saved_regs_blink_out_of_prologue
|
||||
bl @arg_regs_fp
|
||||
bl @arg_regs_fp_mov_s
|
||||
bl @arg_regs_sp
|
||||
bl @enter_s_nop
|
||||
bl @enter_s_blink
|
||||
bl @enter_s_fp
|
||||
bl @enter_s_r13
|
||||
bl @enter_s_r15
|
||||
bl @enter_s_all
|
||||
bl @nested_prologue_outer
|
||||
bl @max_length_prologue
|
||||
bl @branch_in_prologue
|
||||
bl @cond_branch_in_prologue
|
||||
bl @jump_in_prologue
|
||||
bl @cond_jump_in_prologue
|
||||
bl @predicated_insn
|
||||
bl @loop_in_prologue
|
||||
bl @store_constant
|
||||
bl @st_c_limm
|
||||
bl @st_ab_writeback
|
||||
bl @st_as_writeback
|
||||
bl @sth_as_writeback
|
||||
bl @std_as_writeback
|
||||
bl @st_halfword
|
||||
bl @sts_halfword
|
||||
bl @st_byte
|
||||
bl @sts_byte
|
||||
bl @sts_byte_sp
|
||||
bl @st_double
|
||||
bl @r_relative_store
|
||||
bl @r_relative_sub_store
|
||||
bl @r_relative_store_st_s
|
||||
bl @r_relative_store_unknown
|
||||
bl @st_s_r0gp
|
||||
bl @push_s_prologue
|
||||
bl @sub_s_cbu3
|
||||
bl @sub_s_bbc
|
||||
bl @sub_s_bbu5
|
||||
bl @sub_0bc
|
||||
bl @sub_alimmb
|
||||
bl @sub_s_ne_bbb
|
||||
bl @mov_limm
|
||||
bl @mov0c_limm
|
||||
bl @mov_s_hs3
|
||||
bl @mov_s_bu8
|
||||
bl @mov_s_ne_bh
|
||||
bl @unstored_reg
|
||||
bl @double_store
|
||||
bl @alloca_outer
|
||||
add sp,sp,16
|
||||
pop gp
|
||||
pop blink
|
||||
j_s [blink]
|
||||
|
||||
.align 4
|
|
@ -0,0 +1,201 @@
|
|||
# This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
# Copyright 2017 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/>.
|
||||
|
||||
if {![istarget "arc*-*-*"]} then {
|
||||
verbose "Skipping ARC prologue test."
|
||||
return
|
||||
}
|
||||
|
||||
standard_testfile .S
|
||||
|
||||
if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
|
||||
return -1
|
||||
}
|
||||
|
||||
if ![runto_main] {
|
||||
fail "Can't run to main"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Convert list of saved registers and their offsets to a GDB string.
|
||||
proc saved_regs_to_str { savedregs funcname } {
|
||||
set str ""
|
||||
# If blink is stored, that it is present twice in saved regs - as blink and
|
||||
# as pc.
|
||||
set has_blink 0
|
||||
set blink_addr 0
|
||||
foreach r $savedregs {
|
||||
if { [llength $r] == 1 } {
|
||||
append str ".*$r at.*"
|
||||
} else {
|
||||
set name [lindex $r 0]
|
||||
set offset [lindex $r 1]
|
||||
set addr [get_hexadecimal_valueof "\$sp+$offset" 0 \
|
||||
"get value of $name@sp+$offset in $funcname"]
|
||||
append str "\\s*$name at $addr,?"
|
||||
if { $name == "blink" } {
|
||||
set has_blink 1
|
||||
set blink_addr $addr
|
||||
}
|
||||
}
|
||||
}
|
||||
if { $has_blink == 1 } {
|
||||
append str "\\s*pc at $blink_addr"
|
||||
}
|
||||
return $str
|
||||
}
|
||||
|
||||
# Arguments:
|
||||
# funcname - name of function to test
|
||||
# savedregs - list of register saved in the frame. Each entry can be either
|
||||
# a string, where it is a register name, or it is a list of two
|
||||
# items - name of register and it's offset relatively to SP in
|
||||
# the memory. SP value is at the moment of prologue end.
|
||||
# fp_offset - if not an empty string, then proc will test that FP register
|
||||
# has a value that is (SP + offset).
|
||||
|
||||
proc prologue_test {funcname {savedregs ""} {fp_offset ""} } {
|
||||
global hex
|
||||
gdb_breakpoint $funcname temporary
|
||||
gdb_continue_to_breakpoint $funcname
|
||||
gdb_test "backtrace 10" \
|
||||
"#0\[ \t\]*$hex in $funcname .*\r\n#1\[ \t\]*$hex in main.*" \
|
||||
"backtrace in $funcname"
|
||||
if { $savedregs != "" } {
|
||||
set str [saved_regs_to_str $savedregs $funcname]
|
||||
gdb_test "info frame" \
|
||||
".*Saved registers:$str" \
|
||||
"saved registers in $funcname"
|
||||
}
|
||||
if { $fp_offset != "" } {
|
||||
set sp [get_integer_valueof \$sp -1 "get value of sp in $funcname"]
|
||||
set fp_val [expr $sp + $fp_offset]
|
||||
set fp_real_val \
|
||||
[get_integer_valueof \$fp 0 "get value of fp in $funcname"]
|
||||
if { $fp_real_val != $fp_val } {
|
||||
fail "check FP value in $funcname"
|
||||
} else {
|
||||
pass "check FP value in $funcname"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
prologue_test "standard_prologue" { {r13 0} {r14 4} {r18 8} {blink 12} }
|
||||
prologue_test "mini_prologue" { {r13 0} {r14 8} {r15 4} {blink 12} }
|
||||
prologue_test "no_subsp_prologue" { {r13 8} {r20 4} {r25 0} {blink 12} }
|
||||
prologue_test "leaf_prologue" { {r13 0} {r15 4} }
|
||||
prologue_test "pushfp_prologue" { {r13 8} {r14 4} {fp 0} } 0
|
||||
prologue_test "fp_prologue_with_store" { {r13 12} {r14 8} {r15 0} {fp 4} } 4
|
||||
prologue_test "noncallee_saved_regs_r12_st" { {r12 0} {r13 4} }
|
||||
# Register offset is specified relatively to SP at the prologue end, so
|
||||
# "push r12" hasn't been executed at this moment.
|
||||
prologue_test "noncallee_saved_regs_r12_push" { {r12 0} {r13 4} }
|
||||
prologue_test "noncallee_saved_regs_r2_push" { {r2 0} {r13 4} }
|
||||
prologue_test "noncallee_saved_regs_gp_push" { {r25 4} {gp 0} }
|
||||
prologue_test "noncallee_saved_regs_lp_count" { {r25 4} {lp_count 0} }
|
||||
prologue_test "noncallee_saved_regs_blink_out_of_prologue" { {r25 8} {gp 4} \
|
||||
{blink 0}}
|
||||
# Argument registers are not reported as "saved" regs.
|
||||
prologue_test "arg_regs_fp" { {r0 12} {r1 8} {r7 4} {r8 0} {fp 16} } 16
|
||||
prologue_test "arg_regs_fp_mov_s" { {r0 4} {r8 0} {fp 8} } 8
|
||||
prologue_test "arg_regs_sp" { {r0 0} {r1 4} {r7 8} {r8 12} {r13 16} {r14 20} }
|
||||
prologue_test "enter_s_nop"
|
||||
prologue_test "enter_s_blink" { {blink 0} }
|
||||
prologue_test "enter_s_fp" { {fp 0} } 0
|
||||
# Layout of registers as stored by enter_s doesn't conform to ARC ABI.
|
||||
prologue_test "enter_s_r13" { {r13 4} {fp 8} {blink 0} } 0
|
||||
prologue_test "enter_s_r15" { {r13 0} {r14 4} {r15 8} } 0
|
||||
# This enter_s saves GP, however because it is not a "calle-saved register",
|
||||
# GDB will not report it as "saved register" (but maybe it should). GP is at
|
||||
# offset 56.
|
||||
prologue_test "enter_s_all" { {r13 4} {r14 8} {r15 12} {r16 16} {r17 20} \
|
||||
{r18 24} {r19 28} {r20 32} {r21 36} {r22 40} {r23 44} {r24 48} {r25 52} \
|
||||
{gp 56} {fp 60} {blink 0} } 0
|
||||
|
||||
# Test more levels of backtrace.
|
||||
gdb_breakpoint nested_prologue_inner temporary
|
||||
gdb_continue_to_breakpoint nested_prologue_inner
|
||||
gdb_test "backtrace 10" \
|
||||
"#0\[ \t\]*$hex in nested_prologue_inner .*\r\n#1\[ \t\]*$hex in nested_prologue_outer .*\r\n#2\[ \t\]*$hex in main.*" \
|
||||
"backtrace in nested_prologue_inner"
|
||||
set regs [saved_regs_to_str {r13 r18} "nested_prologue_inner"]
|
||||
gdb_test "info frame" ".*Saved registers:$regs" \
|
||||
"saved registers in nested_prologue_inner"
|
||||
set regs [saved_regs_to_str {r14 r15 blink} "nested_prologue_inner"]
|
||||
gdb_test "info frame 1" ".*Saved registers:$regs" \
|
||||
"saved registers in nested_prologue_outer"
|
||||
|
||||
# sub sp,sp for local variables is part of prologue, hence should be added to
|
||||
# all of those offsets.
|
||||
prologue_test "max_length_prologue" { {r0 72} {r1 76} {r2 80} {r3 84} {r4 88} \
|
||||
{r5 92} {r6 96} {r7 100} {r13 20} {r14 24} {r15 28} {r16 32} \
|
||||
{r17 36} {r18 40} {r19 44} {r20 48} {r21 52} {r22 56} {r23 60} {r24 64} \
|
||||
{r25 68} {fp 16} {blink 104} }
|
||||
|
||||
prologue_test "branch_in_prologue" { {r13 0} }
|
||||
prologue_test "cond_branch_in_prologue" { {r13 4} }
|
||||
prologue_test "jump_in_prologue" { {r13 0} }
|
||||
prologue_test "cond_jump_in_prologue" { {r13 4} }
|
||||
prologue_test "predicated_insn" { {r13 8} {r15 0} }
|
||||
prologue_test "loop_in_prologue" { {r25 4} {lp_count 0} }
|
||||
prologue_test "store_constant" { {r13 8} {r14 4} }
|
||||
prologue_test "st_c_limm" { {r15 0} }
|
||||
prologue_test "st_ab_writeback" { {r13 8} {r14 4} {r15 0} }
|
||||
prologue_test "st_as_writeback" { {r13 8} {r14 4} {r15 0} }
|
||||
prologue_test "sth_as_writeback" { {r13 8} {r15 0} }
|
||||
prologue_test "std_as_writeback" { {r13 12} {r14 4} {r15 8} {r16 0} }
|
||||
prologue_test "st_halfword" { {r13 8} {r15 0} }
|
||||
prologue_test "sts_halfword" { {r13 8} {r15 0} }
|
||||
prologue_test "st_byte" { {r13 8} {r15 0} }
|
||||
prologue_test "sts_byte" { {r13 8} {r15 0} }
|
||||
prologue_test "sts_byte_sp" { {r13 8} {r15 0} }
|
||||
prologue_test "st_double" { {r14 16} {r15 20} {r18 8} {r19 12}}
|
||||
prologue_test "r_relative_store" { {r13 8} {r14 4} {r15 0} }
|
||||
prologue_test "r_relative_sub_store" { {r13 8} {r14 4} {r15 0} }
|
||||
prologue_test "r_relative_store_st_s" { {r13 8} {r14 0} {r15 4} }
|
||||
prologue_test "r_relative_store_unknown" { {r13 8} }
|
||||
prologue_test "st_s_r0gp" { {r13 8} }
|
||||
prologue_test "push_s_prologue" { {r0 28} {r1 16} {r2 4} {r3 24} {r12 32} \
|
||||
{r13 20} {r14 12} {r15 8} {blink 0}}
|
||||
prologue_test "sub_s_cbu3" { {r13 4} {r14 0} }
|
||||
prologue_test "sub_s_bbc" { {r1 4} {r13 12} {r14 0} }
|
||||
prologue_test "sub_s_bbu5" { {r13 8} {r14 0} }
|
||||
prologue_test "sub_0bc" { {r13 4} {r14 0} }
|
||||
prologue_test "sub_alimmb" { {r13 4} {r14 0} }
|
||||
prologue_test "sub_s_ne_bbb" { {r13 0} }
|
||||
prologue_test "mov_limm" { {r13 4} {r14 0} }
|
||||
prologue_test "mov0c_limm" { {r13 4} {r14 0} }
|
||||
prologue_test "mov_s_hs3" { {r13 4} {r14 0} }
|
||||
prologue_test "mov_s_bu8" { {r13 4} {r14 0} }
|
||||
prologue_test "mov_s_ne_bh" { {r13 0} }
|
||||
prologue_test "unstored_reg" { {r13 0} {r14 4} }
|
||||
prologue_test "double_store" { {r14 0} }
|
||||
|
||||
# alloca() tests
|
||||
gdb_breakpoint alloca_inner temporary
|
||||
gdb_continue_to_breakpoint alloca_inner
|
||||
gdb_test "backtrace 3" \
|
||||
"#0\[ \t\]*$hex in alloca_inner .*\r\n#1\[ \t\]*$hex in alloca_outer .*\r\n#2\[ \t\]*$hex in main.*" \
|
||||
"backtrace in alloca_inner"
|
||||
set regs [saved_regs_to_str {r13 r14} alloca_inner]
|
||||
gdb_test "info frame 0" ".*Saved registers:$regs" \
|
||||
"saved registers in alloca_inner"
|
||||
set regs [saved_regs_to_str {fp blink} alloca_inner]
|
||||
gdb_test "info frame 1" ".*Saved registers:$regs" \
|
||||
"saved registers in alloca_outer"
|
||||
|
Loading…
Reference in New Issue