blob: 2744df068cc9ed39fa18b8f09ad91b9cf663b96f [file] [log] [blame]
/*
* This file is part of ltrace.
* Copyright (C) 2014 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 <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "fetch.h"
#include "proc.h"
#include "type.h"
#include "value.h"
int aarch64_read_gregs(struct process *proc, struct user_pt_regs *regs);
int aarch64_read_fregs(struct process *proc, struct user_fpsimd_state *regs);
struct fetch_context
{
struct user_pt_regs gregs;
struct user_fpsimd_state fpregs;
arch_addr_t nsaa;
unsigned ngrn;
unsigned nsrn;
arch_addr_t x8;
};
static int
context_init(struct fetch_context *context, struct process *proc)
{
if (aarch64_read_gregs(proc, &context->gregs) < 0
|| aarch64_read_fregs(proc, &context->fpregs) < 0)
return -1;
context->ngrn = 0;
context->nsrn = 0;
/* XXX double cast */
context->nsaa = (arch_addr_t) (uintptr_t) context->gregs.sp;
context->x8 = 0;
return 0;
}
struct fetch_context *
arch_fetch_arg_clone(struct process *proc, struct fetch_context *context)
{
struct fetch_context *ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
return memcpy(ret, context, sizeof(*ret));
}
static void
fetch_next_gpr(struct fetch_context *context, unsigned char *buf)
{
uint64_t u = context->gregs.regs[context->ngrn++];
memcpy(buf, &u, 8);
}
static int
fetch_gpr(struct fetch_context *context, struct value *value, size_t sz)
{
if (sz < 8)
sz = 8;
unsigned char *buf = value_reserve(value, sz);
if (buf == NULL)
return -1;
size_t i;
for (i = 0; i < sz; i += 8)
fetch_next_gpr(context, buf + i);
return 0;
}
static void
fetch_next_sse(struct fetch_context *context, unsigned char *buf, size_t sz)
{
__int128 u = context->fpregs.vregs[context->nsrn++];
memcpy(buf, &u, sz);
}
static int
fetch_sse(struct fetch_context *context, struct value *value, size_t sz)
{
unsigned char *buf = value_reserve(value, sz);
if (buf == NULL)
return -1;
fetch_next_sse(context, buf, sz);
return 0;
}
static int
fetch_hfa(struct fetch_context *context,
struct value *value, struct arg_type_info *hfa_t, size_t count)
{
size_t sz = type_sizeof(value->inferior, hfa_t);
unsigned char *buf = value_reserve(value, sz * count);
if (buf == NULL)
return -1;
size_t i;
for (i = 0; i < count; ++i) {
fetch_next_sse(context, buf, sz);
buf += sz;
}
return 0;
}
static int
fetch_stack(struct fetch_context *context, struct value *value,
size_t align, size_t sz)
{
if (align < 8)
align = 8;
size_t amount = ((sz + align - 1) / align) * align;
/* XXX double casts */
uintptr_t sp = (uintptr_t) context->nsaa;
sp = ((sp + align - 1) / align) * align;
value_in_inferior(value, (arch_addr_t) sp);
sp += amount;
context->nsaa = (arch_addr_t) sp;
return 0;
}
enum convert_method {
CVT_ERR = -1,
CVT_NOP = 0,
CVT_BYREF,
};
enum fetch_method {
FETCH_NOP,
FETCH_STACK,
FETCH_GPR,
FETCH_SSE,
FETCH_HFA,
};
struct fetch_script {
enum convert_method c;
enum fetch_method f;
size_t sz;
struct arg_type_info *hfa_t;
size_t count;
};
static struct fetch_script
pass_arg(struct fetch_context const *context,
struct process *proc, struct arg_type_info *info)
{
enum fetch_method cvt = CVT_NOP;
size_t sz = type_sizeof(proc, info);
if (sz == (size_t) -1)
return (struct fetch_script) { CVT_ERR, FETCH_NOP, sz };
switch (info->type) {
case ARGTYPE_VOID:
return (struct fetch_script) { cvt, FETCH_NOP, sz };
case ARGTYPE_STRUCT:
case ARGTYPE_ARRAY:;
size_t count;
struct arg_type_info *hfa_t = type_get_hfa_type(info, &count);
if (hfa_t != NULL && count <= 4) {
if (context->nsrn + count <= 8)
return (struct fetch_script)
{ cvt, FETCH_HFA, sz, hfa_t, count };
return (struct fetch_script)
{ cvt, FETCH_STACK, sz, hfa_t, count };
}
if (sz <= 16) {
size_t count = sz / 8;
if (context->ngrn + count <= 8)
return (struct fetch_script)
{ cvt, FETCH_GPR, sz };
}
cvt = CVT_BYREF;
sz = 8;
/* Fall through. */
case ARGTYPE_POINTER:
case ARGTYPE_INT:
case ARGTYPE_UINT:
case ARGTYPE_LONG:
case ARGTYPE_ULONG:
case ARGTYPE_CHAR:
case ARGTYPE_SHORT:
case ARGTYPE_USHORT:
if (context->ngrn < 8 && sz <= 8)
return (struct fetch_script) { cvt, FETCH_GPR, sz };
/* We don't support types wider than 8 bytes as of
* now. */
assert(sz <= 8);
return (struct fetch_script) { cvt, FETCH_STACK, sz };
case ARGTYPE_FLOAT:
case ARGTYPE_DOUBLE:
if (context->nsrn < 8) {
/* ltrace doesn't support float128. */
assert(sz <= 8);
return (struct fetch_script) { cvt, FETCH_SSE, sz };
}
return (struct fetch_script) { cvt, FETCH_STACK, sz };
}
assert(! "Failed to allocate argument.");
abort();
}
static int
convert_arg(struct value *value, struct fetch_script how)
{
switch (how.c) {
case CVT_NOP:
return 0;
case CVT_BYREF:
return value_pass_by_reference(value);
case CVT_ERR:
return -1;
}
assert(! "Don't know how to convert argument.");
abort();
}
static int
fetch_arg(struct fetch_context *context,
struct process *proc, struct arg_type_info *info,
struct value *value, struct fetch_script how)
{
if (convert_arg(value, how) < 0)
return -1;
switch (how.f) {
case FETCH_NOP:
return 0;
case FETCH_STACK:
if (how.hfa_t != NULL && how.count != 0 && how.count <= 8)
context->nsrn = 8;
return fetch_stack(context, value,
type_alignof(proc, info), how.sz);
case FETCH_GPR:
return fetch_gpr(context, value, how.sz);
case FETCH_SSE:
return fetch_sse(context, value, how.sz);
case FETCH_HFA:
return fetch_hfa(context, value, how.hfa_t, how.count);
}
assert(! "Don't know how to fetch argument.");
abort();
}
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);
if (context == NULL || context_init(context, proc) < 0) {
fail:
free(context);
return NULL;
}
/* There's a provision in ARMv8 parameter passing convention
* for returning types that, if passed as first argument to a
* function, would be passed on stack. For those types, x8
* contains an address where the return argument should be
* placed. The callee doesn't need to preserve the value of
* x8, so we need to fetch it now.
*
* To my knowledge, there are currently no types where this
* holds, but the code is here, utterly untested. */
struct fetch_script how = pass_arg(context, proc, ret_info);
if (how.c == CVT_ERR)
goto fail;
if (how.c == CVT_NOP && how.f == FETCH_STACK) {
/* XXX double cast. */
context->x8 = (arch_addr_t) (uintptr_t) context->gregs.regs[8];
/* See the comment above about the assert. */
assert(! "Unexpected: first argument passed on stack.");
abort();
}
return context;
}
int
arch_fetch_arg_next(struct fetch_context *context, enum tof type,
struct process *proc, struct arg_type_info *info,
struct value *value)
{
return fetch_arg(context, proc, info, value,
pass_arg(context, proc, info));
}
int
arch_fetch_retval(struct fetch_context *context, enum tof type,
struct process *proc, struct arg_type_info *info,
struct value *value)
{
if (context->x8 != 0) {
value_in_inferior(value, context->x8);
return 0;
}
if (context_init(context, proc) < 0)
return -1;
return fetch_arg(context, proc, info, value,
pass_arg(context, proc, info));
}
void
arch_fetch_arg_done(struct fetch_context *context)
{
if (context != NULL)
free(context);
}
size_t
arch_type_sizeof(struct process *proc, struct arg_type_info *arg)
{
return (size_t) -2;
}
size_t
arch_type_alignof(struct process *proc, struct arg_type_info *arg)
{
return (size_t) -2;
}