gdb: LoongArch: Implement the push_dummy_call gdbarch method

According to "Procedure Calling Convention" in "LoongArch ELF ABI
specification" [1], implement the push_dummy_call gdbarch method
as clear as possible.

[1] https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html#_procedure_calling_convention

Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
This commit is contained in:
Tiezhu Yang
2022-07-07 14:33:19 +08:00
parent 045f385d9a
commit 88de583569
2 changed files with 598 additions and 0 deletions

View File

@ -452,6 +452,599 @@ static const struct frame_unwind loongarch_frame_unwind = {
/*.prev_arch =*/nullptr,
};
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);
}
static void
pass_in_far (struct regcache *regcache, unsigned int far, const gdb_byte *val)
{
unsigned int regnum = LOONGARCH_ARG_REGNUM - far + LOONGARCH_BADV_REGNUM + 1;
regcache->cooked_write (regnum, val);
}
static __attribute__((aligned(16))) gdb_byte buf[1024] = { 0 };
static gdb_byte *addr = buf;
static void
pass_on_stack (struct regcache *regcache, const gdb_byte *val, size_t len, int align)
{
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;
}
static unsigned int fixed_point_members = 0;
static unsigned int floating_point_members = 0;
static bool first_member_is_fixed_point = false;
static void
compute_struct_member (struct type *type)
{
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);
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;
if (return_method != return_method_normal)
pass_in_gar (regcache, gar--, (gdb_byte *) &struct_addr);
addr = buf;
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, its 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);
}
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);
}
}
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, its 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);
}
else
{
pass_on_stack (regcache, val, len, align);
}
}
else
{
/* The other floating-point type is passed in FAR.
* If no FAR is available, its passed in GAR.
* If no GAR is available, its 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);
}
break;
case TYPE_CODE_STRUCT:
{
fixed_point_members = 0;
floating_point_members = 0;
first_member_is_fixed_point = false;
compute_struct_member (type);
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, its passed on the stack. */
if (gar > 0)
pass_in_gar (regcache, gar--, val);
else
pass_on_stack (regcache, val, len, align);
}
/* The structure has only floating-point members. */
else if (fixed_point_members == 0 && floating_point_members > 0)
{
/* 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);
}
/* 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, its 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);
}
}
}
/* The structure has both fixed-point and floating-point members. */
else if (fixed_point_members > 0 && floating_point_members > 0)
{
/* 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);
}
/* One float member and only 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, its passed in GAR;
* If no GAR is available, its 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);
}
}
}
}
else if (len > regsize && len <= 2 * regsize)
{
/* 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);
}
else
{
pass_on_stack (regcache, val, len, align);
}
}
/* 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);
}
else
{
pass_on_stack (regcache, val, len, align);
}
}
/* The structure 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,
* its 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.
* A structure with one double member and one float member is same. */
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);
}
else
{
pass_on_stack (regcache, val, len, align);
}
}
}
/* 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 only 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,
* its 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 its 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);
}
else if (far == 0 && gar == 0)
{
pass_on_stack (regcache, val, len, align);
}
}
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);
}
else
{
pass_on_stack (regcache, val, len, align);
}
}
}
}
else if (len > 2 * regsize)
{
/* Its 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);
}
}
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);
}
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);
}
else
{
pass_on_stack (regcache, val, len, align);
}
}
else if (len > 2 * regsize)
{
/* Its 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);
}
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, its 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);
}
}
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,
* its 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);
}
else
{
pass_on_stack (regcache, val, len, align);
}
}
}
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);
}
}
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;
}
/* Implement the return_value gdbarch method. */
static enum return_value_convention
@ -644,6 +1237,9 @@ loongarch_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
/* 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);

View File

@ -39,6 +39,8 @@ enum
LOONGARCH_PC_REGNUM = 33, /* Program Counter. */
LOONGARCH_BADV_REGNUM = 34, /* Bad Vaddr for Addressing Exception. */
LOONGARCH_LINUX_NUM_GREGSET = 45, /* 32 GPR, ORIG_A0, PC, BADV, RESERVED 10. */
LOONGARCH_ARG_REGNUM = 8, /* r4-r11: general-purpose argument registers.
f0-f7: floating-point argument registers. */
};
/* Register set definitions. */