mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-24 10:35:12 +08:00
77c5f49648
This unifies arch_float_type and init_float_type by using a type allocator. Reviewed-By: Simon Marchi <simon.marchi@efficios.com>
2097 lines
57 KiB
C
2097 lines
57 KiB
C
/* Target-dependent code for the NDS32 architecture, for GDB.
|
||
|
||
Copyright (C) 2013-2023 Free Software Foundation, Inc.
|
||
Contributed by Andes Technology Corporation.
|
||
|
||
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 "frame-unwind.h"
|
||
#include "frame-base.h"
|
||
#include "symtab.h"
|
||
#include "gdbtypes.h"
|
||
#include "gdbcore.h"
|
||
#include "value.h"
|
||
#include "reggroups.h"
|
||
#include "inferior.h"
|
||
#include "osabi.h"
|
||
#include "arch-utils.h"
|
||
#include "regcache.h"
|
||
#include "dis-asm.h"
|
||
#include "user-regs.h"
|
||
#include "elf-bfd.h"
|
||
#include "dwarf2/frame.h"
|
||
#include "remote.h"
|
||
#include "target-descriptions.h"
|
||
|
||
#include "nds32-tdep.h"
|
||
#include "elf/nds32.h"
|
||
#include "opcode/nds32.h"
|
||
#include <algorithm>
|
||
|
||
#include "features/nds32.c"
|
||
|
||
/* Simple macros for instruction analysis. */
|
||
#define CHOP_BITS(insn, n) (insn & ~__MASK (n))
|
||
#define N32_LSMW_ENABLE4(insn) (((insn) >> 6) & 0xf)
|
||
#define N32_SMW_ADM \
|
||
N32_TYPE4 (LSMW, 0, 0, 0, 1, (N32_LSMW_ADM << 2) | N32_LSMW_LSMW)
|
||
#define N32_LMW_BIM \
|
||
N32_TYPE4 (LSMW, 0, 0, 0, 0, (N32_LSMW_BIM << 2) | N32_LSMW_LSMW)
|
||
#define N32_FLDI_SP \
|
||
N32_TYPE2 (LDC, 0, REG_SP, 0)
|
||
|
||
/* Use an invalid address value as 'not available' marker. */
|
||
enum { REG_UNAVAIL = (CORE_ADDR) -1 };
|
||
|
||
/* Use an impossible value as invalid offset. */
|
||
enum { INVALID_OFFSET = (CORE_ADDR) -1 };
|
||
|
||
/* Instruction groups for NDS32 epilogue analysis. */
|
||
enum
|
||
{
|
||
/* Instructions used everywhere, not only in epilogue. */
|
||
INSN_NORMAL,
|
||
/* Instructions used to reset sp for local vars, arguments, etc. */
|
||
INSN_RESET_SP,
|
||
/* Instructions used to recover saved regs and to recover padding. */
|
||
INSN_RECOVER,
|
||
/* Instructions used to return to the caller. */
|
||
INSN_RETURN,
|
||
/* Instructions used to recover saved regs and to return to the caller. */
|
||
INSN_RECOVER_RETURN,
|
||
};
|
||
|
||
static const char *const nds32_register_names[] =
|
||
{
|
||
/* 32 GPRs. */
|
||
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
||
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
||
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
|
||
"r24", "r25", "r26", "r27", "fp", "gp", "lp", "sp",
|
||
/* PC. */
|
||
"pc",
|
||
};
|
||
|
||
static const char *const nds32_fdr_register_names[] =
|
||
{
|
||
"fd0", "fd1", "fd2", "fd3", "fd4", "fd5", "fd6", "fd7",
|
||
"fd8", "fd9", "fd10", "fd11", "fd12", "fd13", "fd14", "fd15",
|
||
"fd16", "fd17", "fd18", "fd19", "fd20", "fd21", "fd22", "fd23",
|
||
"fd24", "fd25", "fd26", "fd27", "fd28", "fd29", "fd30", "fd31"
|
||
};
|
||
|
||
static const char *const nds32_fsr_register_names[] =
|
||
{
|
||
"fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7",
|
||
"fs8", "fs9", "fs10", "fs11", "fs12", "fs13", "fs14", "fs15",
|
||
"fs16", "fs17", "fs18", "fs19", "fs20", "fs21", "fs22", "fs23",
|
||
"fs24", "fs25", "fs26", "fs27", "fs28", "fs29", "fs30", "fs31"
|
||
};
|
||
|
||
/* The number of registers for four FPU configuration options. */
|
||
const int num_fdr_map[] = { 4, 8, 16, 32 };
|
||
const int num_fsr_map[] = { 8, 16, 32, 32 };
|
||
|
||
/* Aliases for registers. */
|
||
static const struct
|
||
{
|
||
const char *name;
|
||
const char *alias;
|
||
} nds32_register_aliases[] =
|
||
{
|
||
{"r15", "ta"},
|
||
{"r26", "p0"},
|
||
{"r27", "p1"},
|
||
{"fp", "r28"},
|
||
{"gp", "r29"},
|
||
{"lp", "r30"},
|
||
{"sp", "r31"},
|
||
|
||
{"cr0", "cpu_ver"},
|
||
{"cr1", "icm_cfg"},
|
||
{"cr2", "dcm_cfg"},
|
||
{"cr3", "mmu_cfg"},
|
||
{"cr4", "msc_cfg"},
|
||
{"cr5", "core_id"},
|
||
{"cr6", "fucop_exist"},
|
||
{"cr7", "msc_cfg2"},
|
||
|
||
{"ir0", "psw"},
|
||
{"ir1", "ipsw"},
|
||
{"ir2", "p_psw"},
|
||
{"ir3", "ivb"},
|
||
{"ir4", "eva"},
|
||
{"ir5", "p_eva"},
|
||
{"ir6", "itype"},
|
||
{"ir7", "p_itype"},
|
||
{"ir8", "merr"},
|
||
{"ir9", "ipc"},
|
||
{"ir10", "p_ipc"},
|
||
{"ir11", "oipc"},
|
||
{"ir12", "p_p0"},
|
||
{"ir13", "p_p1"},
|
||
{"ir14", "int_mask"},
|
||
{"ir15", "int_pend"},
|
||
{"ir16", "sp_usr"},
|
||
{"ir17", "sp_priv"},
|
||
{"ir18", "int_pri"},
|
||
{"ir19", "int_ctrl"},
|
||
{"ir20", "sp_usr1"},
|
||
{"ir21", "sp_priv1"},
|
||
{"ir22", "sp_usr2"},
|
||
{"ir23", "sp_priv2"},
|
||
{"ir24", "sp_usr3"},
|
||
{"ir25", "sp_priv3"},
|
||
{"ir26", "int_mask2"},
|
||
{"ir27", "int_pend2"},
|
||
{"ir28", "int_pri2"},
|
||
{"ir29", "int_trigger"},
|
||
|
||
{"mr0", "mmu_ctl"},
|
||
{"mr1", "l1_pptb"},
|
||
{"mr2", "tlb_vpn"},
|
||
{"mr3", "tlb_data"},
|
||
{"mr4", "tlb_misc"},
|
||
{"mr5", "vlpt_idx"},
|
||
{"mr6", "ilmb"},
|
||
{"mr7", "dlmb"},
|
||
{"mr8", "cache_ctl"},
|
||
{"mr9", "hsmp_saddr"},
|
||
{"mr10", "hsmp_eaddr"},
|
||
{"mr11", "bg_region"},
|
||
|
||
{"dr0", "bpc0"},
|
||
{"dr1", "bpc1"},
|
||
{"dr2", "bpc2"},
|
||
{"dr3", "bpc3"},
|
||
{"dr4", "bpc4"},
|
||
{"dr5", "bpc5"},
|
||
{"dr6", "bpc6"},
|
||
{"dr7", "bpc7"},
|
||
{"dr8", "bpa0"},
|
||
{"dr9", "bpa1"},
|
||
{"dr10", "bpa2"},
|
||
{"dr11", "bpa3"},
|
||
{"dr12", "bpa4"},
|
||
{"dr13", "bpa5"},
|
||
{"dr14", "bpa6"},
|
||
{"dr15", "bpa7"},
|
||
{"dr16", "bpam0"},
|
||
{"dr17", "bpam1"},
|
||
{"dr18", "bpam2"},
|
||
{"dr19", "bpam3"},
|
||
{"dr20", "bpam4"},
|
||
{"dr21", "bpam5"},
|
||
{"dr22", "bpam6"},
|
||
{"dr23", "bpam7"},
|
||
{"dr24", "bpv0"},
|
||
{"dr25", "bpv1"},
|
||
{"dr26", "bpv2"},
|
||
{"dr27", "bpv3"},
|
||
{"dr28", "bpv4"},
|
||
{"dr29", "bpv5"},
|
||
{"dr30", "bpv6"},
|
||
{"dr31", "bpv7"},
|
||
{"dr32", "bpcid0"},
|
||
{"dr33", "bpcid1"},
|
||
{"dr34", "bpcid2"},
|
||
{"dr35", "bpcid3"},
|
||
{"dr36", "bpcid4"},
|
||
{"dr37", "bpcid5"},
|
||
{"dr38", "bpcid6"},
|
||
{"dr39", "bpcid7"},
|
||
{"dr40", "edm_cfg"},
|
||
{"dr41", "edmsw"},
|
||
{"dr42", "edm_ctl"},
|
||
{"dr43", "edm_dtr"},
|
||
{"dr44", "bpmtc"},
|
||
{"dr45", "dimbr"},
|
||
{"dr46", "tecr0"},
|
||
{"dr47", "tecr1"},
|
||
|
||
{"hspr0", "hsp_ctl"},
|
||
{"hspr1", "sp_bound"},
|
||
{"hspr2", "sp_bound_priv"},
|
||
|
||
{"pfr0", "pfmc0"},
|
||
{"pfr1", "pfmc1"},
|
||
{"pfr2", "pfmc2"},
|
||
{"pfr3", "pfm_ctl"},
|
||
{"pfr4", "pft_ctl"},
|
||
|
||
{"dmar0", "dma_cfg"},
|
||
{"dmar1", "dma_gcsw"},
|
||
{"dmar2", "dma_chnsel"},
|
||
{"dmar3", "dma_act"},
|
||
{"dmar4", "dma_setup"},
|
||
{"dmar5", "dma_isaddr"},
|
||
{"dmar6", "dma_esaddr"},
|
||
{"dmar7", "dma_tcnt"},
|
||
{"dmar8", "dma_status"},
|
||
{"dmar9", "dma_2dset"},
|
||
{"dmar10", "dma_2dsctl"},
|
||
{"dmar11", "dma_rcnt"},
|
||
{"dmar12", "dma_hstatus"},
|
||
|
||
{"racr0", "prusr_acc_ctl"},
|
||
{"fucpr", "fucop_ctl"},
|
||
|
||
{"idr0", "sdz_ctl"},
|
||
{"idr1", "misc_ctl"},
|
||
{"idr2", "ecc_misc"},
|
||
|
||
{"secur0", "sfcr"},
|
||
{"secur1", "sign"},
|
||
{"secur2", "isign"},
|
||
{"secur3", "p_isign"},
|
||
};
|
||
|
||
/* Value of a register alias. BATON is the regnum of the corresponding
|
||
register. */
|
||
|
||
static struct value *
|
||
value_of_nds32_reg (frame_info_ptr frame, const void *baton)
|
||
{
|
||
return value_of_register ((int) (intptr_t) baton, frame);
|
||
}
|
||
|
||
/* Implement the "frame_align" gdbarch method. */
|
||
|
||
static CORE_ADDR
|
||
nds32_frame_align (struct gdbarch *gdbarch, CORE_ADDR sp)
|
||
{
|
||
/* 8-byte aligned. */
|
||
return align_down (sp, 8);
|
||
}
|
||
|
||
/* The same insn machine code is used for little-endian and big-endian. */
|
||
constexpr gdb_byte nds32_break_insn[] = { 0xEA, 0x00 };
|
||
|
||
typedef BP_MANIPULATION (nds32_break_insn) nds32_breakpoint;
|
||
|
||
/* Implement the "dwarf2_reg_to_regnum" gdbarch method. */
|
||
|
||
static int
|
||
nds32_dwarf2_reg_to_regnum (struct gdbarch *gdbarch, int num)
|
||
{
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
const int FSR = 38;
|
||
const int FDR = FSR + 32;
|
||
|
||
if (num >= 0 && num < 32)
|
||
{
|
||
/* General-purpose registers (R0 - R31). */
|
||
return num;
|
||
}
|
||
else if (num >= FSR && num < FSR + 32)
|
||
{
|
||
/* Single precision floating-point registers (FS0 - FS31). */
|
||
return num - FSR + tdep->fs0_regnum;
|
||
}
|
||
else if (num >= FDR && num < FDR + 32)
|
||
{
|
||
/* Double precision floating-point registers (FD0 - FD31). */
|
||
return num - FDR + NDS32_FD0_REGNUM;
|
||
}
|
||
|
||
/* No match, return a inaccessible register number. */
|
||
return -1;
|
||
}
|
||
|
||
/* NDS32 register groups. */
|
||
static const reggroup *nds32_cr_reggroup;
|
||
static const reggroup *nds32_ir_reggroup;
|
||
static const reggroup *nds32_mr_reggroup;
|
||
static const reggroup *nds32_dr_reggroup;
|
||
static const reggroup *nds32_pfr_reggroup;
|
||
static const reggroup *nds32_hspr_reggroup;
|
||
static const reggroup *nds32_dmar_reggroup;
|
||
static const reggroup *nds32_racr_reggroup;
|
||
static const reggroup *nds32_idr_reggroup;
|
||
static const reggroup *nds32_secur_reggroup;
|
||
|
||
static void
|
||
nds32_init_reggroups (void)
|
||
{
|
||
nds32_cr_reggroup = reggroup_new ("cr", USER_REGGROUP);
|
||
nds32_ir_reggroup = reggroup_new ("ir", USER_REGGROUP);
|
||
nds32_mr_reggroup = reggroup_new ("mr", USER_REGGROUP);
|
||
nds32_dr_reggroup = reggroup_new ("dr", USER_REGGROUP);
|
||
nds32_pfr_reggroup = reggroup_new ("pfr", USER_REGGROUP);
|
||
nds32_hspr_reggroup = reggroup_new ("hspr", USER_REGGROUP);
|
||
nds32_dmar_reggroup = reggroup_new ("dmar", USER_REGGROUP);
|
||
nds32_racr_reggroup = reggroup_new ("racr", USER_REGGROUP);
|
||
nds32_idr_reggroup = reggroup_new ("idr", USER_REGGROUP);
|
||
nds32_secur_reggroup = reggroup_new ("secur", USER_REGGROUP);
|
||
}
|
||
|
||
static void
|
||
nds32_add_reggroups (struct gdbarch *gdbarch)
|
||
{
|
||
/* Add NDS32 register groups. */
|
||
reggroup_add (gdbarch, nds32_cr_reggroup);
|
||
reggroup_add (gdbarch, nds32_ir_reggroup);
|
||
reggroup_add (gdbarch, nds32_mr_reggroup);
|
||
reggroup_add (gdbarch, nds32_dr_reggroup);
|
||
reggroup_add (gdbarch, nds32_pfr_reggroup);
|
||
reggroup_add (gdbarch, nds32_hspr_reggroup);
|
||
reggroup_add (gdbarch, nds32_dmar_reggroup);
|
||
reggroup_add (gdbarch, nds32_racr_reggroup);
|
||
reggroup_add (gdbarch, nds32_idr_reggroup);
|
||
reggroup_add (gdbarch, nds32_secur_reggroup);
|
||
}
|
||
|
||
/* Implement the "register_reggroup_p" gdbarch method. */
|
||
|
||
static int
|
||
nds32_register_reggroup_p (struct gdbarch *gdbarch, int regnum,
|
||
const struct reggroup *reggroup)
|
||
{
|
||
const char *reg_name;
|
||
const char *group_name;
|
||
int ret;
|
||
|
||
if (reggroup == all_reggroup)
|
||
return 1;
|
||
|
||
/* General reggroup contains only GPRs and PC. */
|
||
if (reggroup == general_reggroup)
|
||
return regnum <= NDS32_PC_REGNUM;
|
||
|
||
if (reggroup == float_reggroup || reggroup == save_reggroup
|
||
|| reggroup == restore_reggroup)
|
||
{
|
||
ret = tdesc_register_in_reggroup_p (gdbarch, regnum, reggroup);
|
||
if (ret != -1)
|
||
return ret;
|
||
|
||
return default_register_reggroup_p (gdbarch, regnum, reggroup);
|
||
}
|
||
|
||
if (reggroup == system_reggroup)
|
||
return (regnum > NDS32_PC_REGNUM)
|
||
&& !nds32_register_reggroup_p (gdbarch, regnum, float_reggroup);
|
||
|
||
/* The NDS32 reggroup contains registers whose name is prefixed
|
||
by reggroup name. */
|
||
reg_name = gdbarch_register_name (gdbarch, regnum);
|
||
group_name = reggroup->name ();
|
||
return !strncmp (reg_name, group_name, strlen (group_name));
|
||
}
|
||
|
||
/* Implement the "pseudo_register_type" tdesc_arch_data method. */
|
||
|
||
static struct type *
|
||
nds32_pseudo_register_type (struct gdbarch *gdbarch, int regnum)
|
||
{
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
/* Currently, only FSRs could be defined as pseudo registers. */
|
||
if (regnum < gdbarch_num_pseudo_regs (gdbarch))
|
||
{
|
||
type_allocator alloc (gdbarch);
|
||
return init_float_type (alloc, -1, "builtin_type_ieee_single",
|
||
floatformats_ieee_single);
|
||
}
|
||
|
||
warning (_("Unknown nds32 pseudo register %d."), regnum);
|
||
return NULL;
|
||
}
|
||
|
||
/* Implement the "pseudo_register_name" tdesc_arch_data method. */
|
||
|
||
static const char *
|
||
nds32_pseudo_register_name (struct gdbarch *gdbarch, int regnum)
|
||
{
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
/* Currently, only FSRs could be defined as pseudo registers. */
|
||
gdb_assert (regnum < gdbarch_num_pseudo_regs (gdbarch));
|
||
return nds32_fsr_register_names[regnum];
|
||
}
|
||
|
||
/* Implement the "pseudo_register_read" gdbarch method. */
|
||
|
||
static enum register_status
|
||
nds32_pseudo_register_read (struct gdbarch *gdbarch,
|
||
readable_regcache *regcache, int regnum,
|
||
gdb_byte *buf)
|
||
{
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
gdb_byte reg_buf[8];
|
||
int offset, fdr_regnum;
|
||
enum register_status status;
|
||
|
||
/* This function is registered in nds32_gdbarch_init only after these are
|
||
set. */
|
||
gdb_assert (tdep->fpu_freg != -1);
|
||
gdb_assert (tdep->use_pseudo_fsrs != 0);
|
||
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
/* Currently, only FSRs could be defined as pseudo registers. */
|
||
if (regnum < gdbarch_num_pseudo_regs (gdbarch))
|
||
{
|
||
/* fs0 is always the most significant half of fd0. */
|
||
if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG)
|
||
offset = (regnum & 1) ? 4 : 0;
|
||
else
|
||
offset = (regnum & 1) ? 0 : 4;
|
||
|
||
fdr_regnum = NDS32_FD0_REGNUM + (regnum >> 1);
|
||
status = regcache->raw_read (fdr_regnum, reg_buf);
|
||
if (status == REG_VALID)
|
||
memcpy (buf, reg_buf + offset, 4);
|
||
|
||
return status;
|
||
}
|
||
|
||
gdb_assert_not_reached ("invalid pseudo register number");
|
||
}
|
||
|
||
/* Implement the "pseudo_register_write" gdbarch method. */
|
||
|
||
static void
|
||
nds32_pseudo_register_write (struct gdbarch *gdbarch,
|
||
struct regcache *regcache, int regnum,
|
||
const gdb_byte *buf)
|
||
{
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
gdb_byte reg_buf[8];
|
||
int offset, fdr_regnum;
|
||
|
||
/* This function is registered in nds32_gdbarch_init only after these are
|
||
set. */
|
||
gdb_assert (tdep->fpu_freg != -1);
|
||
gdb_assert (tdep->use_pseudo_fsrs != 0);
|
||
|
||
regnum -= gdbarch_num_regs (gdbarch);
|
||
|
||
/* Currently, only FSRs could be defined as pseudo registers. */
|
||
if (regnum < gdbarch_num_pseudo_regs (gdbarch))
|
||
{
|
||
/* fs0 is always the most significant half of fd0. */
|
||
if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG)
|
||
offset = (regnum & 1) ? 4 : 0;
|
||
else
|
||
offset = (regnum & 1) ? 0 : 4;
|
||
|
||
fdr_regnum = NDS32_FD0_REGNUM + (regnum >> 1);
|
||
regcache->raw_read (fdr_regnum, reg_buf);
|
||
memcpy (reg_buf + offset, buf, 4);
|
||
regcache->raw_write (fdr_regnum, reg_buf);
|
||
return;
|
||
}
|
||
|
||
gdb_assert_not_reached ("invalid pseudo register number");
|
||
}
|
||
|
||
/* Helper function for NDS32 ABI. Return true if FPRs can be used
|
||
to pass function arguments and return value. */
|
||
|
||
static int
|
||
nds32_abi_use_fpr (int elf_abi)
|
||
{
|
||
return elf_abi == E_NDS_ABI_V2FP_PLUS;
|
||
}
|
||
|
||
/* Helper function for NDS32 ABI. Return true if GPRs and stack
|
||
can be used together to pass an argument. */
|
||
|
||
static int
|
||
nds32_abi_split (int elf_abi)
|
||
{
|
||
return elf_abi == E_NDS_ABI_AABI;
|
||
}
|
||
|
||
#define NDS32_NUM_SAVED_REGS (NDS32_LP_REGNUM + 1)
|
||
|
||
struct nds32_frame_cache
|
||
{
|
||
/* The previous frame's inner most stack address. Used as this
|
||
frame ID's stack_addr. */
|
||
CORE_ADDR prev_sp;
|
||
|
||
/* The frame's base, optionally used by the high-level debug info. */
|
||
CORE_ADDR base;
|
||
|
||
/* During prologue analysis, keep how far the SP and FP have been offset
|
||
from the start of the stack frame (as defined by the previous frame's
|
||
stack pointer).
|
||
During epilogue analysis, keep how far the SP has been offset from the
|
||
current stack pointer. */
|
||
CORE_ADDR sp_offset;
|
||
CORE_ADDR fp_offset;
|
||
|
||
/* The address of the first instruction in this function. */
|
||
CORE_ADDR pc;
|
||
|
||
/* Saved registers. */
|
||
CORE_ADDR saved_regs[NDS32_NUM_SAVED_REGS];
|
||
};
|
||
|
||
/* Allocate and initialize a frame cache. */
|
||
|
||
static struct nds32_frame_cache *
|
||
nds32_alloc_frame_cache (void)
|
||
{
|
||
struct nds32_frame_cache *cache;
|
||
int i;
|
||
|
||
cache = FRAME_OBSTACK_ZALLOC (struct nds32_frame_cache);
|
||
|
||
/* Initialize fp_offset to check if FP is set in prologue. */
|
||
cache->fp_offset = INVALID_OFFSET;
|
||
|
||
/* Saved registers. We initialize these to -1 since zero is a valid
|
||
offset. */
|
||
for (i = 0; i < NDS32_NUM_SAVED_REGS; i++)
|
||
cache->saved_regs[i] = REG_UNAVAIL;
|
||
|
||
return cache;
|
||
}
|
||
|
||
/* Helper function for instructions used to push multiple words. */
|
||
|
||
static void
|
||
nds32_push_multiple_words (struct nds32_frame_cache *cache, int rb, int re,
|
||
int enable4)
|
||
{
|
||
CORE_ADDR sp_offset = cache->sp_offset;
|
||
int i;
|
||
|
||
/* Check LP, GP, FP in enable4. */
|
||
for (i = 1; i <= 3; i++)
|
||
{
|
||
if ((enable4 >> i) & 0x1)
|
||
{
|
||
sp_offset += 4;
|
||
cache->saved_regs[NDS32_SP_REGNUM - i] = sp_offset;
|
||
}
|
||
}
|
||
|
||
/* Skip case where re == rb == sp. */
|
||
if ((rb < REG_FP) && (re < REG_FP))
|
||
{
|
||
for (i = re; i >= rb; i--)
|
||
{
|
||
sp_offset += 4;
|
||
cache->saved_regs[i] = sp_offset;
|
||
}
|
||
}
|
||
|
||
/* For sp, update the offset. */
|
||
cache->sp_offset = sp_offset;
|
||
}
|
||
|
||
/* Analyze the instructions within the given address range. If CACHE
|
||
is non-NULL, fill it in. Return the first address beyond the given
|
||
address range. If CACHE is NULL, return the first address not
|
||
recognized as a prologue instruction. */
|
||
|
||
static CORE_ADDR
|
||
nds32_analyze_prologue (struct gdbarch *gdbarch, CORE_ADDR pc,
|
||
CORE_ADDR limit_pc, struct nds32_frame_cache *cache)
|
||
{
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
int abi_use_fpr = nds32_abi_use_fpr (tdep->elf_abi);
|
||
/* Current scanning status. */
|
||
int in_prologue_bb = 0;
|
||
int val_ta = 0;
|
||
uint32_t insn, insn_len;
|
||
|
||
for (; pc < limit_pc; pc += insn_len)
|
||
{
|
||
insn = read_memory_unsigned_integer (pc, 4, BFD_ENDIAN_BIG);
|
||
|
||
if ((insn & 0x80000000) == 0)
|
||
{
|
||
/* 32-bit instruction */
|
||
insn_len = 4;
|
||
|
||
if (CHOP_BITS (insn, 15) == N32_TYPE2 (ADDI, REG_SP, REG_SP, 0))
|
||
{
|
||
/* addi $sp, $sp, imm15s */
|
||
int imm15s = N32_IMM15S (insn);
|
||
|
||
if (imm15s < 0)
|
||
{
|
||
if (cache != NULL)
|
||
cache->sp_offset += -imm15s;
|
||
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
}
|
||
else if (CHOP_BITS (insn, 15) == N32_TYPE2 (ADDI, REG_FP, REG_SP, 0))
|
||
{
|
||
/* addi $fp, $sp, imm15s */
|
||
int imm15s = N32_IMM15S (insn);
|
||
|
||
if (imm15s > 0)
|
||
{
|
||
if (cache != NULL)
|
||
cache->fp_offset = cache->sp_offset - imm15s;
|
||
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
}
|
||
else if ((insn & ~(__MASK (19) << 6)) == N32_SMW_ADM
|
||
&& N32_RA5 (insn) == REG_SP)
|
||
{
|
||
/* smw.adm Rb, [$sp], Re, enable4 */
|
||
if (cache != NULL)
|
||
nds32_push_multiple_words (cache, N32_RT5 (insn),
|
||
N32_RB5 (insn),
|
||
N32_LSMW_ENABLE4 (insn));
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else if (insn == N32_ALU1 (ADD, REG_SP, REG_SP, REG_TA)
|
||
|| insn == N32_ALU1 (ADD, REG_SP, REG_TA, REG_SP))
|
||
{
|
||
/* add $sp, $sp, $ta */
|
||
/* add $sp, $ta, $sp */
|
||
if (val_ta < 0)
|
||
{
|
||
if (cache != NULL)
|
||
cache->sp_offset += -val_ta;
|
||
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
}
|
||
else if (CHOP_BITS (insn, 20) == N32_TYPE1 (MOVI, REG_TA, 0))
|
||
{
|
||
/* movi $ta, imm20s */
|
||
if (cache != NULL)
|
||
val_ta = N32_IMM20S (insn);
|
||
|
||
continue;
|
||
}
|
||
else if (CHOP_BITS (insn, 20) == N32_TYPE1 (SETHI, REG_TA, 0))
|
||
{
|
||
/* sethi $ta, imm20u */
|
||
if (cache != NULL)
|
||
val_ta = N32_IMM20U (insn) << 12;
|
||
|
||
continue;
|
||
}
|
||
else if (CHOP_BITS (insn, 15) == N32_TYPE2 (ORI, REG_TA, REG_TA, 0))
|
||
{
|
||
/* ori $ta, $ta, imm15u */
|
||
if (cache != NULL)
|
||
val_ta |= N32_IMM15U (insn);
|
||
|
||
continue;
|
||
}
|
||
else if (CHOP_BITS (insn, 15) == N32_TYPE2 (ADDI, REG_TA, REG_TA, 0))
|
||
{
|
||
/* addi $ta, $ta, imm15s */
|
||
if (cache != NULL)
|
||
val_ta += N32_IMM15S (insn);
|
||
|
||
continue;
|
||
}
|
||
if (insn == N32_ALU1 (ADD, REG_GP, REG_TA, REG_GP)
|
||
|| insn == N32_ALU1 (ADD, REG_GP, REG_GP, REG_TA))
|
||
{
|
||
/* add $gp, $ta, $gp */
|
||
/* add $gp, $gp, $ta */
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else if (CHOP_BITS (insn, 20) == N32_TYPE1 (MOVI, REG_GP, 0))
|
||
{
|
||
/* movi $gp, imm20s */
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else if (CHOP_BITS (insn, 20) == N32_TYPE1 (SETHI, REG_GP, 0))
|
||
{
|
||
/* sethi $gp, imm20u */
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else if (CHOP_BITS (insn, 15) == N32_TYPE2 (ORI, REG_GP, REG_GP, 0))
|
||
{
|
||
/* ori $gp, $gp, imm15u */
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
/* Jump/Branch insns never appear in prologue basic block.
|
||
The loop can be escaped early when these insns are met. */
|
||
if (in_prologue_bb == 1)
|
||
{
|
||
int op = N32_OP6 (insn);
|
||
|
||
if (op == N32_OP6_JI
|
||
|| op == N32_OP6_JREG
|
||
|| op == N32_OP6_BR1
|
||
|| op == N32_OP6_BR2
|
||
|| op == N32_OP6_BR3)
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (abi_use_fpr && N32_OP6 (insn) == N32_OP6_SDC
|
||
&& __GF (insn, 12, 3) == 0)
|
||
{
|
||
/* For FPU insns, CP (bit [13:14]) should be CP0, and only
|
||
normal form (bit [12] == 0) is used. */
|
||
|
||
/* fsdi FDt, [$sp + (imm12s << 2)] */
|
||
if (N32_RA5 (insn) == REG_SP)
|
||
continue;
|
||
}
|
||
|
||
/* The optimizer might shove anything into the prologue, if
|
||
we build up cache (cache != NULL) from analyzing prologue,
|
||
we just skip what we don't recognize and analyze further to
|
||
make cache as complete as possible. However, if we skip
|
||
prologue, we'll stop immediately on unrecognized
|
||
instruction. */
|
||
if (cache == NULL)
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
/* 16-bit instruction */
|
||
insn_len = 2;
|
||
|
||
insn >>= 16;
|
||
|
||
if (CHOP_BITS (insn, 10) == N16_TYPE10 (ADDI10S, 0))
|
||
{
|
||
/* addi10s.sp */
|
||
int imm10s = N16_IMM10S (insn);
|
||
|
||
if (imm10s < 0)
|
||
{
|
||
if (cache != NULL)
|
||
cache->sp_offset += -imm10s;
|
||
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
}
|
||
else if (__GF (insn, 7, 8) == N16_T25_PUSH25)
|
||
{
|
||
/* push25 */
|
||
if (cache != NULL)
|
||
{
|
||
int imm8u = (insn & 0x1f) << 3;
|
||
int re = (insn >> 5) & 0x3;
|
||
const int reg_map[] = { 6, 8, 10, 14 };
|
||
|
||
/* Operation 1 -- smw.adm R6, [$sp], Re, #0xe */
|
||
nds32_push_multiple_words (cache, 6, reg_map[re], 0xe);
|
||
|
||
/* Operation 2 -- sp = sp - (imm5u << 3) */
|
||
cache->sp_offset += imm8u;
|
||
}
|
||
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else if (insn == N16_TYPE5 (ADD5PC, REG_GP))
|
||
{
|
||
/* add5.pc $gp */
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else if (CHOP_BITS (insn, 5) == N16_TYPE55 (MOVI55, REG_GP, 0))
|
||
{
|
||
/* movi55 $gp, imm5s */
|
||
in_prologue_bb = 1;
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
/* Jump/Branch insns never appear in prologue basic block.
|
||
The loop can be escaped early when these insns are met. */
|
||
if (in_prologue_bb == 1)
|
||
{
|
||
uint32_t insn5 = CHOP_BITS (insn, 5);
|
||
uint32_t insn8 = CHOP_BITS (insn, 8);
|
||
uint32_t insn38 = CHOP_BITS (insn, 11);
|
||
|
||
if (insn5 == N16_TYPE5 (JR5, 0)
|
||
|| insn5 == N16_TYPE5 (JRAL5, 0)
|
||
|| insn5 == N16_TYPE5 (RET5, 0)
|
||
|| insn8 == N16_TYPE8 (J8, 0)
|
||
|| insn8 == N16_TYPE8 (BEQZS8, 0)
|
||
|| insn8 == N16_TYPE8 (BNEZS8, 0)
|
||
|| insn38 == N16_TYPE38 (BEQZ38, 0, 0)
|
||
|| insn38 == N16_TYPE38 (BNEZ38, 0, 0)
|
||
|| insn38 == N16_TYPE38 (BEQS38, 0, 0)
|
||
|| insn38 == N16_TYPE38 (BNES38, 0, 0))
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* The optimizer might shove anything into the prologue, if
|
||
we build up cache (cache != NULL) from analyzing prologue,
|
||
we just skip what we don't recognize and analyze further to
|
||
make cache as complete as possible. However, if we skip
|
||
prologue, we'll stop immediately on unrecognized
|
||
instruction. */
|
||
if (cache == NULL)
|
||
break;
|
||
}
|
||
}
|
||
|
||
return pc;
|
||
}
|
||
|
||
/* Implement the "skip_prologue" gdbarch method.
|
||
|
||
Find the end of function prologue. */
|
||
|
||
static CORE_ADDR
|
||
nds32_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. */
|
||
|
||
/* Find the end of prologue. */
|
||
return nds32_analyze_prologue (gdbarch, pc, limit_pc, NULL);
|
||
}
|
||
|
||
/* Allocate and fill in *THIS_CACHE with information about the prologue of
|
||
*THIS_FRAME. Do not do this if *THIS_CACHE was already allocated. Return
|
||
a pointer to the current nds32_frame_cache in *THIS_CACHE. */
|
||
|
||
static struct nds32_frame_cache *
|
||
nds32_frame_cache (frame_info_ptr this_frame, void **this_cache)
|
||
{
|
||
struct gdbarch *gdbarch = get_frame_arch (this_frame);
|
||
struct nds32_frame_cache *cache;
|
||
CORE_ADDR current_pc;
|
||
ULONGEST prev_sp;
|
||
ULONGEST this_base;
|
||
int i;
|
||
|
||
if (*this_cache)
|
||
return (struct nds32_frame_cache *) *this_cache;
|
||
|
||
cache = nds32_alloc_frame_cache ();
|
||
*this_cache = cache;
|
||
|
||
cache->pc = get_frame_func (this_frame);
|
||
current_pc = get_frame_pc (this_frame);
|
||
nds32_analyze_prologue (gdbarch, cache->pc, current_pc, cache);
|
||
|
||
/* Compute the previous frame's stack pointer (which is also the
|
||
frame's ID's stack address), and this frame's base pointer. */
|
||
if (cache->fp_offset != INVALID_OFFSET)
|
||
{
|
||
/* FP is set in prologue, so it can be used to calculate other info. */
|
||
this_base = get_frame_register_unsigned (this_frame, NDS32_FP_REGNUM);
|
||
prev_sp = this_base + cache->fp_offset;
|
||
}
|
||
else
|
||
{
|
||
this_base = get_frame_register_unsigned (this_frame, NDS32_SP_REGNUM);
|
||
prev_sp = this_base + cache->sp_offset;
|
||
}
|
||
|
||
cache->prev_sp = prev_sp;
|
||
cache->base = this_base;
|
||
|
||
/* Adjust all the saved registers such that they contain addresses
|
||
instead of offsets. */
|
||
for (i = 0; i < NDS32_NUM_SAVED_REGS; i++)
|
||
if (cache->saved_regs[i] != REG_UNAVAIL)
|
||
cache->saved_regs[i] = cache->prev_sp - cache->saved_regs[i];
|
||
|
||
return cache;
|
||
}
|
||
|
||
/* Implement the "this_id" frame_unwind method.
|
||
|
||
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
|
||
nds32_frame_this_id (frame_info_ptr this_frame,
|
||
void **this_cache, struct frame_id *this_id)
|
||
{
|
||
struct nds32_frame_cache *cache = nds32_frame_cache (this_frame, this_cache);
|
||
|
||
/* This marks the outermost frame. */
|
||
if (cache->prev_sp == 0)
|
||
return;
|
||
|
||
*this_id = frame_id_build (cache->prev_sp, cache->pc);
|
||
}
|
||
|
||
/* Implement the "prev_register" frame_unwind method. */
|
||
|
||
static struct value *
|
||
nds32_frame_prev_register (frame_info_ptr this_frame, void **this_cache,
|
||
int regnum)
|
||
{
|
||
struct nds32_frame_cache *cache = nds32_frame_cache (this_frame, this_cache);
|
||
|
||
if (regnum == NDS32_SP_REGNUM)
|
||
return frame_unwind_got_constant (this_frame, regnum, cache->prev_sp);
|
||
|
||
/* The PC of the previous frame is stored in the LP register of
|
||
the current frame. */
|
||
if (regnum == NDS32_PC_REGNUM)
|
||
regnum = NDS32_LP_REGNUM;
|
||
|
||
if (regnum < NDS32_NUM_SAVED_REGS && cache->saved_regs[regnum] != REG_UNAVAIL)
|
||
return frame_unwind_got_memory (this_frame, regnum,
|
||
cache->saved_regs[regnum]);
|
||
|
||
return frame_unwind_got_register (this_frame, regnum, regnum);
|
||
}
|
||
|
||
static const struct frame_unwind nds32_frame_unwind =
|
||
{
|
||
"nds32 prologue",
|
||
NORMAL_FRAME,
|
||
default_frame_unwind_stop_reason,
|
||
nds32_frame_this_id,
|
||
nds32_frame_prev_register,
|
||
NULL,
|
||
default_frame_sniffer,
|
||
};
|
||
|
||
/* Return the frame base address of *THIS_FRAME. */
|
||
|
||
static CORE_ADDR
|
||
nds32_frame_base_address (frame_info_ptr this_frame, void **this_cache)
|
||
{
|
||
struct nds32_frame_cache *cache = nds32_frame_cache (this_frame, this_cache);
|
||
|
||
return cache->base;
|
||
}
|
||
|
||
static const struct frame_base nds32_frame_base =
|
||
{
|
||
&nds32_frame_unwind,
|
||
nds32_frame_base_address,
|
||
nds32_frame_base_address,
|
||
nds32_frame_base_address
|
||
};
|
||
|
||
/* Helper function for instructions used to pop multiple words. */
|
||
|
||
static void
|
||
nds32_pop_multiple_words (struct nds32_frame_cache *cache, int rb, int re,
|
||
int enable4)
|
||
{
|
||
CORE_ADDR sp_offset = cache->sp_offset;
|
||
int i;
|
||
|
||
/* Skip case where re == rb == sp. */
|
||
if ((rb < REG_FP) && (re < REG_FP))
|
||
{
|
||
for (i = rb; i <= re; i++)
|
||
{
|
||
cache->saved_regs[i] = sp_offset;
|
||
sp_offset += 4;
|
||
}
|
||
}
|
||
|
||
/* Check FP, GP, LP in enable4. */
|
||
for (i = 3; i >= 1; i--)
|
||
{
|
||
if ((enable4 >> i) & 0x1)
|
||
{
|
||
cache->saved_regs[NDS32_SP_REGNUM - i] = sp_offset;
|
||
sp_offset += 4;
|
||
}
|
||
}
|
||
|
||
/* For sp, update the offset. */
|
||
cache->sp_offset = sp_offset;
|
||
}
|
||
|
||
/* The instruction sequences in NDS32 epilogue are
|
||
|
||
INSN_RESET_SP (optional)
|
||
(If exists, this must be the first instruction in epilogue
|
||
and the stack has not been destroyed.).
|
||
INSN_RECOVER (optional).
|
||
INSN_RETURN/INSN_RECOVER_RETURN (required). */
|
||
|
||
/* Helper function for analyzing the given 32-bit INSN. If CACHE is non-NULL,
|
||
the necessary information will be recorded. */
|
||
|
||
static inline int
|
||
nds32_analyze_epilogue_insn32 (int abi_use_fpr, uint32_t insn,
|
||
struct nds32_frame_cache *cache)
|
||
{
|
||
if (CHOP_BITS (insn, 15) == N32_TYPE2 (ADDI, REG_SP, REG_SP, 0)
|
||
&& N32_IMM15S (insn) > 0)
|
||
/* addi $sp, $sp, imm15s */
|
||
return INSN_RESET_SP;
|
||
else if (CHOP_BITS (insn, 15) == N32_TYPE2 (ADDI, REG_SP, REG_FP, 0)
|
||
&& N32_IMM15S (insn) < 0)
|
||
/* addi $sp, $fp, imm15s */
|
||
return INSN_RESET_SP;
|
||
else if ((insn & ~(__MASK (19) << 6)) == N32_LMW_BIM
|
||
&& N32_RA5 (insn) == REG_SP)
|
||
{
|
||
/* lmw.bim Rb, [$sp], Re, enable4 */
|
||
if (cache != NULL)
|
||
nds32_pop_multiple_words (cache, N32_RT5 (insn),
|
||
N32_RB5 (insn), N32_LSMW_ENABLE4 (insn));
|
||
|
||
return INSN_RECOVER;
|
||
}
|
||
else if (insn == N32_JREG (JR, 0, REG_LP, 0, 1))
|
||
/* ret $lp */
|
||
return INSN_RETURN;
|
||
else if (insn == N32_ALU1 (ADD, REG_SP, REG_SP, REG_TA)
|
||
|| insn == N32_ALU1 (ADD, REG_SP, REG_TA, REG_SP))
|
||
/* add $sp, $sp, $ta */
|
||
/* add $sp, $ta, $sp */
|
||
return INSN_RESET_SP;
|
||
else if (abi_use_fpr
|
||
&& (insn & ~(__MASK (5) << 20 | __MASK (13))) == N32_FLDI_SP)
|
||
{
|
||
if (__GF (insn, 12, 1) == 0)
|
||
/* fldi FDt, [$sp + (imm12s << 2)] */
|
||
return INSN_RECOVER;
|
||
else
|
||
{
|
||
/* fldi.bi FDt, [$sp], (imm12s << 2) */
|
||
int offset = N32_IMM12S (insn) << 2;
|
||
|
||
if (offset == 8 || offset == 12)
|
||
{
|
||
if (cache != NULL)
|
||
cache->sp_offset += offset;
|
||
|
||
return INSN_RECOVER;
|
||
}
|
||
}
|
||
}
|
||
|
||
return INSN_NORMAL;
|
||
}
|
||
|
||
/* Helper function for analyzing the given 16-bit INSN. If CACHE is non-NULL,
|
||
the necessary information will be recorded. */
|
||
|
||
static inline int
|
||
nds32_analyze_epilogue_insn16 (uint32_t insn, struct nds32_frame_cache *cache)
|
||
{
|
||
if (insn == N16_TYPE5 (RET5, REG_LP))
|
||
/* ret5 $lp */
|
||
return INSN_RETURN;
|
||
else if (CHOP_BITS (insn, 10) == N16_TYPE10 (ADDI10S, 0))
|
||
{
|
||
/* addi10s.sp */
|
||
int imm10s = N16_IMM10S (insn);
|
||
|
||
if (imm10s > 0)
|
||
{
|
||
if (cache != NULL)
|
||
cache->sp_offset += imm10s;
|
||
|
||
return INSN_RECOVER;
|
||
}
|
||
}
|
||
else if (__GF (insn, 7, 8) == N16_T25_POP25)
|
||
{
|
||
/* pop25 */
|
||
if (cache != NULL)
|
||
{
|
||
int imm8u = (insn & 0x1f) << 3;
|
||
int re = (insn >> 5) & 0x3;
|
||
const int reg_map[] = { 6, 8, 10, 14 };
|
||
|
||
/* Operation 1 -- sp = sp + (imm5u << 3) */
|
||
cache->sp_offset += imm8u;
|
||
|
||
/* Operation 2 -- lmw.bim R6, [$sp], Re, #0xe */
|
||
nds32_pop_multiple_words (cache, 6, reg_map[re], 0xe);
|
||
}
|
||
|
||
/* Operation 3 -- ret $lp */
|
||
return INSN_RECOVER_RETURN;
|
||
}
|
||
|
||
return INSN_NORMAL;
|
||
}
|
||
|
||
/* Analyze a reasonable amount of instructions from the given PC to find
|
||
the instruction used to return to the caller. Return 1 if the 'return'
|
||
instruction could be found, 0 otherwise.
|
||
|
||
If CACHE is non-NULL, fill it in. */
|
||
|
||
static int
|
||
nds32_analyze_epilogue (struct gdbarch *gdbarch, CORE_ADDR pc,
|
||
struct nds32_frame_cache *cache)
|
||
{
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
int abi_use_fpr = nds32_abi_use_fpr (tdep->elf_abi);
|
||
CORE_ADDR limit_pc;
|
||
uint32_t insn, insn_len;
|
||
int insn_type = INSN_NORMAL;
|
||
|
||
if (abi_use_fpr)
|
||
limit_pc = pc + 48;
|
||
else
|
||
limit_pc = pc + 16;
|
||
|
||
for (; pc < limit_pc; pc += insn_len)
|
||
{
|
||
insn = read_memory_unsigned_integer (pc, 4, BFD_ENDIAN_BIG);
|
||
|
||
if ((insn & 0x80000000) == 0)
|
||
{
|
||
/* 32-bit instruction */
|
||
insn_len = 4;
|
||
|
||
insn_type = nds32_analyze_epilogue_insn32 (abi_use_fpr, insn, cache);
|
||
if (insn_type == INSN_RETURN)
|
||
return 1;
|
||
else if (insn_type == INSN_RECOVER)
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
/* 16-bit instruction */
|
||
insn_len = 2;
|
||
|
||
insn >>= 16;
|
||
insn_type = nds32_analyze_epilogue_insn16 (insn, cache);
|
||
if (insn_type == INSN_RETURN || insn_type == INSN_RECOVER_RETURN)
|
||
return 1;
|
||
else if (insn_type == INSN_RECOVER)
|
||
continue;
|
||
}
|
||
|
||
/* Stop the scan if this is an unexpected instruction. */
|
||
break;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Implement the "stack_frame_destroyed_p" gdbarch method. */
|
||
|
||
static int
|
||
nds32_stack_frame_destroyed_p (struct gdbarch *gdbarch, CORE_ADDR addr)
|
||
{
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
int abi_use_fpr = nds32_abi_use_fpr (tdep->elf_abi);
|
||
int insn_type = INSN_NORMAL;
|
||
int ret_found = 0;
|
||
uint32_t insn;
|
||
|
||
insn = read_memory_unsigned_integer (addr, 4, BFD_ENDIAN_BIG);
|
||
|
||
if ((insn & 0x80000000) == 0)
|
||
{
|
||
/* 32-bit instruction */
|
||
|
||
insn_type = nds32_analyze_epilogue_insn32 (abi_use_fpr, insn, NULL);
|
||
}
|
||
else
|
||
{
|
||
/* 16-bit instruction */
|
||
|
||
insn >>= 16;
|
||
insn_type = nds32_analyze_epilogue_insn16 (insn, NULL);
|
||
}
|
||
|
||
if (insn_type == INSN_NORMAL || insn_type == INSN_RESET_SP)
|
||
return 0;
|
||
|
||
/* Search the required 'return' instruction within the following reasonable
|
||
instructions. */
|
||
ret_found = nds32_analyze_epilogue (gdbarch, addr, NULL);
|
||
if (ret_found == 0)
|
||
return 0;
|
||
|
||
/* Scan backwards to make sure that the last instruction has adjusted
|
||
stack. Both a 16-bit and a 32-bit instruction will be tried. This is
|
||
just a heuristic, so the false positives will be acceptable. */
|
||
insn = read_memory_unsigned_integer (addr - 2, 4, BFD_ENDIAN_BIG);
|
||
|
||
/* Only 16-bit instructions are possible at addr - 2. */
|
||
if ((insn & 0x80000000) != 0)
|
||
{
|
||
/* This may be a 16-bit instruction or part of a 32-bit instruction. */
|
||
|
||
insn_type = nds32_analyze_epilogue_insn16 (insn >> 16, NULL);
|
||
if (insn_type == INSN_RECOVER)
|
||
return 1;
|
||
}
|
||
|
||
insn = read_memory_unsigned_integer (addr - 4, 4, BFD_ENDIAN_BIG);
|
||
|
||
/* If this is a 16-bit instruction at addr - 4, then there must be another
|
||
16-bit instruction at addr - 2, so only 32-bit instructions need to
|
||
be analyzed here. */
|
||
if ((insn & 0x80000000) == 0)
|
||
{
|
||
/* This may be a 32-bit instruction or part of a 32-bit instruction. */
|
||
|
||
insn_type = nds32_analyze_epilogue_insn32 (abi_use_fpr, insn, NULL);
|
||
if (insn_type == INSN_RECOVER || insn_type == INSN_RESET_SP)
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Implement the "sniffer" frame_unwind method. */
|
||
|
||
static int
|
||
nds32_epilogue_frame_sniffer (const struct frame_unwind *self,
|
||
frame_info_ptr this_frame, void **this_cache)
|
||
{
|
||
if (frame_relative_level (this_frame) == 0)
|
||
return nds32_stack_frame_destroyed_p (get_frame_arch (this_frame),
|
||
get_frame_pc (this_frame));
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
/* Allocate and fill in *THIS_CACHE with information needed to unwind
|
||
*THIS_FRAME within epilogue. Do not do this if *THIS_CACHE was already
|
||
allocated. Return a pointer to the current nds32_frame_cache in
|
||
*THIS_CACHE. */
|
||
|
||
static struct nds32_frame_cache *
|
||
nds32_epilogue_frame_cache (frame_info_ptr this_frame, void **this_cache)
|
||
{
|
||
struct gdbarch *gdbarch = get_frame_arch (this_frame);
|
||
struct nds32_frame_cache *cache;
|
||
CORE_ADDR current_pc, current_sp;
|
||
int i;
|
||
|
||
if (*this_cache)
|
||
return (struct nds32_frame_cache *) *this_cache;
|
||
|
||
cache = nds32_alloc_frame_cache ();
|
||
*this_cache = cache;
|
||
|
||
cache->pc = get_frame_func (this_frame);
|
||
current_pc = get_frame_pc (this_frame);
|
||
nds32_analyze_epilogue (gdbarch, current_pc, cache);
|
||
|
||
current_sp = get_frame_register_unsigned (this_frame, NDS32_SP_REGNUM);
|
||
cache->prev_sp = current_sp + cache->sp_offset;
|
||
|
||
/* Adjust all the saved registers such that they contain addresses
|
||
instead of offsets. */
|
||
for (i = 0; i < NDS32_NUM_SAVED_REGS; i++)
|
||
if (cache->saved_regs[i] != REG_UNAVAIL)
|
||
cache->saved_regs[i] = current_sp + cache->saved_regs[i];
|
||
|
||
return cache;
|
||
}
|
||
|
||
/* Implement the "this_id" frame_unwind method. */
|
||
|
||
static void
|
||
nds32_epilogue_frame_this_id (frame_info_ptr this_frame,
|
||
void **this_cache, struct frame_id *this_id)
|
||
{
|
||
struct nds32_frame_cache *cache
|
||
= nds32_epilogue_frame_cache (this_frame, this_cache);
|
||
|
||
/* This marks the outermost frame. */
|
||
if (cache->prev_sp == 0)
|
||
return;
|
||
|
||
*this_id = frame_id_build (cache->prev_sp, cache->pc);
|
||
}
|
||
|
||
/* Implement the "prev_register" frame_unwind method. */
|
||
|
||
static struct value *
|
||
nds32_epilogue_frame_prev_register (frame_info_ptr this_frame,
|
||
void **this_cache, int regnum)
|
||
{
|
||
struct nds32_frame_cache *cache
|
||
= nds32_epilogue_frame_cache (this_frame, this_cache);
|
||
|
||
if (regnum == NDS32_SP_REGNUM)
|
||
return frame_unwind_got_constant (this_frame, regnum, cache->prev_sp);
|
||
|
||
/* The PC of the previous frame is stored in the LP register of
|
||
the current frame. */
|
||
if (regnum == NDS32_PC_REGNUM)
|
||
regnum = NDS32_LP_REGNUM;
|
||
|
||
if (regnum < NDS32_NUM_SAVED_REGS && cache->saved_regs[regnum] != REG_UNAVAIL)
|
||
return frame_unwind_got_memory (this_frame, regnum,
|
||
cache->saved_regs[regnum]);
|
||
|
||
return frame_unwind_got_register (this_frame, regnum, regnum);
|
||
}
|
||
|
||
static const struct frame_unwind nds32_epilogue_frame_unwind =
|
||
{
|
||
"nds32 epilogue",
|
||
NORMAL_FRAME,
|
||
default_frame_unwind_stop_reason,
|
||
nds32_epilogue_frame_this_id,
|
||
nds32_epilogue_frame_prev_register,
|
||
NULL,
|
||
nds32_epilogue_frame_sniffer
|
||
};
|
||
|
||
|
||
/* Floating type and struct type that has only one floating type member
|
||
can pass value using FPU registers (when FPU ABI is used). */
|
||
|
||
static int
|
||
nds32_check_calling_use_fpr (struct type *type)
|
||
{
|
||
struct type *t;
|
||
enum type_code typecode;
|
||
|
||
t = type;
|
||
while (1)
|
||
{
|
||
t = check_typedef (t);
|
||
typecode = t->code ();
|
||
if (typecode != TYPE_CODE_STRUCT)
|
||
break;
|
||
else if (t->num_fields () != 1)
|
||
return 0;
|
||
else
|
||
t = t->field (0).type ();
|
||
}
|
||
|
||
return typecode == TYPE_CODE_FLT;
|
||
}
|
||
|
||
/* Implement the "push_dummy_call" gdbarch method. */
|
||
|
||
static CORE_ADDR
|
||
nds32_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
|
||
struct regcache *regcache, CORE_ADDR bp_addr,
|
||
int nargs, struct value **args, CORE_ADDR sp,
|
||
function_call_return_method return_method,
|
||
CORE_ADDR struct_addr)
|
||
{
|
||
const int REND = 6; /* End for register offset. */
|
||
int goff = 0; /* Current gpr offset for argument. */
|
||
int foff = 0; /* Current fpr offset for argument. */
|
||
int soff = 0; /* Current stack offset for argument. */
|
||
int i;
|
||
ULONGEST regval;
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
struct type *func_type = function->type ();
|
||
int abi_use_fpr = nds32_abi_use_fpr (tdep->elf_abi);
|
||
int abi_split = nds32_abi_split (tdep->elf_abi);
|
||
|
||
/* Set the return address. For the NDS32, the return breakpoint is
|
||
always at BP_ADDR. */
|
||
regcache_cooked_write_unsigned (regcache, NDS32_LP_REGNUM, bp_addr);
|
||
|
||
/* If STRUCT_RETURN is true, then the struct return address (in
|
||
STRUCT_ADDR) will consume the first argument-passing register.
|
||
Both adjust the register count and store that value. */
|
||
if (return_method == return_method_struct)
|
||
{
|
||
regcache_cooked_write_unsigned (regcache, NDS32_R0_REGNUM, struct_addr);
|
||
goff++;
|
||
}
|
||
|
||
/* Now make sure there's space on the stack */
|
||
for (i = 0; i < nargs; i++)
|
||
{
|
||
struct type *type = args[i]->type ();
|
||
int align = type_align (type);
|
||
|
||
/* If align is zero, it may be an empty struct.
|
||
Just ignore the argument of empty struct. */
|
||
if (align == 0)
|
||
continue;
|
||
|
||
sp -= type->length ();
|
||
sp = align_down (sp, align);
|
||
}
|
||
|
||
/* Stack must be 8-byte aligned. */
|
||
sp = align_down (sp, 8);
|
||
|
||
soff = 0;
|
||
for (i = 0; i < nargs; i++)
|
||
{
|
||
const gdb_byte *val;
|
||
int align, len;
|
||
struct type *type;
|
||
int calling_use_fpr;
|
||
int use_fpr = 0;
|
||
|
||
type = args[i]->type ();
|
||
calling_use_fpr = nds32_check_calling_use_fpr (type);
|
||
len = type->length ();
|
||
align = type_align (type);
|
||
val = args[i]->contents ().data ();
|
||
|
||
/* The size of a composite type larger than 4 bytes will be rounded
|
||
up to the nearest multiple of 4. */
|
||
if (len > 4)
|
||
len = align_up (len, 4);
|
||
|
||
/* Variadic functions are handled differently between AABI and ABI2FP+.
|
||
|
||
For AABI, the caller pushes arguments in registers, callee stores
|
||
unnamed arguments in stack, and then va_arg fetch arguments in stack.
|
||
Therefore, we don't have to handle variadic functions specially.
|
||
|
||
For ABI2FP+, the caller pushes only named arguments in registers
|
||
and pushes all unnamed arguments in stack. */
|
||
|
||
if (abi_use_fpr && func_type->has_varargs ()
|
||
&& i >= func_type->num_fields ())
|
||
goto use_stack;
|
||
|
||
/* Try to use FPRs to pass arguments only when
|
||
1. The program is built using toolchain with FPU support.
|
||
2. The type of this argument can use FPR to pass value. */
|
||
use_fpr = abi_use_fpr && calling_use_fpr;
|
||
|
||
if (use_fpr)
|
||
{
|
||
if (tdep->fpu_freg == -1)
|
||
goto error_no_fpr;
|
||
|
||
/* Adjust alignment. */
|
||
if ((align >> 2) > 0)
|
||
foff = align_up (foff, align >> 2);
|
||
|
||
if (foff < REND)
|
||
{
|
||
switch (len)
|
||
{
|
||
case 4:
|
||
regcache->cooked_write (tdep->fs0_regnum + foff, val);
|
||
foff++;
|
||
break;
|
||
case 8:
|
||
regcache->cooked_write (NDS32_FD0_REGNUM + (foff >> 1), val);
|
||
foff += 2;
|
||
break;
|
||
default:
|
||
/* Long double? */
|
||
internal_error ("Do not know how to handle %d-byte double.\n",
|
||
len);
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/*
|
||
When passing arguments using GPRs,
|
||
|
||
* A composite type not larger than 4 bytes is passed in $rN.
|
||
The format is as if the value is loaded with load instruction
|
||
of corresponding size (e.g., LB, LH, LW).
|
||
|
||
For example,
|
||
|
||
r0
|
||
31 0
|
||
LITTLE: [x x b a]
|
||
BIG: [x x a b]
|
||
|
||
* Otherwise, a composite type is passed in consecutive registers.
|
||
The size is rounded up to the nearest multiple of 4.
|
||
The successive registers hold the parts of the argument as if
|
||
were loaded using lmw instructions.
|
||
|
||
For example,
|
||
|
||
r0 r1
|
||
31 0 31 0
|
||
LITTLE: [d c b a] [x x x e]
|
||
BIG: [a b c d] [e x x x]
|
||
*/
|
||
|
||
/* Adjust alignment. */
|
||
if ((align >> 2) > 0)
|
||
goff = align_up (goff, align >> 2);
|
||
|
||
if (len <= (REND - goff) * 4)
|
||
{
|
||
/* This argument can be passed wholly via GPRs. */
|
||
while (len > 0)
|
||
{
|
||
regval = extract_unsigned_integer (val, (len > 4) ? 4 : len,
|
||
byte_order);
|
||
regcache_cooked_write_unsigned (regcache,
|
||
NDS32_R0_REGNUM + goff,
|
||
regval);
|
||
len -= 4;
|
||
val += 4;
|
||
goff++;
|
||
}
|
||
continue;
|
||
}
|
||
else if (abi_split)
|
||
{
|
||
/* Some parts of this argument can be passed via GPRs. */
|
||
while (goff < REND)
|
||
{
|
||
regval = extract_unsigned_integer (val, (len > 4) ? 4 : len,
|
||
byte_order);
|
||
regcache_cooked_write_unsigned (regcache,
|
||
NDS32_R0_REGNUM + goff,
|
||
regval);
|
||
len -= 4;
|
||
val += 4;
|
||
goff++;
|
||
}
|
||
}
|
||
}
|
||
|
||
use_stack:
|
||
/*
|
||
When pushing (split parts of) an argument into stack,
|
||
|
||
* A composite type not larger than 4 bytes is copied to different
|
||
base address.
|
||
In little-endian, the first byte of this argument is aligned
|
||
at the low address of the next free word.
|
||
In big-endian, the last byte of this argument is aligned
|
||
at the high address of the next free word.
|
||
|
||
For example,
|
||
|
||
sp [ - ] [ c ] hi
|
||
[ c ] [ b ]
|
||
[ b ] [ a ]
|
||
[ a ] [ - ] lo
|
||
LITTLE BIG
|
||
*/
|
||
|
||
/* Adjust alignment. */
|
||
soff = align_up (soff, align);
|
||
|
||
while (len > 0)
|
||
{
|
||
int rlen = (len > 4) ? 4 : len;
|
||
|
||
if (byte_order == BFD_ENDIAN_BIG)
|
||
write_memory (sp + soff + 4 - rlen, val, rlen);
|
||
else
|
||
write_memory (sp + soff, val, rlen);
|
||
|
||
len -= 4;
|
||
val += 4;
|
||
soff += 4;
|
||
}
|
||
}
|
||
|
||
/* Finally, update the SP register. */
|
||
regcache_cooked_write_unsigned (regcache, NDS32_SP_REGNUM, sp);
|
||
|
||
return sp;
|
||
|
||
error_no_fpr:
|
||
/* If use_fpr, but no floating-point register exists,
|
||
then it is an error. */
|
||
error (_("Fail to call. FPU registers are required."));
|
||
}
|
||
|
||
/* Read, for architecture GDBARCH, a function return value of TYPE
|
||
from REGCACHE, and copy that into VALBUF. */
|
||
|
||
static void
|
||
nds32_extract_return_value (struct gdbarch *gdbarch, struct type *type,
|
||
struct regcache *regcache, gdb_byte *valbuf)
|
||
{
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
int abi_use_fpr = nds32_abi_use_fpr (tdep->elf_abi);
|
||
int calling_use_fpr;
|
||
int len;
|
||
|
||
calling_use_fpr = nds32_check_calling_use_fpr (type);
|
||
len = type->length ();
|
||
|
||
if (abi_use_fpr && calling_use_fpr)
|
||
{
|
||
if (len == 4)
|
||
regcache->cooked_read (tdep->fs0_regnum, valbuf);
|
||
else if (len == 8)
|
||
regcache->cooked_read (NDS32_FD0_REGNUM, valbuf);
|
||
else
|
||
internal_error (_("Cannot extract return value of %d bytes "
|
||
"long floating-point."), len);
|
||
}
|
||
else
|
||
{
|
||
/*
|
||
When returning result,
|
||
|
||
* A composite type not larger than 4 bytes is returned in $r0.
|
||
The format is as if the result is loaded with load instruction
|
||
of corresponding size (e.g., LB, LH, LW).
|
||
|
||
For example,
|
||
|
||
r0
|
||
31 0
|
||
LITTLE: [x x b a]
|
||
BIG: [x x a b]
|
||
|
||
* Otherwise, a composite type not larger than 8 bytes is returned
|
||
in $r0 and $r1.
|
||
In little-endian, the first word is loaded in $r0.
|
||
In big-endian, the last word is loaded in $r1.
|
||
|
||
For example,
|
||
|
||
r0 r1
|
||
31 0 31 0
|
||
LITTLE: [d c b a] [x x x e]
|
||
BIG: [x x x a] [b c d e]
|
||
*/
|
||
|
||
ULONGEST tmp;
|
||
|
||
if (len < 4)
|
||
{
|
||
/* By using store_unsigned_integer we avoid having to do
|
||
anything special for small big-endian values. */
|
||
regcache_cooked_read_unsigned (regcache, NDS32_R0_REGNUM, &tmp);
|
||
store_unsigned_integer (valbuf, len, byte_order, tmp);
|
||
}
|
||
else if (len == 4)
|
||
{
|
||
regcache->cooked_read (NDS32_R0_REGNUM, valbuf);
|
||
}
|
||
else if (len < 8)
|
||
{
|
||
int len1, len2;
|
||
|
||
len1 = byte_order == BFD_ENDIAN_BIG ? len - 4 : 4;
|
||
len2 = len - len1;
|
||
|
||
regcache_cooked_read_unsigned (regcache, NDS32_R0_REGNUM, &tmp);
|
||
store_unsigned_integer (valbuf, len1, byte_order, tmp);
|
||
|
||
regcache_cooked_read_unsigned (regcache, NDS32_R0_REGNUM + 1, &tmp);
|
||
store_unsigned_integer (valbuf + len1, len2, byte_order, tmp);
|
||
}
|
||
else
|
||
{
|
||
regcache->cooked_read (NDS32_R0_REGNUM, valbuf);
|
||
regcache->cooked_read (NDS32_R0_REGNUM + 1, valbuf + 4);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Write, for architecture GDBARCH, a function return value of TYPE
|
||
from VALBUF into REGCACHE. */
|
||
|
||
static void
|
||
nds32_store_return_value (struct gdbarch *gdbarch, struct type *type,
|
||
struct regcache *regcache, const gdb_byte *valbuf)
|
||
{
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
int abi_use_fpr = nds32_abi_use_fpr (tdep->elf_abi);
|
||
int calling_use_fpr;
|
||
int len;
|
||
|
||
calling_use_fpr = nds32_check_calling_use_fpr (type);
|
||
len = type->length ();
|
||
|
||
if (abi_use_fpr && calling_use_fpr)
|
||
{
|
||
if (len == 4)
|
||
regcache->cooked_write (tdep->fs0_regnum, valbuf);
|
||
else if (len == 8)
|
||
regcache->cooked_write (NDS32_FD0_REGNUM, valbuf);
|
||
else
|
||
internal_error (_("Cannot store return value of %d bytes "
|
||
"long floating-point."), len);
|
||
}
|
||
else
|
||
{
|
||
ULONGEST regval;
|
||
|
||
if (len < 4)
|
||
{
|
||
regval = extract_unsigned_integer (valbuf, len, byte_order);
|
||
regcache_cooked_write_unsigned (regcache, NDS32_R0_REGNUM, regval);
|
||
}
|
||
else if (len == 4)
|
||
{
|
||
regcache->cooked_write (NDS32_R0_REGNUM, valbuf);
|
||
}
|
||
else if (len < 8)
|
||
{
|
||
int len1, len2;
|
||
|
||
len1 = byte_order == BFD_ENDIAN_BIG ? len - 4 : 4;
|
||
len2 = len - len1;
|
||
|
||
regval = extract_unsigned_integer (valbuf, len1, byte_order);
|
||
regcache_cooked_write_unsigned (regcache, NDS32_R0_REGNUM, regval);
|
||
|
||
regval = extract_unsigned_integer (valbuf + len1, len2, byte_order);
|
||
regcache_cooked_write_unsigned (regcache, NDS32_R0_REGNUM + 1,
|
||
regval);
|
||
}
|
||
else
|
||
{
|
||
regcache->cooked_write (NDS32_R0_REGNUM, valbuf);
|
||
regcache->cooked_write (NDS32_R0_REGNUM + 1, valbuf + 4);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Implement the "return_value" gdbarch method.
|
||
|
||
Determine, for architecture GDBARCH, how a return value of TYPE
|
||
should be returned. If it is supposed to be returned in registers,
|
||
and READBUF is non-zero, read the appropriate value from REGCACHE,
|
||
and copy it into READBUF. If WRITEBUF is non-zero, write the value
|
||
from WRITEBUF into REGCACHE. */
|
||
|
||
static enum return_value_convention
|
||
nds32_return_value (struct gdbarch *gdbarch, struct value *func_type,
|
||
struct type *type, struct regcache *regcache,
|
||
gdb_byte *readbuf, const gdb_byte *writebuf)
|
||
{
|
||
if (type->length () > 8)
|
||
{
|
||
return RETURN_VALUE_STRUCT_CONVENTION;
|
||
}
|
||
else
|
||
{
|
||
if (readbuf != NULL)
|
||
nds32_extract_return_value (gdbarch, type, regcache, readbuf);
|
||
if (writebuf != NULL)
|
||
nds32_store_return_value (gdbarch, type, regcache, writebuf);
|
||
|
||
return RETURN_VALUE_REGISTER_CONVENTION;
|
||
}
|
||
}
|
||
|
||
/* Implement the "get_longjmp_target" gdbarch method. */
|
||
|
||
static int
|
||
nds32_get_longjmp_target (frame_info_ptr frame, CORE_ADDR *pc)
|
||
{
|
||
gdb_byte buf[4];
|
||
CORE_ADDR jb_addr;
|
||
struct gdbarch *gdbarch = get_frame_arch (frame);
|
||
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
|
||
|
||
jb_addr = get_frame_register_unsigned (frame, NDS32_R0_REGNUM);
|
||
|
||
if (target_read_memory (jb_addr + 11 * 4, buf, 4))
|
||
return 0;
|
||
|
||
*pc = extract_unsigned_integer (buf, 4, byte_order);
|
||
return 1;
|
||
}
|
||
|
||
/* Validate the given TDESC, and fixed-number some registers in it.
|
||
Return 0 if the given TDESC does not contain the required feature
|
||
or not contain required registers. */
|
||
|
||
static int
|
||
nds32_validate_tdesc_p (const struct target_desc *tdesc,
|
||
struct tdesc_arch_data *tdesc_data,
|
||
int *fpu_freg, int *use_pseudo_fsrs)
|
||
{
|
||
const struct tdesc_feature *feature;
|
||
int i, valid_p;
|
||
|
||
feature = tdesc_find_feature (tdesc, "org.gnu.gdb.nds32.core");
|
||
if (feature == NULL)
|
||
return 0;
|
||
|
||
valid_p = 1;
|
||
/* Validate and fixed-number R0-R10. */
|
||
for (i = NDS32_R0_REGNUM; i <= NDS32_R0_REGNUM + 10; i++)
|
||
valid_p &= tdesc_numbered_register (feature, tdesc_data, i,
|
||
nds32_register_names[i]);
|
||
|
||
/* Validate R15. */
|
||
valid_p &= tdesc_unnumbered_register (feature,
|
||
nds32_register_names[NDS32_TA_REGNUM]);
|
||
|
||
/* Validate and fixed-number FP, GP, LP, SP, PC. */
|
||
for (i = NDS32_FP_REGNUM; i <= NDS32_PC_REGNUM; i++)
|
||
valid_p &= tdesc_numbered_register (feature, tdesc_data, i,
|
||
nds32_register_names[i]);
|
||
|
||
if (!valid_p)
|
||
return 0;
|
||
|
||
/* Fixed-number R11-R27. */
|
||
for (i = NDS32_R0_REGNUM + 11; i <= NDS32_R0_REGNUM + 27; i++)
|
||
tdesc_numbered_register (feature, tdesc_data, i, nds32_register_names[i]);
|
||
|
||
feature = tdesc_find_feature (tdesc, "org.gnu.gdb.nds32.fpu");
|
||
if (feature != NULL)
|
||
{
|
||
int num_fdr_regs, num_fsr_regs, fs0_regnum, num_listed_fsr;
|
||
int freg = -1;
|
||
|
||
/* Guess FPU configuration via listed registers. */
|
||
if (tdesc_unnumbered_register (feature, "fd31"))
|
||
freg = 3;
|
||
else if (tdesc_unnumbered_register (feature, "fd15"))
|
||
freg = 2;
|
||
else if (tdesc_unnumbered_register (feature, "fd7"))
|
||
freg = 1;
|
||
else if (tdesc_unnumbered_register (feature, "fd3"))
|
||
freg = 0;
|
||
|
||
if (freg == -1)
|
||
/* Required FDR is not found. */
|
||
return 0;
|
||
else
|
||
*fpu_freg = freg;
|
||
|
||
/* Validate and fixed-number required FDRs. */
|
||
num_fdr_regs = num_fdr_map[freg];
|
||
for (i = 0; i < num_fdr_regs; i++)
|
||
valid_p &= tdesc_numbered_register (feature, tdesc_data,
|
||
NDS32_FD0_REGNUM + i,
|
||
nds32_fdr_register_names[i]);
|
||
if (!valid_p)
|
||
return 0;
|
||
|
||
/* Count the number of listed FSRs, and fixed-number them if present. */
|
||
num_fsr_regs = num_fsr_map[freg];
|
||
fs0_regnum = NDS32_FD0_REGNUM + num_fdr_regs;
|
||
num_listed_fsr = 0;
|
||
for (i = 0; i < num_fsr_regs; i++)
|
||
num_listed_fsr += tdesc_numbered_register (feature, tdesc_data,
|
||
fs0_regnum + i,
|
||
nds32_fsr_register_names[i]);
|
||
|
||
if (num_listed_fsr == 0)
|
||
/* No required FSRs are listed explicitly, make them pseudo registers
|
||
of FDRs. */
|
||
*use_pseudo_fsrs = 1;
|
||
else if (num_listed_fsr == num_fsr_regs)
|
||
/* All required FSRs are listed explicitly. */
|
||
*use_pseudo_fsrs = 0;
|
||
else
|
||
/* Some required FSRs are missing. */
|
||
return 0;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* 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 *
|
||
nds32_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
|
||
{
|
||
struct gdbarch_list *best_arch;
|
||
tdesc_arch_data_up tdesc_data;
|
||
const struct target_desc *tdesc = info.target_desc;
|
||
int elf_abi = E_NDS_ABI_AABI;
|
||
int fpu_freg = -1;
|
||
int use_pseudo_fsrs = 0;
|
||
int i, num_regs, maxregs;
|
||
|
||
/* Extract the elf_flags if available. */
|
||
if (info.abfd && bfd_get_flavour (info.abfd) == bfd_target_elf_flavour)
|
||
elf_abi = elf_elfheader (info.abfd)->e_flags & EF_NDS_ABI;
|
||
|
||
/* 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))
|
||
{
|
||
nds32_gdbarch_tdep *idep
|
||
= gdbarch_tdep<nds32_gdbarch_tdep> (best_arch->gdbarch);
|
||
|
||
if (idep->elf_abi != elf_abi)
|
||
continue;
|
||
|
||
/* Found a match. */
|
||
break;
|
||
}
|
||
|
||
if (best_arch != NULL)
|
||
return best_arch->gdbarch;
|
||
|
||
if (!tdesc_has_registers (tdesc))
|
||
tdesc = tdesc_nds32;
|
||
|
||
tdesc_data = tdesc_data_alloc ();
|
||
|
||
if (!nds32_validate_tdesc_p (tdesc, tdesc_data.get (), &fpu_freg,
|
||
&use_pseudo_fsrs))
|
||
return NULL;
|
||
|
||
/* Allocate space for the new architecture. */
|
||
gdbarch *gdbarch
|
||
= gdbarch_alloc (&info, gdbarch_tdep_up (new nds32_gdbarch_tdep));
|
||
nds32_gdbarch_tdep *tdep = gdbarch_tdep<nds32_gdbarch_tdep> (gdbarch);
|
||
|
||
tdep->fpu_freg = fpu_freg;
|
||
tdep->use_pseudo_fsrs = use_pseudo_fsrs;
|
||
tdep->fs0_regnum = -1;
|
||
tdep->elf_abi = elf_abi;
|
||
|
||
set_gdbarch_wchar_bit (gdbarch, 16);
|
||
set_gdbarch_wchar_signed (gdbarch, 0);
|
||
|
||
if (fpu_freg == -1)
|
||
num_regs = NDS32_NUM_REGS;
|
||
else if (use_pseudo_fsrs == 1)
|
||
{
|
||
set_gdbarch_pseudo_register_read (gdbarch, nds32_pseudo_register_read);
|
||
set_gdbarch_pseudo_register_write (gdbarch, nds32_pseudo_register_write);
|
||
set_tdesc_pseudo_register_name (gdbarch, nds32_pseudo_register_name);
|
||
set_tdesc_pseudo_register_type (gdbarch, nds32_pseudo_register_type);
|
||
set_gdbarch_num_pseudo_regs (gdbarch, num_fsr_map[fpu_freg]);
|
||
|
||
num_regs = NDS32_NUM_REGS + num_fdr_map[fpu_freg];
|
||
}
|
||
else
|
||
num_regs = NDS32_NUM_REGS + num_fdr_map[fpu_freg] + num_fsr_map[fpu_freg];
|
||
|
||
set_gdbarch_num_regs (gdbarch, num_regs);
|
||
tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
|
||
|
||
/* Cache the register number of fs0. */
|
||
if (fpu_freg != -1)
|
||
tdep->fs0_regnum = user_reg_map_name_to_regnum (gdbarch, "fs0", -1);
|
||
|
||
/* Add NDS32 register aliases. To avoid search in user register name space,
|
||
user_reg_map_name_to_regnum is not used. */
|
||
maxregs = gdbarch_num_cooked_regs (gdbarch);
|
||
for (i = 0; i < ARRAY_SIZE (nds32_register_aliases); i++)
|
||
{
|
||
int regnum, j;
|
||
|
||
regnum = -1;
|
||
/* Search register name space. */
|
||
for (j = 0; j < maxregs; j++)
|
||
{
|
||
const char *regname = gdbarch_register_name (gdbarch, j);
|
||
|
||
if (strcmp (regname, nds32_register_aliases[i].name) == 0)
|
||
{
|
||
regnum = j;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Try next alias entry if the given name can not be found in register
|
||
name space. */
|
||
if (regnum == -1)
|
||
continue;
|
||
|
||
user_reg_add (gdbarch, nds32_register_aliases[i].alias,
|
||
value_of_nds32_reg, (const void *) (intptr_t) regnum);
|
||
}
|
||
|
||
nds32_add_reggroups (gdbarch);
|
||
|
||
/* Hook in ABI-specific overrides, if they have been registered. */
|
||
info.tdesc_data = tdesc_data.get ();
|
||
gdbarch_init_osabi (info, gdbarch);
|
||
|
||
/* Override tdesc_register callbacks for system registers. */
|
||
set_gdbarch_register_reggroup_p (gdbarch, nds32_register_reggroup_p);
|
||
|
||
set_gdbarch_sp_regnum (gdbarch, NDS32_SP_REGNUM);
|
||
set_gdbarch_pc_regnum (gdbarch, NDS32_PC_REGNUM);
|
||
set_gdbarch_stack_frame_destroyed_p (gdbarch, nds32_stack_frame_destroyed_p);
|
||
set_gdbarch_dwarf2_reg_to_regnum (gdbarch, nds32_dwarf2_reg_to_regnum);
|
||
|
||
set_gdbarch_push_dummy_call (gdbarch, nds32_push_dummy_call);
|
||
set_gdbarch_return_value (gdbarch, nds32_return_value);
|
||
|
||
set_gdbarch_skip_prologue (gdbarch, nds32_skip_prologue);
|
||
set_gdbarch_inner_than (gdbarch, core_addr_lessthan);
|
||
set_gdbarch_breakpoint_kind_from_pc (gdbarch,
|
||
nds32_breakpoint::kind_from_pc);
|
||
set_gdbarch_sw_breakpoint_from_kind (gdbarch,
|
||
nds32_breakpoint::bp_from_kind);
|
||
|
||
set_gdbarch_frame_align (gdbarch, nds32_frame_align);
|
||
frame_base_set_default (gdbarch, &nds32_frame_base);
|
||
|
||
/* Handle longjmp. */
|
||
set_gdbarch_get_longjmp_target (gdbarch, nds32_get_longjmp_target);
|
||
|
||
/* The order of appending is the order it check frame. */
|
||
dwarf2_append_unwinders (gdbarch);
|
||
frame_unwind_append_unwinder (gdbarch, &nds32_epilogue_frame_unwind);
|
||
frame_unwind_append_unwinder (gdbarch, &nds32_frame_unwind);
|
||
|
||
return gdbarch;
|
||
}
|
||
|
||
void _initialize_nds32_tdep ();
|
||
void
|
||
_initialize_nds32_tdep ()
|
||
{
|
||
/* Initialize gdbarch. */
|
||
gdbarch_register (bfd_arch_nds32, nds32_gdbarch_init);
|
||
|
||
initialize_tdesc_nds32 ();
|
||
nds32_init_reggroups ();
|
||
}
|