343 lines
8.1 KiB
C
343 lines
8.1 KiB
C
/*
|
|
* 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 "proc.h"
|
|
#include "ptrace.h"
|
|
#include "regs.h"
|
|
#include "type.h"
|
|
#include "value.h"
|
|
|
|
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;
|
|
}
|