4077 lines
116 KiB
C
4077 lines
116 KiB
C
/* Common target dependent code for GDB on AArch64 systems.
|
||
|
||
Copyright (C) 2009-2018 Free Software Foundation, Inc.
|
||
Contributed by ARM Ltd.
|
||
|
||
This file is part of GDB.
|
||
|
||
This program is free software; you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation; either version 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||
|
||
#include "defs.h"
|
||
|
||
#include "frame.h"
|
||
#include "inferior.h"
|
||
#include "gdbcmd.h"
|
||
#include "gdbcore.h"
|
||
#include "dis-asm.h"
|
||
#include "regcache.h"
|
||
#include "reggroups.h"
|
||
#include "value.h"
|
||
#include "arch-utils.h"
|
||
#include "osabi.h"
|
||
#include "frame-unwind.h"
|
||
#include "frame-base.h"
|
||
#include "trad-frame.h"
|
||
#include "objfiles.h"
|
||
#include "dwarf2-frame.h"
|
||
#include "gdbtypes.h"
|
||
#include "prologue-value.h"
|
||
#include "target-descriptions.h"
|
||
#include "user-regs.h"
|
||
#include "language.h"
|
||
#include "infcall.h"
|
||
#include "ax.h"
|
||
#include "ax-gdb.h"
|
||
#include "selftest.h"
|
||
|
||
#include "aarch64-tdep.h"
|
||
|
||
#include "elf-bfd.h"
|
||
#include "elf/aarch64.h"
|
||
|
||
#include "vec.h"
|
||
|
||
#include "record.h"
|
||
#include "record-full.h"
|
||
#include "arch/aarch64-insn.h"
|
||
|
||
#include "opcode/aarch64.h"
|
||
#include <algorithm>
|
||
|
||
#define submask(x) ((1L << ((x) + 1)) - 1)
|
||
#define bit(obj,st) (((obj) >> (st)) & 1)
|
||
#define bits(obj,st,fn) (((obj) >> (st)) & submask ((fn) - (st)))
|
||
|
||
/* Pseudo register base numbers. */
|
||
#define AARCH64_Q0_REGNUM 0
|
||
#define AARCH64_D0_REGNUM (AARCH64_Q0_REGNUM + AARCH64_D_REGISTER_COUNT)
|
||
#define AARCH64_S0_REGNUM (AARCH64_D0_REGNUM + 32)
|
||
#define AARCH64_H0_REGNUM (AARCH64_S0_REGNUM + 32)
|
||
#define AARCH64_B0_REGNUM (AARCH64_H0_REGNUM + 32)
|
||
|
||
/* The standard register names, and all the valid aliases for them. */
|
||
static const struct
|
||
{
|
||
const char *const name;
|
||
int regnum;
|
||
} aarch64_register_aliases[] =
|
||
{
|
||
/* 64-bit register names. */
|
||
{"fp", AARCH64_FP_REGNUM},
|
||
{"lr", AARCH64_LR_REGNUM},
|
||
{"sp", AARCH64_SP_REGNUM},
|
||
|
||
/* 32-bit register names. */
|
||
{"w0", AARCH64_X0_REGNUM + 0},
|
||
{"w1", AARCH64_X0_REGNUM + 1},
|
||
{"w2", AARCH64_X0_REGNUM + 2},
|
||
{"w3", AARCH64_X0_REGNUM + 3},
|
||
{"w4", AARCH64_X0_REGNUM + 4},
|
||
{"w5", AARCH64_X0_REGNUM + 5},
|
||
{"w6", AARCH64_X0_REGNUM + 6},
|
||
{"w7", AARCH64_X0_REGNUM + 7},
|
||
{"w8", AARCH64_X0_REGNUM + 8},
|
||
{"w9", AARCH64_X0_REGNUM + 9},
|
||
{"w10", AARCH64_X0_REGNUM + 10},
|
||
{"w11", AARCH64_X0_REGNUM + 11},
|
||
{"w12", AARCH64_X0_REGNUM + 12},
|
||
{"w13", AARCH64_X0_REGNUM + 13},
|
||
{"w14", AARCH64_X0_REGNUM + 14},
|
||
{"w15", AARCH64_X0_REGNUM + 15},
|
||
{"w16", AARCH64_X0_REGNUM + 16},
|
||
{"w17", AARCH64_X0_REGNUM + 17},
|
||
{"w18", AARCH64_X0_REGNUM + 18},
|
||
{"w19", AARCH64_X0_REGNUM + 19},
|
||
{"w20", AARCH64_X0_REGNUM + 20},
|
||
{"w21", AARCH64_X0_REGNUM + 21},
|
||
{"w22", AARCH64_X0_REGNUM + 22},
|
||
{"w23", AARCH64_X0_REGNUM + 23},
|
||
{"w24", AARCH64_X0_REGNUM + 24},
|
||
{"w25", AARCH64_X0_REGNUM + 25},
|
||
{"w26", AARCH64_X0_REGNUM + 26},
|
||
{"w27", AARCH64_X0_REGNUM + 27},
|
||
{"w28", AARCH64_X0_REGNUM + 28},
|
||
{"w29", AARCH64_X0_REGNUM + 29},
|
||
{"w30", AARCH64_X0_REGNUM + 30},
|
||
|
||
/* specials */
|
||
{"ip0", AARCH64_X0_REGNUM + 16},
|
||
{"ip1", AARCH64_X0_REGNUM + 17}
|
||
};
|
||
|
||
/* The required core 'R' registers. */
|
||
static const char *const aarch64_r_register_names[] =
|
||
{
|
||
/* These registers must appear in consecutive RAW register number
|
||
order and they must begin with AARCH64_X0_REGNUM! */
|
||
"x0", "x1", "x2", "x3",
|
||
"x4", "x5", "x6", "x7",
|
||
"x8", "x9", "x10", "x11",
|
||
"x12", "x13", "x14", "x15",
|
||
"x16", "x17", "x18", "x19",
|
||
"x20", "x21", "x22", "x23",
|
||
"x24", "x25", "x26", "x27",
|
||
"x28", "x29", "x30", "sp",
|
||
"pc", "cpsr"
|
||
};
|
||
|
||
/* The FP/SIMD 'V' registers. */
|
||
static const char *const aarch64_v_register_names[] =
|
||
{
|
||
/* These registers must appear in consecutive RAW register number
|
||
order and they must begin with AARCH64_V0_REGNUM! */
|
||
"v0", "v1", "v2", "v3",
|
||
"v4", "v5", "v6", "v7",
|
||
"v8", "v9", "v10", "v11",
|
||
"v12", "v13", "v14", "v15",
|
||
"v16", "v17", "v18", "v19",
|
||
"v20", "v21", "v22", "v23",
|
||
"v24", "v25", "v26", "v27",
|
||
"v28", "v29", "v30", "v31",
|
||
"fpsr",
|
||
"fpcr"
|
||
};
|
||
|
||
/* AArch64 prologue cache structure. */
|
||
struct aarch64_prologue_cache
|
||
{
|
||
/* The program counter at the start of the function. It is used to
|
||
identify this frame as a prologue frame. */
|
||
CORE_ADDR func;
|
||
|
||
/* The program counter at the time this frame was created; i.e. where
|
||
this function was called from. It is used to identify this frame as a
|
||
stub frame. */
|
||
CORE_ADDR prev_pc;
|
||
|
||
/* The stack pointer at the time this frame was created; i.e. the
|
||
caller's stack pointer when this function was called. It is used
|
||
to identify this frame. */
|
||
CORE_ADDR prev_sp;
|
||
|
||
/* Is the target available to read from? */
|
||
int available_p;
|
||
|
||
/* The frame base for this frame is just prev_sp - frame size.
|
||
FRAMESIZE is the distance from the frame pointer to the
|
||
initial stack pointer. */
|
||
int framesize;
|
||
|
||
/* The register used to hold the frame pointer for this frame. */
|
||
int framereg;
|
||
|
||
/* Saved register offsets. */
|
||
struct trad_frame_saved_reg *saved_regs;
|
||
};
|
||
|
||
static void
|
||
show_aarch64_debug (struct ui_file *file, int from_tty,
|
||
struct cmd_list_element *c, const char *value)
|
||
{
|
||
fprintf_filtered (file, _("AArch64 debugging is %s.\n"), value);
|
||
}
|
||
|
||
namespace {
|
||
|
||
/* Abstract instruction reader. */
|
||
|
||
class abstract_instruction_reader
|
||
{
|
||
public:
|
||
/* Read in one instruction. */
|
||
virtual ULONGEST read (CORE_ADDR memaddr, int len,
|
||
enum bfd_endian byte_order) = 0;
|
||
};
|
||
|
||
/* Instruction reader from real target. */
|
||
|
||
class instruction_reader : public abstract_instruction_reader
|
||
{
|
||
public:
|
||
ULONGEST read (CORE_ADDR memaddr, int len, enum bfd_endian byte_order)
|
||
override
|
||
{
|
||
return read_code_unsigned_integer (memaddr, len, byte_order);
|
||
}
|
||
};
|
||
|
||
} // namespace
|
||
|
||
/* Analyze a prologue, looking for a recognizable stack frame
|
||
and frame pointer. Scan until we encounter a store that could
|
||
clobber the stack frame unexpectedly, or an unknown instruction. */
|
||
|
||
static CORE_ADDR
|
||
aarch64_analyze_prologue (struct gdbarch *gdbarch,
|
||
CORE_ADDR start, CORE_ADDR limit,
|
||
struct aarch64_prologue_cache *cache,
|
||
abstract_instruction_reader& reader)
|
||
{
|
||
enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
|
||
int i;
|
||
/* Track X registers and D registers in prologue. */
|
||
pv_t regs[AARCH64_X_REGISTER_COUNT + AARCH64_D_REGISTER_COUNT];
|
||
|
||
for (i = 0; i < AARCH64_X_REGISTER_COUNT + AARCH64_D_REGISTER_COUNT; i++)
|
||
regs[i] = pv_register (i, 0);
|
||
pv_area stack (AARCH64_SP_REGNUM, gdbarch_addr_bit (gdbarch));
|
||
|
||
for (; start < limit; start += 4)
|
||
{
|
||
uint32_t insn;
|
||
aarch64_inst inst;
|
||
|
||
insn = reader.read (start, 4, byte_order_for_code);
|
||
|
||
if (aarch64_decode_insn (insn, &inst, 1, NULL) != 0)
|
||
break;
|
||
|
||
if (inst.opcode->iclass == addsub_imm
|
||
&& (inst.opcode->op == OP_ADD
|
||
|| strcmp ("sub", inst.opcode->name) == 0))
|
||
{
|
||
unsigned rd = inst.operands[0].reg.regno;
|
||
unsigned rn = inst.operands[1].reg.regno;
|
||
|
||
gdb_assert (aarch64_num_of_operands (inst.opcode) == 3);
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_Rd_SP);
|
||
gdb_assert (inst.operands[1].type == AARCH64_OPND_Rn_SP);
|
||
gdb_assert (inst.operands[2].type == AARCH64_OPND_AIMM);
|
||
|
||
if (inst.opcode->op == OP_ADD)
|
||
{
|
||
regs[rd] = pv_add_constant (regs[rn],
|
||
inst.operands[2].imm.value);
|
||
}
|
||
else
|
||
{
|
||
regs[rd] = pv_add_constant (regs[rn],
|
||
-inst.operands[2].imm.value);
|
||
}
|
||
}
|
||
else if (inst.opcode->iclass == pcreladdr
|
||
&& inst.operands[1].type == AARCH64_OPND_ADDR_ADRP)
|
||
{
|
||
gdb_assert (aarch64_num_of_operands (inst.opcode) == 2);
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_Rd);
|
||
|
||
regs[inst.operands[0].reg.regno] = pv_unknown ();
|
||
}
|
||
else if (inst.opcode->iclass == branch_imm)
|
||
{
|
||
/* Stop analysis on branch. */
|
||
break;
|
||
}
|
||
else if (inst.opcode->iclass == condbranch)
|
||
{
|
||
/* Stop analysis on branch. */
|
||
break;
|
||
}
|
||
else if (inst.opcode->iclass == branch_reg)
|
||
{
|
||
/* Stop analysis on branch. */
|
||
break;
|
||
}
|
||
else if (inst.opcode->iclass == compbranch)
|
||
{
|
||
/* Stop analysis on branch. */
|
||
break;
|
||
}
|
||
else if (inst.opcode->op == OP_MOVZ)
|
||
{
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_Rd);
|
||
regs[inst.operands[0].reg.regno] = pv_unknown ();
|
||
}
|
||
else if (inst.opcode->iclass == log_shift
|
||
&& strcmp (inst.opcode->name, "orr") == 0)
|
||
{
|
||
unsigned rd = inst.operands[0].reg.regno;
|
||
unsigned rn = inst.operands[1].reg.regno;
|
||
unsigned rm = inst.operands[2].reg.regno;
|
||
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_Rd);
|
||
gdb_assert (inst.operands[1].type == AARCH64_OPND_Rn);
|
||
gdb_assert (inst.operands[2].type == AARCH64_OPND_Rm_SFT);
|
||
|
||
if (inst.operands[2].shifter.amount == 0
|
||
&& rn == AARCH64_SP_REGNUM)
|
||
regs[rd] = regs[rm];
|
||
else
|
||
{
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("aarch64: prologue analysis gave up "
|
||
"addr=%s opcode=0x%x (orr x register)\n",
|
||
core_addr_to_string_nz (start), insn);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
else if (inst.opcode->op == OP_STUR)
|
||
{
|
||
unsigned rt = inst.operands[0].reg.regno;
|
||
unsigned rn = inst.operands[1].addr.base_regno;
|
||
int is64
|
||
= (aarch64_get_qualifier_esize (inst.operands[0].qualifier) == 8);
|
||
|
||
gdb_assert (aarch64_num_of_operands (inst.opcode) == 2);
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_Rt);
|
||
gdb_assert (inst.operands[1].type == AARCH64_OPND_ADDR_SIMM9);
|
||
gdb_assert (!inst.operands[1].addr.offset.is_reg);
|
||
|
||
stack.store (pv_add_constant (regs[rn],
|
||
inst.operands[1].addr.offset.imm),
|
||
is64 ? 8 : 4, regs[rt]);
|
||
}
|
||
else if ((inst.opcode->iclass == ldstpair_off
|
||
|| (inst.opcode->iclass == ldstpair_indexed
|
||
&& inst.operands[2].addr.preind))
|
||
&& strcmp ("stp", inst.opcode->name) == 0)
|
||
{
|
||
/* STP with addressing mode Pre-indexed and Base register. */
|
||
unsigned rt1;
|
||
unsigned rt2;
|
||
unsigned rn = inst.operands[2].addr.base_regno;
|
||
int32_t imm = inst.operands[2].addr.offset.imm;
|
||
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_Rt
|
||
|| inst.operands[0].type == AARCH64_OPND_Ft);
|
||
gdb_assert (inst.operands[1].type == AARCH64_OPND_Rt2
|
||
|| inst.operands[1].type == AARCH64_OPND_Ft2);
|
||
gdb_assert (inst.operands[2].type == AARCH64_OPND_ADDR_SIMM7);
|
||
gdb_assert (!inst.operands[2].addr.offset.is_reg);
|
||
|
||
/* If recording this store would invalidate the store area
|
||
(perhaps because rn is not known) then we should abandon
|
||
further prologue analysis. */
|
||
if (stack.store_would_trash (pv_add_constant (regs[rn], imm)))
|
||
break;
|
||
|
||
if (stack.store_would_trash (pv_add_constant (regs[rn], imm + 8)))
|
||
break;
|
||
|
||
rt1 = inst.operands[0].reg.regno;
|
||
rt2 = inst.operands[1].reg.regno;
|
||
if (inst.operands[0].type == AARCH64_OPND_Ft)
|
||
{
|
||
/* Only bottom 64-bit of each V register (D register) need
|
||
to be preserved. */
|
||
gdb_assert (inst.operands[0].qualifier == AARCH64_OPND_QLF_S_D);
|
||
rt1 += AARCH64_X_REGISTER_COUNT;
|
||
rt2 += AARCH64_X_REGISTER_COUNT;
|
||
}
|
||
|
||
stack.store (pv_add_constant (regs[rn], imm), 8,
|
||
regs[rt1]);
|
||
stack.store (pv_add_constant (regs[rn], imm + 8), 8,
|
||
regs[rt2]);
|
||
|
||
if (inst.operands[2].addr.writeback)
|
||
regs[rn] = pv_add_constant (regs[rn], imm);
|
||
|
||
}
|
||
else if ((inst.opcode->iclass == ldst_imm9 /* Signed immediate. */
|
||
|| (inst.opcode->iclass == ldst_pos /* Unsigned immediate. */
|
||
&& (inst.opcode->op == OP_STR_POS
|
||
|| inst.opcode->op == OP_STRF_POS)))
|
||
&& inst.operands[1].addr.base_regno == AARCH64_SP_REGNUM
|
||
&& strcmp ("str", inst.opcode->name) == 0)
|
||
{
|
||
/* STR (immediate) */
|
||
unsigned int rt = inst.operands[0].reg.regno;
|
||
int32_t imm = inst.operands[1].addr.offset.imm;
|
||
unsigned int rn = inst.operands[1].addr.base_regno;
|
||
bool is64
|
||
= (aarch64_get_qualifier_esize (inst.operands[0].qualifier) == 8);
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_Rt
|
||
|| inst.operands[0].type == AARCH64_OPND_Ft);
|
||
|
||
if (inst.operands[0].type == AARCH64_OPND_Ft)
|
||
{
|
||
/* Only bottom 64-bit of each V register (D register) need
|
||
to be preserved. */
|
||
gdb_assert (inst.operands[0].qualifier == AARCH64_OPND_QLF_S_D);
|
||
rt += AARCH64_X_REGISTER_COUNT;
|
||
}
|
||
|
||
stack.store (pv_add_constant (regs[rn], imm),
|
||
is64 ? 8 : 4, regs[rt]);
|
||
if (inst.operands[1].addr.writeback)
|
||
regs[rn] = pv_add_constant (regs[rn], imm);
|
||
}
|
||
else if (inst.opcode->iclass == testbranch)
|
||
{
|
||
/* Stop analysis on branch. */
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("aarch64: prologue analysis gave up addr=%s"
|
||
" opcode=0x%x\n",
|
||
core_addr_to_string_nz (start), insn);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (cache == NULL)
|
||
return start;
|
||
|
||
if (pv_is_register (regs[AARCH64_FP_REGNUM], AARCH64_SP_REGNUM))
|
||
{
|
||
/* Frame pointer is fp. Frame size is constant. */
|
||
cache->framereg = AARCH64_FP_REGNUM;
|
||
cache->framesize = -regs[AARCH64_FP_REGNUM].k;
|
||
}
|
||
else if (pv_is_register (regs[AARCH64_SP_REGNUM], AARCH64_SP_REGNUM))
|
||
{
|
||
/* Try the stack pointer. */
|
||
cache->framesize = -regs[AARCH64_SP_REGNUM].k;
|
||
cache->framereg = AARCH64_SP_REGNUM;
|
||
}
|
||
else
|
||
{
|
||
/* We're just out of luck. We don't know where the frame is. */
|
||
cache->framereg = -1;
|
||
cache->framesize = 0;
|
||
}
|
||
|
||
for (i = 0; i < AARCH64_X_REGISTER_COUNT; i++)
|
||
{
|
||
CORE_ADDR offset;
|
||
|
||
if (stack.find_reg (gdbarch, i, &offset))
|
||
cache->saved_regs[i].addr = offset;
|
||
}
|
||
|
||
for (i = 0; i < AARCH64_D_REGISTER_COUNT; i++)
|
||
{
|
||
int regnum = gdbarch_num_regs (gdbarch);
|
||
CORE_ADDR offset;
|
||
|
||
if (stack.find_reg (gdbarch, i + AARCH64_X_REGISTER_COUNT,
|
||
&offset))
|
||
cache->saved_regs[i + regnum + AARCH64_D0_REGNUM].addr = offset;
|
||
}
|
||
|
||
return start;
|
||
}
|
||
|
||
static CORE_ADDR
|
||
aarch64_analyze_prologue (struct gdbarch *gdbarch,
|
||
CORE_ADDR start, CORE_ADDR limit,
|
||
struct aarch64_prologue_cache *cache)
|
||
{
|
||
instruction_reader reader;
|
||
|
||
return aarch64_analyze_prologue (gdbarch, start, limit, cache,
|
||
reader);
|
||
}
|
||
|
||
#if GDB_SELF_TEST
|
||
|
||
namespace selftests {
|
||
|
||
/* Instruction reader from manually cooked instruction sequences. */
|
||
|
||
class instruction_reader_test : public abstract_instruction_reader
|
||
{
|
||
public:
|
||
template<size_t SIZE>
|
||
explicit instruction_reader_test (const uint32_t (&insns)[SIZE])
|
||
: m_insns (insns), m_insns_size (SIZE)
|
||
{}
|
||
|
||
ULONGEST read (CORE_ADDR memaddr, int len, enum bfd_endian byte_order)
|
||
override
|
||
{
|
||
SELF_CHECK (len == 4);
|
||
SELF_CHECK (memaddr % 4 == 0);
|
||
SELF_CHECK (memaddr / 4 < m_insns_size);
|
||
|
||
return m_insns[memaddr / 4];
|
||
}
|
||
|
||
private:
|
||
const uint32_t *m_insns;
|
||
size_t m_insns_size;
|
||
};
|
||
|
||
static void
|
||
aarch64_analyze_prologue_test (void)
|
||
{
|
||
struct gdbarch_info info;
|
||
|
||
gdbarch_info_init (&info);
|
||
info.bfd_arch_info = bfd_scan_arch ("aarch64");
|
||
|
||
struct gdbarch *gdbarch = gdbarch_find_by_info (info);
|
||
SELF_CHECK (gdbarch != NULL);
|
||
|
||
/* Test the simple prologue in which frame pointer is used. */
|
||
{
|
||
struct aarch64_prologue_cache cache;
|
||
cache.saved_regs = trad_frame_alloc_saved_regs (gdbarch);
|
||
|
||
static const uint32_t insns[] = {
|
||
0xa9af7bfd, /* stp x29, x30, [sp,#-272]! */
|
||
0x910003fd, /* mov x29, sp */
|
||
0x97ffffe6, /* bl 0x400580 */
|
||
};
|
||
instruction_reader_test reader (insns);
|
||
|
||
CORE_ADDR end = aarch64_analyze_prologue (gdbarch, 0, 128, &cache, reader);
|
||
SELF_CHECK (end == 4 * 2);
|
||
|
||
SELF_CHECK (cache.framereg == AARCH64_FP_REGNUM);
|
||
SELF_CHECK (cache.framesize == 272);
|
||
|
||
for (int i = 0; i < AARCH64_X_REGISTER_COUNT; i++)
|
||
{
|
||
if (i == AARCH64_FP_REGNUM)
|
||
SELF_CHECK (cache.saved_regs[i].addr == -272);
|
||
else if (i == AARCH64_LR_REGNUM)
|
||
SELF_CHECK (cache.saved_regs[i].addr == -264);
|
||
else
|
||
SELF_CHECK (cache.saved_regs[i].addr == -1);
|
||
}
|
||
|
||
for (int i = 0; i < AARCH64_D_REGISTER_COUNT; i++)
|
||
{
|
||
int regnum = gdbarch_num_regs (gdbarch);
|
||
|
||
SELF_CHECK (cache.saved_regs[i + regnum + AARCH64_D0_REGNUM].addr
|
||
== -1);
|
||
}
|
||
}
|
||
|
||
/* Test a prologue in which STR is used and frame pointer is not
|
||
used. */
|
||
{
|
||
struct aarch64_prologue_cache cache;
|
||
cache.saved_regs = trad_frame_alloc_saved_regs (gdbarch);
|
||
|
||
static const uint32_t insns[] = {
|
||
0xf81d0ff3, /* str x19, [sp, #-48]! */
|
||
0xb9002fe0, /* str w0, [sp, #44] */
|
||
0xf90013e1, /* str x1, [sp, #32]*/
|
||
0xfd000fe0, /* str d0, [sp, #24] */
|
||
0xaa0203f3, /* mov x19, x2 */
|
||
0xf94013e0, /* ldr x0, [sp, #32] */
|
||
};
|
||
instruction_reader_test reader (insns);
|
||
|
||
CORE_ADDR end = aarch64_analyze_prologue (gdbarch, 0, 128, &cache, reader);
|
||
|
||
SELF_CHECK (end == 4 * 5);
|
||
|
||
SELF_CHECK (cache.framereg == AARCH64_SP_REGNUM);
|
||
SELF_CHECK (cache.framesize == 48);
|
||
|
||
for (int i = 0; i < AARCH64_X_REGISTER_COUNT; i++)
|
||
{
|
||
if (i == 1)
|
||
SELF_CHECK (cache.saved_regs[i].addr == -16);
|
||
else if (i == 19)
|
||
SELF_CHECK (cache.saved_regs[i].addr == -48);
|
||
else
|
||
SELF_CHECK (cache.saved_regs[i].addr == -1);
|
||
}
|
||
|
||
for (int i = 0; i < AARCH64_D_REGISTER_COUNT; i++)
|
||
{
|
||
int regnum = gdbarch_num_regs (gdbarch);
|
||
|
||
if (i == 0)
|
||
SELF_CHECK (cache.saved_regs[i + regnum + AARCH64_D0_REGNUM].addr
|
||
== -24);
|
||
else
|
||
SELF_CHECK (cache.saved_regs[i + regnum + AARCH64_D0_REGNUM].addr
|
||
== -1);
|
||
}
|
||
}
|
||
}
|
||
} // namespace selftests
|
||
#endif /* GDB_SELF_TEST */
|
||
|
||
/* Implement the "skip_prologue" gdbarch method. */
|
||
|
||
static CORE_ADDR
|
||
aarch64_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
|
||
{
|
||
CORE_ADDR func_addr, limit_pc;
|
||
|
||
/* See if we can determine the end of the prologue via the symbol
|
||
table. If so, then return either PC, or the PC after the
|
||
prologue, whichever is greater. */
|
||
if (find_pc_partial_function (pc, NULL, &func_addr, NULL))
|
||
{
|
||
CORE_ADDR post_prologue_pc
|
||
= skip_prologue_using_sal (gdbarch, func_addr);
|
||
|
||
if (post_prologue_pc != 0)
|
||
return std::max (pc, post_prologue_pc);
|
||
}
|
||
|
||
/* Can't determine prologue from the symbol table, need to examine
|
||
instructions. */
|
||
|
||
/* 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 use an arbitrary large number as the
|
||
upper bound. */
|
||
limit_pc = skip_prologue_using_sal (gdbarch, pc);
|
||
if (limit_pc == 0)
|
||
limit_pc = pc + 128; /* Magic. */
|
||
|
||
/* Try disassembling prologue. */
|
||
return aarch64_analyze_prologue (gdbarch, pc, limit_pc, NULL);
|
||
}
|
||
|
||
/* Scan the function prologue for THIS_FRAME and populate the prologue
|
||
cache CACHE. */
|
||
|
||
static void
|
||
aarch64_scan_prologue (struct frame_info *this_frame,
|
||
struct aarch64_prologue_cache *cache)
|
||
{
|
||
CORE_ADDR block_addr = get_frame_address_in_block (this_frame);
|
||
CORE_ADDR prologue_start;
|
||
CORE_ADDR prologue_end;
|
||
CORE_ADDR prev_pc = get_frame_pc (this_frame);
|
||
struct gdbarch *gdbarch = get_frame_arch (this_frame);
|
||
|
||
cache->prev_pc = prev_pc;
|
||
|
||
/* Assume we do not find a frame. */
|
||
cache->framereg = -1;
|
||
cache->framesize = 0;
|
||
|
||
if (find_pc_partial_function (block_addr, NULL, &prologue_start,
|
||
&prologue_end))
|
||
{
|
||
struct symtab_and_line sal = find_pc_line (prologue_start, 0);
|
||
|
||
if (sal.line == 0)
|
||
{
|
||
/* No line info so use the current PC. */
|
||
prologue_end = prev_pc;
|
||
}
|
||
else if (sal.end < prologue_end)
|
||
{
|
||
/* The next line begins after the function end. */
|
||
prologue_end = sal.end;
|
||
}
|
||
|
||
prologue_end = std::min (prologue_end, prev_pc);
|
||
aarch64_analyze_prologue (gdbarch, prologue_start, prologue_end, cache);
|
||
}
|
||
else
|
||
{
|
||
CORE_ADDR frame_loc;
|
||
|
||
frame_loc = get_frame_register_unsigned (this_frame, AARCH64_FP_REGNUM);
|
||
if (frame_loc == 0)
|
||
return;
|
||
|
||
cache->framereg = AARCH64_FP_REGNUM;
|
||
cache->framesize = 16;
|
||
cache->saved_regs[29].addr = 0;
|
||
cache->saved_regs[30].addr = 8;
|
||
}
|
||
}
|
||
|
||
/* Fill in *CACHE with information about the prologue of *THIS_FRAME. This
|
||
function may throw an exception if the inferior's registers or memory is
|
||
not available. */
|
||
|
||
static void
|
||
aarch64_make_prologue_cache_1 (struct frame_info *this_frame,
|
||
struct aarch64_prologue_cache *cache)
|
||
{
|
||
CORE_ADDR unwound_fp;
|
||
int reg;
|
||
|
||
aarch64_scan_prologue (this_frame, cache);
|
||
|
||
if (cache->framereg == -1)
|
||
return;
|
||
|
||
unwound_fp = get_frame_register_unsigned (this_frame, cache->framereg);
|
||
if (unwound_fp == 0)
|
||
return;
|
||
|
||
cache->prev_sp = unwound_fp + cache->framesize;
|
||
|
||
/* Calculate actual addresses of saved registers using offsets
|
||
determined by aarch64_analyze_prologue. */
|
||
for (reg = 0; reg < gdbarch_num_regs (get_frame_arch (this_frame)); reg++)
|
||
if (trad_frame_addr_p (cache->saved_regs, reg))
|
||
cache->saved_regs[reg].addr += cache->prev_sp;
|
||
|
||
cache->func = get_frame_func (this_frame);
|
||
|
||
cache->available_p = 1;
|
||
}
|
||
|
||
/* Allocate and fill in *THIS_CACHE with information about the prologue of
|
||
*THIS_FRAME. Do not do this is if *THIS_CACHE was already allocated.
|
||
Return a pointer to the current aarch64_prologue_cache in
|
||
*THIS_CACHE. */
|
||
|
||
static struct aarch64_prologue_cache *
|
||
aarch64_make_prologue_cache (struct frame_info *this_frame, void **this_cache)
|
||
{
|
||
struct aarch64_prologue_cache *cache;
|
||
|
||
if (*this_cache != NULL)
|
||
return (struct aarch64_prologue_cache *) *this_cache;
|
||
|
||
cache = FRAME_OBSTACK_ZALLOC (struct aarch64_prologue_cache);
|
||
cache->saved_regs = trad_frame_alloc_saved_regs (this_frame);
|
||
*this_cache = cache;
|
||
|
||
TRY
|
||
{
|
||
aarch64_make_prologue_cache_1 (this_frame, cache);
|
||
}
|
||
CATCH (ex, RETURN_MASK_ERROR)
|
||
{
|
||
if (ex.error != NOT_AVAILABLE_ERROR)
|
||
throw_exception (ex);
|
||
}
|
||
END_CATCH
|
||
|
||
return cache;
|
||
}
|
||
|
||
/* Implement the "stop_reason" frame_unwind method. */
|
||
|
||
static enum unwind_stop_reason
|
||
aarch64_prologue_frame_unwind_stop_reason (struct frame_info *this_frame,
|
||
void **this_cache)
|
||
{
|
||
struct aarch64_prologue_cache *cache
|
||
= aarch64_make_prologue_cache (this_frame, this_cache);
|
||
|
||
if (!cache->available_p)
|
||
return UNWIND_UNAVAILABLE;
|
||
|
||
/* Halt the backtrace at "_start". */
|
||
if (cache->prev_pc <= gdbarch_tdep (get_frame_arch (this_frame))->lowest_pc)
|
||
return UNWIND_OUTERMOST;
|
||
|
||
/* We've hit a wall, stop. */
|
||
if (cache->prev_sp == 0)
|
||
return UNWIND_OUTERMOST;
|
||
|
||
return UNWIND_NO_REASON;
|
||
}
|
||
|
||
/* Our frame ID for a normal frame is the current function's starting
|
||
PC and the caller's SP when we were called. */
|
||
|
||
static void
|
||
aarch64_prologue_this_id (struct frame_info *this_frame,
|
||
void **this_cache, struct frame_id *this_id)
|
||
{
|
||
struct aarch64_prologue_cache *cache
|
||
= aarch64_make_prologue_cache (this_frame, this_cache);
|
||
|
||
if (!cache->available_p)
|
||
*this_id = frame_id_build_unavailable_stack (cache->func);
|
||
else
|
||
*this_id = frame_id_build (cache->prev_sp, cache->func);
|
||
}
|
||
|
||
/* Implement the "prev_register" frame_unwind method. */
|
||
|
||
static struct value *
|
||
aarch64_prologue_prev_register (struct frame_info *this_frame,
|
||
void **this_cache, int prev_regnum)
|
||
{
|
||
struct aarch64_prologue_cache *cache
|
||
= aarch64_make_prologue_cache (this_frame, this_cache);
|
||
|
||
/* If we are asked to unwind the PC, then we need to return the LR
|
||
instead. The prologue may save PC, but it will point into this
|
||
frame's prologue, not the next frame's resume location. */
|
||
if (prev_regnum == AARCH64_PC_REGNUM)
|
||
{
|
||
CORE_ADDR lr;
|
||
|
||
lr = frame_unwind_register_unsigned (this_frame, AARCH64_LR_REGNUM);
|
||
return frame_unwind_got_constant (this_frame, prev_regnum, lr);
|
||
}
|
||
|
||
/* SP is generally not saved to the stack, but this frame is
|
||
identified by the next frame's stack pointer at the time of the
|
||
call. The value was already reconstructed into PREV_SP. */
|
||
/*
|
||
+----------+ ^
|
||
| saved lr | |
|
||
+->| saved fp |--+
|
||
| | |
|
||
| | | <- Previous SP
|
||
| +----------+
|
||
| | saved lr |
|
||
+--| saved fp |<- FP
|
||
| |
|
||
| |<- SP
|
||
+----------+ */
|
||
if (prev_regnum == AARCH64_SP_REGNUM)
|
||
return frame_unwind_got_constant (this_frame, prev_regnum,
|
||
cache->prev_sp);
|
||
|
||
return trad_frame_get_prev_register (this_frame, cache->saved_regs,
|
||
prev_regnum);
|
||
}
|
||
|
||
/* AArch64 prologue unwinder. */
|
||
struct frame_unwind aarch64_prologue_unwind =
|
||
{
|
||
NORMAL_FRAME,
|
||
aarch64_prologue_frame_unwind_stop_reason,
|
||
aarch64_prologue_this_id,
|
||
aarch64_prologue_prev_register,
|
||
NULL,
|
||
default_frame_sniffer
|
||
};
|
||
|
||
/* Allocate and fill in *THIS_CACHE with information about the prologue of
|
||
*THIS_FRAME. Do not do this is if *THIS_CACHE was already allocated.
|
||
Return a pointer to the current aarch64_prologue_cache in
|
||
*THIS_CACHE. */
|
||
|
||
static struct aarch64_prologue_cache *
|
||
aarch64_make_stub_cache (struct frame_info *this_frame, void **this_cache)
|
||
{
|
||
struct aarch64_prologue_cache *cache;
|
||
|
||
if (*this_cache != NULL)
|
||
return (struct aarch64_prologue_cache *) *this_cache;
|
||
|
||
cache = FRAME_OBSTACK_ZALLOC (struct aarch64_prologue_cache);
|
||
cache->saved_regs = trad_frame_alloc_saved_regs (this_frame);
|
||
*this_cache = cache;
|
||
|
||
TRY
|
||
{
|
||
cache->prev_sp = get_frame_register_unsigned (this_frame,
|
||
AARCH64_SP_REGNUM);
|
||
cache->prev_pc = get_frame_pc (this_frame);
|
||
cache->available_p = 1;
|
||
}
|
||
CATCH (ex, RETURN_MASK_ERROR)
|
||
{
|
||
if (ex.error != NOT_AVAILABLE_ERROR)
|
||
throw_exception (ex);
|
||
}
|
||
END_CATCH
|
||
|
||
return cache;
|
||
}
|
||
|
||
/* Implement the "stop_reason" frame_unwind method. */
|
||
|
||
static enum unwind_stop_reason
|
||
aarch64_stub_frame_unwind_stop_reason (struct frame_info *this_frame,
|
||
void **this_cache)
|
||
{
|
||
struct aarch64_prologue_cache *cache
|
||
= aarch64_make_stub_cache (this_frame, this_cache);
|
||
|
||
if (!cache->available_p)
|
||
return UNWIND_UNAVAILABLE;
|
||
|
||
return UNWIND_NO_REASON;
|
||
}
|
||
|
||
/* Our frame ID for a stub frame is the current SP and LR. */
|
||
|
||
static void
|
||
aarch64_stub_this_id (struct frame_info *this_frame,
|
||
void **this_cache, struct frame_id *this_id)
|
||
{
|
||
struct aarch64_prologue_cache *cache
|
||
= aarch64_make_stub_cache (this_frame, this_cache);
|
||
|
||
if (cache->available_p)
|
||
*this_id = frame_id_build (cache->prev_sp, cache->prev_pc);
|
||
else
|
||
*this_id = frame_id_build_unavailable_stack (cache->prev_pc);
|
||
}
|
||
|
||
/* Implement the "sniffer" frame_unwind method. */
|
||
|
||
static int
|
||
aarch64_stub_unwind_sniffer (const struct frame_unwind *self,
|
||
struct frame_info *this_frame,
|
||
void **this_prologue_cache)
|
||
{
|
||
CORE_ADDR addr_in_block;
|
||
gdb_byte dummy[4];
|
||
|
||
addr_in_block = get_frame_address_in_block (this_frame);
|
||
if (in_plt_section (addr_in_block)
|
||
/* We also use the stub winder if the target memory is unreadable
|
||
to avoid having the prologue unwinder trying to read it. */
|
||
|| target_read_memory (get_frame_pc (this_frame), dummy, 4) != 0)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* AArch64 stub unwinder. */
|
||
struct frame_unwind aarch64_stub_unwind =
|
||
{
|
||
NORMAL_FRAME,
|
||
aarch64_stub_frame_unwind_stop_reason,
|
||
aarch64_stub_this_id,
|
||
aarch64_prologue_prev_register,
|
||
NULL,
|
||
aarch64_stub_unwind_sniffer
|
||
};
|
||
|
||
/* Return the frame base address of *THIS_FRAME. */
|
||
|
||
static CORE_ADDR
|
||
aarch64_normal_frame_base (struct frame_info *this_frame, void **this_cache)
|
||
{
|
||
struct aarch64_prologue_cache *cache
|
||
= aarch64_make_prologue_cache (this_frame, this_cache);
|
||
|
||
return cache->prev_sp - cache->framesize;
|
||
}
|
||
|
||
/* AArch64 default frame base information. */
|
||
struct frame_base aarch64_normal_base =
|
||
{
|
||
&aarch64_prologue_unwind,
|
||
aarch64_normal_frame_base,
|
||
aarch64_normal_frame_base,
|
||
aarch64_normal_frame_base
|
||
};
|
||
|
||
/* Assuming THIS_FRAME is a dummy, return the frame ID of that
|
||
dummy frame. The frame ID's base needs to match the TOS value
|
||
saved by save_dummy_frame_tos () and returned from
|
||
aarch64_push_dummy_call, and the PC needs to match the dummy
|
||
frame's breakpoint. */
|
||
|
||
static struct frame_id
|
||
aarch64_dummy_id (struct gdbarch *gdbarch, struct frame_info *this_frame)
|
||
{
|
||
return frame_id_build (get_frame_register_unsigned (this_frame,
|
||
AARCH64_SP_REGNUM),
|
||
get_frame_pc (this_frame));
|
||
}
|
||
|
||
/* Implement the "unwind_pc" gdbarch method. */
|
||
|
||
static CORE_ADDR
|
||
aarch64_unwind_pc (struct gdbarch *gdbarch, struct frame_info *this_frame)
|
||
{
|
||
CORE_ADDR pc
|
||
= frame_unwind_register_unsigned (this_frame, AARCH64_PC_REGNUM);
|
||
|
||
return pc;
|
||
}
|
||
|
||
/* Implement the "unwind_sp" gdbarch method. */
|
||
|
||
static CORE_ADDR
|
||
aarch64_unwind_sp (struct gdbarch *gdbarch, struct frame_info *this_frame)
|
||
{
|
||
return frame_unwind_register_unsigned (this_frame, AARCH64_SP_REGNUM);
|
||
}
|
||
|
||
/* Return the value of the REGNUM register in the previous frame of
|
||
*THIS_FRAME. */
|
||
|
||
static struct value *
|
||
aarch64_dwarf2_prev_register (struct frame_info *this_frame,
|
||
void **this_cache, int regnum)
|
||
{
|
||
CORE_ADDR lr;
|
||
|
||
switch (regnum)
|
||
{
|
||
case AARCH64_PC_REGNUM:
|
||
lr = frame_unwind_register_unsigned (this_frame, AARCH64_LR_REGNUM);
|
||
return frame_unwind_got_constant (this_frame, regnum, lr);
|
||
|
||
default:
|
||
internal_error (__FILE__, __LINE__,
|
||
_("Unexpected register %d"), regnum);
|
||
}
|
||
}
|
||
|
||
/* Implement the "init_reg" dwarf2_frame_ops method. */
|
||
|
||
static void
|
||
aarch64_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum,
|
||
struct dwarf2_frame_state_reg *reg,
|
||
struct frame_info *this_frame)
|
||
{
|
||
switch (regnum)
|
||
{
|
||
case AARCH64_PC_REGNUM:
|
||
reg->how = DWARF2_FRAME_REG_FN;
|
||
reg->loc.fn = aarch64_dwarf2_prev_register;
|
||
break;
|
||
case AARCH64_SP_REGNUM:
|
||
reg->how = DWARF2_FRAME_REG_CFA;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* When arguments must be pushed onto the stack, they go on in reverse
|
||
order. The code below implements a FILO (stack) to do this. */
|
||
|
||
typedef struct
|
||
{
|
||
/* Value to pass on stack. It can be NULL if this item is for stack
|
||
padding. */
|
||
const gdb_byte *data;
|
||
|
||
/* Size in bytes of value to pass on stack. */
|
||
int len;
|
||
} stack_item_t;
|
||
|
||
DEF_VEC_O (stack_item_t);
|
||
|
||
/* Return the alignment (in bytes) of the given type. */
|
||
|
||
static int
|
||
aarch64_type_align (struct type *t)
|
||
{
|
||
int n;
|
||
int align;
|
||
int falign;
|
||
|
||
t = check_typedef (t);
|
||
switch (TYPE_CODE (t))
|
||
{
|
||
default:
|
||
/* Should never happen. */
|
||
internal_error (__FILE__, __LINE__, _("unknown type alignment"));
|
||
return 4;
|
||
|
||
case TYPE_CODE_PTR:
|
||
case TYPE_CODE_ENUM:
|
||
case TYPE_CODE_INT:
|
||
case TYPE_CODE_FLT:
|
||
case TYPE_CODE_SET:
|
||
case TYPE_CODE_RANGE:
|
||
case TYPE_CODE_BITSTRING:
|
||
case TYPE_CODE_REF:
|
||
case TYPE_CODE_RVALUE_REF:
|
||
case TYPE_CODE_CHAR:
|
||
case TYPE_CODE_BOOL:
|
||
return TYPE_LENGTH (t);
|
||
|
||
case TYPE_CODE_ARRAY:
|
||
if (TYPE_VECTOR (t))
|
||
{
|
||
/* Use the natural alignment for vector types (the same for
|
||
scalar type), but the maximum alignment is 128-bit. */
|
||
if (TYPE_LENGTH (t) > 16)
|
||
return 16;
|
||
else
|
||
return TYPE_LENGTH (t);
|
||
}
|
||
else
|
||
return aarch64_type_align (TYPE_TARGET_TYPE (t));
|
||
case TYPE_CODE_COMPLEX:
|
||
return aarch64_type_align (TYPE_TARGET_TYPE (t));
|
||
|
||
case TYPE_CODE_STRUCT:
|
||
case TYPE_CODE_UNION:
|
||
align = 1;
|
||
for (n = 0; n < TYPE_NFIELDS (t); n++)
|
||
{
|
||
falign = aarch64_type_align (TYPE_FIELD_TYPE (t, n));
|
||
if (falign > align)
|
||
align = falign;
|
||
}
|
||
return align;
|
||
}
|
||
}
|
||
|
||
/* Return 1 if *TY is a homogeneous floating-point aggregate or
|
||
homogeneous short-vector aggregate as defined in the AAPCS64 ABI
|
||
document; otherwise return 0. */
|
||
|
||
static int
|
||
is_hfa_or_hva (struct type *ty)
|
||
{
|
||
switch (TYPE_CODE (ty))
|
||
{
|
||
case TYPE_CODE_ARRAY:
|
||
{
|
||
struct type *target_ty = TYPE_TARGET_TYPE (ty);
|
||
|
||
if (TYPE_VECTOR (ty))
|
||
return 0;
|
||
|
||
if (TYPE_LENGTH (ty) <= 4 /* HFA or HVA has at most 4 members. */
|
||
&& (TYPE_CODE (target_ty) == TYPE_CODE_FLT /* HFA */
|
||
|| (TYPE_CODE (target_ty) == TYPE_CODE_ARRAY /* HVA */
|
||
&& TYPE_VECTOR (target_ty))))
|
||
return 1;
|
||
break;
|
||
}
|
||
|
||
case TYPE_CODE_UNION:
|
||
case TYPE_CODE_STRUCT:
|
||
{
|
||
/* HFA or HVA has at most four members. */
|
||
if (TYPE_NFIELDS (ty) > 0 && TYPE_NFIELDS (ty) <= 4)
|
||
{
|
||
struct type *member0_type;
|
||
|
||
member0_type = check_typedef (TYPE_FIELD_TYPE (ty, 0));
|
||
if (TYPE_CODE (member0_type) == TYPE_CODE_FLT
|
||
|| (TYPE_CODE (member0_type) == TYPE_CODE_ARRAY
|
||
&& TYPE_VECTOR (member0_type)))
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < TYPE_NFIELDS (ty); i++)
|
||
{
|
||
struct type *member1_type;
|
||
|
||
member1_type = check_typedef (TYPE_FIELD_TYPE (ty, i));
|
||
if (TYPE_CODE (member0_type) != TYPE_CODE (member1_type)
|
||
|| (TYPE_LENGTH (member0_type)
|
||
!= TYPE_LENGTH (member1_type)))
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* AArch64 function call information structure. */
|
||
struct aarch64_call_info
|
||
{
|
||
/* the current argument number. */
|
||
unsigned argnum;
|
||
|
||
/* The next general purpose register number, equivalent to NGRN as
|
||
described in the AArch64 Procedure Call Standard. */
|
||
unsigned ngrn;
|
||
|
||
/* The next SIMD and floating point register number, equivalent to
|
||
NSRN as described in the AArch64 Procedure Call Standard. */
|
||
unsigned nsrn;
|
||
|
||
/* The next stacked argument address, equivalent to NSAA as
|
||
described in the AArch64 Procedure Call Standard. */
|
||
unsigned nsaa;
|
||
|
||
/* Stack item vector. */
|
||
VEC(stack_item_t) *si;
|
||
};
|
||
|
||
/* Pass a value in a sequence of consecutive X registers. The caller
|
||
is responsbile for ensuring sufficient registers are available. */
|
||
|
||
static void
|
||
pass_in_x (struct gdbarch *gdbarch, struct regcache *regcache,
|
||
struct aarch64_call_info *info, struct type *type,
|
||
struct value *arg)
|
||
{
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
int len = TYPE_LENGTH (type);
|
||
enum type_code typecode = TYPE_CODE (type);
|
||
int regnum = AARCH64_X0_REGNUM + info->ngrn;
|
||
const bfd_byte *buf = value_contents (arg);
|
||
|
||
info->argnum++;
|
||
|
||
while (len > 0)
|
||
{
|
||
int partial_len = len < X_REGISTER_SIZE ? len : X_REGISTER_SIZE;
|
||
CORE_ADDR regval = extract_unsigned_integer (buf, partial_len,
|
||
byte_order);
|
||
|
||
|
||
/* Adjust sub-word struct/union args when big-endian. */
|
||
if (byte_order == BFD_ENDIAN_BIG
|
||
&& partial_len < X_REGISTER_SIZE
|
||
&& (typecode == TYPE_CODE_STRUCT || typecode == TYPE_CODE_UNION))
|
||
regval <<= ((X_REGISTER_SIZE - partial_len) * TARGET_CHAR_BIT);
|
||
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("arg %d in %s = 0x%s\n", info->argnum,
|
||
gdbarch_register_name (gdbarch, regnum),
|
||
phex (regval, X_REGISTER_SIZE));
|
||
}
|
||
regcache_cooked_write_unsigned (regcache, regnum, regval);
|
||
len -= partial_len;
|
||
buf += partial_len;
|
||
regnum++;
|
||
}
|
||
}
|
||
|
||
/* Attempt to marshall a value in a V register. Return 1 if
|
||
successful, or 0 if insufficient registers are available. This
|
||
function, unlike the equivalent pass_in_x() function does not
|
||
handle arguments spread across multiple registers. */
|
||
|
||
static int
|
||
pass_in_v (struct gdbarch *gdbarch,
|
||
struct regcache *regcache,
|
||
struct aarch64_call_info *info,
|
||
int len, const bfd_byte *buf)
|
||
{
|
||
if (info->nsrn < 8)
|
||
{
|
||
int regnum = AARCH64_V0_REGNUM + info->nsrn;
|
||
gdb_byte reg[V_REGISTER_SIZE];
|
||
|
||
info->argnum++;
|
||
info->nsrn++;
|
||
|
||
memset (reg, 0, sizeof (reg));
|
||
/* PCS C.1, the argument is allocated to the least significant
|
||
bits of V register. */
|
||
memcpy (reg, buf, len);
|
||
regcache_cooked_write (regcache, regnum, reg);
|
||
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("arg %d in %s\n", info->argnum,
|
||
gdbarch_register_name (gdbarch, regnum));
|
||
}
|
||
return 1;
|
||
}
|
||
info->nsrn = 8;
|
||
return 0;
|
||
}
|
||
|
||
/* Marshall an argument onto the stack. */
|
||
|
||
static void
|
||
pass_on_stack (struct aarch64_call_info *info, struct type *type,
|
||
struct value *arg)
|
||
{
|
||
const bfd_byte *buf = value_contents (arg);
|
||
int len = TYPE_LENGTH (type);
|
||
int align;
|
||
stack_item_t item;
|
||
|
||
info->argnum++;
|
||
|
||
align = aarch64_type_align (type);
|
||
|
||
/* PCS C.17 Stack should be aligned to the larger of 8 bytes or the
|
||
Natural alignment of the argument's type. */
|
||
align = align_up (align, 8);
|
||
|
||
/* The AArch64 PCS requires at most doubleword alignment. */
|
||
if (align > 16)
|
||
align = 16;
|
||
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("arg %d len=%d @ sp + %d\n", info->argnum, len,
|
||
info->nsaa);
|
||
}
|
||
|
||
item.len = len;
|
||
item.data = buf;
|
||
VEC_safe_push (stack_item_t, info->si, &item);
|
||
|
||
info->nsaa += len;
|
||
if (info->nsaa & (align - 1))
|
||
{
|
||
/* Push stack alignment padding. */
|
||
int pad = align - (info->nsaa & (align - 1));
|
||
|
||
item.len = pad;
|
||
item.data = NULL;
|
||
|
||
VEC_safe_push (stack_item_t, info->si, &item);
|
||
info->nsaa += pad;
|
||
}
|
||
}
|
||
|
||
/* Marshall an argument into a sequence of one or more consecutive X
|
||
registers or, if insufficient X registers are available then onto
|
||
the stack. */
|
||
|
||
static void
|
||
pass_in_x_or_stack (struct gdbarch *gdbarch, struct regcache *regcache,
|
||
struct aarch64_call_info *info, struct type *type,
|
||
struct value *arg)
|
||
{
|
||
int len = TYPE_LENGTH (type);
|
||
int nregs = (len + X_REGISTER_SIZE - 1) / X_REGISTER_SIZE;
|
||
|
||
/* PCS C.13 - Pass in registers if we have enough spare */
|
||
if (info->ngrn + nregs <= 8)
|
||
{
|
||
pass_in_x (gdbarch, regcache, info, type, arg);
|
||
info->ngrn += nregs;
|
||
}
|
||
else
|
||
{
|
||
info->ngrn = 8;
|
||
pass_on_stack (info, type, arg);
|
||
}
|
||
}
|
||
|
||
/* Pass a value in a V register, or on the stack if insufficient are
|
||
available. */
|
||
|
||
static void
|
||
pass_in_v_or_stack (struct gdbarch *gdbarch,
|
||
struct regcache *regcache,
|
||
struct aarch64_call_info *info,
|
||
struct type *type,
|
||
struct value *arg)
|
||
{
|
||
if (!pass_in_v (gdbarch, regcache, info, TYPE_LENGTH (type),
|
||
value_contents (arg)))
|
||
pass_on_stack (info, type, arg);
|
||
}
|
||
|
||
/* Implement the "push_dummy_call" gdbarch method. */
|
||
|
||
static CORE_ADDR
|
||
aarch64_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
|
||
struct regcache *regcache, CORE_ADDR bp_addr,
|
||
int nargs,
|
||
struct value **args, CORE_ADDR sp, int struct_return,
|
||
CORE_ADDR struct_addr)
|
||
{
|
||
int argnum;
|
||
struct aarch64_call_info info;
|
||
struct type *func_type;
|
||
struct type *return_type;
|
||
int lang_struct_return;
|
||
|
||
memset (&info, 0, sizeof (info));
|
||
|
||
/* We need to know what the type of the called function is in order
|
||
to determine the number of named/anonymous arguments for the
|
||
actual argument placement, and the return type in order to handle
|
||
return value correctly.
|
||
|
||
The generic code above us views the decision of return in memory
|
||
or return in registers as a two stage processes. The language
|
||
handler is consulted first and may decide to return in memory (eg
|
||
class with copy constructor returned by value), this will cause
|
||
the generic code to allocate space AND insert an initial leading
|
||
argument.
|
||
|
||
If the language code does not decide to pass in memory then the
|
||
target code is consulted.
|
||
|
||
If the language code decides to pass in memory we want to move
|
||
the pointer inserted as the initial argument from the argument
|
||
list and into X8, the conventional AArch64 struct return pointer
|
||
register.
|
||
|
||
This is slightly awkward, ideally the flag "lang_struct_return"
|
||
would be passed to the targets implementation of push_dummy_call.
|
||
Rather that change the target interface we call the language code
|
||
directly ourselves. */
|
||
|
||
func_type = check_typedef (value_type (function));
|
||
|
||
/* Dereference function pointer types. */
|
||
if (TYPE_CODE (func_type) == TYPE_CODE_PTR)
|
||
func_type = TYPE_TARGET_TYPE (func_type);
|
||
|
||
gdb_assert (TYPE_CODE (func_type) == TYPE_CODE_FUNC
|
||
|| TYPE_CODE (func_type) == TYPE_CODE_METHOD);
|
||
|
||
/* If language_pass_by_reference () returned true we will have been
|
||
given an additional initial argument, a hidden pointer to the
|
||
return slot in memory. */
|
||
return_type = TYPE_TARGET_TYPE (func_type);
|
||
lang_struct_return = language_pass_by_reference (return_type);
|
||
|
||
/* Set the return address. For the AArch64, the return breakpoint
|
||
is always at BP_ADDR. */
|
||
regcache_cooked_write_unsigned (regcache, AARCH64_LR_REGNUM, bp_addr);
|
||
|
||
/* If we were given an initial argument for the return slot because
|
||
lang_struct_return was true, lose it. */
|
||
if (lang_struct_return)
|
||
{
|
||
args++;
|
||
nargs--;
|
||
}
|
||
|
||
/* The struct_return pointer occupies X8. */
|
||
if (struct_return || lang_struct_return)
|
||
{
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("struct return in %s = 0x%s\n",
|
||
gdbarch_register_name (gdbarch,
|
||
AARCH64_STRUCT_RETURN_REGNUM),
|
||
paddress (gdbarch, struct_addr));
|
||
}
|
||
regcache_cooked_write_unsigned (regcache, AARCH64_STRUCT_RETURN_REGNUM,
|
||
struct_addr);
|
||
}
|
||
|
||
for (argnum = 0; argnum < nargs; argnum++)
|
||
{
|
||
struct value *arg = args[argnum];
|
||
struct type *arg_type;
|
||
int len;
|
||
|
||
arg_type = check_typedef (value_type (arg));
|
||
len = TYPE_LENGTH (arg_type);
|
||
|
||
switch (TYPE_CODE (arg_type))
|
||
{
|
||
case TYPE_CODE_INT:
|
||
case TYPE_CODE_BOOL:
|
||
case TYPE_CODE_CHAR:
|
||
case TYPE_CODE_RANGE:
|
||
case TYPE_CODE_ENUM:
|
||
if (len < 4)
|
||
{
|
||
/* Promote to 32 bit integer. */
|
||
if (TYPE_UNSIGNED (arg_type))
|
||
arg_type = builtin_type (gdbarch)->builtin_uint32;
|
||
else
|
||
arg_type = builtin_type (gdbarch)->builtin_int32;
|
||
arg = value_cast (arg_type, arg);
|
||
}
|
||
pass_in_x_or_stack (gdbarch, regcache, &info, arg_type, arg);
|
||
break;
|
||
|
||
case TYPE_CODE_COMPLEX:
|
||
if (info.nsrn <= 6)
|
||
{
|
||
const bfd_byte *buf = value_contents (arg);
|
||
struct type *target_type =
|
||
check_typedef (TYPE_TARGET_TYPE (arg_type));
|
||
|
||
pass_in_v (gdbarch, regcache, &info,
|
||
TYPE_LENGTH (target_type), buf);
|
||
pass_in_v (gdbarch, regcache, &info,
|
||
TYPE_LENGTH (target_type),
|
||
buf + TYPE_LENGTH (target_type));
|
||
}
|
||
else
|
||
{
|
||
info.nsrn = 8;
|
||
pass_on_stack (&info, arg_type, arg);
|
||
}
|
||
break;
|
||
case TYPE_CODE_FLT:
|
||
pass_in_v_or_stack (gdbarch, regcache, &info, arg_type, arg);
|
||
break;
|
||
|
||
case TYPE_CODE_STRUCT:
|
||
case TYPE_CODE_ARRAY:
|
||
case TYPE_CODE_UNION:
|
||
if (is_hfa_or_hva (arg_type))
|
||
{
|
||
int elements = TYPE_NFIELDS (arg_type);
|
||
|
||
/* Homogeneous Aggregates */
|
||
if (info.nsrn + elements < 8)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < elements; i++)
|
||
{
|
||
/* We know that we have sufficient registers
|
||
available therefore this will never fallback
|
||
to the stack. */
|
||
struct value *field =
|
||
value_primitive_field (arg, 0, i, arg_type);
|
||
struct type *field_type =
|
||
check_typedef (value_type (field));
|
||
|
||
pass_in_v_or_stack (gdbarch, regcache, &info,
|
||
field_type, field);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
info.nsrn = 8;
|
||
pass_on_stack (&info, arg_type, arg);
|
||
}
|
||
}
|
||
else if (TYPE_CODE (arg_type) == TYPE_CODE_ARRAY
|
||
&& TYPE_VECTOR (arg_type) && (len == 16 || len == 8))
|
||
{
|
||
/* Short vector types are passed in V registers. */
|
||
pass_in_v_or_stack (gdbarch, regcache, &info, arg_type, arg);
|
||
}
|
||
else if (len > 16)
|
||
{
|
||
/* PCS B.7 Aggregates larger than 16 bytes are passed by
|
||
invisible reference. */
|
||
|
||
/* Allocate aligned storage. */
|
||
sp = align_down (sp - len, 16);
|
||
|
||
/* Write the real data into the stack. */
|
||
write_memory (sp, value_contents (arg), len);
|
||
|
||
/* Construct the indirection. */
|
||
arg_type = lookup_pointer_type (arg_type);
|
||
arg = value_from_pointer (arg_type, sp);
|
||
pass_in_x_or_stack (gdbarch, regcache, &info, arg_type, arg);
|
||
}
|
||
else
|
||
/* PCS C.15 / C.18 multiple values pass. */
|
||
pass_in_x_or_stack (gdbarch, regcache, &info, arg_type, arg);
|
||
break;
|
||
|
||
default:
|
||
pass_in_x_or_stack (gdbarch, regcache, &info, arg_type, arg);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Make sure stack retains 16 byte alignment. */
|
||
if (info.nsaa & 15)
|
||
sp -= 16 - (info.nsaa & 15);
|
||
|
||
while (!VEC_empty (stack_item_t, info.si))
|
||
{
|
||
stack_item_t *si = VEC_last (stack_item_t, info.si);
|
||
|
||
sp -= si->len;
|
||
if (si->data != NULL)
|
||
write_memory (sp, si->data, si->len);
|
||
VEC_pop (stack_item_t, info.si);
|
||
}
|
||
|
||
VEC_free (stack_item_t, info.si);
|
||
|
||
/* Finally, update the SP register. */
|
||
regcache_cooked_write_unsigned (regcache, AARCH64_SP_REGNUM, sp);
|
||
|
||
return sp;
|
||
}
|
||
|
||
/* Implement the "frame_align" gdbarch method. */
|
||
|
||
static CORE_ADDR
|
||
aarch64_frame_align (struct gdbarch *gdbarch, CORE_ADDR sp)
|
||
{
|
||
/* Align the stack to sixteen bytes. */
|
||
return sp & ~(CORE_ADDR) 15;
|
||
}
|
||
|
||
/* Return the type for an AdvSISD Q register. */
|
||
|
||
static struct type *
|
||
aarch64_vnq_type (struct gdbarch *gdbarch)
|
||
{
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
|
||
|
||
if (tdep->vnq_type == NULL)
|
||
{
|
||
struct type *t;
|
||
struct type *elem;
|
||
|
||
t = arch_composite_type (gdbarch, "__gdb_builtin_type_vnq",
|
||
TYPE_CODE_UNION);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_uint128;
|
||
append_composite_type_field (t, "u", elem);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_int128;
|
||
append_composite_type_field (t, "s", elem);
|
||
|
||
tdep->vnq_type = t;
|
||
}
|
||
|
||
return tdep->vnq_type;
|
||
}
|
||
|
||
/* Return the type for an AdvSISD D register. */
|
||
|
||
static struct type *
|
||
aarch64_vnd_type (struct gdbarch *gdbarch)
|
||
{
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
|
||
|
||
if (tdep->vnd_type == NULL)
|
||
{
|
||
struct type *t;
|
||
struct type *elem;
|
||
|
||
t = arch_composite_type (gdbarch, "__gdb_builtin_type_vnd",
|
||
TYPE_CODE_UNION);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_double;
|
||
append_composite_type_field (t, "f", elem);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_uint64;
|
||
append_composite_type_field (t, "u", elem);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_int64;
|
||
append_composite_type_field (t, "s", elem);
|
||
|
||
tdep->vnd_type = t;
|
||
}
|
||
|
||
return tdep->vnd_type;
|
||
}
|
||
|
||
/* Return the type for an AdvSISD S register. */
|
||
|
||
static struct type *
|
||
aarch64_vns_type (struct gdbarch *gdbarch)
|
||
{
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
|
||
|
||
if (tdep->vns_type == NULL)
|
||
{
|
||
struct type *t;
|
||
struct type *elem;
|
||
|
||
t = arch_composite_type (gdbarch, "__gdb_builtin_type_vns",
|
||
TYPE_CODE_UNION);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_float;
|
||
append_composite_type_field (t, "f", elem);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_uint32;
|
||
append_composite_type_field (t, "u", elem);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_int32;
|
||
append_composite_type_field (t, "s", elem);
|
||
|
||
tdep->vns_type = t;
|
||
}
|
||
|
||
return tdep->vns_type;
|
||
}
|
||
|
||
/* Return the type for an AdvSISD H register. */
|
||
|
||
static struct type *
|
||
aarch64_vnh_type (struct gdbarch *gdbarch)
|
||
{
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
|
||
|
||
if (tdep->vnh_type == NULL)
|
||
{
|
||
struct type *t;
|
||
struct type *elem;
|
||
|
||
t = arch_composite_type (gdbarch, "__gdb_builtin_type_vnh",
|
||
TYPE_CODE_UNION);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_uint16;
|
||
append_composite_type_field (t, "u", elem);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_int16;
|
||
append_composite_type_field (t, "s", elem);
|
||
|
||
tdep->vnh_type = t;
|
||
}
|
||
|
||
return tdep->vnh_type;
|
||
}
|
||
|
||
/* Return the type for an AdvSISD B register. */
|
||
|
||
static struct type *
|
||
aarch64_vnb_type (struct gdbarch *gdbarch)
|
||
{
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
|
||
|
||
if (tdep->vnb_type == NULL)
|
||
{
|
||
struct type *t;
|
||
struct type *elem;
|
||
|
||
t = arch_composite_type (gdbarch, "__gdb_builtin_type_vnb",
|
||
TYPE_CODE_UNION);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_uint8;
|
||
append_composite_type_field (t, "u", elem);
|
||
|
||
elem = builtin_type (gdbarch)->builtin_int8;
|
||
append_composite_type_field (t, "s", elem);
|
||
|
||
tdep->vnb_type = t;
|
||
}
|
||
|
||
return tdep->vnb_type;
|
||
}
|
||
|
||
/* Implement the "dwarf2_reg_to_regnum" gdbarch method. */
|
||
|
||
static int
|
||
aarch64_dwarf_reg_to_regnum (struct gdbarch *gdbarch, int reg)
|
||
{
|
||
if (reg >= AARCH64_DWARF_X0 && reg <= AARCH64_DWARF_X0 + 30)
|
||
return AARCH64_X0_REGNUM + reg - AARCH64_DWARF_X0;
|
||
|
||
if (reg == AARCH64_DWARF_SP)
|
||
return AARCH64_SP_REGNUM;
|
||
|
||
if (reg >= AARCH64_DWARF_V0 && reg <= AARCH64_DWARF_V0 + 31)
|
||
return AARCH64_V0_REGNUM + reg - AARCH64_DWARF_V0;
|
||
|
||
return -1;
|
||
}
|
||
|
||
|
||
/* Implement the "print_insn" gdbarch method. */
|
||
|
||
static int
|
||
aarch64_gdb_print_insn (bfd_vma memaddr, disassemble_info *info)
|
||
{
|
||
info->symbols = NULL;
|
||
return default_print_insn (memaddr, info);
|
||
}
|
||
|
||
/* AArch64 BRK software debug mode instruction.
|
||
Note that AArch64 code is always little-endian.
|
||
1101.0100.0010.0000.0000.0000.0000.0000 = 0xd4200000. */
|
||
constexpr gdb_byte aarch64_default_breakpoint[] = {0x00, 0x00, 0x20, 0xd4};
|
||
|
||
typedef BP_MANIPULATION (aarch64_default_breakpoint) aarch64_breakpoint;
|
||
|
||
/* Extract from an array REGS containing the (raw) register state a
|
||
function return value of type TYPE, and copy that, in virtual
|
||
format, into VALBUF. */
|
||
|
||
static void
|
||
aarch64_extract_return_value (struct type *type, struct regcache *regs,
|
||
gdb_byte *valbuf)
|
||
{
|
||
struct gdbarch *gdbarch = regs->arch ();
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
|
||
if (TYPE_CODE (type) == TYPE_CODE_FLT)
|
||
{
|
||
bfd_byte buf[V_REGISTER_SIZE];
|
||
int len = TYPE_LENGTH (type);
|
||
|
||
regcache_cooked_read (regs, AARCH64_V0_REGNUM, buf);
|
||
memcpy (valbuf, buf, len);
|
||
}
|
||
else if (TYPE_CODE (type) == TYPE_CODE_INT
|
||
|| TYPE_CODE (type) == TYPE_CODE_CHAR
|
||
|| TYPE_CODE (type) == TYPE_CODE_BOOL
|
||
|| TYPE_CODE (type) == TYPE_CODE_PTR
|
||
|| TYPE_IS_REFERENCE (type)
|
||
|| TYPE_CODE (type) == TYPE_CODE_ENUM)
|
||
{
|
||
/* If the the type is a plain integer, then the access is
|
||
straight-forward. Otherwise we have to play around a bit
|
||
more. */
|
||
int len = TYPE_LENGTH (type);
|
||
int regno = AARCH64_X0_REGNUM;
|
||
ULONGEST tmp;
|
||
|
||
while (len > 0)
|
||
{
|
||
/* By using store_unsigned_integer we avoid having to do
|
||
anything special for small big-endian values. */
|
||
regcache_cooked_read_unsigned (regs, regno++, &tmp);
|
||
store_unsigned_integer (valbuf,
|
||
(len > X_REGISTER_SIZE
|
||
? X_REGISTER_SIZE : len), byte_order, tmp);
|
||
len -= X_REGISTER_SIZE;
|
||
valbuf += X_REGISTER_SIZE;
|
||
}
|
||
}
|
||
else if (TYPE_CODE (type) == TYPE_CODE_COMPLEX)
|
||
{
|
||
int regno = AARCH64_V0_REGNUM;
|
||
bfd_byte buf[V_REGISTER_SIZE];
|
||
struct type *target_type = check_typedef (TYPE_TARGET_TYPE (type));
|
||
int len = TYPE_LENGTH (target_type);
|
||
|
||
regcache_cooked_read (regs, regno, buf);
|
||
memcpy (valbuf, buf, len);
|
||
valbuf += len;
|
||
regcache_cooked_read (regs, regno + 1, buf);
|
||
memcpy (valbuf, buf, len);
|
||
valbuf += len;
|
||
}
|
||
else if (is_hfa_or_hva (type))
|
||
{
|
||
int elements = TYPE_NFIELDS (type);
|
||
struct type *member_type = check_typedef (TYPE_FIELD_TYPE (type, 0));
|
||
int len = TYPE_LENGTH (member_type);
|
||
int i;
|
||
|
||
for (i = 0; i < elements; i++)
|
||
{
|
||
int regno = AARCH64_V0_REGNUM + i;
|
||
bfd_byte buf[V_REGISTER_SIZE];
|
||
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("read HFA or HVA return value element %d from %s\n",
|
||
i + 1,
|
||
gdbarch_register_name (gdbarch, regno));
|
||
}
|
||
regcache_cooked_read (regs, regno, buf);
|
||
|
||
memcpy (valbuf, buf, len);
|
||
valbuf += len;
|
||
}
|
||
}
|
||
else if (TYPE_CODE (type) == TYPE_CODE_ARRAY && TYPE_VECTOR (type)
|
||
&& (TYPE_LENGTH (type) == 16 || TYPE_LENGTH (type) == 8))
|
||
{
|
||
/* Short vector is returned in V register. */
|
||
gdb_byte buf[V_REGISTER_SIZE];
|
||
|
||
regcache_cooked_read (regs, AARCH64_V0_REGNUM, buf);
|
||
memcpy (valbuf, buf, TYPE_LENGTH (type));
|
||
}
|
||
else
|
||
{
|
||
/* For a structure or union the behaviour is as if the value had
|
||
been stored to word-aligned memory and then loaded into
|
||
registers with 64-bit load instruction(s). */
|
||
int len = TYPE_LENGTH (type);
|
||
int regno = AARCH64_X0_REGNUM;
|
||
bfd_byte buf[X_REGISTER_SIZE];
|
||
|
||
while (len > 0)
|
||
{
|
||
regcache_cooked_read (regs, regno++, buf);
|
||
memcpy (valbuf, buf, len > X_REGISTER_SIZE ? X_REGISTER_SIZE : len);
|
||
len -= X_REGISTER_SIZE;
|
||
valbuf += X_REGISTER_SIZE;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Will a function return an aggregate type in memory or in a
|
||
register? Return 0 if an aggregate type can be returned in a
|
||
register, 1 if it must be returned in memory. */
|
||
|
||
static int
|
||
aarch64_return_in_memory (struct gdbarch *gdbarch, struct type *type)
|
||
{
|
||
type = check_typedef (type);
|
||
|
||
if (is_hfa_or_hva (type))
|
||
{
|
||
/* v0-v7 are used to return values and one register is allocated
|
||
for one member. However, HFA or HVA has at most four members. */
|
||
return 0;
|
||
}
|
||
|
||
if (TYPE_LENGTH (type) > 16)
|
||
{
|
||
/* PCS B.6 Aggregates larger than 16 bytes are passed by
|
||
invisible reference. */
|
||
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Write into appropriate registers a function return value of type
|
||
TYPE, given in virtual format. */
|
||
|
||
static void
|
||
aarch64_store_return_value (struct type *type, struct regcache *regs,
|
||
const gdb_byte *valbuf)
|
||
{
|
||
struct gdbarch *gdbarch = regs->arch ();
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
|
||
if (TYPE_CODE (type) == TYPE_CODE_FLT)
|
||
{
|
||
bfd_byte buf[V_REGISTER_SIZE];
|
||
int len = TYPE_LENGTH (type);
|
||
|
||
memcpy (buf, valbuf, len > V_REGISTER_SIZE ? V_REGISTER_SIZE : len);
|
||
regcache_cooked_write (regs, AARCH64_V0_REGNUM, buf);
|
||
}
|
||
else if (TYPE_CODE (type) == TYPE_CODE_INT
|
||
|| TYPE_CODE (type) == TYPE_CODE_CHAR
|
||
|| TYPE_CODE (type) == TYPE_CODE_BOOL
|
||
|| TYPE_CODE (type) == TYPE_CODE_PTR
|
||
|| TYPE_IS_REFERENCE (type)
|
||
|| TYPE_CODE (type) == TYPE_CODE_ENUM)
|
||
{
|
||
if (TYPE_LENGTH (type) <= X_REGISTER_SIZE)
|
||
{
|
||
/* Values of one word or less are zero/sign-extended and
|
||
returned in r0. */
|
||
bfd_byte tmpbuf[X_REGISTER_SIZE];
|
||
LONGEST val = unpack_long (type, valbuf);
|
||
|
||
store_signed_integer (tmpbuf, X_REGISTER_SIZE, byte_order, val);
|
||
regcache_cooked_write (regs, AARCH64_X0_REGNUM, tmpbuf);
|
||
}
|
||
else
|
||
{
|
||
/* Integral values greater than one word are stored in
|
||
consecutive registers starting with r0. This will always
|
||
be a multiple of the regiser size. */
|
||
int len = TYPE_LENGTH (type);
|
||
int regno = AARCH64_X0_REGNUM;
|
||
|
||
while (len > 0)
|
||
{
|
||
regcache_cooked_write (regs, regno++, valbuf);
|
||
len -= X_REGISTER_SIZE;
|
||
valbuf += X_REGISTER_SIZE;
|
||
}
|
||
}
|
||
}
|
||
else if (is_hfa_or_hva (type))
|
||
{
|
||
int elements = TYPE_NFIELDS (type);
|
||
struct type *member_type = check_typedef (TYPE_FIELD_TYPE (type, 0));
|
||
int len = TYPE_LENGTH (member_type);
|
||
int i;
|
||
|
||
for (i = 0; i < elements; i++)
|
||
{
|
||
int regno = AARCH64_V0_REGNUM + i;
|
||
bfd_byte tmpbuf[V_REGISTER_SIZE];
|
||
|
||
if (aarch64_debug)
|
||
{
|
||
debug_printf ("write HFA or HVA return value element %d to %s\n",
|
||
i + 1,
|
||
gdbarch_register_name (gdbarch, regno));
|
||
}
|
||
|
||
memcpy (tmpbuf, valbuf, len);
|
||
regcache_cooked_write (regs, regno, tmpbuf);
|
||
valbuf += len;
|
||
}
|
||
}
|
||
else if (TYPE_CODE (type) == TYPE_CODE_ARRAY && TYPE_VECTOR (type)
|
||
&& (TYPE_LENGTH (type) == 8 || TYPE_LENGTH (type) == 16))
|
||
{
|
||
/* Short vector. */
|
||
gdb_byte buf[V_REGISTER_SIZE];
|
||
|
||
memcpy (buf, valbuf, TYPE_LENGTH (type));
|
||
regcache_cooked_write (regs, AARCH64_V0_REGNUM, buf);
|
||
}
|
||
else
|
||
{
|
||
/* For a structure or union the behaviour is as if the value had
|
||
been stored to word-aligned memory and then loaded into
|
||
registers with 64-bit load instruction(s). */
|
||
int len = TYPE_LENGTH (type);
|
||
int regno = AARCH64_X0_REGNUM;
|
||
bfd_byte tmpbuf[X_REGISTER_SIZE];
|
||
|
||
while (len > 0)
|
||
{
|
||
memcpy (tmpbuf, valbuf,
|
||
len > X_REGISTER_SIZE ? X_REGISTER_SIZE : len);
|
||
regcache_cooked_write (regs, regno++, tmpbuf);
|
||
len -= X_REGISTER_SIZE;
|
||
valbuf += X_REGISTER_SIZE;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Implement the "return_value" gdbarch method. */
|
||
|
||
static enum return_value_convention
|
||
aarch64_return_value (struct gdbarch *gdbarch, struct value *func_value,
|
||
struct type *valtype, struct regcache *regcache,
|
||
gdb_byte *readbuf, const gdb_byte *writebuf)
|
||
{
|
||
|
||
if (TYPE_CODE (valtype) == TYPE_CODE_STRUCT
|
||
|| TYPE_CODE (valtype) == TYPE_CODE_UNION
|
||
|| TYPE_CODE (valtype) == TYPE_CODE_ARRAY)
|
||
{
|
||
if (aarch64_return_in_memory (gdbarch, valtype))
|
||
{
|
||
if (aarch64_debug)
|
||
debug_printf ("return value in memory\n");
|
||
return RETURN_VALUE_STRUCT_CONVENTION;
|
||
}
|
||
}
|
||
|
||
if (writebuf)
|
||
aarch64_store_return_value (valtype, regcache, writebuf);
|
||
|
||
if (readbuf)
|
||
aarch64_extract_return_value (valtype, regcache, readbuf);
|
||
|
||
if (aarch64_debug)
|
||
debug_printf ("return value in registers\n");
|
||
|
||
return RETURN_VALUE_REGISTER_CONVENTION;
|
||
}
|
||
|
||
/* Implement the "get_longjmp_target" gdbarch method. */
|
||
|
||
static int
|
||
aarch64_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
|
||
{
|
||
CORE_ADDR jb_addr;
|
||
gdb_byte buf[X_REGISTER_SIZE];
|
||
struct gdbarch *gdbarch = get_frame_arch (frame);
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
|
||
jb_addr = get_frame_register_unsigned (frame, AARCH64_X0_REGNUM);
|
||
|
||
if (target_read_memory (jb_addr + tdep->jb_pc * tdep->jb_elt_size, buf,
|
||
X_REGISTER_SIZE))
|
||
return 0;
|
||
|
||
*pc = extract_unsigned_integer (buf, X_REGISTER_SIZE, byte_order);
|
||
return 1;
|
||
}
|
||
|
||
/* Implement the "gen_return_address" gdbarch method. */
|
||
|
||
static void
|
||
aarch64_gen_return_address (struct gdbarch *gdbarch,
|
||
struct agent_expr *ax, struct axs_value *value,
|
||
CORE_ADDR scope)
|
||
{
|
||
value->type = register_type (gdbarch, AARCH64_LR_REGNUM);
|
||
value->kind = axs_lvalue_register;
|
||
value->u.reg = AARCH64_LR_REGNUM;
|
||
}
|
||
|
||
|
||
/* Return the pseudo register name corresponding to register regnum. */
|
||
|
||
static const char *
|
||
aarch64_pseudo_register_name (struct gdbarch *gdbarch, int regnum)
|
||
{
|
||
static const char *const q_name[] =
|
||
{
|
||
"q0", "q1", "q2", "q3",
|
||
"q4", "q5", "q6", "q7",
|
||
"q8", "q9", "q10", "q11",
|
||
"q12", "q13", "q14", "q15",
|
||
"q16", "q17", "q18", "q19",
|
||
"q20", "q21", "q22", "q23",
|
||
"q24", "q25", "q26", "q27",
|
||
"q28", "q29", "q30", "q31",
|
||
};
|
||
|
||
static const char *const d_name[] =
|
||
{
|
||
"d0", "d1", "d2", "d3",
|
||
"d4", "d5", "d6", "d7",
|
||
"d8", "d9", "d10", "d11",
|
||
"d12", "d13", "d14", "d15",
|
||
"d16", "d17", "d18", "d19",
|
||
"d20", "d21", "d22", "d23",
|
||
"d24", "d25", "d26", "d27",
|
||
"d28", "d29", "d30", "d31",
|
||
};
|
||
|
||
static const char *const s_name[] =
|
||
{
|
||
"s0", "s1", "s2", "s3",
|
||
"s4", "s5", "s6", "s7",
|
||
"s8", "s9", "s10", "s11",
|
||
"s12", "s13", "s14", "s15",
|
||
"s16", "s17", "s18", "s19",
|
||
"s20", "s21", "s22", "s23",
|
||
"s24", "s25", "s26", "s27",
|
||
"s28", "s29", "s30", "s31",
|
||
};
|
||
|
||
static const char *const h_name[] =
|
||
{
|
||
"h0", "h1", "h2", "h3",
|
||
"h4", "h5", "h6", "h7",
|
||
"h8", "h9", "h10", "h11",
|
||
"h12", "h13", "h14", "h15",
|
||
"h16", "h17", "h18", "h19",
|
||
"h20", "h21", "h22", "h23",
|
||
"h24", "h25", "h26", "h27",
|
||
"h28", "h29", "h30", "h31",
|
||
};
|
||
|
||
static const char *const b_name[] =
|
||
{
|
||
"b0", "b1", "b2", "b3",
|
||
"b4", "b5", "b6", "b7",
|
||
"b8", "b9", "b10", "b11",
|
||
"b12", "b13", "b14", "b15",
|
||
"b16", "b17", "b18", "b19",
|
||
"b20", "b21", "b22", "b23",
|
||
"b24", "b25", "b26", "b27",
|
||
"b28", "b29", "b30", "b31",
|
||
};
|
||
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
if (regnum >= AARCH64_Q0_REGNUM && regnum < AARCH64_Q0_REGNUM + 32)
|
||
return q_name[regnum - AARCH64_Q0_REGNUM];
|
||
|
||
if (regnum >= AARCH64_D0_REGNUM && regnum < AARCH64_D0_REGNUM + 32)
|
||
return d_name[regnum - AARCH64_D0_REGNUM];
|
||
|
||
if (regnum >= AARCH64_S0_REGNUM && regnum < AARCH64_S0_REGNUM + 32)
|
||
return s_name[regnum - AARCH64_S0_REGNUM];
|
||
|
||
if (regnum >= AARCH64_H0_REGNUM && regnum < AARCH64_H0_REGNUM + 32)
|
||
return h_name[regnum - AARCH64_H0_REGNUM];
|
||
|
||
if (regnum >= AARCH64_B0_REGNUM && regnum < AARCH64_B0_REGNUM + 32)
|
||
return b_name[regnum - AARCH64_B0_REGNUM];
|
||
|
||
internal_error (__FILE__, __LINE__,
|
||
_("aarch64_pseudo_register_name: bad register number %d"),
|
||
regnum);
|
||
}
|
||
|
||
/* Implement the "pseudo_register_type" tdesc_arch_data method. */
|
||
|
||
static struct type *
|
||
aarch64_pseudo_register_type (struct gdbarch *gdbarch, int regnum)
|
||
{
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
if (regnum >= AARCH64_Q0_REGNUM && regnum < AARCH64_Q0_REGNUM + 32)
|
||
return aarch64_vnq_type (gdbarch);
|
||
|
||
if (regnum >= AARCH64_D0_REGNUM && regnum < AARCH64_D0_REGNUM + 32)
|
||
return aarch64_vnd_type (gdbarch);
|
||
|
||
if (regnum >= AARCH64_S0_REGNUM && regnum < AARCH64_S0_REGNUM + 32)
|
||
return aarch64_vns_type (gdbarch);
|
||
|
||
if (regnum >= AARCH64_H0_REGNUM && regnum < AARCH64_H0_REGNUM + 32)
|
||
return aarch64_vnh_type (gdbarch);
|
||
|
||
if (regnum >= AARCH64_B0_REGNUM && regnum < AARCH64_B0_REGNUM + 32)
|
||
return aarch64_vnb_type (gdbarch);
|
||
|
||
internal_error (__FILE__, __LINE__,
|
||
_("aarch64_pseudo_register_type: bad register number %d"),
|
||
regnum);
|
||
}
|
||
|
||
/* Implement the "pseudo_register_reggroup_p" tdesc_arch_data method. */
|
||
|
||
static int
|
||
aarch64_pseudo_register_reggroup_p (struct gdbarch *gdbarch, int regnum,
|
||
struct reggroup *group)
|
||
{
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
if (regnum >= AARCH64_Q0_REGNUM && regnum < AARCH64_Q0_REGNUM + 32)
|
||
return group == all_reggroup || group == vector_reggroup;
|
||
else if (regnum >= AARCH64_D0_REGNUM && regnum < AARCH64_D0_REGNUM + 32)
|
||
return (group == all_reggroup || group == vector_reggroup
|
||
|| group == float_reggroup);
|
||
else if (regnum >= AARCH64_S0_REGNUM && regnum < AARCH64_S0_REGNUM + 32)
|
||
return (group == all_reggroup || group == vector_reggroup
|
||
|| group == float_reggroup);
|
||
else if (regnum >= AARCH64_H0_REGNUM && regnum < AARCH64_H0_REGNUM + 32)
|
||
return group == all_reggroup || group == vector_reggroup;
|
||
else if (regnum >= AARCH64_B0_REGNUM && regnum < AARCH64_B0_REGNUM + 32)
|
||
return group == all_reggroup || group == vector_reggroup;
|
||
|
||
return group == all_reggroup;
|
||
}
|
||
|
||
/* Implement the "pseudo_register_read_value" gdbarch method. */
|
||
|
||
static struct value *
|
||
aarch64_pseudo_read_value (struct gdbarch *gdbarch,
|
||
readable_regcache *regcache,
|
||
int regnum)
|
||
{
|
||
gdb_byte reg_buf[V_REGISTER_SIZE];
|
||
struct value *result_value;
|
||
gdb_byte *buf;
|
||
|
||
result_value = allocate_value (register_type (gdbarch, regnum));
|
||
VALUE_LVAL (result_value) = lval_register;
|
||
VALUE_REGNUM (result_value) = regnum;
|
||
buf = value_contents_raw (result_value);
|
||
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
if (regnum >= AARCH64_Q0_REGNUM && regnum < AARCH64_Q0_REGNUM + 32)
|
||
{
|
||
enum register_status status;
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_Q0_REGNUM;
|
||
status = regcache->raw_read (v_regnum, reg_buf);
|
||
if (status != REG_VALID)
|
||
mark_value_bytes_unavailable (result_value, 0,
|
||
TYPE_LENGTH (value_type (result_value)));
|
||
else
|
||
memcpy (buf, reg_buf, Q_REGISTER_SIZE);
|
||
return result_value;
|
||
}
|
||
|
||
if (regnum >= AARCH64_D0_REGNUM && regnum < AARCH64_D0_REGNUM + 32)
|
||
{
|
||
enum register_status status;
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_D0_REGNUM;
|
||
status = regcache->raw_read (v_regnum, reg_buf);
|
||
if (status != REG_VALID)
|
||
mark_value_bytes_unavailable (result_value, 0,
|
||
TYPE_LENGTH (value_type (result_value)));
|
||
else
|
||
memcpy (buf, reg_buf, D_REGISTER_SIZE);
|
||
return result_value;
|
||
}
|
||
|
||
if (regnum >= AARCH64_S0_REGNUM && regnum < AARCH64_S0_REGNUM + 32)
|
||
{
|
||
enum register_status status;
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_S0_REGNUM;
|
||
status = regcache->raw_read (v_regnum, reg_buf);
|
||
if (status != REG_VALID)
|
||
mark_value_bytes_unavailable (result_value, 0,
|
||
TYPE_LENGTH (value_type (result_value)));
|
||
else
|
||
memcpy (buf, reg_buf, S_REGISTER_SIZE);
|
||
return result_value;
|
||
}
|
||
|
||
if (regnum >= AARCH64_H0_REGNUM && regnum < AARCH64_H0_REGNUM + 32)
|
||
{
|
||
enum register_status status;
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_H0_REGNUM;
|
||
status = regcache->raw_read (v_regnum, reg_buf);
|
||
if (status != REG_VALID)
|
||
mark_value_bytes_unavailable (result_value, 0,
|
||
TYPE_LENGTH (value_type (result_value)));
|
||
else
|
||
memcpy (buf, reg_buf, H_REGISTER_SIZE);
|
||
return result_value;
|
||
}
|
||
|
||
if (regnum >= AARCH64_B0_REGNUM && regnum < AARCH64_B0_REGNUM + 32)
|
||
{
|
||
enum register_status status;
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_B0_REGNUM;
|
||
status = regcache->raw_read (v_regnum, reg_buf);
|
||
if (status != REG_VALID)
|
||
mark_value_bytes_unavailable (result_value, 0,
|
||
TYPE_LENGTH (value_type (result_value)));
|
||
else
|
||
memcpy (buf, reg_buf, B_REGISTER_SIZE);
|
||
return result_value;
|
||
}
|
||
|
||
gdb_assert_not_reached ("regnum out of bound");
|
||
}
|
||
|
||
/* Implement the "pseudo_register_write" gdbarch method. */
|
||
|
||
static void
|
||
aarch64_pseudo_write (struct gdbarch *gdbarch, struct regcache *regcache,
|
||
int regnum, const gdb_byte *buf)
|
||
{
|
||
gdb_byte reg_buf[V_REGISTER_SIZE];
|
||
|
||
/* Ensure the register buffer is zero, we want gdb writes of the
|
||
various 'scalar' pseudo registers to behavior like architectural
|
||
writes, register width bytes are written the remainder are set to
|
||
zero. */
|
||
memset (reg_buf, 0, sizeof (reg_buf));
|
||
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
if (regnum >= AARCH64_Q0_REGNUM && regnum < AARCH64_Q0_REGNUM + 32)
|
||
{
|
||
/* pseudo Q registers */
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_Q0_REGNUM;
|
||
memcpy (reg_buf, buf, Q_REGISTER_SIZE);
|
||
regcache_raw_write (regcache, v_regnum, reg_buf);
|
||
return;
|
||
}
|
||
|
||
if (regnum >= AARCH64_D0_REGNUM && regnum < AARCH64_D0_REGNUM + 32)
|
||
{
|
||
/* pseudo D registers */
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_D0_REGNUM;
|
||
memcpy (reg_buf, buf, D_REGISTER_SIZE);
|
||
regcache_raw_write (regcache, v_regnum, reg_buf);
|
||
return;
|
||
}
|
||
|
||
if (regnum >= AARCH64_S0_REGNUM && regnum < AARCH64_S0_REGNUM + 32)
|
||
{
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_S0_REGNUM;
|
||
memcpy (reg_buf, buf, S_REGISTER_SIZE);
|
||
regcache_raw_write (regcache, v_regnum, reg_buf);
|
||
return;
|
||
}
|
||
|
||
if (regnum >= AARCH64_H0_REGNUM && regnum < AARCH64_H0_REGNUM + 32)
|
||
{
|
||
/* pseudo H registers */
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_H0_REGNUM;
|
||
memcpy (reg_buf, buf, H_REGISTER_SIZE);
|
||
regcache_raw_write (regcache, v_regnum, reg_buf);
|
||
return;
|
||
}
|
||
|
||
if (regnum >= AARCH64_B0_REGNUM && regnum < AARCH64_B0_REGNUM + 32)
|
||
{
|
||
/* pseudo B registers */
|
||
unsigned v_regnum;
|
||
|
||
v_regnum = AARCH64_V0_REGNUM + regnum - AARCH64_B0_REGNUM;
|
||
memcpy (reg_buf, buf, B_REGISTER_SIZE);
|
||
regcache_raw_write (regcache, v_regnum, reg_buf);
|
||
return;
|
||
}
|
||
|
||
gdb_assert_not_reached ("regnum out of bound");
|
||
}
|
||
|
||
/* Callback function for user_reg_add. */
|
||
|
||
static struct value *
|
||
value_of_aarch64_user_reg (struct frame_info *frame, const void *baton)
|
||
{
|
||
const int *reg_p = (const int *) baton;
|
||
|
||
return value_of_register (*reg_p, frame);
|
||
}
|
||
|
||
|
||
/* Implement the "software_single_step" gdbarch method, needed to
|
||
single step through atomic sequences on AArch64. */
|
||
|
||
static std::vector<CORE_ADDR>
|
||
aarch64_software_single_step (struct regcache *regcache)
|
||
{
|
||
struct gdbarch *gdbarch = regcache->arch ();
|
||
enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
|
||
const int insn_size = 4;
|
||
const int atomic_sequence_length = 16; /* Instruction sequence length. */
|
||
CORE_ADDR pc = regcache_read_pc (regcache);
|
||
CORE_ADDR breaks[2] = { -1, -1 };
|
||
CORE_ADDR loc = pc;
|
||
CORE_ADDR closing_insn = 0;
|
||
uint32_t insn = read_memory_unsigned_integer (loc, insn_size,
|
||
byte_order_for_code);
|
||
int index;
|
||
int insn_count;
|
||
int bc_insn_count = 0; /* Conditional branch instruction count. */
|
||
int last_breakpoint = 0; /* Defaults to 0 (no breakpoints placed). */
|
||
aarch64_inst inst;
|
||
|
||
if (aarch64_decode_insn (insn, &inst, 1, NULL) != 0)
|
||
return {};
|
||
|
||
/* Look for a Load Exclusive instruction which begins the sequence. */
|
||
if (inst.opcode->iclass != ldstexcl || bit (insn, 22) == 0)
|
||
return {};
|
||
|
||
for (insn_count = 0; insn_count < atomic_sequence_length; ++insn_count)
|
||
{
|
||
loc += insn_size;
|
||
insn = read_memory_unsigned_integer (loc, insn_size,
|
||
byte_order_for_code);
|
||
|
||
if (aarch64_decode_insn (insn, &inst, 1, NULL) != 0)
|
||
return {};
|
||
/* Check if the instruction is a conditional branch. */
|
||
if (inst.opcode->iclass == condbranch)
|
||
{
|
||
gdb_assert (inst.operands[0].type == AARCH64_OPND_ADDR_PCREL19);
|
||
|
||
if (bc_insn_count >= 1)
|
||
return {};
|
||
|
||
/* It is, so we'll try to set a breakpoint at the destination. */
|
||
breaks[1] = loc + inst.operands[0].imm.value;
|
||
|
||
bc_insn_count++;
|
||
last_breakpoint++;
|
||
}
|
||
|
||
/* Look for the Store Exclusive which closes the atomic sequence. */
|
||
if (inst.opcode->iclass == ldstexcl && bit (insn, 22) == 0)
|
||
{
|
||
closing_insn = loc;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* We didn't find a closing Store Exclusive instruction, fall back. */
|
||
if (!closing_insn)
|
||
return {};
|
||
|
||
/* Insert breakpoint after the end of the atomic sequence. */
|
||
breaks[0] = loc + insn_size;
|
||
|
||
/* Check for duplicated breakpoints, and also check that the second
|
||
breakpoint is not within the atomic sequence. */
|
||
if (last_breakpoint
|
||
&& (breaks[1] == breaks[0]
|
||
|| (breaks[1] >= pc && breaks[1] <= closing_insn)))
|
||
last_breakpoint = 0;
|
||
|
||
std::vector<CORE_ADDR> next_pcs;
|
||
|
||
/* Insert the breakpoint at the end of the sequence, and one at the
|
||
destination of the conditional branch, if it exists. */
|
||
for (index = 0; index <= last_breakpoint; index++)
|
||
next_pcs.push_back (breaks[index]);
|
||
|
||
return next_pcs;
|
||
}
|
||
|
||
struct aarch64_displaced_step_closure : public displaced_step_closure
|
||
{
|
||
/* It is true when condition instruction, such as B.CON, TBZ, etc,
|
||
is being displaced stepping. */
|
||
int cond = 0;
|
||
|
||
/* PC adjustment offset after displaced stepping. */
|
||
int32_t pc_adjust = 0;
|
||
};
|
||
|
||
/* Data when visiting instructions for displaced stepping. */
|
||
|
||
struct aarch64_displaced_step_data
|
||
{
|
||
struct aarch64_insn_data base;
|
||
|
||
/* The address where the instruction will be executed at. */
|
||
CORE_ADDR new_addr;
|
||
/* Buffer of instructions to be copied to NEW_ADDR to execute. */
|
||
uint32_t insn_buf[DISPLACED_MODIFIED_INSNS];
|
||
/* Number of instructions in INSN_BUF. */
|
||
unsigned insn_count;
|
||
/* Registers when doing displaced stepping. */
|
||
struct regcache *regs;
|
||
|
||
aarch64_displaced_step_closure *dsc;
|
||
};
|
||
|
||
/* Implementation of aarch64_insn_visitor method "b". */
|
||
|
||
static void
|
||
aarch64_displaced_step_b (const int is_bl, const int32_t offset,
|
||
struct aarch64_insn_data *data)
|
||
{
|
||
struct aarch64_displaced_step_data *dsd
|
||
= (struct aarch64_displaced_step_data *) data;
|
||
int64_t new_offset = data->insn_addr - dsd->new_addr + offset;
|
||
|
||
if (can_encode_int32 (new_offset, 28))
|
||
{
|
||
/* Emit B rather than BL, because executing BL on a new address
|
||
will get the wrong address into LR. In order to avoid this,
|
||
we emit B, and update LR if the instruction is BL. */
|
||
emit_b (dsd->insn_buf, 0, new_offset);
|
||
dsd->insn_count++;
|
||
}
|
||
else
|
||
{
|
||
/* Write NOP. */
|
||
emit_nop (dsd->insn_buf);
|
||
dsd->insn_count++;
|
||
dsd->dsc->pc_adjust = offset;
|
||
}
|
||
|
||
if (is_bl)
|
||
{
|
||
/* Update LR. */
|
||
regcache_cooked_write_unsigned (dsd->regs, AARCH64_LR_REGNUM,
|
||
data->insn_addr + 4);
|
||
}
|
||
}
|
||
|
||
/* Implementation of aarch64_insn_visitor method "b_cond". */
|
||
|
||
static void
|
||
aarch64_displaced_step_b_cond (const unsigned cond, const int32_t offset,
|
||
struct aarch64_insn_data *data)
|
||
{
|
||
struct aarch64_displaced_step_data *dsd
|
||
= (struct aarch64_displaced_step_data *) data;
|
||
|
||
/* GDB has to fix up PC after displaced step this instruction
|
||
differently according to the condition is true or false. Instead
|
||
of checking COND against conditional flags, we can use
|
||
the following instructions, and GDB can tell how to fix up PC
|
||
according to the PC value.
|
||
|
||
B.COND TAKEN ; If cond is true, then jump to TAKEN.
|
||
INSN1 ;
|
||
TAKEN:
|
||
INSN2
|
||
*/
|
||
|
||
emit_bcond (dsd->insn_buf, cond, 8);
|
||
dsd->dsc->cond = 1;
|
||
dsd->dsc->pc_adjust = offset;
|
||
dsd->insn_count = 1;
|
||
}
|
||
|
||
/* Dynamically allocate a new register. If we know the register
|
||
statically, we should make it a global as above instead of using this
|
||
helper function. */
|
||
|
||
static struct aarch64_register
|
||
aarch64_register (unsigned num, int is64)
|
||
{
|
||
return (struct aarch64_register) { num, is64 };
|
||
}
|
||
|
||
/* Implementation of aarch64_insn_visitor method "cb". */
|
||
|
||
static void
|
||
aarch64_displaced_step_cb (const int32_t offset, const int is_cbnz,
|
||
const unsigned rn, int is64,
|
||
struct aarch64_insn_data *data)
|
||
{
|
||
struct aarch64_displaced_step_data *dsd
|
||
= (struct aarch64_displaced_step_data *) data;
|
||
|
||
/* The offset is out of range for a compare and branch
|
||
instruction. We can use the following instructions instead:
|
||
|
||
CBZ xn, TAKEN ; xn == 0, then jump to TAKEN.
|
||
INSN1 ;
|
||
TAKEN:
|
||
INSN2
|
||
*/
|
||
emit_cb (dsd->insn_buf, is_cbnz, aarch64_register (rn, is64), 8);
|
||
dsd->insn_count = 1;
|
||
dsd->dsc->cond = 1;
|
||
dsd->dsc->pc_adjust = offset;
|
||
}
|
||
|
||
/* Implementation of aarch64_insn_visitor method "tb". */
|
||
|
||
static void
|
||
aarch64_displaced_step_tb (const int32_t offset, int is_tbnz,
|
||
const unsigned rt, unsigned bit,
|
||
struct aarch64_insn_data *data)
|
||
{
|
||
struct aarch64_displaced_step_data *dsd
|
||
= (struct aarch64_displaced_step_data *) data;
|
||
|
||
/* The offset is out of range for a test bit and branch
|
||
instruction We can use the following instructions instead:
|
||
|
||
TBZ xn, #bit, TAKEN ; xn[bit] == 0, then jump to TAKEN.
|
||
INSN1 ;
|
||
TAKEN:
|
||
INSN2
|
||
|
||
*/
|
||
emit_tb (dsd->insn_buf, is_tbnz, bit, aarch64_register (rt, 1), 8);
|
||
dsd->insn_count = 1;
|
||
dsd->dsc->cond = 1;
|
||
dsd->dsc->pc_adjust = offset;
|
||
}
|
||
|
||
/* Implementation of aarch64_insn_visitor method "adr". */
|
||
|
||
static void
|
||
aarch64_displaced_step_adr (const int32_t offset, const unsigned rd,
|
||
const int is_adrp, struct aarch64_insn_data *data)
|
||
{
|
||
struct aarch64_displaced_step_data *dsd
|
||
= (struct aarch64_displaced_step_data *) data;
|
||
/* We know exactly the address the ADR{P,} instruction will compute.
|
||
We can just write it to the destination register. */
|
||
CORE_ADDR address = data->insn_addr + offset;
|
||
|
||
if (is_adrp)
|
||
{
|
||
/* Clear the lower 12 bits of the offset to get the 4K page. */
|
||
regcache_cooked_write_unsigned (dsd->regs, AARCH64_X0_REGNUM + rd,
|
||
address & ~0xfff);
|
||
}
|
||
else
|
||
regcache_cooked_write_unsigned (dsd->regs, AARCH64_X0_REGNUM + rd,
|
||
address);
|
||
|
||
dsd->dsc->pc_adjust = 4;
|
||
emit_nop (dsd->insn_buf);
|
||
dsd->insn_count = 1;
|
||
}
|
||
|
||
/* Implementation of aarch64_insn_visitor method "ldr_literal". */
|
||
|
||
static void
|
||
aarch64_displaced_step_ldr_literal (const int32_t offset, const int is_sw,
|
||
const unsigned rt, const int is64,
|
||
struct aarch64_insn_data *data)
|
||
{
|
||
struct aarch64_displaced_step_data *dsd
|
||
= (struct aarch64_displaced_step_data *) data;
|
||
CORE_ADDR address = data->insn_addr + offset;
|
||
struct aarch64_memory_operand zero = { MEMORY_OPERAND_OFFSET, 0 };
|
||
|
||
regcache_cooked_write_unsigned (dsd->regs, AARCH64_X0_REGNUM + rt,
|
||
address);
|
||
|
||
if (is_sw)
|
||
dsd->insn_count = emit_ldrsw (dsd->insn_buf, aarch64_register (rt, 1),
|
||
aarch64_register (rt, 1), zero);
|
||
else
|
||
dsd->insn_count = emit_ldr (dsd->insn_buf, aarch64_register (rt, is64),
|
||
aarch64_register (rt, 1), zero);
|
||
|
||
dsd->dsc->pc_adjust = 4;
|
||
}
|
||
|
||
/* Implementation of aarch64_insn_visitor method "others". */
|
||
|
||
static void
|
||
aarch64_displaced_step_others (const uint32_t insn,
|
||
struct aarch64_insn_data *data)
|
||
{
|
||
struct aarch64_displaced_step_data *dsd
|
||
= (struct aarch64_displaced_step_data *) data;
|
||
|
||
aarch64_emit_insn (dsd->insn_buf, insn);
|
||
dsd->insn_count = 1;
|
||
|
||
if ((insn & 0xfffffc1f) == 0xd65f0000)
|
||
{
|
||
/* RET */
|
||
dsd->dsc->pc_adjust = 0;
|
||
}
|
||
else
|
||
dsd->dsc->pc_adjust = 4;
|
||
}
|
||
|
||
static const struct aarch64_insn_visitor visitor =
|
||
{
|
||
aarch64_displaced_step_b,
|
||
aarch64_displaced_step_b_cond,
|
||
aarch64_displaced_step_cb,
|
||
aarch64_displaced_step_tb,
|
||
aarch64_displaced_step_adr,
|
||
aarch64_displaced_step_ldr_literal,
|
||
aarch64_displaced_step_others,
|
||
};
|
||
|
||
/* Implement the "displaced_step_copy_insn" gdbarch method. */
|
||
|
||
struct displaced_step_closure *
|
||
aarch64_displaced_step_copy_insn (struct gdbarch *gdbarch,
|
||
CORE_ADDR from, CORE_ADDR to,
|
||
struct regcache *regs)
|
||
{
|
||
enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
|
||
uint32_t insn = read_memory_unsigned_integer (from, 4, byte_order_for_code);
|
||
struct aarch64_displaced_step_data dsd;
|
||
aarch64_inst inst;
|
||
|
||
if (aarch64_decode_insn (insn, &inst, 1, NULL) != 0)
|
||
return NULL;
|
||
|
||
/* Look for a Load Exclusive instruction which begins the sequence. */
|
||
if (inst.opcode->iclass == ldstexcl && bit (insn, 22))
|
||
{
|
||
/* We can't displaced step atomic sequences. */
|
||
return NULL;
|
||
}
|
||
|
||
std::unique_ptr<aarch64_displaced_step_closure> dsc
|
||
(new aarch64_displaced_step_closure);
|
||
dsd.base.insn_addr = from;
|
||
dsd.new_addr = to;
|
||
dsd.regs = regs;
|
||
dsd.dsc = dsc.get ();
|
||
dsd.insn_count = 0;
|
||
aarch64_relocate_instruction (insn, &visitor,
|
||
(struct aarch64_insn_data *) &dsd);
|
||
gdb_assert (dsd.insn_count <= DISPLACED_MODIFIED_INSNS);
|
||
|
||
if (dsd.insn_count != 0)
|
||
{
|
||
int i;
|
||
|
||
/* Instruction can be relocated to scratch pad. Copy
|
||
relocated instruction(s) there. */
|
||
for (i = 0; i < dsd.insn_count; i++)
|
||
{
|
||
if (debug_displaced)
|
||
{
|
||
debug_printf ("displaced: writing insn ");
|
||
debug_printf ("%.8x", dsd.insn_buf[i]);
|
||
debug_printf (" at %s\n", paddress (gdbarch, to + i * 4));
|
||
}
|
||
write_memory_unsigned_integer (to + i * 4, 4, byte_order_for_code,
|
||
(ULONGEST) dsd.insn_buf[i]);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
dsc = NULL;
|
||
}
|
||
|
||
return dsc.release ();
|
||
}
|
||
|
||
/* Implement the "displaced_step_fixup" gdbarch method. */
|
||
|
||
void
|
||
aarch64_displaced_step_fixup (struct gdbarch *gdbarch,
|
||
struct displaced_step_closure *dsc_,
|
||
CORE_ADDR from, CORE_ADDR to,
|
||
struct regcache *regs)
|
||
{
|
||
aarch64_displaced_step_closure *dsc = (aarch64_displaced_step_closure *) dsc_;
|
||
|
||
if (dsc->cond)
|
||
{
|
||
ULONGEST pc;
|
||
|
||
regcache_cooked_read_unsigned (regs, AARCH64_PC_REGNUM, &pc);
|
||
if (pc - to == 8)
|
||
{
|
||
/* Condition is true. */
|
||
}
|
||
else if (pc - to == 4)
|
||
{
|
||
/* Condition is false. */
|
||
dsc->pc_adjust = 4;
|
||
}
|
||
else
|
||
gdb_assert_not_reached ("Unexpected PC value after displaced stepping");
|
||
}
|
||
|
||
if (dsc->pc_adjust != 0)
|
||
{
|
||
if (debug_displaced)
|
||
{
|
||
debug_printf ("displaced: fixup: set PC to %s:%d\n",
|
||
paddress (gdbarch, from), dsc->pc_adjust);
|
||
}
|
||
regcache_cooked_write_unsigned (regs, AARCH64_PC_REGNUM,
|
||
from + dsc->pc_adjust);
|
||
}
|
||
}
|
||
|
||
/* Implement the "displaced_step_hw_singlestep" gdbarch method. */
|
||
|
||
int
|
||
aarch64_displaced_step_hw_singlestep (struct gdbarch *gdbarch,
|
||
struct displaced_step_closure *closure)
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
/* Get the correct target description. */
|
||
|
||
const target_desc *
|
||
aarch64_read_description ()
|
||
{
|
||
static target_desc *aarch64_tdesc = NULL;
|
||
target_desc **tdesc = &aarch64_tdesc;
|
||
|
||
if (*tdesc == NULL)
|
||
*tdesc = aarch64_create_target_description ();
|
||
|
||
return *tdesc;
|
||
}
|
||
|
||
/* Initialize the current architecture based on INFO. If possible,
|
||
re-use an architecture from ARCHES, which is a list of
|
||
architectures already created during this debugging session.
|
||
|
||
Called e.g. at program startup, when reading a core file, and when
|
||
reading a binary file. */
|
||
|
||
static struct gdbarch *
|
||
aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
|
||
{
|
||
struct gdbarch_tdep *tdep;
|
||
struct gdbarch *gdbarch;
|
||
struct gdbarch_list *best_arch;
|
||
struct tdesc_arch_data *tdesc_data = NULL;
|
||
const struct target_desc *tdesc = info.target_desc;
|
||
int i;
|
||
int valid_p = 1;
|
||
const struct tdesc_feature *feature;
|
||
int num_regs = 0;
|
||
int num_pseudo_regs = 0;
|
||
|
||
/* Ensure we always have a target descriptor. */
|
||
if (!tdesc_has_registers (tdesc))
|
||
tdesc = aarch64_read_description ();
|
||
|
||
gdb_assert (tdesc);
|
||
|
||
feature = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.core");
|
||
|
||
if (feature == NULL)
|
||
return NULL;
|
||
|
||
tdesc_data = tdesc_data_alloc ();
|
||
|
||
/* Validate the descriptor provides the mandatory core R registers
|
||
and allocate their numbers. */
|
||
for (i = 0; i < ARRAY_SIZE (aarch64_r_register_names); i++)
|
||
valid_p &=
|
||
tdesc_numbered_register (feature, tdesc_data, AARCH64_X0_REGNUM + i,
|
||
aarch64_r_register_names[i]);
|
||
|
||
num_regs = AARCH64_X0_REGNUM + i;
|
||
|
||
/* Look for the V registers. */
|
||
feature = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.fpu");
|
||
if (feature)
|
||
{
|
||
/* Validate the descriptor provides the mandatory V registers
|
||
and allocate their numbers. */
|
||
for (i = 0; i < ARRAY_SIZE (aarch64_v_register_names); i++)
|
||
valid_p &=
|
||
tdesc_numbered_register (feature, tdesc_data, AARCH64_V0_REGNUM + i,
|
||
aarch64_v_register_names[i]);
|
||
|
||
num_regs = AARCH64_V0_REGNUM + i;
|
||
|
||
num_pseudo_regs += 32; /* add the Qn scalar register pseudos */
|
||
num_pseudo_regs += 32; /* add the Dn scalar register pseudos */
|
||
num_pseudo_regs += 32; /* add the Sn scalar register pseudos */
|
||
num_pseudo_regs += 32; /* add the Hn scalar register pseudos */
|
||
num_pseudo_regs += 32; /* add the Bn scalar register pseudos */
|
||
}
|
||
|
||
if (!valid_p)
|
||
{
|
||
tdesc_data_cleanup (tdesc_data);
|
||
return NULL;
|
||
}
|
||
|
||
/* AArch64 code is always little-endian. */
|
||
info.byte_order_for_code = BFD_ENDIAN_LITTLE;
|
||
|
||
/* If there is already a candidate, use it. */
|
||
for (best_arch = gdbarch_list_lookup_by_info (arches, &info);
|
||
best_arch != NULL;
|
||
best_arch = gdbarch_list_lookup_by_info (best_arch->next, &info))
|
||
{
|
||
/* Found a match. */
|
||
break;
|
||
}
|
||
|
||
if (best_arch != NULL)
|
||
{
|
||
if (tdesc_data != NULL)
|
||
tdesc_data_cleanup (tdesc_data);
|
||
return best_arch->gdbarch;
|
||
}
|
||
|
||
tdep = XCNEW (struct gdbarch_tdep);
|
||
gdbarch = gdbarch_alloc (&info, tdep);
|
||
|
||
/* This should be low enough for everything. */
|
||
tdep->lowest_pc = 0x20;
|
||
tdep->jb_pc = -1; /* Longjump support not enabled by default. */
|
||
tdep->jb_elt_size = 8;
|
||
|
||
set_gdbarch_push_dummy_call (gdbarch, aarch64_push_dummy_call);
|
||
set_gdbarch_frame_align (gdbarch, aarch64_frame_align);
|
||
|
||
/* Frame handling. */
|
||
set_gdbarch_dummy_id (gdbarch, aarch64_dummy_id);
|
||
set_gdbarch_unwind_pc (gdbarch, aarch64_unwind_pc);
|
||
set_gdbarch_unwind_sp (gdbarch, aarch64_unwind_sp);
|
||
|
||
/* Advance PC across function entry code. */
|
||
set_gdbarch_skip_prologue (gdbarch, aarch64_skip_prologue);
|
||
|
||
/* The stack grows downward. */
|
||
set_gdbarch_inner_than (gdbarch, core_addr_lessthan);
|
||
|
||
/* Breakpoint manipulation. */
|
||
set_gdbarch_breakpoint_kind_from_pc (gdbarch,
|
||
aarch64_breakpoint::kind_from_pc);
|
||
set_gdbarch_sw_breakpoint_from_kind (gdbarch,
|
||
aarch64_breakpoint::bp_from_kind);
|
||
set_gdbarch_have_nonsteppable_watchpoint (gdbarch, 1);
|
||
set_gdbarch_software_single_step (gdbarch, aarch64_software_single_step);
|
||
|
||
/* Information about registers, etc. */
|
||
set_gdbarch_sp_regnum (gdbarch, AARCH64_SP_REGNUM);
|
||
set_gdbarch_pc_regnum (gdbarch, AARCH64_PC_REGNUM);
|
||
set_gdbarch_num_regs (gdbarch, num_regs);
|
||
|
||
set_gdbarch_num_pseudo_regs (gdbarch, num_pseudo_regs);
|
||
set_gdbarch_pseudo_register_read_value (gdbarch, aarch64_pseudo_read_value);
|
||
set_gdbarch_pseudo_register_write (gdbarch, aarch64_pseudo_write);
|
||
set_tdesc_pseudo_register_name (gdbarch, aarch64_pseudo_register_name);
|
||
set_tdesc_pseudo_register_type (gdbarch, aarch64_pseudo_register_type);
|
||
set_tdesc_pseudo_register_reggroup_p (gdbarch,
|
||
aarch64_pseudo_register_reggroup_p);
|
||
|
||
/* ABI */
|
||
set_gdbarch_short_bit (gdbarch, 16);
|
||
set_gdbarch_int_bit (gdbarch, 32);
|
||
set_gdbarch_float_bit (gdbarch, 32);
|
||
set_gdbarch_double_bit (gdbarch, 64);
|
||
set_gdbarch_long_double_bit (gdbarch, 128);
|
||
set_gdbarch_long_bit (gdbarch, 64);
|
||
set_gdbarch_long_long_bit (gdbarch, 64);
|
||
set_gdbarch_ptr_bit (gdbarch, 64);
|
||
set_gdbarch_char_signed (gdbarch, 0);
|
||
set_gdbarch_wchar_signed (gdbarch, 0);
|
||
set_gdbarch_float_format (gdbarch, floatformats_ieee_single);
|
||
set_gdbarch_double_format (gdbarch, floatformats_ieee_double);
|
||
set_gdbarch_long_double_format (gdbarch, floatformats_ia64_quad);
|
||
|
||
/* Internal <-> external register number maps. */
|
||
set_gdbarch_dwarf2_reg_to_regnum (gdbarch, aarch64_dwarf_reg_to_regnum);
|
||
|
||
/* Returning results. */
|
||
set_gdbarch_return_value (gdbarch, aarch64_return_value);
|
||
|
||
/* Disassembly. */
|
||
set_gdbarch_print_insn (gdbarch, aarch64_gdb_print_insn);
|
||
|
||
/* Virtual tables. */
|
||
set_gdbarch_vbit_in_delta (gdbarch, 1);
|
||
|
||
/* Hook in the ABI-specific overrides, if they have been registered. */
|
||
info.target_desc = tdesc;
|
||
info.tdesc_data = tdesc_data;
|
||
gdbarch_init_osabi (info, gdbarch);
|
||
|
||
dwarf2_frame_set_init_reg (gdbarch, aarch64_dwarf2_frame_init_reg);
|
||
|
||
/* Add some default predicates. */
|
||
frame_unwind_append_unwinder (gdbarch, &aarch64_stub_unwind);
|
||
dwarf2_append_unwinders (gdbarch);
|
||
frame_unwind_append_unwinder (gdbarch, &aarch64_prologue_unwind);
|
||
|
||
frame_base_set_default (gdbarch, &aarch64_normal_base);
|
||
|
||
/* Now we have tuned the configuration, set a few final things,
|
||
based on what the OS ABI has told us. */
|
||
|
||
if (tdep->jb_pc >= 0)
|
||
set_gdbarch_get_longjmp_target (gdbarch, aarch64_get_longjmp_target);
|
||
|
||
set_gdbarch_gen_return_address (gdbarch, aarch64_gen_return_address);
|
||
|
||
tdesc_use_registers (gdbarch, tdesc, tdesc_data);
|
||
|
||
/* Add standard register aliases. */
|
||
for (i = 0; i < ARRAY_SIZE (aarch64_register_aliases); i++)
|
||
user_reg_add (gdbarch, aarch64_register_aliases[i].name,
|
||
value_of_aarch64_user_reg,
|
||
&aarch64_register_aliases[i].regnum);
|
||
|
||
return gdbarch;
|
||
}
|
||
|
||
static void
|
||
aarch64_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file)
|
||
{
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
|
||
|
||
if (tdep == NULL)
|
||
return;
|
||
|
||
fprintf_unfiltered (file, _("aarch64_dump_tdep: Lowest pc = 0x%s"),
|
||
paddress (gdbarch, tdep->lowest_pc));
|
||
}
|
||
|
||
#if GDB_SELF_TEST
|
||
namespace selftests
|
||
{
|
||
static void aarch64_process_record_test (void);
|
||
}
|
||
#endif
|
||
|
||
void
|
||
_initialize_aarch64_tdep (void)
|
||
{
|
||
gdbarch_register (bfd_arch_aarch64, aarch64_gdbarch_init,
|
||
aarch64_dump_tdep);
|
||
|
||
/* Debug this file's internals. */
|
||
add_setshow_boolean_cmd ("aarch64", class_maintenance, &aarch64_debug, _("\
|
||
Set AArch64 debugging."), _("\
|
||
Show AArch64 debugging."), _("\
|
||
When on, AArch64 specific debugging is enabled."),
|
||
NULL,
|
||
show_aarch64_debug,
|
||
&setdebuglist, &showdebuglist);
|
||
|
||
#if GDB_SELF_TEST
|
||
selftests::register_test ("aarch64-analyze-prologue",
|
||
selftests::aarch64_analyze_prologue_test);
|
||
selftests::register_test ("aarch64-process-record",
|
||
selftests::aarch64_process_record_test);
|
||
selftests::record_xml_tdesc ("aarch64.xml",
|
||
aarch64_create_target_description ());
|
||
#endif
|
||
}
|
||
|
||
/* AArch64 process record-replay related structures, defines etc. */
|
||
|
||
#define REG_ALLOC(REGS, LENGTH, RECORD_BUF) \
|
||
do \
|
||
{ \
|
||
unsigned int reg_len = LENGTH; \
|
||
if (reg_len) \
|
||
{ \
|
||
REGS = XNEWVEC (uint32_t, reg_len); \
|
||
memcpy(®S[0], &RECORD_BUF[0], sizeof(uint32_t)*LENGTH); \
|
||
} \
|
||
} \
|
||
while (0)
|
||
|
||
#define MEM_ALLOC(MEMS, LENGTH, RECORD_BUF) \
|
||
do \
|
||
{ \
|
||
unsigned int mem_len = LENGTH; \
|
||
if (mem_len) \
|
||
{ \
|
||
MEMS = XNEWVEC (struct aarch64_mem_r, mem_len); \
|
||
memcpy(&MEMS->len, &RECORD_BUF[0], \
|
||
sizeof(struct aarch64_mem_r) * LENGTH); \
|
||
} \
|
||
} \
|
||
while (0)
|
||
|
||
/* AArch64 record/replay structures and enumerations. */
|
||
|
||
struct aarch64_mem_r
|
||
{
|
||
uint64_t len; /* Record length. */
|
||
uint64_t addr; /* Memory address. */
|
||
};
|
||
|
||
enum aarch64_record_result
|
||
{
|
||
AARCH64_RECORD_SUCCESS,
|
||
AARCH64_RECORD_UNSUPPORTED,
|
||
AARCH64_RECORD_UNKNOWN
|
||
};
|
||
|
||
typedef struct insn_decode_record_t
|
||
{
|
||
struct gdbarch *gdbarch;
|
||
struct regcache *regcache;
|
||
CORE_ADDR this_addr; /* Address of insn to be recorded. */
|
||
uint32_t aarch64_insn; /* Insn to be recorded. */
|
||
uint32_t mem_rec_count; /* Count of memory records. */
|
||
uint32_t reg_rec_count; /* Count of register records. */
|
||
uint32_t *aarch64_regs; /* Registers to be recorded. */
|
||
struct aarch64_mem_r *aarch64_mems; /* Memory locations to be recorded. */
|
||
} insn_decode_record;
|
||
|
||
/* Record handler for data processing - register instructions. */
|
||
|
||
static unsigned int
|
||
aarch64_record_data_proc_reg (insn_decode_record *aarch64_insn_r)
|
||
{
|
||
uint8_t reg_rd, insn_bits24_27, insn_bits21_23;
|
||
uint32_t record_buf[4];
|
||
|
||
reg_rd = bits (aarch64_insn_r->aarch64_insn, 0, 4);
|
||
insn_bits24_27 = bits (aarch64_insn_r->aarch64_insn, 24, 27);
|
||
insn_bits21_23 = bits (aarch64_insn_r->aarch64_insn, 21, 23);
|
||
|
||
if (!bit (aarch64_insn_r->aarch64_insn, 28))
|
||
{
|
||
uint8_t setflags;
|
||
|
||
/* Logical (shifted register). */
|
||
if (insn_bits24_27 == 0x0a)
|
||
setflags = (bits (aarch64_insn_r->aarch64_insn, 29, 30) == 0x03);
|
||
/* Add/subtract. */
|
||
else if (insn_bits24_27 == 0x0b)
|
||
setflags = bit (aarch64_insn_r->aarch64_insn, 29);
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
|
||
record_buf[0] = reg_rd;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
if (setflags)
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_CPSR_REGNUM;
|
||
}
|
||
else
|
||
{
|
||
if (insn_bits24_27 == 0x0b)
|
||
{
|
||
/* Data-processing (3 source). */
|
||
record_buf[0] = reg_rd;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
else if (insn_bits24_27 == 0x0a)
|
||
{
|
||
if (insn_bits21_23 == 0x00)
|
||
{
|
||
/* Add/subtract (with carry). */
|
||
record_buf[0] = reg_rd;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
if (bit (aarch64_insn_r->aarch64_insn, 29))
|
||
{
|
||
record_buf[1] = AARCH64_CPSR_REGNUM;
|
||
aarch64_insn_r->reg_rec_count = 2;
|
||
}
|
||
}
|
||
else if (insn_bits21_23 == 0x02)
|
||
{
|
||
/* Conditional compare (register) and conditional compare
|
||
(immediate) instructions. */
|
||
record_buf[0] = AARCH64_CPSR_REGNUM;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
else if (insn_bits21_23 == 0x04 || insn_bits21_23 == 0x06)
|
||
{
|
||
/* CConditional select. */
|
||
/* Data-processing (2 source). */
|
||
/* Data-processing (1 source). */
|
||
record_buf[0] = reg_rd;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
}
|
||
}
|
||
|
||
REG_ALLOC (aarch64_insn_r->aarch64_regs, aarch64_insn_r->reg_rec_count,
|
||
record_buf);
|
||
return AARCH64_RECORD_SUCCESS;
|
||
}
|
||
|
||
/* Record handler for data processing - immediate instructions. */
|
||
|
||
static unsigned int
|
||
aarch64_record_data_proc_imm (insn_decode_record *aarch64_insn_r)
|
||
{
|
||
uint8_t reg_rd, insn_bit23, insn_bits24_27, setflags;
|
||
uint32_t record_buf[4];
|
||
|
||
reg_rd = bits (aarch64_insn_r->aarch64_insn, 0, 4);
|
||
insn_bit23 = bit (aarch64_insn_r->aarch64_insn, 23);
|
||
insn_bits24_27 = bits (aarch64_insn_r->aarch64_insn, 24, 27);
|
||
|
||
if (insn_bits24_27 == 0x00 /* PC rel addressing. */
|
||
|| insn_bits24_27 == 0x03 /* Bitfield and Extract. */
|
||
|| (insn_bits24_27 == 0x02 && insn_bit23)) /* Move wide (immediate). */
|
||
{
|
||
record_buf[0] = reg_rd;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
else if (insn_bits24_27 == 0x01)
|
||
{
|
||
/* Add/Subtract (immediate). */
|
||
setflags = bit (aarch64_insn_r->aarch64_insn, 29);
|
||
record_buf[0] = reg_rd;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
if (setflags)
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_CPSR_REGNUM;
|
||
}
|
||
else if (insn_bits24_27 == 0x02 && !insn_bit23)
|
||
{
|
||
/* Logical (immediate). */
|
||
setflags = bits (aarch64_insn_r->aarch64_insn, 29, 30) == 0x03;
|
||
record_buf[0] = reg_rd;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
if (setflags)
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_CPSR_REGNUM;
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
|
||
REG_ALLOC (aarch64_insn_r->aarch64_regs, aarch64_insn_r->reg_rec_count,
|
||
record_buf);
|
||
return AARCH64_RECORD_SUCCESS;
|
||
}
|
||
|
||
/* Record handler for branch, exception generation and system instructions. */
|
||
|
||
static unsigned int
|
||
aarch64_record_branch_except_sys (insn_decode_record *aarch64_insn_r)
|
||
{
|
||
struct gdbarch_tdep *tdep = gdbarch_tdep (aarch64_insn_r->gdbarch);
|
||
uint8_t insn_bits24_27, insn_bits28_31, insn_bits22_23;
|
||
uint32_t record_buf[4];
|
||
|
||
insn_bits24_27 = bits (aarch64_insn_r->aarch64_insn, 24, 27);
|
||
insn_bits28_31 = bits (aarch64_insn_r->aarch64_insn, 28, 31);
|
||
insn_bits22_23 = bits (aarch64_insn_r->aarch64_insn, 22, 23);
|
||
|
||
if (insn_bits28_31 == 0x0d)
|
||
{
|
||
/* Exception generation instructions. */
|
||
if (insn_bits24_27 == 0x04)
|
||
{
|
||
if (!bits (aarch64_insn_r->aarch64_insn, 2, 4)
|
||
&& !bits (aarch64_insn_r->aarch64_insn, 21, 23)
|
||
&& bits (aarch64_insn_r->aarch64_insn, 0, 1) == 0x01)
|
||
{
|
||
ULONGEST svc_number;
|
||
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache, 8,
|
||
&svc_number);
|
||
return tdep->aarch64_syscall_record (aarch64_insn_r->regcache,
|
||
svc_number);
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNSUPPORTED;
|
||
}
|
||
/* System instructions. */
|
||
else if (insn_bits24_27 == 0x05 && insn_bits22_23 == 0x00)
|
||
{
|
||
uint32_t reg_rt, reg_crn;
|
||
|
||
reg_rt = bits (aarch64_insn_r->aarch64_insn, 0, 4);
|
||
reg_crn = bits (aarch64_insn_r->aarch64_insn, 12, 15);
|
||
|
||
/* Record rt in case of sysl and mrs instructions. */
|
||
if (bit (aarch64_insn_r->aarch64_insn, 21))
|
||
{
|
||
record_buf[0] = reg_rt;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
/* Record cpsr for hint and msr(immediate) instructions. */
|
||
else if (reg_crn == 0x02 || reg_crn == 0x04)
|
||
{
|
||
record_buf[0] = AARCH64_CPSR_REGNUM;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
}
|
||
/* Unconditional branch (register). */
|
||
else if((insn_bits24_27 & 0x0e) == 0x06)
|
||
{
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_PC_REGNUM;
|
||
if (bits (aarch64_insn_r->aarch64_insn, 21, 22) == 0x01)
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_LR_REGNUM;
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
}
|
||
/* Unconditional branch (immediate). */
|
||
else if ((insn_bits28_31 & 0x07) == 0x01 && (insn_bits24_27 & 0x0c) == 0x04)
|
||
{
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_PC_REGNUM;
|
||
if (bit (aarch64_insn_r->aarch64_insn, 31))
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_LR_REGNUM;
|
||
}
|
||
else
|
||
/* Compare & branch (immediate), Test & branch (immediate) and
|
||
Conditional branch (immediate). */
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = AARCH64_PC_REGNUM;
|
||
|
||
REG_ALLOC (aarch64_insn_r->aarch64_regs, aarch64_insn_r->reg_rec_count,
|
||
record_buf);
|
||
return AARCH64_RECORD_SUCCESS;
|
||
}
|
||
|
||
/* Record handler for advanced SIMD load and store instructions. */
|
||
|
||
static unsigned int
|
||
aarch64_record_asimd_load_store (insn_decode_record *aarch64_insn_r)
|
||
{
|
||
CORE_ADDR address;
|
||
uint64_t addr_offset = 0;
|
||
uint32_t record_buf[24];
|
||
uint64_t record_buf_mem[24];
|
||
uint32_t reg_rn, reg_rt;
|
||
uint32_t reg_index = 0, mem_index = 0;
|
||
uint8_t opcode_bits, size_bits;
|
||
|
||
reg_rt = bits (aarch64_insn_r->aarch64_insn, 0, 4);
|
||
reg_rn = bits (aarch64_insn_r->aarch64_insn, 5, 9);
|
||
size_bits = bits (aarch64_insn_r->aarch64_insn, 10, 11);
|
||
opcode_bits = bits (aarch64_insn_r->aarch64_insn, 12, 15);
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn, &address);
|
||
|
||
if (record_debug)
|
||
debug_printf ("Process record: Advanced SIMD load/store\n");
|
||
|
||
/* Load/store single structure. */
|
||
if (bit (aarch64_insn_r->aarch64_insn, 24))
|
||
{
|
||
uint8_t sindex, scale, selem, esize, replicate = 0;
|
||
scale = opcode_bits >> 2;
|
||
selem = ((opcode_bits & 0x02) |
|
||
bit (aarch64_insn_r->aarch64_insn, 21)) + 1;
|
||
switch (scale)
|
||
{
|
||
case 1:
|
||
if (size_bits & 0x01)
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
break;
|
||
case 2:
|
||
if ((size_bits >> 1) & 0x01)
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
if (size_bits & 0x01)
|
||
{
|
||
if (!((opcode_bits >> 1) & 0x01))
|
||
scale = 3;
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
}
|
||
break;
|
||
case 3:
|
||
if (bit (aarch64_insn_r->aarch64_insn, 22) && !(opcode_bits & 0x01))
|
||
{
|
||
scale = size_bits;
|
||
replicate = 1;
|
||
break;
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
default:
|
||
break;
|
||
}
|
||
esize = 8 << scale;
|
||
if (replicate)
|
||
for (sindex = 0; sindex < selem; sindex++)
|
||
{
|
||
record_buf[reg_index++] = reg_rt + AARCH64_V0_REGNUM;
|
||
reg_rt = (reg_rt + 1) % 32;
|
||
}
|
||
else
|
||
{
|
||
for (sindex = 0; sindex < selem; sindex++)
|
||
{
|
||
if (bit (aarch64_insn_r->aarch64_insn, 22))
|
||
record_buf[reg_index++] = reg_rt + AARCH64_V0_REGNUM;
|
||
else
|
||
{
|
||
record_buf_mem[mem_index++] = esize / 8;
|
||
record_buf_mem[mem_index++] = address + addr_offset;
|
||
}
|
||
addr_offset = addr_offset + (esize / 8);
|
||
reg_rt = (reg_rt + 1) % 32;
|
||
}
|
||
}
|
||
}
|
||
/* Load/store multiple structure. */
|
||
else
|
||
{
|
||
uint8_t selem, esize, rpt, elements;
|
||
uint8_t eindex, rindex;
|
||
|
||
esize = 8 << size_bits;
|
||
if (bit (aarch64_insn_r->aarch64_insn, 30))
|
||
elements = 128 / esize;
|
||
else
|
||
elements = 64 / esize;
|
||
|
||
switch (opcode_bits)
|
||
{
|
||
/*LD/ST4 (4 Registers). */
|
||
case 0:
|
||
rpt = 1;
|
||
selem = 4;
|
||
break;
|
||
/*LD/ST1 (4 Registers). */
|
||
case 2:
|
||
rpt = 4;
|
||
selem = 1;
|
||
break;
|
||
/*LD/ST3 (3 Registers). */
|
||
case 4:
|
||
rpt = 1;
|
||
selem = 3;
|
||
break;
|
||
/*LD/ST1 (3 Registers). */
|
||
case 6:
|
||
rpt = 3;
|
||
selem = 1;
|
||
break;
|
||
/*LD/ST1 (1 Register). */
|
||
case 7:
|
||
rpt = 1;
|
||
selem = 1;
|
||
break;
|
||
/*LD/ST2 (2 Registers). */
|
||
case 8:
|
||
rpt = 1;
|
||
selem = 2;
|
||
break;
|
||
/*LD/ST1 (2 Registers). */
|
||
case 10:
|
||
rpt = 2;
|
||
selem = 1;
|
||
break;
|
||
default:
|
||
return AARCH64_RECORD_UNSUPPORTED;
|
||
break;
|
||
}
|
||
for (rindex = 0; rindex < rpt; rindex++)
|
||
for (eindex = 0; eindex < elements; eindex++)
|
||
{
|
||
uint8_t reg_tt, sindex;
|
||
reg_tt = (reg_rt + rindex) % 32;
|
||
for (sindex = 0; sindex < selem; sindex++)
|
||
{
|
||
if (bit (aarch64_insn_r->aarch64_insn, 22))
|
||
record_buf[reg_index++] = reg_tt + AARCH64_V0_REGNUM;
|
||
else
|
||
{
|
||
record_buf_mem[mem_index++] = esize / 8;
|
||
record_buf_mem[mem_index++] = address + addr_offset;
|
||
}
|
||
addr_offset = addr_offset + (esize / 8);
|
||
reg_tt = (reg_tt + 1) % 32;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (bit (aarch64_insn_r->aarch64_insn, 23))
|
||
record_buf[reg_index++] = reg_rn;
|
||
|
||
aarch64_insn_r->reg_rec_count = reg_index;
|
||
aarch64_insn_r->mem_rec_count = mem_index / 2;
|
||
MEM_ALLOC (aarch64_insn_r->aarch64_mems, aarch64_insn_r->mem_rec_count,
|
||
record_buf_mem);
|
||
REG_ALLOC (aarch64_insn_r->aarch64_regs, aarch64_insn_r->reg_rec_count,
|
||
record_buf);
|
||
return AARCH64_RECORD_SUCCESS;
|
||
}
|
||
|
||
/* Record handler for load and store instructions. */
|
||
|
||
static unsigned int
|
||
aarch64_record_load_store (insn_decode_record *aarch64_insn_r)
|
||
{
|
||
uint8_t insn_bits24_27, insn_bits28_29, insn_bits10_11;
|
||
uint8_t insn_bit23, insn_bit21;
|
||
uint8_t opc, size_bits, ld_flag, vector_flag;
|
||
uint32_t reg_rn, reg_rt, reg_rt2;
|
||
uint64_t datasize, offset;
|
||
uint32_t record_buf[8];
|
||
uint64_t record_buf_mem[8];
|
||
CORE_ADDR address;
|
||
|
||
insn_bits10_11 = bits (aarch64_insn_r->aarch64_insn, 10, 11);
|
||
insn_bits24_27 = bits (aarch64_insn_r->aarch64_insn, 24, 27);
|
||
insn_bits28_29 = bits (aarch64_insn_r->aarch64_insn, 28, 29);
|
||
insn_bit21 = bit (aarch64_insn_r->aarch64_insn, 21);
|
||
insn_bit23 = bit (aarch64_insn_r->aarch64_insn, 23);
|
||
ld_flag = bit (aarch64_insn_r->aarch64_insn, 22);
|
||
vector_flag = bit (aarch64_insn_r->aarch64_insn, 26);
|
||
reg_rt = bits (aarch64_insn_r->aarch64_insn, 0, 4);
|
||
reg_rn = bits (aarch64_insn_r->aarch64_insn, 5, 9);
|
||
reg_rt2 = bits (aarch64_insn_r->aarch64_insn, 10, 14);
|
||
size_bits = bits (aarch64_insn_r->aarch64_insn, 30, 31);
|
||
|
||
/* Load/store exclusive. */
|
||
if (insn_bits24_27 == 0x08 && insn_bits28_29 == 0x00)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("Process record: load/store exclusive\n");
|
||
|
||
if (ld_flag)
|
||
{
|
||
record_buf[0] = reg_rt;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
if (insn_bit21)
|
||
{
|
||
record_buf[1] = reg_rt2;
|
||
aarch64_insn_r->reg_rec_count = 2;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (insn_bit21)
|
||
datasize = (8 << size_bits) * 2;
|
||
else
|
||
datasize = (8 << size_bits);
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn,
|
||
&address);
|
||
record_buf_mem[0] = datasize / 8;
|
||
record_buf_mem[1] = address;
|
||
aarch64_insn_r->mem_rec_count = 1;
|
||
if (!insn_bit23)
|
||
{
|
||
/* Save register rs. */
|
||
record_buf[0] = bits (aarch64_insn_r->aarch64_insn, 16, 20);
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
}
|
||
}
|
||
/* Load register (literal) instructions decoding. */
|
||
else if ((insn_bits24_27 & 0x0b) == 0x08 && insn_bits28_29 == 0x01)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("Process record: load register (literal)\n");
|
||
if (vector_flag)
|
||
record_buf[0] = reg_rt + AARCH64_V0_REGNUM;
|
||
else
|
||
record_buf[0] = reg_rt;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
/* All types of load/store pair instructions decoding. */
|
||
else if ((insn_bits24_27 & 0x0a) == 0x08 && insn_bits28_29 == 0x02)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("Process record: load/store pair\n");
|
||
|
||
if (ld_flag)
|
||
{
|
||
if (vector_flag)
|
||
{
|
||
record_buf[0] = reg_rt + AARCH64_V0_REGNUM;
|
||
record_buf[1] = reg_rt2 + AARCH64_V0_REGNUM;
|
||
}
|
||
else
|
||
{
|
||
record_buf[0] = reg_rt;
|
||
record_buf[1] = reg_rt2;
|
||
}
|
||
aarch64_insn_r->reg_rec_count = 2;
|
||
}
|
||
else
|
||
{
|
||
uint16_t imm7_off;
|
||
imm7_off = bits (aarch64_insn_r->aarch64_insn, 15, 21);
|
||
if (!vector_flag)
|
||
size_bits = size_bits >> 1;
|
||
datasize = 8 << (2 + size_bits);
|
||
offset = (imm7_off & 0x40) ? (~imm7_off & 0x007f) + 1 : imm7_off;
|
||
offset = offset << (2 + size_bits);
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn,
|
||
&address);
|
||
if (!((insn_bits24_27 & 0x0b) == 0x08 && insn_bit23))
|
||
{
|
||
if (imm7_off & 0x40)
|
||
address = address - offset;
|
||
else
|
||
address = address + offset;
|
||
}
|
||
|
||
record_buf_mem[0] = datasize / 8;
|
||
record_buf_mem[1] = address;
|
||
record_buf_mem[2] = datasize / 8;
|
||
record_buf_mem[3] = address + (datasize / 8);
|
||
aarch64_insn_r->mem_rec_count = 2;
|
||
}
|
||
if (bit (aarch64_insn_r->aarch64_insn, 23))
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = reg_rn;
|
||
}
|
||
/* Load/store register (unsigned immediate) instructions. */
|
||
else if ((insn_bits24_27 & 0x0b) == 0x09 && insn_bits28_29 == 0x03)
|
||
{
|
||
opc = bits (aarch64_insn_r->aarch64_insn, 22, 23);
|
||
if (!(opc >> 1))
|
||
{
|
||
if (opc & 0x01)
|
||
ld_flag = 0x01;
|
||
else
|
||
ld_flag = 0x0;
|
||
}
|
||
else
|
||
{
|
||
if (size_bits == 0x3 && vector_flag == 0x0 && opc == 0x2)
|
||
{
|
||
/* PRFM (immediate) */
|
||
return AARCH64_RECORD_SUCCESS;
|
||
}
|
||
else if (size_bits == 0x2 && vector_flag == 0x0 && opc == 0x2)
|
||
{
|
||
/* LDRSW (immediate) */
|
||
ld_flag = 0x1;
|
||
}
|
||
else
|
||
{
|
||
if (opc & 0x01)
|
||
ld_flag = 0x01;
|
||
else
|
||
ld_flag = 0x0;
|
||
}
|
||
}
|
||
|
||
if (record_debug)
|
||
{
|
||
debug_printf ("Process record: load/store (unsigned immediate):"
|
||
" size %x V %d opc %x\n", size_bits, vector_flag,
|
||
opc);
|
||
}
|
||
|
||
if (!ld_flag)
|
||
{
|
||
offset = bits (aarch64_insn_r->aarch64_insn, 10, 21);
|
||
datasize = 8 << size_bits;
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn,
|
||
&address);
|
||
offset = offset << size_bits;
|
||
address = address + offset;
|
||
|
||
record_buf_mem[0] = datasize >> 3;
|
||
record_buf_mem[1] = address;
|
||
aarch64_insn_r->mem_rec_count = 1;
|
||
}
|
||
else
|
||
{
|
||
if (vector_flag)
|
||
record_buf[0] = reg_rt + AARCH64_V0_REGNUM;
|
||
else
|
||
record_buf[0] = reg_rt;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
}
|
||
/* Load/store register (register offset) instructions. */
|
||
else if ((insn_bits24_27 & 0x0b) == 0x08 && insn_bits28_29 == 0x03
|
||
&& insn_bits10_11 == 0x02 && insn_bit21)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("Process record: load/store (register offset)\n");
|
||
opc = bits (aarch64_insn_r->aarch64_insn, 22, 23);
|
||
if (!(opc >> 1))
|
||
if (opc & 0x01)
|
||
ld_flag = 0x01;
|
||
else
|
||
ld_flag = 0x0;
|
||
else
|
||
if (size_bits != 0x03)
|
||
ld_flag = 0x01;
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
|
||
if (!ld_flag)
|
||
{
|
||
ULONGEST reg_rm_val;
|
||
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache,
|
||
bits (aarch64_insn_r->aarch64_insn, 16, 20), ®_rm_val);
|
||
if (bit (aarch64_insn_r->aarch64_insn, 12))
|
||
offset = reg_rm_val << size_bits;
|
||
else
|
||
offset = reg_rm_val;
|
||
datasize = 8 << size_bits;
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn,
|
||
&address);
|
||
address = address + offset;
|
||
record_buf_mem[0] = datasize >> 3;
|
||
record_buf_mem[1] = address;
|
||
aarch64_insn_r->mem_rec_count = 1;
|
||
}
|
||
else
|
||
{
|
||
if (vector_flag)
|
||
record_buf[0] = reg_rt + AARCH64_V0_REGNUM;
|
||
else
|
||
record_buf[0] = reg_rt;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
}
|
||
/* Load/store register (immediate and unprivileged) instructions. */
|
||
else if ((insn_bits24_27 & 0x0b) == 0x08 && insn_bits28_29 == 0x03
|
||
&& !insn_bit21)
|
||
{
|
||
if (record_debug)
|
||
{
|
||
debug_printf ("Process record: load/store "
|
||
"(immediate and unprivileged)\n");
|
||
}
|
||
opc = bits (aarch64_insn_r->aarch64_insn, 22, 23);
|
||
if (!(opc >> 1))
|
||
if (opc & 0x01)
|
||
ld_flag = 0x01;
|
||
else
|
||
ld_flag = 0x0;
|
||
else
|
||
if (size_bits != 0x03)
|
||
ld_flag = 0x01;
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
|
||
if (!ld_flag)
|
||
{
|
||
uint16_t imm9_off;
|
||
imm9_off = bits (aarch64_insn_r->aarch64_insn, 12, 20);
|
||
offset = (imm9_off & 0x0100) ? (((~imm9_off) & 0x01ff) + 1) : imm9_off;
|
||
datasize = 8 << size_bits;
|
||
regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn,
|
||
&address);
|
||
if (insn_bits10_11 != 0x01)
|
||
{
|
||
if (imm9_off & 0x0100)
|
||
address = address - offset;
|
||
else
|
||
address = address + offset;
|
||
}
|
||
record_buf_mem[0] = datasize >> 3;
|
||
record_buf_mem[1] = address;
|
||
aarch64_insn_r->mem_rec_count = 1;
|
||
}
|
||
else
|
||
{
|
||
if (vector_flag)
|
||
record_buf[0] = reg_rt + AARCH64_V0_REGNUM;
|
||
else
|
||
record_buf[0] = reg_rt;
|
||
aarch64_insn_r->reg_rec_count = 1;
|
||
}
|
||
if (insn_bits10_11 == 0x01 || insn_bits10_11 == 0x03)
|
||
record_buf[aarch64_insn_r->reg_rec_count++] = reg_rn;
|
||
}
|
||
/* Advanced SIMD load/store instructions. */
|
||
else
|
||
return aarch64_record_asimd_load_store (aarch64_insn_r);
|
||
|
||
MEM_ALLOC (aarch64_insn_r->aarch64_mems, aarch64_insn_r->mem_rec_count,
|
||
record_buf_mem);
|
||
REG_ALLOC (aarch64_insn_r->aarch64_regs, aarch64_insn_r->reg_rec_count,
|
||
record_buf);
|
||
return AARCH64_RECORD_SUCCESS;
|
||
}
|
||
|
||
/* Record handler for data processing SIMD and floating point instructions. */
|
||
|
||
static unsigned int
|
||
aarch64_record_data_proc_simd_fp (insn_decode_record *aarch64_insn_r)
|
||
{
|
||
uint8_t insn_bit21, opcode, rmode, reg_rd;
|
||
uint8_t insn_bits24_27, insn_bits28_31, insn_bits10_11, insn_bits12_15;
|
||
uint8_t insn_bits11_14;
|
||
uint32_t record_buf[2];
|
||
|
||
insn_bits24_27 = bits (aarch64_insn_r->aarch64_insn, 24, 27);
|
||
insn_bits28_31 = bits (aarch64_insn_r->aarch64_insn, 28, 31);
|
||
insn_bits10_11 = bits (aarch64_insn_r->aarch64_insn, 10, 11);
|
||
insn_bits12_15 = bits (aarch64_insn_r->aarch64_insn, 12, 15);
|
||
insn_bits11_14 = bits (aarch64_insn_r->aarch64_insn, 11, 14);
|
||
opcode = bits (aarch64_insn_r->aarch64_insn, 16, 18);
|
||
rmode = bits (aarch64_insn_r->aarch64_insn, 19, 20);
|
||
reg_rd = bits (aarch64_insn_r->aarch64_insn, 0, 4);
|
||
insn_bit21 = bit (aarch64_insn_r->aarch64_insn, 21);
|
||
|
||
if (record_debug)
|
||
debug_printf ("Process record: data processing SIMD/FP: ");
|
||
|
||
if ((insn_bits28_31 & 0x05) == 0x01 && insn_bits24_27 == 0x0e)
|
||
{
|
||
/* Floating point - fixed point conversion instructions. */
|
||
if (!insn_bit21)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("FP - fixed point conversion");
|
||
|
||
if ((opcode >> 1) == 0x0 && rmode == 0x03)
|
||
record_buf[0] = reg_rd;
|
||
else
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
/* Floating point - conditional compare instructions. */
|
||
else if (insn_bits10_11 == 0x01)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("FP - conditional compare");
|
||
|
||
record_buf[0] = AARCH64_CPSR_REGNUM;
|
||
}
|
||
/* Floating point - data processing (2-source) and
|
||
conditional select instructions. */
|
||
else if (insn_bits10_11 == 0x02 || insn_bits10_11 == 0x03)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("FP - DP (2-source)");
|
||
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
else if (insn_bits10_11 == 0x00)
|
||
{
|
||
/* Floating point - immediate instructions. */
|
||
if ((insn_bits12_15 & 0x01) == 0x01
|
||
|| (insn_bits12_15 & 0x07) == 0x04)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("FP - immediate");
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
/* Floating point - compare instructions. */
|
||
else if ((insn_bits12_15 & 0x03) == 0x02)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("FP - immediate");
|
||
record_buf[0] = AARCH64_CPSR_REGNUM;
|
||
}
|
||
/* Floating point - integer conversions instructions. */
|
||
else if (insn_bits12_15 == 0x00)
|
||
{
|
||
/* Convert float to integer instruction. */
|
||
if (!(opcode >> 1) || ((opcode >> 1) == 0x02 && !rmode))
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("float to int conversion");
|
||
|
||
record_buf[0] = reg_rd + AARCH64_X0_REGNUM;
|
||
}
|
||
/* Convert integer to float instruction. */
|
||
else if ((opcode >> 1) == 0x01 && !rmode)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("int to float conversion");
|
||
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
/* Move float to integer instruction. */
|
||
else if ((opcode >> 1) == 0x03)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("move float to int");
|
||
|
||
if (!(opcode & 0x01))
|
||
record_buf[0] = reg_rd + AARCH64_X0_REGNUM;
|
||
else
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
}
|
||
else
|
||
return AARCH64_RECORD_UNKNOWN;
|
||
}
|
||
else if ((insn_bits28_31 & 0x09) == 0x00 && insn_bits24_27 == 0x0e)
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("SIMD copy");
|
||
|
||
/* Advanced SIMD copy instructions. */
|
||
if (!bits (aarch64_insn_r->aarch64_insn, 21, 23)
|
||
&& !bit (aarch64_insn_r->aarch64_insn, 15)
|
||
&& bit (aarch64_insn_r->aarch64_insn, 10))
|
||
{
|
||
if (insn_bits11_14 == 0x05 || insn_bits11_14 == 0x07)
|
||
record_buf[0] = reg_rd + AARCH64_X0_REGNUM;
|
||
else
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
else
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
/* All remaining floating point or advanced SIMD instructions. */
|
||
else
|
||
{
|
||
if (record_debug)
|
||
debug_printf ("all remain");
|
||
|
||
record_buf[0] = reg_rd + AARCH64_V0_REGNUM;
|
||
}
|
||
|
||
if (record_debug)
|
||
debug_printf ("\n");
|
||
|
||
aarch64_insn_r->reg_rec_count++;
|
||
gdb_assert (aarch64_insn_r->reg_rec_count == 1);
|
||
REG_ALLOC (aarch64_insn_r->aarch64_regs, aarch64_insn_r->reg_rec_count,
|
||
record_buf);
|
||
return AARCH64_RECORD_SUCCESS;
|
||
}
|
||
|
||
/* Decodes insns type and invokes its record handler. */
|
||
|
||
static unsigned int
|
||
aarch64_record_decode_insn_handler (insn_decode_record *aarch64_insn_r)
|
||
{
|
||
uint32_t ins_bit25, ins_bit26, ins_bit27, ins_bit28;
|
||
|
||
ins_bit25 = bit (aarch64_insn_r->aarch64_insn, 25);
|
||
ins_bit26 = bit (aarch64_insn_r->aarch64_insn, 26);
|
||
ins_bit27 = bit (aarch64_insn_r->aarch64_insn, 27);
|
||
ins_bit28 = bit (aarch64_insn_r->aarch64_insn, 28);
|
||
|
||
/* Data processing - immediate instructions. */
|
||
if (!ins_bit26 && !ins_bit27 && ins_bit28)
|
||
return aarch64_record_data_proc_imm (aarch64_insn_r);
|
||
|
||
/* Branch, exception generation and system instructions. */
|
||
if (ins_bit26 && !ins_bit27 && ins_bit28)
|
||
return aarch64_record_branch_except_sys (aarch64_insn_r);
|
||
|
||
/* Load and store instructions. */
|
||
if (!ins_bit25 && ins_bit27)
|
||
return aarch64_record_load_store (aarch64_insn_r);
|
||
|
||
/* Data processing - register instructions. */
|
||
if (ins_bit25 && !ins_bit26 && ins_bit27)
|
||
return aarch64_record_data_proc_reg (aarch64_insn_r);
|
||
|
||
/* Data processing - SIMD and floating point instructions. */
|
||
if (ins_bit25 && ins_bit26 && ins_bit27)
|
||
return aarch64_record_data_proc_simd_fp (aarch64_insn_r);
|
||
|
||
return AARCH64_RECORD_UNSUPPORTED;
|
||
}
|
||
|
||
/* Cleans up local record registers and memory allocations. */
|
||
|
||
static void
|
||
deallocate_reg_mem (insn_decode_record *record)
|
||
{
|
||
xfree (record->aarch64_regs);
|
||
xfree (record->aarch64_mems);
|
||
}
|
||
|
||
#if GDB_SELF_TEST
|
||
namespace selftests {
|
||
|
||
static void
|
||
aarch64_process_record_test (void)
|
||
{
|
||
struct gdbarch_info info;
|
||
uint32_t ret;
|
||
|
||
gdbarch_info_init (&info);
|
||
info.bfd_arch_info = bfd_scan_arch ("aarch64");
|
||
|
||
struct gdbarch *gdbarch = gdbarch_find_by_info (info);
|
||
SELF_CHECK (gdbarch != NULL);
|
||
|
||
insn_decode_record aarch64_record;
|
||
|
||
memset (&aarch64_record, 0, sizeof (insn_decode_record));
|
||
aarch64_record.regcache = NULL;
|
||
aarch64_record.this_addr = 0;
|
||
aarch64_record.gdbarch = gdbarch;
|
||
|
||
/* 20 00 80 f9 prfm pldl1keep, [x1] */
|
||
aarch64_record.aarch64_insn = 0xf9800020;
|
||
ret = aarch64_record_decode_insn_handler (&aarch64_record);
|
||
SELF_CHECK (ret == AARCH64_RECORD_SUCCESS);
|
||
SELF_CHECK (aarch64_record.reg_rec_count == 0);
|
||
SELF_CHECK (aarch64_record.mem_rec_count == 0);
|
||
|
||
deallocate_reg_mem (&aarch64_record);
|
||
}
|
||
|
||
} // namespace selftests
|
||
#endif /* GDB_SELF_TEST */
|
||
|
||
/* Parse the current instruction and record the values of the registers and
|
||
memory that will be changed in current instruction to record_arch_list
|
||
return -1 if something is wrong. */
|
||
|
||
int
|
||
aarch64_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
|
||
CORE_ADDR insn_addr)
|
||
{
|
||
uint32_t rec_no = 0;
|
||
uint8_t insn_size = 4;
|
||
uint32_t ret = 0;
|
||
gdb_byte buf[insn_size];
|
||
insn_decode_record aarch64_record;
|
||
|
||
memset (&buf[0], 0, insn_size);
|
||
memset (&aarch64_record, 0, sizeof (insn_decode_record));
|
||
target_read_memory (insn_addr, &buf[0], insn_size);
|
||
aarch64_record.aarch64_insn
|
||
= (uint32_t) extract_unsigned_integer (&buf[0],
|
||
insn_size,
|
||
gdbarch_byte_order (gdbarch));
|
||
aarch64_record.regcache = regcache;
|
||
aarch64_record.this_addr = insn_addr;
|
||
aarch64_record.gdbarch = gdbarch;
|
||
|
||
ret = aarch64_record_decode_insn_handler (&aarch64_record);
|
||
if (ret == AARCH64_RECORD_UNSUPPORTED)
|
||
{
|
||
printf_unfiltered (_("Process record does not support instruction "
|
||
"0x%0x at address %s.\n"),
|
||
aarch64_record.aarch64_insn,
|
||
paddress (gdbarch, insn_addr));
|
||
ret = -1;
|
||
}
|
||
|
||
if (0 == ret)
|
||
{
|
||
/* Record registers. */
|
||
record_full_arch_list_add_reg (aarch64_record.regcache,
|
||
AARCH64_PC_REGNUM);
|
||
/* Always record register CPSR. */
|
||
record_full_arch_list_add_reg (aarch64_record.regcache,
|
||
AARCH64_CPSR_REGNUM);
|
||
if (aarch64_record.aarch64_regs)
|
||
for (rec_no = 0; rec_no < aarch64_record.reg_rec_count; rec_no++)
|
||
if (record_full_arch_list_add_reg (aarch64_record.regcache,
|
||
aarch64_record.aarch64_regs[rec_no]))
|
||
ret = -1;
|
||
|
||
/* Record memories. */
|
||
if (aarch64_record.aarch64_mems)
|
||
for (rec_no = 0; rec_no < aarch64_record.mem_rec_count; rec_no++)
|
||
if (record_full_arch_list_add_mem
|
||
((CORE_ADDR)aarch64_record.aarch64_mems[rec_no].addr,
|
||
aarch64_record.aarch64_mems[rec_no].len))
|
||
ret = -1;
|
||
|
||
if (record_full_arch_list_add_end ())
|
||
ret = -1;
|
||
}
|
||
|
||
deallocate_reg_mem (&aarch64_record);
|
||
return ret;
|
||
}
|