mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-15 04:31:49 +08:00
ea3352172e
The current implementation of the fcc register is referenced to the user_fp_state structure of the kernel uapi [1]. struct user_fp_state { uint64_t fpr[32]; uint64_t fcc; uint32_t fcsr; }; But it is mistakenly defined as a 64-bit fputype register, resulting in a confusing output of "info register". (gdb) info register ... fcc {f = 0x0, d = 0x0} {f = 0, d = 0} ... According to "Condition Flag Register" in "LoongArch Reference Manual" [2], there are 8 condition flag registers of size 1. Use 8 registers of uint8 to make it easier for users to view the fcc register groups. (gdb) info register ... fcc0 0x1 1 fcc1 0x0 0 fcc2 0x0 0 fcc3 0x0 0 fcc4 0x0 0 fcc5 0x0 0 fcc6 0x0 0 fcc7 0x0 0 ... [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/loongarch/include/uapi/asm/ptrace.h [2] https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#_condition_flag_register Signed-off-by: Feiyang Chen <chenfeiyang@loongson.cn> Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
1551 lines
51 KiB
C
1551 lines
51 KiB
C
/* Target-dependent code for the LoongArch architecture, for GDB.
|
|
|
|
Copyright (C) 2022 Free Software Foundation, Inc.
|
|
|
|
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 "arch-utils.h"
|
|
#include "dwarf2/frame.h"
|
|
#include "elf-bfd.h"
|
|
#include "frame-unwind.h"
|
|
#include "gdbcore.h"
|
|
#include "loongarch-tdep.h"
|
|
#include "target.h"
|
|
#include "target-descriptions.h"
|
|
#include "trad-frame.h"
|
|
#include "user-regs.h"
|
|
|
|
/* Fetch the instruction at PC. */
|
|
|
|
static insn_t
|
|
loongarch_fetch_instruction (CORE_ADDR pc)
|
|
{
|
|
size_t insn_len = loongarch_insn_length (0);
|
|
gdb_byte buf[insn_len];
|
|
int err;
|
|
|
|
err = target_read_memory (pc, buf, insn_len);
|
|
if (err)
|
|
memory_error (TARGET_XFER_E_IO, pc);
|
|
|
|
return extract_unsigned_integer (buf, insn_len, BFD_ENDIAN_LITTLE);
|
|
}
|
|
|
|
/* Return TRUE if INSN is a unconditional branch instruction, otherwise return FALSE. */
|
|
|
|
static bool
|
|
loongarch_insn_is_uncond_branch (insn_t insn)
|
|
{
|
|
if ((insn & 0xfc000000) == 0x4c000000 /* jirl */
|
|
|| (insn & 0xfc000000) == 0x50000000 /* b */
|
|
|| (insn & 0xfc000000) == 0x54000000) /* bl */
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return TRUE if INSN is a conditional branch instruction, otherwise return FALSE. */
|
|
|
|
static bool
|
|
loongarch_insn_is_cond_branch (insn_t insn)
|
|
{
|
|
if ((insn & 0xfc000000) == 0x58000000 /* beq */
|
|
|| (insn & 0xfc000000) == 0x5c000000 /* bne */
|
|
|| (insn & 0xfc000000) == 0x60000000 /* blt */
|
|
|| (insn & 0xfc000000) == 0x64000000 /* bge */
|
|
|| (insn & 0xfc000000) == 0x68000000 /* bltu */
|
|
|| (insn & 0xfc000000) == 0x6c000000 /* bgeu */
|
|
|| (insn & 0xfc000000) == 0x40000000 /* beqz */
|
|
|| (insn & 0xfc000000) == 0x44000000) /* bnez */
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return TRUE if INSN is a branch instruction, otherwise return FALSE. */
|
|
|
|
static bool
|
|
loongarch_insn_is_branch (insn_t insn)
|
|
{
|
|
bool is_uncond = loongarch_insn_is_uncond_branch (insn);
|
|
bool is_cond = loongarch_insn_is_cond_branch (insn);
|
|
|
|
return (is_uncond || is_cond);
|
|
}
|
|
|
|
/* Return TRUE if INSN is a Load Linked instruction, otherwise return FALSE. */
|
|
|
|
static bool
|
|
loongarch_insn_is_ll (insn_t insn)
|
|
{
|
|
if ((insn & 0xff000000) == 0x20000000 /* ll.w */
|
|
|| (insn & 0xff000000) == 0x22000000) /* ll.d */
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return TRUE if INSN is a Store Conditional instruction, otherwise return FALSE. */
|
|
|
|
static bool
|
|
loongarch_insn_is_sc (insn_t insn)
|
|
{
|
|
if ((insn & 0xff000000) == 0x21000000 /* sc.w */
|
|
|| (insn & 0xff000000) == 0x23000000) /* sc.d */
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Analyze the function prologue from START_PC to LIMIT_PC.
|
|
Return the address of the first instruction past the prologue. */
|
|
|
|
static CORE_ADDR
|
|
loongarch_scan_prologue (struct gdbarch *gdbarch, CORE_ADDR start_pc,
|
|
CORE_ADDR limit_pc, struct frame_info *this_frame,
|
|
struct trad_frame_cache *this_cache)
|
|
{
|
|
CORE_ADDR cur_pc = start_pc, prologue_end = 0;
|
|
int32_t sp = LOONGARCH_SP_REGNUM;
|
|
int32_t fp = LOONGARCH_FP_REGNUM;
|
|
int32_t reg_value[32] = {0};
|
|
int32_t reg_used[32] = {1, 0};
|
|
|
|
while (cur_pc < limit_pc)
|
|
{
|
|
insn_t insn = loongarch_fetch_instruction (cur_pc);
|
|
size_t insn_len = loongarch_insn_length (insn);
|
|
int32_t rd = loongarch_decode_imm ("0:5", insn, 0);
|
|
int32_t rj = loongarch_decode_imm ("5:5", insn, 0);
|
|
int32_t rk = loongarch_decode_imm ("10:5", insn, 0);
|
|
int32_t si12 = loongarch_decode_imm ("10:12", insn, 1);
|
|
int32_t si20 = loongarch_decode_imm ("5:20", insn, 1);
|
|
|
|
if ((insn & 0xffc00000) == 0x02c00000 /* addi.d sp,sp,si12 */
|
|
&& rd == sp && rj == sp && si12 < 0)
|
|
{
|
|
prologue_end = cur_pc + insn_len;
|
|
}
|
|
else if ((insn & 0xffc00000) == 0x02c00000 /* addi.d fp,sp,si12 */
|
|
&& rd == fp && rj == sp && si12 > 0)
|
|
{
|
|
prologue_end = cur_pc + insn_len;
|
|
}
|
|
else if ((insn & 0xffc00000) == 0x29c00000 /* st.d rd,sp,si12 */
|
|
&& rj == sp)
|
|
{
|
|
prologue_end = cur_pc + insn_len;
|
|
}
|
|
else if ((insn & 0xff000000) == 0x27000000 /* stptr.d rd,sp,si14 */
|
|
&& rj == sp)
|
|
{
|
|
prologue_end = cur_pc + insn_len;
|
|
}
|
|
else if ((insn & 0xfe000000) == 0x14000000) /* lu12i.w rd,si20 */
|
|
{
|
|
reg_value[rd] = si20 << 12;
|
|
reg_used[rd] = 1;
|
|
}
|
|
else if ((insn & 0xffc00000) == 0x03800000) /* ori rd,rj,si12 */
|
|
{
|
|
if (reg_used[rj])
|
|
{
|
|
reg_value[rd] = reg_value[rj] | (si12 & 0xfff);
|
|
reg_used[rd] = 1;
|
|
}
|
|
}
|
|
else if ((insn & 0xffff8000) == 0x00108000 /* add.d sp,sp,rk */
|
|
&& rd == sp && rj == sp)
|
|
{
|
|
if (reg_used[rk] == 1 && reg_value[rk] < 0)
|
|
{
|
|
prologue_end = cur_pc + insn_len;
|
|
break;
|
|
}
|
|
}
|
|
else if (loongarch_insn_is_branch (insn))
|
|
{
|
|
break;
|
|
}
|
|
|
|
cur_pc += insn_len;
|
|
}
|
|
|
|
if (prologue_end == 0)
|
|
prologue_end = cur_pc;
|
|
|
|
return prologue_end;
|
|
}
|
|
|
|
/* Implement the loongarch_skip_prologue gdbarch method. */
|
|
|
|
static CORE_ADDR
|
|
loongarch_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
|
|
{
|
|
CORE_ADDR func_addr;
|
|
|
|
/* 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, nullptr, &func_addr, nullptr))
|
|
{
|
|
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. */
|
|
CORE_ADDR limit_pc = skip_prologue_using_sal (gdbarch, pc);
|
|
if (limit_pc == 0)
|
|
limit_pc = pc + 100; /* Arbitrary large number. */
|
|
|
|
return loongarch_scan_prologue (gdbarch, pc, limit_pc, nullptr, nullptr);
|
|
}
|
|
|
|
/* Decode the current instruction and determine the address of the
|
|
next instruction. */
|
|
|
|
static CORE_ADDR
|
|
loongarch_next_pc (struct regcache *regcache, CORE_ADDR cur_pc)
|
|
{
|
|
struct gdbarch *gdbarch = regcache->arch ();
|
|
loongarch_gdbarch_tdep *tdep = gdbarch_tdep<loongarch_gdbarch_tdep> (gdbarch);
|
|
insn_t insn = loongarch_fetch_instruction (cur_pc);
|
|
size_t insn_len = loongarch_insn_length (insn);
|
|
CORE_ADDR next_pc = cur_pc + insn_len;
|
|
|
|
if ((insn & 0xfc000000) == 0x4c000000) /* jirl rd, rj, offs16 */
|
|
{
|
|
LONGEST rj = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
next_pc = rj + loongarch_decode_imm ("10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x50000000 /* b offs26 */
|
|
|| (insn & 0xfc000000) == 0x54000000) /* bl offs26 */
|
|
{
|
|
next_pc = cur_pc + loongarch_decode_imm ("0:10|10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x58000000) /* beq rj, rd, offs16 */
|
|
{
|
|
LONGEST rj = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
LONGEST rd = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("0:5", insn, 0));
|
|
if (rj == rd)
|
|
next_pc = cur_pc + loongarch_decode_imm ("10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x5c000000) /* bne rj, rd, offs16 */
|
|
{
|
|
LONGEST rj = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
LONGEST rd = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("0:5", insn, 0));
|
|
if (rj != rd)
|
|
next_pc = cur_pc + loongarch_decode_imm ("10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x60000000) /* blt rj, rd, offs16 */
|
|
{
|
|
LONGEST rj = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
LONGEST rd = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("0:5", insn, 0));
|
|
if (rj < rd)
|
|
next_pc = cur_pc + loongarch_decode_imm ("10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x64000000) /* bge rj, rd, offs16 */
|
|
{
|
|
LONGEST rj = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
LONGEST rd = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("0:5", insn, 0));
|
|
if (rj >= rd)
|
|
next_pc = cur_pc + loongarch_decode_imm ("10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x68000000) /* bltu rj, rd, offs16 */
|
|
{
|
|
ULONGEST rj = regcache_raw_get_unsigned (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
ULONGEST rd = regcache_raw_get_unsigned (regcache,
|
|
loongarch_decode_imm ("0:5", insn, 0));
|
|
if (rj < rd)
|
|
next_pc = cur_pc + loongarch_decode_imm ("10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x6c000000) /* bgeu rj, rd, offs16 */
|
|
{
|
|
ULONGEST rj = regcache_raw_get_unsigned (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
ULONGEST rd = regcache_raw_get_unsigned (regcache,
|
|
loongarch_decode_imm ("0:5", insn, 0));
|
|
if (rj >= rd)
|
|
next_pc = cur_pc + loongarch_decode_imm ("10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x40000000) /* beqz rj, offs21 */
|
|
{
|
|
LONGEST rj = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
if (rj == 0)
|
|
next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xfc000000) == 0x44000000) /* bnez rj, offs21 */
|
|
{
|
|
LONGEST rj = regcache_raw_get_signed (regcache,
|
|
loongarch_decode_imm ("5:5", insn, 0));
|
|
if (rj != 0)
|
|
next_pc = cur_pc + loongarch_decode_imm ("0:5|10:16<<2", insn, 1);
|
|
}
|
|
else if ((insn & 0xffff8000) == 0x002b0000) /* syscall */
|
|
{
|
|
if (tdep->syscall_next_pc != nullptr)
|
|
next_pc = tdep->syscall_next_pc (get_current_frame ());
|
|
}
|
|
|
|
return next_pc;
|
|
}
|
|
|
|
/* We can't put a breakpoint in the middle of a ll/sc atomic sequence,
|
|
so look for the end of the sequence and put the breakpoint there. */
|
|
|
|
static std::vector<CORE_ADDR>
|
|
loongarch_deal_with_atomic_sequence (struct regcache *regcache, CORE_ADDR cur_pc)
|
|
{
|
|
CORE_ADDR next_pc;
|
|
std::vector<CORE_ADDR> next_pcs;
|
|
insn_t insn = loongarch_fetch_instruction (cur_pc);
|
|
size_t insn_len = loongarch_insn_length (insn);
|
|
const int atomic_sequence_length = 16;
|
|
bool found_atomic_sequence_endpoint = false;
|
|
|
|
/* Look for a Load Linked instruction which begins the atomic sequence. */
|
|
if (!loongarch_insn_is_ll (insn))
|
|
return {};
|
|
|
|
/* Assume that no atomic sequence is longer than "atomic_sequence_length" instructions. */
|
|
for (int insn_count = 0; insn_count < atomic_sequence_length; ++insn_count)
|
|
{
|
|
cur_pc += insn_len;
|
|
insn = loongarch_fetch_instruction (cur_pc);
|
|
|
|
/* Look for a unconditional branch instruction, fallback to the standard code. */
|
|
if (loongarch_insn_is_uncond_branch (insn))
|
|
{
|
|
return {};
|
|
}
|
|
/* Look for a conditional branch instruction, put a breakpoint in its destination address. */
|
|
else if (loongarch_insn_is_cond_branch (insn))
|
|
{
|
|
next_pc = loongarch_next_pc (regcache, cur_pc);
|
|
next_pcs.push_back (next_pc);
|
|
}
|
|
/* Look for a Store Conditional instruction which closes the atomic sequence. */
|
|
else if (loongarch_insn_is_sc (insn))
|
|
{
|
|
found_atomic_sequence_endpoint = true;
|
|
next_pc = cur_pc + insn_len;
|
|
next_pcs.push_back (next_pc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We didn't find a closing Store Conditional instruction, fallback to the standard code. */
|
|
if (!found_atomic_sequence_endpoint)
|
|
return {};
|
|
|
|
return next_pcs;
|
|
}
|
|
|
|
/* Implement the software_single_step gdbarch method */
|
|
|
|
static std::vector<CORE_ADDR>
|
|
loongarch_software_single_step (struct regcache *regcache)
|
|
{
|
|
CORE_ADDR cur_pc = regcache_read_pc (regcache);
|
|
std::vector<CORE_ADDR> next_pcs
|
|
= loongarch_deal_with_atomic_sequence (regcache, cur_pc);
|
|
|
|
if (!next_pcs.empty ())
|
|
return next_pcs;
|
|
|
|
CORE_ADDR next_pc = loongarch_next_pc (regcache, cur_pc);
|
|
|
|
return {next_pc};
|
|
}
|
|
|
|
/* Implement the frame_align gdbarch method. */
|
|
|
|
static CORE_ADDR
|
|
loongarch_frame_align (struct gdbarch *gdbarch, CORE_ADDR addr)
|
|
{
|
|
return align_down (addr, 16);
|
|
}
|
|
|
|
/* Generate, or return the cached frame cache for frame unwinder. */
|
|
|
|
static struct trad_frame_cache *
|
|
loongarch_frame_cache (struct frame_info *this_frame, void **this_cache)
|
|
{
|
|
struct trad_frame_cache *cache;
|
|
CORE_ADDR pc;
|
|
|
|
if (*this_cache != nullptr)
|
|
return (struct trad_frame_cache *) *this_cache;
|
|
|
|
cache = trad_frame_cache_zalloc (this_frame);
|
|
*this_cache = cache;
|
|
|
|
trad_frame_set_reg_realreg (cache, LOONGARCH_PC_REGNUM, LOONGARCH_RA_REGNUM);
|
|
|
|
pc = get_frame_address_in_block (this_frame);
|
|
trad_frame_set_id (cache, frame_id_build_unavailable_stack (pc));
|
|
|
|
return cache;
|
|
}
|
|
|
|
/* Implement the this_id callback for frame unwinder. */
|
|
|
|
static void
|
|
loongarch_frame_this_id (struct frame_info *this_frame, void **prologue_cache,
|
|
struct frame_id *this_id)
|
|
{
|
|
struct trad_frame_cache *info;
|
|
|
|
info = loongarch_frame_cache (this_frame, prologue_cache);
|
|
trad_frame_get_id (info, this_id);
|
|
}
|
|
|
|
/* Implement the prev_register callback for frame unwinder. */
|
|
|
|
static struct value *
|
|
loongarch_frame_prev_register (struct frame_info *this_frame,
|
|
void **prologue_cache, int regnum)
|
|
{
|
|
struct trad_frame_cache *info;
|
|
|
|
info = loongarch_frame_cache (this_frame, prologue_cache);
|
|
return trad_frame_get_register (info, this_frame, regnum);
|
|
}
|
|
|
|
static const struct frame_unwind loongarch_frame_unwind = {
|
|
"loongarch prologue",
|
|
/*.type =*/NORMAL_FRAME,
|
|
/*.stop_reason =*/default_frame_unwind_stop_reason,
|
|
/*.this_id =*/loongarch_frame_this_id,
|
|
/*.prev_register =*/loongarch_frame_prev_register,
|
|
/*.unwind_data =*/nullptr,
|
|
/*.sniffer =*/default_frame_sniffer,
|
|
/*.dealloc_cache =*/nullptr,
|
|
/*.prev_arch =*/nullptr,
|
|
};
|
|
|
|
/* Write the contents of buffer VAL into the general-purpose argument
|
|
register defined by GAR in REGCACHE. GAR indicates the available
|
|
general-purpose argument registers which should be a value in the
|
|
range 1 to 8 (LOONGARCH_ARG_REGNUM), which correspond to registers
|
|
a7 and a0 respectively, that is to say, regnum is a7 if GAR is 1,
|
|
regnum is a6 if GAR is 2, regnum is a5 if GAR is 3, regnum is a4
|
|
if GAR is 4, regnum is a3 if GAR is 5, regnum is a2 if GAR is 6,
|
|
regnum is a1 if GAR is 7, regnum is a0 if GAR is 8. */
|
|
|
|
static void
|
|
pass_in_gar (struct regcache *regcache, unsigned int gar, const gdb_byte *val)
|
|
{
|
|
unsigned int regnum = LOONGARCH_ARG_REGNUM - gar + LOONGARCH_A0_REGNUM;
|
|
regcache->cooked_write (regnum, val);
|
|
}
|
|
|
|
/* Write the contents of buffer VAL into the floating-point argument
|
|
register defined by FAR in REGCACHE. FAR indicates the available
|
|
floating-point argument registers which should be a value in the
|
|
range 1 to 8 (LOONGARCH_ARG_REGNUM), which correspond to registers
|
|
f7 and f0 respectively, that is to say, regnum is f7 if FAR is 1,
|
|
regnum is f6 if FAR is 2, regnum is f5 if FAR is 3, regnum is f4
|
|
if FAR is 4, regnum is f3 if FAR is 5, regnum is f2 if FAR is 6,
|
|
regnum is f1 if FAR is 7, regnum is f0 if FAR is 8. */
|
|
|
|
static void
|
|
pass_in_far (struct regcache *regcache, unsigned int far, const gdb_byte *val)
|
|
{
|
|
unsigned int regnum = LOONGARCH_ARG_REGNUM - far + LOONGARCH_FIRST_FP_REGNUM;
|
|
regcache->cooked_write (regnum, val);
|
|
}
|
|
|
|
/* Pass a value on the stack. */
|
|
|
|
static void
|
|
pass_on_stack (struct regcache *regcache, const gdb_byte *val,
|
|
size_t len, int align, gdb_byte **addr)
|
|
{
|
|
align = align_up (align, 8);
|
|
if (align > 16)
|
|
align = 16;
|
|
|
|
CORE_ADDR align_addr = (CORE_ADDR) (*addr);
|
|
align_addr = align_up (align_addr, align);
|
|
*addr = (gdb_byte *) align_addr;
|
|
memcpy (*addr, val, len);
|
|
*addr += len;
|
|
}
|
|
|
|
/* Compute the numbers of struct member. */
|
|
|
|
static void
|
|
compute_struct_member (struct type *type,
|
|
unsigned int *fixed_point_members,
|
|
unsigned int *floating_point_members,
|
|
bool *first_member_is_fixed_point)
|
|
{
|
|
for (int i = 0; i < type->num_fields (); i++)
|
|
{
|
|
struct type *field_type = check_typedef (type->field (i).type ());
|
|
|
|
if (field_type->code () == TYPE_CODE_INT
|
|
|| field_type->code () == TYPE_CODE_BOOL
|
|
|| field_type->code () == TYPE_CODE_CHAR
|
|
|| field_type->code () == TYPE_CODE_RANGE
|
|
|| field_type->code () == TYPE_CODE_ENUM
|
|
|| field_type->code () == TYPE_CODE_PTR)
|
|
{
|
|
(*fixed_point_members)++;
|
|
|
|
if (*floating_point_members == 0)
|
|
*first_member_is_fixed_point = true;
|
|
}
|
|
else if (field_type->code () == TYPE_CODE_FLT)
|
|
(*floating_point_members)++;
|
|
else if (field_type->code () == TYPE_CODE_STRUCT)
|
|
compute_struct_member (field_type,
|
|
fixed_point_members,
|
|
floating_point_members,
|
|
first_member_is_fixed_point);
|
|
else if (field_type->code () == TYPE_CODE_COMPLEX)
|
|
(*floating_point_members) += 2;
|
|
}
|
|
}
|
|
|
|
/* Implement the push_dummy_call gdbarch method. */
|
|
|
|
static CORE_ADDR
|
|
loongarch_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)
|
|
{
|
|
int regsize = register_size (gdbarch, 0);
|
|
unsigned int gar = LOONGARCH_ARG_REGNUM;
|
|
unsigned int far = LOONGARCH_ARG_REGNUM;
|
|
unsigned int fixed_point_members;
|
|
unsigned int floating_point_members;
|
|
bool first_member_is_fixed_point;
|
|
gdb_byte buf[1024] = { 0 };
|
|
gdb_byte *addr = buf;
|
|
|
|
if (return_method != return_method_normal)
|
|
pass_in_gar (regcache, gar--, (gdb_byte *) &struct_addr);
|
|
|
|
for (int i = 0; i < nargs; i++)
|
|
{
|
|
struct value *arg = args[i];
|
|
const gdb_byte *val = value_contents (arg).data ();
|
|
struct type *type = check_typedef (value_type (arg));
|
|
size_t len = TYPE_LENGTH (type);
|
|
int align = type_align (type);
|
|
enum type_code code = type->code ();
|
|
|
|
switch (code)
|
|
{
|
|
case TYPE_CODE_INT:
|
|
case TYPE_CODE_BOOL:
|
|
case TYPE_CODE_CHAR:
|
|
case TYPE_CODE_RANGE:
|
|
case TYPE_CODE_ENUM:
|
|
case TYPE_CODE_PTR:
|
|
{
|
|
/* integer or pointer type is passed in GAR.
|
|
If no GAR is available, it's passed on the stack.
|
|
When passed in registers or on the stack,
|
|
the unsigned integer scalars are zero-extended to GRLEN bits,
|
|
and the signed integer scalars are sign-extended. */
|
|
if (type->is_unsigned ())
|
|
{
|
|
ULONGEST data = extract_unsigned_integer (val, len, BFD_ENDIAN_LITTLE);
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, (gdb_byte *) &data);
|
|
else
|
|
pass_on_stack (regcache, (gdb_byte *) &data, len, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
LONGEST data = extract_signed_integer (val, len, BFD_ENDIAN_LITTLE);
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, (gdb_byte *) &data);
|
|
else
|
|
pass_on_stack (regcache, (gdb_byte *) &data, len, align, &addr);
|
|
}
|
|
}
|
|
break;
|
|
case TYPE_CODE_FLT:
|
|
if (len == 2 * regsize)
|
|
{
|
|
/* long double type is passed in a pair of GAR,
|
|
with the low-order GRLEN bits in the lower-numbered register
|
|
and the high-order GRLEN bits in the higher-numbered register.
|
|
If exactly one register is available,
|
|
the low-order GRLEN bits are passed in the register
|
|
and the high-order GRLEN bits are passed on the stack.
|
|
If no GAR is available, it's passed on the stack. */
|
|
if (gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else if (gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + regsize, len - regsize, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The other floating-point type is passed in FAR.
|
|
If no FAR is available, it's passed in GAR.
|
|
If no GAR is available, it's passed on the stack. */
|
|
if (far > 0)
|
|
pass_in_far (regcache, far--, val);
|
|
else if (gar > 0)
|
|
pass_in_gar (regcache, gar--, val);
|
|
else
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
break;
|
|
case TYPE_CODE_STRUCT:
|
|
{
|
|
fixed_point_members = 0;
|
|
floating_point_members = 0;
|
|
first_member_is_fixed_point = false;
|
|
compute_struct_member (type,
|
|
&fixed_point_members,
|
|
&floating_point_members,
|
|
&first_member_is_fixed_point);
|
|
|
|
if (len > 0 && len <= regsize)
|
|
{
|
|
/* The structure has only fixed-point members. */
|
|
if (fixed_point_members > 0 && floating_point_members == 0)
|
|
{
|
|
/* If there is an available GAR,
|
|
the structure is passed through the GAR by value passing;
|
|
If no GAR is available, it's passed on the stack. */
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, val);
|
|
else
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
/* The structure has only floating-point members. */
|
|
else if (fixed_point_members == 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one floating-point member.
|
|
The argument is passed in a FAR.
|
|
If no FAR is available, the value is passed in a GAR.
|
|
if no GAR is available, the value is passed on the stack. */
|
|
if (floating_point_members == 1)
|
|
{
|
|
if (far > 0)
|
|
pass_in_far (regcache, far--, val);
|
|
else if (gar > 0)
|
|
pass_in_gar (regcache, gar--, val);
|
|
else
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
/* The structure has two floating-point members.
|
|
The argument is passed in a pair of available FAR,
|
|
with the low-order float member bits in the lower-numbered FAR
|
|
and the high-order float member bits in the higher-numbered FAR.
|
|
If the number of available FAR is less than 2, it's passed in a GAR,
|
|
and passed on the stack if no GAR is available. */
|
|
else if (floating_point_members == 2)
|
|
{
|
|
if (far >= 2)
|
|
{
|
|
pass_in_far (regcache, far--, val);
|
|
pass_in_far (regcache, far--, val + align);
|
|
}
|
|
else if (gar > 0)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
}
|
|
/* The structure has both fixed-point and floating-point members. */
|
|
else if (fixed_point_members > 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one float member and multiple fixed-point members.
|
|
If there are available GAR, the structure is passed in a GAR,
|
|
and passed on the stack if no GAR is available. */
|
|
if (floating_point_members == 1 && fixed_point_members > 1)
|
|
{
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, val);
|
|
else
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
/* The structure has one float member and one fixed-point member.
|
|
If one FAR and one GAR are available,
|
|
the floating-point member of the structure is passed in the FAR,
|
|
and the fixed-point member of the structure is passed in the GAR.
|
|
If no floating-point register but one GAR is available, it's passed in GAR;
|
|
If no GAR is available, it's passed on the stack. */
|
|
else if (floating_point_members == 1 && fixed_point_members == 1)
|
|
{
|
|
if (far > 0 && gar > 0)
|
|
{
|
|
if (first_member_is_fixed_point == false)
|
|
{
|
|
pass_in_far (regcache, far--, val);
|
|
pass_in_gar (regcache, gar--, val + align);
|
|
}
|
|
else
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_far (regcache, far--, val + align);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, val);
|
|
else
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (len > regsize && len <= 2 * regsize)
|
|
{
|
|
/* The structure has only fixed-point members. */
|
|
if (fixed_point_members > 0 && floating_point_members == 0)
|
|
{
|
|
/* The argument is passed in a pair of available GAR,
|
|
with the low-order bits in the lower-numbered GAR
|
|
and the high-order bits in the higher-numbered GAR.
|
|
If only one GAR is available,
|
|
the low-order bits are in the GAR
|
|
and the high-order bits are on the stack,
|
|
and passed on the stack if no GAR is available. */
|
|
if (gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else if (gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + regsize, len - regsize, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
/* The structure has only floating-point members. */
|
|
else if (fixed_point_members == 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one long double member
|
|
or one double member and two adjacent float members
|
|
or 3-4 float members.
|
|
The argument is passed in a pair of available GAR,
|
|
with the low-order bits in the lower-numbered GAR
|
|
and the high-order bits in the higher-numbered GAR.
|
|
If only one GAR is available,
|
|
the low-order bits are in the GAR
|
|
and the high-order bits are on the stack,
|
|
and passed on the stack if no GAR is available. */
|
|
if ((len == 16 && floating_point_members == 1)
|
|
|| (len == 16 && floating_point_members == 3)
|
|
|| (len == 12 && floating_point_members == 3)
|
|
|| (len == 16 && floating_point_members == 4))
|
|
{
|
|
if (gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else if (gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + regsize, len - regsize, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
/* The structure has two double members
|
|
or one double member and one float member.
|
|
The argument is passed in a pair of available FAR,
|
|
with the low-order bits in the lower-numbered FAR
|
|
and the high-order bits in the higher-numbered FAR.
|
|
If no a pair of available FAR,
|
|
it's passed in a pair of available GAR,
|
|
with the low-order bits in the lower-numbered GAR
|
|
and the high-order bits in the higher-numbered GAR.
|
|
If only one GAR is available,
|
|
the low-order bits are in the GAR
|
|
and the high-order bits are on stack,
|
|
and passed on the stack if no GAR is available. */
|
|
else if ((len == 16 && floating_point_members == 2)
|
|
|| (len == 12 && floating_point_members == 2))
|
|
{
|
|
if (far >= 2)
|
|
{
|
|
pass_in_far (regcache, far--, val);
|
|
pass_in_far (regcache, far--, val + regsize);
|
|
}
|
|
else if (gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else if (gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + regsize, len - regsize, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
}
|
|
/* The structure has both fixed-point and floating-point members. */
|
|
else if (fixed_point_members > 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one floating-point member and one fixed-point member. */
|
|
if (floating_point_members == 1 && fixed_point_members == 1)
|
|
{
|
|
/* If one FAR and one GAR are available,
|
|
the floating-point member of the structure is passed in the FAR,
|
|
and the fixed-point member of the structure is passed in the GAR;
|
|
If no floating-point registers but two GARs are available,
|
|
it's passed in the two GARs;
|
|
If only one GAR is available,
|
|
the low-order bits are in the GAR
|
|
and the high-order bits are on the stack;
|
|
And it's passed on the stack if no GAR is available. */
|
|
if (far > 0 && gar > 0)
|
|
{
|
|
if (first_member_is_fixed_point == false)
|
|
{
|
|
pass_in_far (regcache, far--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_far (regcache, far--, val + regsize);
|
|
}
|
|
}
|
|
else if (far == 0 && gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else if (far == 0 && gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + regsize, len - regsize, align, &addr);
|
|
}
|
|
else if (far == 0 && gar == 0)
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The argument is passed in a pair of available GAR,
|
|
with the low-order bits in the lower-numbered GAR
|
|
and the high-order bits in the higher-numbered GAR.
|
|
If only one GAR is available,
|
|
the low-order bits are in the GAR
|
|
and the high-order bits are on the stack,
|
|
and passed on the stack if no GAR is available. */
|
|
if (gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else if (gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + regsize, len - regsize, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (len > 2 * regsize)
|
|
{
|
|
/* It's passed by reference and are replaced in the argument list with the address.
|
|
If there is an available GAR, the reference is passed in the GAR,
|
|
and passed on the stack if no GAR is available. */
|
|
sp = align_down (sp - len, 16);
|
|
write_memory (sp, val, len);
|
|
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, (const gdb_byte *) &sp);
|
|
else
|
|
pass_on_stack (regcache, (const gdb_byte*) &sp, len, regsize, &addr);
|
|
}
|
|
}
|
|
break;
|
|
case TYPE_CODE_UNION:
|
|
/* Union is passed in GAR or stack. */
|
|
if (len > 0 && len <= regsize)
|
|
{
|
|
/* The argument is passed in a GAR,
|
|
or on the stack by value if no GAR is available. */
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, val);
|
|
else
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
else if (len > regsize && len <= 2 * regsize)
|
|
{
|
|
/* The argument is passed in a pair of available GAR,
|
|
with the low-order bits in the lower-numbered GAR
|
|
and the high-order bits in the higher-numbered GAR.
|
|
If only one GAR is available,
|
|
the low-order bits are in the GAR
|
|
and the high-order bits are on the stack.
|
|
The arguments are passed on the stack when no GAR is available. */
|
|
if (gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + regsize);
|
|
}
|
|
else if (gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + regsize, len - regsize, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
else if (len > 2 * regsize)
|
|
{
|
|
/* It's passed by reference and are replaced in the argument list with the address.
|
|
If there is an available GAR, the reference is passed in the GAR,
|
|
and passed on the stack if no GAR is available. */
|
|
sp = align_down (sp - len, 16);
|
|
write_memory (sp, val, len);
|
|
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, (const gdb_byte *) &sp);
|
|
else
|
|
pass_on_stack (regcache, (const gdb_byte*) &sp, len, regsize, &addr);
|
|
}
|
|
break;
|
|
case TYPE_CODE_COMPLEX:
|
|
{
|
|
struct type *target_type = check_typedef (TYPE_TARGET_TYPE (type));
|
|
size_t target_len = TYPE_LENGTH (target_type);
|
|
|
|
if (target_len < regsize)
|
|
{
|
|
/* The complex with two float members
|
|
is passed in a pair of available FAR,
|
|
with the low-order float member bits in the lower-numbered FAR
|
|
and the high-order float member bits in the higher-numbered FAR.
|
|
If the number of available FAR is less than 2, it's passed in a GAR,
|
|
and passed on the stack if no GAR is available. */
|
|
if (far >= 2)
|
|
{
|
|
pass_in_far (regcache, far--, val);
|
|
pass_in_far (regcache, far--, val + align);
|
|
}
|
|
else if (gar > 0)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
else if (target_len == regsize)
|
|
{
|
|
/* The complex with two double members
|
|
is passed in a pair of available FAR,
|
|
with the low-order bits in the lower-numbered FAR
|
|
and the high-order bits in the higher-numbered FAR.
|
|
If no a pair of available FAR,
|
|
it's passed in a pair of available GAR,
|
|
with the low-order bits in the lower-numbered GAR
|
|
and the high-order bits in the higher-numbered GAR.
|
|
If only one GAR is available,
|
|
the low-order bits are in the GAR
|
|
and the high-order bits are on stack,
|
|
and passed on the stack if no GAR is available. */
|
|
{
|
|
if (far >= 2)
|
|
{
|
|
pass_in_far (regcache, far--, val);
|
|
pass_in_far (regcache, far--, val + align);
|
|
}
|
|
else if (gar >= 2)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_in_gar (regcache, gar--, val + align);
|
|
}
|
|
else if (gar == 1)
|
|
{
|
|
pass_in_gar (regcache, gar--, val);
|
|
pass_on_stack (regcache, val + align, len - align, align, &addr);
|
|
}
|
|
else
|
|
{
|
|
pass_on_stack (regcache, val, len, align, &addr);
|
|
}
|
|
}
|
|
}
|
|
else if (target_len == 2 * regsize)
|
|
{
|
|
/* The complex with two long double members
|
|
is passed by reference and are replaced in the argument list with the address.
|
|
If there is an available GAR, the reference is passed in the GAR,
|
|
and passed on the stack if no GAR is available. */
|
|
sp = align_down (sp - len, 16);
|
|
write_memory (sp, val, len);
|
|
|
|
if (gar > 0)
|
|
pass_in_gar (regcache, gar--, (const gdb_byte *) &sp);
|
|
else
|
|
pass_on_stack (regcache, (const gdb_byte*) &sp, regsize, regsize, &addr);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (addr > buf)
|
|
{
|
|
sp -= addr - buf;
|
|
sp = align_down (sp, 16);
|
|
write_memory (sp, buf, addr - buf);
|
|
}
|
|
|
|
regcache_cooked_write_unsigned (regcache, LOONGARCH_RA_REGNUM, bp_addr);
|
|
regcache_cooked_write_unsigned (regcache, LOONGARCH_SP_REGNUM, sp);
|
|
|
|
return sp;
|
|
}
|
|
|
|
/* Partial transfer of a cooked register. */
|
|
|
|
static void
|
|
loongarch_xfer_reg (struct regcache *regcache,
|
|
int regnum, int len, gdb_byte *readbuf,
|
|
const gdb_byte *writebuf, size_t offset)
|
|
{
|
|
if (readbuf)
|
|
regcache->cooked_read_part (regnum, 0, len, readbuf + offset);
|
|
if (writebuf)
|
|
regcache->cooked_write_part (regnum, 0, len, writebuf + offset);
|
|
}
|
|
|
|
/* Implement the return_value gdbarch method. */
|
|
|
|
static enum return_value_convention
|
|
loongarch_return_value (struct gdbarch *gdbarch, struct value *function,
|
|
struct type *type, struct regcache *regcache,
|
|
gdb_byte *readbuf, const gdb_byte *writebuf)
|
|
{
|
|
int regsize = register_size (gdbarch, 0);
|
|
enum type_code code = type->code ();
|
|
size_t len = TYPE_LENGTH (type);
|
|
unsigned int fixed_point_members;
|
|
unsigned int floating_point_members;
|
|
bool first_member_is_fixed_point;
|
|
int a0 = LOONGARCH_A0_REGNUM;
|
|
int a1 = LOONGARCH_A0_REGNUM + 1;
|
|
int f0 = LOONGARCH_FIRST_FP_REGNUM;
|
|
int f1 = LOONGARCH_FIRST_FP_REGNUM + 1;
|
|
|
|
if (len > 2 * regsize)
|
|
return RETURN_VALUE_STRUCT_CONVENTION;
|
|
|
|
switch (code)
|
|
{
|
|
case TYPE_CODE_INT:
|
|
case TYPE_CODE_BOOL:
|
|
case TYPE_CODE_CHAR:
|
|
case TYPE_CODE_RANGE:
|
|
case TYPE_CODE_ENUM:
|
|
case TYPE_CODE_PTR:
|
|
{
|
|
/* integer or pointer type.
|
|
The return value is passed in a0,
|
|
the unsigned integer scalars are zero-extended to GRLEN bits,
|
|
and the signed integer scalars are sign-extended. */
|
|
if (writebuf)
|
|
{
|
|
gdb_byte buf[regsize];
|
|
if (type->is_unsigned ())
|
|
{
|
|
ULONGEST data = extract_unsigned_integer (writebuf, len, BFD_ENDIAN_LITTLE);
|
|
store_unsigned_integer (buf, regsize, BFD_ENDIAN_LITTLE, data);
|
|
}
|
|
else
|
|
{
|
|
LONGEST data = extract_signed_integer (writebuf, len, BFD_ENDIAN_LITTLE);
|
|
store_signed_integer (buf, regsize, BFD_ENDIAN_LITTLE, data);
|
|
}
|
|
loongarch_xfer_reg (regcache, a0, regsize, nullptr, buf, 0);
|
|
}
|
|
else
|
|
loongarch_xfer_reg (regcache, a0, len, readbuf, nullptr, 0);
|
|
}
|
|
break;
|
|
case TYPE_CODE_FLT:
|
|
/* long double type.
|
|
The return value is passed in a0 and a1. */
|
|
if (len == 2 * regsize)
|
|
{
|
|
loongarch_xfer_reg (regcache, a0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, a1, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
/* float or double type.
|
|
The return value is passed in f0. */
|
|
else
|
|
{
|
|
loongarch_xfer_reg (regcache, f0, len, readbuf, writebuf, 0);
|
|
}
|
|
break;
|
|
case TYPE_CODE_STRUCT:
|
|
{
|
|
fixed_point_members = 0;
|
|
floating_point_members = 0;
|
|
first_member_is_fixed_point = false;
|
|
compute_struct_member (type,
|
|
&fixed_point_members,
|
|
&floating_point_members,
|
|
&first_member_is_fixed_point);
|
|
|
|
if (len > 0 && len <= regsize)
|
|
{
|
|
/* The structure has only fixed-point members. */
|
|
if (fixed_point_members > 0 && floating_point_members == 0)
|
|
{
|
|
/* The return value is passed in a0. */
|
|
loongarch_xfer_reg (regcache, a0, len, readbuf, writebuf, 0);
|
|
}
|
|
/* The structure has only floating-point members. */
|
|
else if (fixed_point_members == 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one floating-point member.
|
|
The return value is passed in f0. */
|
|
if (floating_point_members == 1)
|
|
{
|
|
loongarch_xfer_reg (regcache, f0, len, readbuf, writebuf, 0);
|
|
}
|
|
/* The structure has two floating-point members.
|
|
The return value is passed in f0 and f1. */
|
|
else if (floating_point_members == 2)
|
|
{
|
|
loongarch_xfer_reg (regcache, f0, len / 2, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, f1, len / 2, readbuf, writebuf, len / 2);
|
|
}
|
|
}
|
|
/* The structure has both fixed-point and floating-point members. */
|
|
else if (fixed_point_members > 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one float member and multiple fixed-point members.
|
|
The return value is passed in a0. */
|
|
if (floating_point_members == 1 && fixed_point_members > 1)
|
|
{
|
|
loongarch_xfer_reg (regcache, a0, len, readbuf, writebuf, 0);
|
|
}
|
|
/* The structure has one float member and one fixed-point member. */
|
|
else if (floating_point_members == 1 && fixed_point_members == 1)
|
|
{
|
|
/* The return value is passed in f0 and a0 if the first member is floating-point. */
|
|
if (first_member_is_fixed_point == false)
|
|
{
|
|
loongarch_xfer_reg (regcache, f0, regsize / 2, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, a0, regsize / 2, readbuf, writebuf, regsize / 2);
|
|
}
|
|
/* The return value is passed in a0 and f0 if the first member is fixed-point. */
|
|
else
|
|
{
|
|
loongarch_xfer_reg (regcache, a0, regsize / 2, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, f0, regsize / 2, readbuf, writebuf, regsize / 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (len > regsize && len <= 2 * regsize)
|
|
{
|
|
/* The structure has only fixed-point members. */
|
|
if (fixed_point_members > 0 && floating_point_members == 0)
|
|
{
|
|
/* The return value is passed in a0 and a1. */
|
|
loongarch_xfer_reg (regcache, a0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, a1, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
/* The structure has only floating-point members. */
|
|
else if (fixed_point_members == 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one long double member
|
|
or one double member and two adjacent float members
|
|
or 3-4 float members.
|
|
The return value is passed in a0 and a1. */
|
|
if ((len == 16 && floating_point_members == 1)
|
|
|| (len == 16 && floating_point_members == 3)
|
|
|| (len == 12 && floating_point_members == 3)
|
|
|| (len == 16 && floating_point_members == 4))
|
|
{
|
|
loongarch_xfer_reg (regcache, a0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, a1, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
/* The structure has two double members
|
|
or one double member and one float member.
|
|
The return value is passed in f0 and f1. */
|
|
else if ((len == 16 && floating_point_members == 2)
|
|
|| (len == 12 && floating_point_members == 2))
|
|
{
|
|
loongarch_xfer_reg (regcache, f0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, f1, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
}
|
|
/* The structure has both fixed-point and floating-point members. */
|
|
else if (fixed_point_members > 0 && floating_point_members > 0)
|
|
{
|
|
/* The structure has one floating-point member and one fixed-point member. */
|
|
if (floating_point_members == 1 && fixed_point_members == 1)
|
|
{
|
|
/* The return value is passed in f0 and a0 if the first member is floating-point. */
|
|
if (first_member_is_fixed_point == false)
|
|
{
|
|
loongarch_xfer_reg (regcache, f0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, a0, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
/* The return value is passed in a0 and f0 if the first member is fixed-point. */
|
|
else
|
|
{
|
|
loongarch_xfer_reg (regcache, a0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, f0, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The return value is passed in a0 and a1. */
|
|
loongarch_xfer_reg (regcache, a0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, a1, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TYPE_CODE_UNION:
|
|
if (len > 0 && len <= regsize)
|
|
{
|
|
/* The return value is passed in a0. */
|
|
loongarch_xfer_reg (regcache, a0, len, readbuf, writebuf, 0);
|
|
}
|
|
else if (len > regsize && len <= 2 * regsize)
|
|
{
|
|
/* The return value is passed in a0 and a1. */
|
|
loongarch_xfer_reg (regcache, a0, regsize, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, a1, len - regsize, readbuf, writebuf, regsize);
|
|
}
|
|
break;
|
|
case TYPE_CODE_COMPLEX:
|
|
{
|
|
/* The return value is passed in f0 and f1. */
|
|
loongarch_xfer_reg (regcache, f0, len / 2, readbuf, writebuf, 0);
|
|
loongarch_xfer_reg (regcache, f1, len / 2, readbuf, writebuf, len / 2);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return RETURN_VALUE_REGISTER_CONVENTION;
|
|
}
|
|
|
|
/* Implement the dwarf2_reg_to_regnum gdbarch method. */
|
|
|
|
static int
|
|
loongarch_dwarf2_reg_to_regnum (struct gdbarch *gdbarch, int regnum)
|
|
{
|
|
if (regnum >= 0 && regnum < 32)
|
|
return regnum;
|
|
else if (regnum >= 32 && regnum < 66)
|
|
return LOONGARCH_FIRST_FP_REGNUM + regnum - 32;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
static constexpr gdb_byte loongarch_default_breakpoint[] = {0x05, 0x00, 0x2a, 0x00};
|
|
typedef BP_MANIPULATION (loongarch_default_breakpoint) loongarch_breakpoint;
|
|
|
|
/* Extract a set of required target features out of ABFD. If ABFD is nullptr
|
|
then a LOONGARCH_GDBARCH_FEATURES is returned in its default state. */
|
|
|
|
static struct loongarch_gdbarch_features
|
|
loongarch_features_from_bfd (const bfd *abfd)
|
|
{
|
|
struct loongarch_gdbarch_features features;
|
|
|
|
/* Now try to improve on the defaults by looking at the binary we are
|
|
going to execute. We assume the user knows what they are doing and
|
|
that the target will match the binary. Remember, this code path is
|
|
only used at all if the target hasn't given us a description, so this
|
|
is really a last ditched effort to do something sane before giving
|
|
up. */
|
|
if (abfd != nullptr && bfd_get_flavour (abfd) == bfd_target_elf_flavour)
|
|
{
|
|
unsigned char eclass = elf_elfheader (abfd)->e_ident[EI_CLASS];
|
|
int e_flags = elf_elfheader (abfd)->e_flags;
|
|
|
|
if (eclass == ELFCLASS32)
|
|
features.xlen = 4;
|
|
else if (eclass == ELFCLASS64)
|
|
features.xlen = 8;
|
|
else
|
|
internal_error (__FILE__, __LINE__,
|
|
_("unknown ELF header class %d"), eclass);
|
|
|
|
if (EF_LOONGARCH_IS_SINGLE_FLOAT (e_flags))
|
|
features.fputype = SINGLE_FLOAT;
|
|
else if (EF_LOONGARCH_IS_DOUBLE_FLOAT (e_flags))
|
|
features.fputype = DOUBLE_FLOAT;
|
|
}
|
|
|
|
return features;
|
|
}
|
|
|
|
/* Find a suitable default target description. Use the contents of INFO,
|
|
specifically the bfd object being executed, to guide the selection of a
|
|
suitable default target description. */
|
|
|
|
static const struct target_desc *
|
|
loongarch_find_default_target_description (const struct gdbarch_info info)
|
|
{
|
|
/* Extract desired feature set from INFO. */
|
|
struct loongarch_gdbarch_features features
|
|
= loongarch_features_from_bfd (info.abfd);
|
|
|
|
/* If the XLEN field is still 0 then we got nothing useful from INFO.BFD,
|
|
maybe there was no bfd object. In this case we fall back to a minimal
|
|
useful target, the x-register size is selected based on the architecture
|
|
from INFO. */
|
|
if (features.xlen == 0)
|
|
features.xlen = info.bfd_arch_info->bits_per_address == 32 ? 4 : 8;
|
|
|
|
/* If the FPUTYPE field is still 0 then we got nothing useful from INFO.BFD,
|
|
maybe there was no bfd object. In this case we fall back to a usual useful
|
|
target with double float. */
|
|
if (features.fputype == 0)
|
|
features.fputype = DOUBLE_FLOAT;
|
|
|
|
/* Now build a target description based on the feature set. */
|
|
return loongarch_lookup_target_description (features);
|
|
}
|
|
|
|
/* Initialize the current architecture based on INFO */
|
|
|
|
static struct gdbarch *
|
|
loongarch_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
|
|
{
|
|
size_t regnum = 0;
|
|
struct loongarch_gdbarch_features features;
|
|
tdesc_arch_data_up tdesc_data = tdesc_data_alloc ();
|
|
loongarch_gdbarch_tdep *tdep = new loongarch_gdbarch_tdep;
|
|
const struct target_desc *tdesc = info.target_desc;
|
|
|
|
/* Ensure we always have a target description. */
|
|
if (!tdesc_has_registers (tdesc))
|
|
tdesc = loongarch_find_default_target_description (info);
|
|
|
|
const struct tdesc_feature *feature_cpu
|
|
= tdesc_find_feature (tdesc, "org.gnu.gdb.loongarch.base");
|
|
if (feature_cpu == nullptr)
|
|
return nullptr;
|
|
|
|
|
|
/* Validate the description provides the mandatory base registers
|
|
and allocate their numbers. */
|
|
bool valid_p = true;
|
|
for (int i = 0; i < 32; i++)
|
|
valid_p &= tdesc_numbered_register (feature_cpu, tdesc_data.get (), regnum++,
|
|
loongarch_r_normal_name[i] + 1);
|
|
valid_p &= tdesc_numbered_register (feature_cpu, tdesc_data.get (), regnum++, "orig_a0");
|
|
valid_p &= tdesc_numbered_register (feature_cpu, tdesc_data.get (), regnum++, "pc");
|
|
valid_p &= tdesc_numbered_register (feature_cpu, tdesc_data.get (), regnum++, "badv");
|
|
if (!valid_p)
|
|
return nullptr;
|
|
|
|
const struct tdesc_feature *feature_fpu
|
|
= tdesc_find_feature (tdesc, "org.gnu.gdb.loongarch.fpu");
|
|
if (feature_fpu == nullptr)
|
|
return nullptr;
|
|
|
|
/* Validate the description provides the fpu registers and
|
|
allocate their numbers. */
|
|
regnum = LOONGARCH_FIRST_FP_REGNUM;
|
|
for (int i = 0; i < LOONGARCH_LINUX_NUM_FPREGSET; i++)
|
|
valid_p &= tdesc_numbered_register (feature_fpu, tdesc_data.get (), regnum++,
|
|
loongarch_f_normal_name[i] + 1);
|
|
for (int i = 0; i < LOONGARCH_LINUX_NUM_FCC; i++)
|
|
valid_p &= tdesc_numbered_register (feature_fpu, tdesc_data.get (), regnum++,
|
|
loongarch_c_normal_name[i] + 1);
|
|
valid_p &= tdesc_numbered_register (feature_fpu, tdesc_data.get (), regnum++, "fcsr");
|
|
if (!valid_p)
|
|
return nullptr;
|
|
|
|
/* LoongArch code is always little-endian. */
|
|
info.byte_order_for_code = BFD_ENDIAN_LITTLE;
|
|
|
|
/* Have a look at what the supplied (if any) bfd object requires of the
|
|
target, then check that this matches with what the target is
|
|
providing. */
|
|
struct loongarch_gdbarch_features abi_features
|
|
= loongarch_features_from_bfd (info.abfd);
|
|
|
|
/* If the ABI_FEATURES xlen or fputype is 0 then this indicates we got
|
|
no useful abi features from the INFO object. In this case we just
|
|
treat the hardware features as defining the abi. */
|
|
if (abi_features.xlen == 0)
|
|
{
|
|
int xlen_bitsize = tdesc_register_bitsize (feature_cpu, "pc");
|
|
features.xlen = (xlen_bitsize / 8);
|
|
features.fputype = abi_features.fputype;
|
|
abi_features = features;
|
|
}
|
|
if (abi_features.fputype == 0)
|
|
{
|
|
features.xlen = abi_features.xlen;
|
|
features.fputype = DOUBLE_FLOAT;
|
|
abi_features = features;
|
|
}
|
|
|
|
/* Find a candidate among the list of pre-declared architectures. */
|
|
for (arches = gdbarch_list_lookup_by_info (arches, &info);
|
|
arches != nullptr;
|
|
arches = gdbarch_list_lookup_by_info (arches->next, &info))
|
|
{
|
|
/* Check that the feature set of the ARCHES matches the feature set
|
|
we are looking for. If it doesn't then we can't reuse this
|
|
gdbarch. */
|
|
loongarch_gdbarch_tdep *candidate_tdep
|
|
= gdbarch_tdep<loongarch_gdbarch_tdep> (arches->gdbarch);
|
|
|
|
if (candidate_tdep->abi_features != abi_features)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if (arches != nullptr)
|
|
return arches->gdbarch;
|
|
|
|
/* None found, so create a new architecture from the information provided. */
|
|
struct gdbarch *gdbarch = gdbarch_alloc (&info, tdep);
|
|
tdep->abi_features = abi_features;
|
|
|
|
/* Target data types. */
|
|
set_gdbarch_short_bit (gdbarch, 16);
|
|
set_gdbarch_int_bit (gdbarch, 32);
|
|
set_gdbarch_long_bit (gdbarch, info.bfd_arch_info->bits_per_address);
|
|
set_gdbarch_long_long_bit (gdbarch, 64);
|
|
set_gdbarch_float_bit (gdbarch, 32);
|
|
set_gdbarch_double_bit (gdbarch, 64);
|
|
set_gdbarch_long_double_bit (gdbarch, 128);
|
|
set_gdbarch_long_double_format (gdbarch, floatformats_ieee_quad);
|
|
set_gdbarch_ptr_bit (gdbarch, info.bfd_arch_info->bits_per_address);
|
|
set_gdbarch_char_signed (gdbarch, 0);
|
|
|
|
info.target_desc = tdesc;
|
|
info.tdesc_data = tdesc_data.get ();
|
|
|
|
/* Information about registers. */
|
|
set_gdbarch_num_regs (gdbarch, regnum);
|
|
set_gdbarch_sp_regnum (gdbarch, LOONGARCH_SP_REGNUM);
|
|
set_gdbarch_pc_regnum (gdbarch, LOONGARCH_PC_REGNUM);
|
|
|
|
/* Finalise the target description registers. */
|
|
tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
|
|
|
|
/* Functions handling dummy frames. */
|
|
set_gdbarch_push_dummy_call (gdbarch, loongarch_push_dummy_call);
|
|
|
|
/* Return value info */
|
|
set_gdbarch_return_value (gdbarch, loongarch_return_value);
|
|
|
|
/* Advance PC across function entry code. */
|
|
set_gdbarch_skip_prologue (gdbarch, loongarch_skip_prologue);
|
|
|
|
/* Stack grows downward. */
|
|
set_gdbarch_inner_than (gdbarch, core_addr_lessthan);
|
|
|
|
/* Frame info. */
|
|
set_gdbarch_frame_align (gdbarch, loongarch_frame_align);
|
|
|
|
/* Breakpoint manipulation. */
|
|
set_gdbarch_software_single_step (gdbarch, loongarch_software_single_step);
|
|
set_gdbarch_breakpoint_kind_from_pc (gdbarch, loongarch_breakpoint::kind_from_pc);
|
|
set_gdbarch_sw_breakpoint_from_kind (gdbarch, loongarch_breakpoint::bp_from_kind);
|
|
|
|
/* Frame unwinders. Use DWARF debug info if available, otherwise use our own unwinder. */
|
|
set_gdbarch_dwarf2_reg_to_regnum (gdbarch, loongarch_dwarf2_reg_to_regnum);
|
|
dwarf2_append_unwinders (gdbarch);
|
|
frame_unwind_append_unwinder (gdbarch, &loongarch_frame_unwind);
|
|
|
|
/* Hook in OS ABI-specific overrides, if they have been registered. */
|
|
gdbarch_init_osabi (info, gdbarch);
|
|
|
|
return gdbarch;
|
|
}
|
|
|
|
void _initialize_loongarch_tdep ();
|
|
void
|
|
_initialize_loongarch_tdep ()
|
|
{
|
|
gdbarch_register (bfd_arch_loongarch, loongarch_gdbarch_init, nullptr);
|
|
}
|