/*
* This file is part of ltrace.
* Copyright (C) 2013 Petr Machata, Red Hat Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <sys/ptrace.h>
#include <asm/ptrace.h>
#include <assert.h>
#include <elf.h>
#include <libelf.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "backend.h"
#include "fetch.h"
#include "library.h"
#include "ltrace-elf.h"
#include "proc.h"
#include "ptrace.h"
#include "regs.h"
#include "type.h"
#include "value.h"
static int
get_hardfp(uint64_t abi_vfp_args)
{
if (abi_vfp_args == 2)
fprintf(stderr,
"Tag_ABI_VFP_args value 2 (tool chain-specific "
"conventions) not supported.\n");
return abi_vfp_args == 1;
}
int
arch_elf_init(struct ltelf *lte, struct library *lib)
{
/* Nothing in this section is strictly critical. It's not
* that much of a deal if we fail to guess right whether the
* ABI is softfp or hardfp. */
unsigned hardfp = 0;
Elf_Scn *scn;
Elf_Data *data;
GElf_Shdr shdr;
if (elf_get_section_type(lte, SHT_ARM_ATTRIBUTES, &scn, &shdr) < 0
|| (scn != NULL && (data = elf_loaddata(scn, &shdr)) == NULL)) {
fprintf(stderr,
"Error when obtaining ARM attribute section: %s\n",
elf_errmsg(-1));
goto done;
} else if (scn != NULL && data != NULL) {
GElf_Xword offset = 0;
uint8_t version;
if (elf_read_next_u8(data, &offset, &version) < 0) {
goto done;
} else if (version != 'A') {
fprintf(stderr, "Unsupported ARM attribute section "
"version %d ('%c').\n", version, version);
goto done;
}
do {
const char signature[] = "aeabi";
/* N.B. LEN is including the length field
* itself. */
uint32_t sec_len;
if (elf_read_u32(data, offset, &sec_len) < 0
|| !elf_can_read_next(data, offset, sec_len)) {
goto done;
}
const GElf_Xword next_offset = offset + sec_len;
offset += 4;
if (sec_len < 4 + sizeof signature
|| strcmp(signature, data->d_buf + offset) != 0)
goto skip;
offset += sizeof signature;
const GElf_Xword offset0 = offset;
uint64_t tag;
uint32_t sub_len;
if (elf_read_next_uleb128(data, &offset, &tag) < 0
|| elf_read_next_u32(data, &offset, &sub_len) < 0
|| !elf_can_read_next(data, offset0, sub_len))
goto done;
if (tag != 1)
/* IHI0045D_ABI_addenda: "section and
* symbol attributes are deprecated
* [...] consumers are permitted to
* ignore them." */
goto skip;
while (offset < offset0 + sub_len) {
if (elf_read_next_uleb128(data,
&offset, &tag) < 0)
goto done;
switch (tag) {
uint64_t v;
case 6: /* Tag_CPU_arch */
case 7: /* Tag_CPU_arch_profile */
case 8: /* Tag_ARM_ISA_use */
case 9: /* Tag_THUMB_ISA_use */
case 10: /* Tag_FP_arch */
case 11: /* Tag_WMMX_arch */
case 12: /* Tag_Advanced_SIMD_arch */
case 13: /* Tag_PCS_config */
case 14: /* Tag_ABI_PCS_R9_use */
case 15: /* Tag_ABI_PCS_RW_data */
case 16: /* Tag_ABI_PCS_RO_data */
case 17: /* Tag_ABI_PCS_GOT_use */
case 18: /* Tag_ABI_PCS_wchar_t */
case 19: /* Tag_ABI_FP_rounding */
case 20: /* Tag_ABI_FP_denormal */
case 21: /* Tag_ABI_FP_exceptions */
case 22: /* Tag_ABI_FP_user_exceptions */
case 23: /* Tag_ABI_FP_number_model */
case 24: /* Tag_ABI_align_needed */
case 25: /* Tag_ABI_align_preserved */
case 26: /* Tag_ABI_enum_size */
case 27: /* Tag_ABI_HardFP_use */
case 28: /* Tag_ABI_VFP_args */
case 29: /* Tag_ABI_WMMX_args */
case 30: /* Tag_ABI_optimization_goals */
case 31: /* Tag_ABI_FP_optimization_goals */
case 32: /* Tag_compatibility */
case 34: /* Tag_CPU_unaligned_access */
case 36: /* Tag_FP_HP_extension */
case 38: /* Tag_ABI_FP_16bit_format */
case 42: /* Tag_MPextension_use */
case 70: /* Tag_MPextension_use as well */
case 44: /* Tag_DIV_use */
case 64: /* Tag_nodefaults */
case 66: /* Tag_T2EE_use */
case 68: /* Tag_Virtualization_use */
uleb128:
if (elf_read_next_uleb128
(data, &offset, &v) < 0)
goto done;
if (tag == 28)
hardfp = get_hardfp(v);
if (tag != 32)
continue;
/* Tag 32 has two arguments,
* fall through. */
case 4: /* Tag_CPU_raw_name */
case 5: /* Tag_CPU_name */
case 65: /* Tag_also_compatible_with */
case 67: /* Tag_conformance */
ntbs:
offset += strlen(data->d_buf
+ offset) + 1;
continue;
}
/* Handle unknown tags in a generic
* manner, if possible. */
if (tag <= 32) {
fprintf(stderr,
"Unknown tag %lld "
"at offset %#llx "
"of ARM attribute section.",
tag, offset);
goto skip;
} else if (tag % 2 == 0) {
goto uleb128;
} else {
goto ntbs;
}
}
skip:
offset = next_offset;
} while (elf_can_read_next(data, offset, 1));
}
done:
lib->arch.hardfp = hardfp;
return 0;
}
void
arch_elf_destroy(struct ltelf *lte)
{
}
int
arch_library_init(struct library *lib)
{
return 0;
}
void
arch_library_destroy(struct library *lib)
{
}
int
arch_library_clone(struct library *retp, struct library *lib)
{
retp->arch = lib->arch;
return 0;
}
enum {
/* How many (double) VFP registers the AAPCS uses for
* parameter passing. */
NUM_VFP_REGS = 8,
};
struct fetch_context {
struct pt_regs regs;
struct {
union {
double d[32];
float s[64];
};
uint32_t fpscr;
} fpregs;
/* VFP register allocation. ALLOC.S tracks whether the
* corresponding FPREGS.S register is taken, ALLOC.D the same
* for FPREGS.D. We only track 8 (16) registers, because
* that's what the ABI uses for parameter passing. */
union {
int16_t d[NUM_VFP_REGS];
int8_t s[NUM_VFP_REGS * 2];
} alloc;
unsigned ncrn;
arch_addr_t sp;
arch_addr_t nsaa;
arch_addr_t ret_struct;
bool hardfp:1;
bool in_varargs:1;
};
static int
fetch_register_banks(struct process *proc, struct fetch_context *context)
{
if (ptrace(PTRACE_GETREGS, proc->pid, NULL, &context->regs) == -1)
return -1;
if (context->hardfp
&& ptrace(PTRACE_GETVFPREGS, proc->pid,
NULL, &context->fpregs) == -1)
return -1;
context->ncrn = 0;
context->nsaa = context->sp = get_stack_pointer(proc);
memset(&context->alloc, 0, sizeof(context->alloc));
return 0;
}
struct fetch_context *
arch_fetch_arg_init(enum tof type, struct process *proc,
struct arg_type_info *ret_info)
{
struct fetch_context *context = malloc(sizeof(*context));
{
struct process *mainp = proc;
while (mainp->libraries == NULL && mainp->parent != NULL)
mainp = mainp->parent;
context->hardfp = mainp->libraries->arch.hardfp;
}
if (context == NULL
|| fetch_register_banks(proc, context) < 0) {
free(context);
return NULL;
}
if (ret_info->type == ARGTYPE_STRUCT
|| ret_info->type == ARGTYPE_ARRAY) {
size_t sz = type_sizeof(proc, ret_info);
assert(sz != (size_t)-1);
if (sz > 4) {
/* XXX double cast */
context->ret_struct
= (arch_addr_t)context->regs.uregs[0];
context->ncrn++;
}
}
return context;
}
struct fetch_context *
arch_fetch_arg_clone(struct process *proc,
struct fetch_context *context)
{
struct fetch_context *clone = malloc(sizeof(*context));
if (clone == NULL)
return NULL;
*clone = *context;
return clone;
}
/* 0 is success, 1 is failure, negative value is an error. */
static int
pass_in_vfp(struct fetch_context *ctx, struct process *proc,
enum arg_type type, size_t count, struct value *valuep)
{
assert(type == ARGTYPE_FLOAT || type == ARGTYPE_DOUBLE);
unsigned max = type == ARGTYPE_DOUBLE ? NUM_VFP_REGS : 2 * NUM_VFP_REGS;
if (count > max)
return 1;
size_t i;
size_t j;
for (i = 0; i < max; ++i) {
for (j = i; j < i + count; ++j)
if ((type == ARGTYPE_DOUBLE && ctx->alloc.d[j] != 0)
|| (type == ARGTYPE_FLOAT && ctx->alloc.s[j] != 0))
goto next;
/* Found COUNT consecutive unallocated registers at I. */
const size_t sz = (type == ARGTYPE_FLOAT ? 4 : 8) * count;
unsigned char *data = value_reserve(valuep, sz);
if (data == NULL)
return -1;
for (j = i; j < i + count; ++j)
if (type == ARGTYPE_DOUBLE)
ctx->alloc.d[j] = -1;
else
ctx->alloc.s[j] = -1;
if (type == ARGTYPE_DOUBLE)
memcpy(data, ctx->fpregs.d + i, sz);
else
memcpy(data, ctx->fpregs.s + i, sz);
return 0;
next:
continue;
}
return 1;
}
/* 0 is success, 1 is failure, negative value is an error. */
static int
consider_vfp(struct fetch_context *ctx, struct process *proc,
struct arg_type_info *info, struct value *valuep)
{
struct arg_type_info *float_info = NULL;
size_t hfa_size = 1;
if (info->type == ARGTYPE_FLOAT || info->type == ARGTYPE_DOUBLE)
float_info = info;
else
float_info = type_get_hfa_type(info, &hfa_size);
if (float_info != NULL && hfa_size <= 4)
return pass_in_vfp(ctx, proc, float_info->type,
hfa_size, valuep);
return 1;
}
int
arch_fetch_arg_next(struct fetch_context *ctx, enum tof type,
struct process *proc,
struct arg_type_info *info, struct value *valuep)
{
const size_t sz = type_sizeof(proc, info);
assert(sz != (size_t)-1);
if (ctx->hardfp && !ctx->in_varargs) {
int rc;
if ((rc = consider_vfp(ctx, proc, info, valuep)) != 1)
return rc;
}
/* IHI0042E_aapcs: If the argument requires double-word
* alignment (8-byte), the NCRN is rounded up to the next even
* register number. */
const size_t al = type_alignof(proc, info);
assert(al != (size_t)-1);
if (al == 8)
ctx->ncrn = ((ctx->ncrn + 1) / 2) * 2;
/* If the size in words of the argument is not more than r4
* minus NCRN, the argument is copied into core registers,
* starting at the NCRN. */
/* If the NCRN is less than r4 and the NSAA is equal to the
* SP, the argument is split between core registers and the
* stack. */
const size_t words = (sz + 3) / 4;
if (ctx->ncrn < 4 && ctx->nsaa == ctx->sp) {
unsigned char *data = value_reserve(valuep, words * 4);
if (data == NULL)
return -1;
size_t i;
for (i = 0; i < words && ctx->ncrn < 4; ++i) {
memcpy(data, &ctx->regs.uregs[ctx->ncrn++], 4);
data += 4;
}
const size_t rest = (words - i) * 4;
if (rest > 0) {
umovebytes(proc, ctx->nsaa, data, rest);
ctx->nsaa += rest;
}
return 0;
}
assert(ctx->ncrn == 4);
/* If the argument required double-word alignment (8-byte),
* then the NSAA is rounded up to the next double-word
* address. */
if (al == 8)
/* XXX double cast. */
ctx->nsaa = (arch_addr_t)((((uintptr_t)ctx->nsaa + 7) / 8) * 8);
else
ctx->nsaa = (arch_addr_t)((((uintptr_t)ctx->nsaa + 3) / 4) * 4);
value_in_inferior(valuep, ctx->nsaa);
ctx->nsaa += sz;
return 0;
}
int
arch_fetch_retval(struct fetch_context *ctx, enum tof type,
struct process *proc, struct arg_type_info *info,
struct value *valuep)
{
if (fetch_register_banks(proc, ctx) < 0)
return -1;
if (ctx->hardfp && !ctx->in_varargs) {
int rc;
if ((rc = consider_vfp(ctx, proc, info, valuep)) != 1)
return rc;
}
size_t sz = type_sizeof(proc, info);
assert(sz != (size_t)-1);
switch (info->type) {
unsigned char *data;
case ARGTYPE_VOID:
return 0;
case ARGTYPE_FLOAT:
case ARGTYPE_DOUBLE:
if (ctx->hardfp && !ctx->in_varargs) {
unsigned char *data = value_reserve(valuep, sz);
if (data == NULL)
return -1;
memmove(data, &ctx->fpregs, sz);
return 0;
}
goto pass_in_registers;
case ARGTYPE_ARRAY:
case ARGTYPE_STRUCT:
if (sz > 4) {
value_in_inferior(valuep, ctx->ret_struct);
return 0;
}
/* Fall through. */
case ARGTYPE_CHAR:
case ARGTYPE_SHORT:
case ARGTYPE_USHORT:
case ARGTYPE_INT:
case ARGTYPE_UINT:
case ARGTYPE_LONG:
case ARGTYPE_ULONG:
case ARGTYPE_POINTER:
pass_in_registers:
if ((data = value_reserve(valuep, sz)) == NULL)
return -1;
memmove(data, ctx->regs.uregs, sz);
return 0;
}
assert(info->type != info->type);
abort();
}
void
arch_fetch_arg_done(struct fetch_context *context)
{
free(context);
}
int
arch_fetch_param_pack_start(struct fetch_context *context,
enum param_pack_flavor ppflavor)
{
if (ppflavor == PARAM_PACK_VARARGS)
context->in_varargs = true;
return 0;
}
void
arch_fetch_param_pack_end(struct fetch_context *context)
{
context->in_varargs = false;
}